My emacs init file.
Much of this has been pulled from other projects/init/EmacsWiki files in the emacs community, and to the best of my knowledge, the original source for each section has been attributed. Please let me know if you find missing or incorrect attributions!
(message "Start processing of user-init ORG file.")
List of software that is used by this config for various features.
Needed for org-download.
Can be installed here on Windows. Use package manager on whichever Linux or WSL.
Set config variables on a per-system basis.
use-frames | Whether or not to tune use frame-oriented options in emacs. StumpWM is assumed |
commit-org | Whether we should git-commit after saving all org buffers. |
---|
(message "SECTION: Localization.")
(defvar my:use-frames nil)
(defvar my:commit-org nil)
(pcase (system-name)
;; x230
("brandon-babypad-linux"
(progn
(setq my:use-frames nil
my:commit-org t)
(set-face-attribute 'default nil :height 70)))
;; P51
("brandon-thinkpad-archlinux"
(set-face-attribute 'default nil :height 75))
;; Desktop VM on home server
("remote-workstation"
))
Utility function to shorten tests against Emacs 27 release.
(defun e27-p ()
(>= emacs-major-version 27))
Define a few utility functions & macros which are used throughout the init script.
This is used to create unnamed commands (no interactive arguments).
(message "SECTION: Init utility functions.")
(defmacro ilambda (arg-list &rest body)
`(lambda ,arg-list
(interactive)
,@body))
This is used to call a function interactively with given prefix argument.
(defun call-with-prefix-arg (arg fn)
(let ((current-prefix-arg (list arg)))
(call-interactively fn)))
A more succinct way to gate Microsoft Windows only behavior.
(defun on-windows-p ()
(equal system-type 'windows-nt))
(defmacro when-on-windows (&rest body)
(when (on-windows-p)
`(progn ,@body)))
(defmacro unless-on-windows (&rest body)
(unless (on-windows-p)
`(progn ,@body)))
Work with unix-like paths.
(defun append-env (env new-item)
(setenv env (concat (getenv env) ":"
new-item)))
(defun path-as-list ()
(split-string (getenv "PATH") ":"))
(defun apply-path-list (list)
(setenv "PATH" (string-join list ":")))
This section ensures that all required packages are loaded.
(message "SECTION: Required Packages.")
Initialize the package system. Tell emacs to load the newest version of anything it looks for (IE, if there’s a foo.el and foo.elc, take the newest one).
(require 'package)
(add-to-list 'load-path "~/.emacs.d/emacs-config/lisp/")
(add-to-list 'load-path "~/.emacs.d/elpa/")
(add-to-list 'package-archives
'("melpa" . "http://melpa.org/packages/") t)
(customize-set-variable 'load-prefer-newer t)
;(package-initialize)
;(setq package-enable-at-startup nil)
Set up use-package, installing if missing.
(unless (package-installed-p 'use-package)
(package-install 'use-package))
(require 'use-package)
Always install if missing (implied :ensure t argument to all uses of use-package).
(require 'use-package-ensure)
(setq use-package-always-ensure t)
The actual package list.
These just need to be required.
(require 'framemove) ; jump between frames and windows
(require 'narrow-indirect) ; Make indirect buffer in new window, then narrow to region
(require 'fence-edit) ; Edit regions of one buffer in another buffer (usually with different major mode)
(require 'dired-x) ; Dired improvements (distributed with emacs, but manually loaded)
(require 'uniquify) ; rename buffers of same name intelligently
(require 'org-inlinetask) ; support for tasks which are not structural to the document's format
(use-package buffer-move) ; swap buffers between adjacent windows
(use-package diff-hl) ; show changes from last commit with edge highlighting
(use-package undo-tree) ; incredible undo/redo support
(use-package auto-complete) ; neat autocompletion
(use-package fuzzy) ; fuzzy completion
(use-package hi-lock) ; arbitrary text hilighting
(use-package button-lock) ; face attribute that makes links from text
(use-package bm) ; bookmark manager
(use-package yasnippet) ; snippet pasting framework
(use-package anzu) ; show total/current matches in isearch
(use-package visual-regexp) ; visualize regular expressions in real-time
(use-package expand-region) ; Expand region
(use-package general) ; sane key mapping architecture
(use-package which-key) ; Display options after prefix keys are entered
(use-package ivy) ; Awesome completion framework
(use-package swiper) ; ivy's swoop
(use-package counsel) ; too many ivy-based utilities to list
(use-package hy-mode) ; Lisp on Python
(use-package org-autolist) ; Automatically add bullets on return
(use-package pdf-tools) ; excellent PDF viewer, replaces docview
(use-package emojify) ; emoji support
(use-package unicode-fonts) ; better unicode support
(use-package persistent-soft) ; caching for unicode mappings
(use-package google-translate) ; translations
(use-package auto-highlight-symbol) ; highlight the thing under cursor, jump between occurrences
(use-package org-bullets) ; unicode, indented bullets for org-mode headings
(use-package calfw) ; emacs calendar framework
(use-package calfw-org) ; org-mode calendar support for calfw
(use-package org-caldav) ; caldav support for emacs
(use-package yequake) ; quake-style dropdown emacs frame
(use-package flymd) ; markdown to html rendering in elisp
(use-package hy-mode) ; lisp via python
(use-package all-the-icons) ; icon set for emacs
(use-package all-the-icons-ivy) ; use icons in buffer switching
(use-package poporg) ; inside-out org-babel -- edit comments in source code as org-mode content
(use-package camcorder) ; record frames to ogv or gif
(use-package slime) ; common lisp debugging and interaction
(use-package es-mode) ; Elasticsearch support in emacs
(use-package battery) ; Battery life info
(use-package emms) ; music player in emacs
(use-package hydra) ; persistent key submaps
(use-package ess) ; emacs speaks statistics -- R support
(use-package treemacs) ; Advanced folder/file/content browser
(use-package notmuch) ; reading emails
(use-package smtpmail) ; sending emails
(use-package lsp-mode) ; language server protocol support
(use-package ccls) ; C/C++/OBJC LSP backend
(use-package lsp-ui) ; LSP UI parts
;; (use-package company-lsp) ; LSP support in company completion
(use-package olivetti) ; Center text with fill-columns, critical for large displays.
(use-package rainbow-delimiters) ; Colorize matched parens, brackets, etc.
(use-package helm-descbinds) ; Extremely useful for learning new major modes.
(use-package magit) ; Incredible Git client interface in emacs
(use-package emmet-mode) ; HTML editing mode which generates HTML from CSS-like expressions
(use-package smex) ; Maintain M-x history -- automatically used by counsel-M-x
(use-package csv-mode) ; Display CSV files in proper columns
(use-package iedit) ; Highlight and edit all occurences of symbol under point
(use-package ac-slime) ; autocompletion for SLIME
(use-package clhs) ; Lookup symbol in Common Lisp Hyper Spec
(use-package ivy-posframe) ; Move minibuffer to child frame
(use-package counsel-notmuch) ; Search notmuch emails with ivy
(use-package ts) ; Timestamp utility functions for org-mode
(use-package auto-yasnippet) ; On the fly text templating
(use-package plantuml-mode) ; Edit plantuml files; UML generation from plain text.
(unless-on-windows ; A more compatible terminal emulator for emacs via libvterm
(use-package vterm))
(use-package org-ql) ; A sane interface for finding things in Org
(use-package realgud) ; Powerful generalized debugger interface
(use-package dired-subtree) ; Better file trees in the directory editor
(use-package real-auto-save) ; Actually save the buffer
(use-package wgrep-ag) ; Edit an ag results buffer in-place (regex replace across all files)
(use-package ob-http) ; Send HTTP requests in org-babel
(use-package editorconfig)
(use-package jsonrpc)
(use-package quelpa)
(use-package gptel) ; Support for GPT conversation buffers & API calls
(use-package org-ai) ; Support for ChatGPT in #+BEGIN_AI blocks, and so much more
(use-package pyvenv) ; Python virtual environments
(use-package clojure-mode) ; Develop Clojure in emacs
(use-package cider) ; Develop Clojure in emacs
(use-package jupyter) ; ipython in org-babel
(use-package org-download) ; attach images on system clipboard to org files
(use-package nix-mode) ; edit system configs in nix
;; Just get copilot working -- but not on windows
(unless-on-windows
(quelpa
'(quelpa-use-package
:fetcher git
:url "https://github.com/quelpa/quelpa-use-package.git"))
(require 'quelpa-use-package)
(use-package copilot
:quelpa (copilot :fetcher github
:repo "copilot-emacs/copilot.el"
:branch "main"
:files ("*.el"))))
(message "SECTION: Windows hacks.")
Convenience functions for accessing Windows paths.
(when-on-windows
(defcustom my:msys2-root-dir "C:/msys64"
"Root path to the MSYS2 installation.")
(defun msys2-path (relpath)
(concat (file-name-as-directory my:msys2-root-dir) relpath))
(defun programs-x86-path (relpath)
(concat (file-name-as-directory (getenv "ProgramFiles(x86)")) relpath))
(defun local-appdata-path (relpath)
(concat (file-name-as-directory (getenv "LOCALAPPDATA")) relpath)))
Use MSYS2 bash as the shell, and clean up PS1 a bit to remove some shell markup which Emacs can’t cope with. Modified from Mastering Emacs.
update Disabled for now. Not using this due to switch to eshell for the most part on Windows, and it was breaking Tramp connections (likely the ctrl-m stripping if I had to guess).
(when-on-windows (setq explicit-shell-file-name (msys2-path "usr/bin/bash.exe") shell-file-name "bash" explicit-bash-args '("--login" "-i")) (setenv "SHELL" shell-file-name) (setenv "MSYS2_PATH_TYPE" "inherit") (append-env "PATH" (programs-x86-path "Git/bin")) (setenv "PS1" "\\n\\[\\e[32m\\]\\u@\\h \\[\\e[32m\\]$MSYSTEM\\[\\e[0m\\] \\[\\e[33m\\]\\w\\[\\e[0m\\]\\n\\$ ") (add-hook 'comint-output-filter-functions 'comint-strip-ctrl-m))
Use a pile of MSYS programs.
(when-on-windows
(setq
find-program (programs-x86-path "Git/bin/find.exe")
git-program (programs-x86-path "Git/bin/git.exe")
diff-program (msys2-path "usr/bin/diff.exe")
diff-command (msys2-path "usr/bin/diff.exe")
ispell-program-name (msys2-path "usr/bin/aspell.exe")
ctags-bin-name (msys2-path "usr/bin/ctags.exe")))
Special hacks for Windows
(when-on-windows
(autoload 'ansi-color-for-comint-mode-on "ansi-color" nil t)
(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on))
This is the one-time code needed to set up copilot on a new machine, other than the quelpa installation code above.
Also need Node installed. If it doesn’t work, set 'copilot-node-executable
(copilot-install-server) (copilot-login) (copilot-diagnose)
For GPTel, make sure to add the ChatGPT API key to ~/.authinfo
machine api.openai.com login apikey password TOKEN
Enable processing of #+BEGIN_AI blocks.
(add-hook 'org-mode-hook
#'org-ai-mode)
Use org-ai snippets
(org-ai-install-yasnippets)
Use org-mode in gptel buffers.
(customize-set-variable 'gptel-default-mode 'org-mode)
Don’t indent so much.
(customize-set-variable 'gptel-prompt-prefix-alist '((markdown-mode . "### ")
(org-mode . "* ")
(text-mode . "### ")))
Function to clear conversation and reset.
(defun my:gptel-reset-conversation ()
(interactive)
(erase-buffer)
(insert (alist-get 'org-mode gptel-prompt-prefix-alist)))
Function to generate a new conversation and store it in the LLM history folder.
(defun my:gptel-new-conversation ()
(interactive)
(let ((file-name (my:generate-gpt-conversation-file-name)))
(let ((directory (file-name-directory file-name)))
(unless (file-exists-p directory)
(make-directory directory t)))
(switch-to-buffer (gptel "*ChatGPT*"))
(write-file file-name)
(gptel-mode)
(call-interactively 'save-buffer)))
(message "SECTION: Key Mappings")
Most of my custom key-bindings use the hyper key. The goal here is to prevent any reasonable possibility of collision between personal keybindings and package keybindings. By convention, C-<key> is already reserved for the user, but we have another modifier available anyway, so why not use it? In my case, the hyper modifier is bound to caps lock.
To map caps lock to hyper under Xorg, you may do the following in ~/.Xmodmap:
clear mod4
keycode 66 = Hyper_L
add mod4 = Super_L Super_R
clear lock
add mod3 = Hyper_L
In windows, install AutoHotkey, and place the following in a startup script…
SetTitleMatchMode, 2
#IfWinActive emacs-nt
{
CapsLock::AppsKey
return
}
…with the following elisp…
(when-on-windows
(setq w32-pass-lwindow-to-system nil
w32-pass-rwindow-to-system nil
w32-pass-apps-to-system nil
w32-lwindow-modifier 'super
w32-rwindow-modifier 'super
w32-apps-modifier 'hyper))
Do note that this leaves the apps key unusable in emacs, except as a secondary hyper modifier. I have not yet found another way to do this which works well for me.
Unfortunately, the w32-* variables defined in the previous example do not work on Linux. And therefore, they won’t work on WSL.
The hyperify function is borrowed from GNU: https://www.gnu.org/software/emacs/manual/html_node/elisp/Translation-Keymaps.html#Translation-Keymaps
keyboard-translation-map is used to ensure that the key cannot be remapped by any major or minor modes.
(defun hyperify (prompt)
(interactive "p")
(let ((e (read-event)))
(vector (if (numberp e)
(logior (ash 1 24) e)
(if (memq 'hyper (event-modifiers e))
e
(add-event-modifier "H-" e))))))
(defun add-event-modifier (string e)
(let ((symbol (if (symbolp e) e (car e))))
(setq symbol (intern (concat string
(symbol-name symbol))))
(if (symbolp e)
symbol
(cons symbol (cdr e)))))
(define-key key-translation-map (kbd "<menu>") 'hyperify)
This strategy probably also would work on genuine Linux with Wayland, too.
Limitation: This only works for single keys. You cannot hold down CAPS and continually hit other keys.
Functions designed specifically to be used as key mappings.
Tab key rebinding.
(defun c-smart-tab-key ()
"Indent when at left margin or right of whitespace, autocomplete elsewhere"
(interactive)
(smart-tab-key 'c-indent-line-or-region 'dabbrev-expand c-basic-offset))
Defined to balance existing function names.
(defun split-window-above ()
"Split current window into top and bottom, with focus left in bottom."
(interactive)
(split-window-below)
(windmove-down))
(defun split-window-left ()
"Split current window into left and right, with focus left in right."
(interactive)
(split-window-right)
(windmove-right))
(defun 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)))
(defun smart-beginning-of-line ()
"Move point to first non-whitespace character or beginning-of-line.
Move point to the first non-whitespace character on this line.
If point was already at that position, move point to beginning of line.
https://www.emacswiki.org/emacs/BackToIndentationOrBeginning#toc2"
(interactive) ; Use (interactive "^") in Emacs 23 to make shift-select work
(if (and (equal major-mode 'org-mode) (org-at-heading-p))
(org-beginning-of-line)
(let ((oldpos (point)))
(back-to-indentation)
(and (= oldpos (point))
(beginning-of-line)))))
(defun smart-copy-paste ()
"Smart copy or paste"
(interactive)
(if (use-region-p)
(if (and (boundp 'rectangle-mark-mode) rectangle-mark-mode)
(copy-rectangle-as-kill (region-beginning) (region-end))
(kill-ring-save (region-beginning) (region-end)))
(yank)))
(defun smart-set-mark ()
"Consecutive calls expand region. First sets mark."
(interactive)
(if mark-active
(call-interactively 'er/expand-region)
(call-interactively 'set-mark-command)))
(defun smart-cut-or-delete ()
"If region, cut, else, delete char"
(interactive)
(if (use-region-p)
(kill-region (region-beginning) (region-end))
(delete-forward-char 1)))
(defun execute-command-toggle (prefixes)
"If region active, clear it. Else, toggle the M-x menu."
(interactive "P")
(cond
((window-minibuffer-p) (my:keyboard-escape-quit))
(mark-active (progn (setq mark-active nil) (run-hooks 'deactivate-mark-hook)))
(t (counsel-M-x prefixes))))
(defun execute-command-toggle-ignore-region (prefixes)
"Toggle M-x menu."
(interactive "P")
(if (window-minibuffer-p)
(my:keyboard-escape-quit)
(counsel-M-x prefixes)))
(defun whack-whitespace-after-point (arg)
;; https://www.emacswiki.org/emacs/DeletingWhitespace
"Delete all white space from point to the next word. With prefix ARG
delete across newlines as well. The only danger in this is that you
don't have to actually be at the end of a word to make it work. It
skips over to the next whitespace and then whacks it all to the next
word."
;; (interactive "P")
(let ((regexp (if arg "[ \t\n]+" "[ \t]+")))
(re-search-forward regexp nil t)
(replace-match "" nil nil)))
(defun smart-batch-delete ()
(interactive)
(if (or (looking-at "$") (looking-at " "))
(whack-whitespace-after-point t)
(kill-word nil)))
(defun my:comment-region-or-line ()
"If region active, comment it. Else, comment current line."
(interactive)
(call-interactively (if (use-region-p)
'comment-or-uncomment-region
'comment-line)))
(defun my:comment-region-or-line-keep-clone ()
"Copy the current line or region below, then comment out the original."
(interactive)
(let (beg end)
(if (use-region-p)
;; If region is active, make sure that it starts at the start
;; of a line, and ends at the end of another line.
(let ((temp-beg (region-beginning))
(temp-end (region-end)))
(save-excursion
(goto-char temp-beg)
(beginning-of-line)
(setq beg (point))
(goto-char temp-end)
(end-of-line)
(setq end (point))))
;; Just set beg and end to the extents of the current line
(save-excursion
(beginning-of-line)
(setq beg (point))
(end-of-line)
(setq end (point))))
;; Select region, copy it, comment it, paste it below
(save-excursion
(set-mark beg)
(goto-char end)
(activate-mark)
(copy-region-as-kill beg end)
(my:comment-region-or-line)
(newline)
(yank))
;; Place point at the start of the pasted area (probably)
(forward-line)
(smart-beginning-of-line)))
(defun newline-after-current ()
"Skip to end of this line, insert a new one, autoindent, recenter cursor."
(interactive)
(move-end-of-line nil)
(newline-and-indent)
(scroll-up 1))
(defun newline-before-current ()
"Move this line down, go to beginning of a new line where this one was."
(interactive)
(move-beginning-of-line nil)
(newline-and-indent)
(move-beginning-of-line nil)
(when (looking-at "[ \t]+$")
(kill-line)
(setq kill-ring (cdr kill-ring))
(insert "\n")
(forward-line -1))
(forward-line -1)
(indent-for-tab-command))
(defun newline-before-and-after-current()
"Insert new lines above and below current line."
(interactive)
(newline-after-current)
(newline-before-current))
(defun previous-appropriate-buffer-if-hidden ()
(previous-appropriate-buffer))
(defun hidden-buffer-p ()
(or
(string-match "\*.*\*" (buffer-name)) ; *buffer*s
(string-match "^COM[0-9]*$" (buffer-name)) ; windows serial buffer
(string-match "^/dev/tty*" (buffer-name)) ; *nix serial buffer
(string-match "^TAGS$" (buffer-name)))) ; TAGS file
(defun next-non-sys-buffer ()
""
(interactive)
(let
((start-buffer (buffer-name)))
(next-non-sys-buffer-with-basecase 'next-buffer start-buffer)))
(defun previous-non-sys-buffer ()
""
(interactive)
(let
((start-buffer (buffer-name)))
(next-non-sys-buffer-with-basecase 'previous-buffer start-buffer)))
(defun next-non-sys-buffer-with-basecase (advance-f base)
(funcall advance-f)
(if (and
(not (eq base (buffer-name)))
(or (hidden-buffer-p)
(my:journal-buffer-p)))
(next-non-sys-buffer-with-basecase advance-f base)))
(defun next-sys-buffer ()
""
(interactive)
(let
((start-buffer (buffer-name)))
(next-sys-buffer-with-basecase 'next-buffer start-buffer)))
(defun previous-sys-buffer ()
""
(interactive)
(let
((start-buffer (buffer-name)))
(next-sys-buffer-with-basecase 'previous-buffer start-buffer)))
(defun next-sys-buffer-with-basecase (advance-f base)
(funcall advance-f)
(if (and
(not (eq base (buffer-name)))
(not( hidden-buffer-p)))
(next-sys-buffer-with-basecase advance-f base)))
(defun next-appropriate-buffer ()
(interactive)
(if (my:journal-buffer-p)
(my:visit-next-journal-page)
(next-non-sys-buffer)))
(defun previous-appropriate-buffer ()
(interactive)
(if (my:journal-buffer-p)
(my:visit-previous-journal-page)
(previous-non-sys-buffer)))
Edit thing at point as source code using fence-edit.
(defun my:edit-as-source ()
"Like fence-edit-dwim, but always prompts for language."
(interactive)
(let* ((block (fence-edit--get-block-around-point))
(beg (car block))
(end (nth 1 block)))
(save-mark-and-excursion
(set-mark beg)
(goto-char end)
(activate-mark)
(call-with-prefix-arg 4 'fence-edit-code-region))))
Run python in shell.
(defun run-python-in-shell ()
(interactive)
(if (use-region-p)
(call-interactively 'python-shell-send-region)
(save-excursion
(progn
(beginning-of-line)
(push-mark)
(end-of-line)
(call-interactively 'python-shell-send-region)
(pop-mark)))))
Use counsel for jedi autocompletions, thanks to abo-abo.
(defun counsel-jedi ()
"Python completion at point."
(interactive)
(let ((bnd (bounds-of-thing-at-point 'symbol)))
(if bnd
(progn
(setq counsel-completion-beg (car bnd))
(setq counsel-completion-end (cdr bnd)))
(setq counsel-completion-beg nil)
(setq counsel-completion-end nil)))
(deferred:sync!
(jedi:complete-request))
(ivy-read "Symbol name: " (jedi:ac-direct-matches)
:action #'counsel--py-action))
(defun counsel--py-action (symbol)
"Insert SYMBOL, erasing the previous one."
(when (stringp symbol)
(with-ivy-window
(when counsel-completion-beg
(delete-region
counsel-completion-beg
counsel-completion-end))
(setq counsel-completion-beg
(move-marker (make-marker) (point)))
(insert symbol)
(setq counsel-completion-end
(move-marker (make-marker) (point)))
(when (equal (get-text-property 0 'symbol symbol) "f")
(insert "()")
(setq counsel-completion-end
(move-marker (make-marker) (point)))
(backward-char 1)))))
Setup the WORKON directories for pyvenv
(let
((dirname (if (on-windows-p)
"C:/virtual-environments"
"~/python-envs/")))
(setenv "WORKON_HOME" dirname))
This needs to run due to a bug in emacs-jupyter.
EDIT: Disabled because this doesn’t seem to work, keeping commented code because I suspect I’ll run into the bug again and don’t want to spend 30 minutes finding the Github comment back with this fix.
(org-babel-jupyter-aliases-from-kernelspecs)
Remember to set the virtual environment before doing jupyter-python
language blocks in org-babel! This can be done with pyvenv-workon
.
(defun my:goto-previous-change ()
(interactive)
(diff-hl-previous-hunk)
(recenter))
(defun my:goto-next-change ()
(interactive)
(diff-hl-next-hunk)
(recenter))
(defun my:goto-line ()
(interactive)
(call-interactively 'goto-line)
(recenter))
(defun my:annotated-bookmark ()
(interactive)
(let* ((bm-annotate-on-create t)) (bm-toggle)))
(defun my:scroll-up-some-lines ()
(interactive)
(if pixel-scroll-mode
(pixel-scroll-up)
(scroll-up 15)))
(defun my:scroll-down-some-lines ()
(interactive)
(if pixel-scroll-mode
(pixel-scroll-down)
(scroll-down 15)))
(defun my:scroll-left-some-lines ()
(interactive)
;; invert axis
(scroll-right 5))
(defun my:scroll-right-some-lines ()
(interactive)
;; invert axis
(scroll-left 5))
(defun my:run-or-save-macro (name)
(if defining-kbd-macro
(progn
(kmacro-end-or-call-macro nil)
(fset
(intern (format "mcr-%s" name))
last-kbd-macro))
(execute-kbd-macro
(intern
(format "mcr-%s" name)))))
(defun my:run-macro (name)
(execute-kbd-macro
(intern
(format "mcr-%s" name))))
Revert buffer unconditionally;
(defun my:revert-buffer-no-prompt ()
"Revert buffer without confirmation. From https://emacs.stackexchange.com/questions/10348/revert-buffer-discard-unsaved-changes-without-y-n-prompt"
(interactive) (revert-buffer t t))
Smart tab key framework
(defun gen-smart-tab-key (indent autocomplete indent-count)
(lexical-let
((indent indent)
(autocomplete autocomplete)
(indent-count indent-count))
(ilambda ()
(smart-tab-key indent autocomplete indent-count))))
(defun smart-tab-key (indent autocomplete indent-count)
"Indent when at left margin or right of whitespace, autocomplete elsewhere."
(cond ((or (eq last-command 'yank) (eq last-command 'yank-pop)) (yank-pop))
(( or
(eq last-command 'find-tag)
(eq last-command 'find-tag-other-window)
(eq last-command 'find-tag-wrapped)
(eq last-command 'find-tag-wrapped-new-window))
(progn
(message "Finding next possible definition...")
(call-with-prefix-arg 4 'find-tag)
(recenter-top-bottom)
(setq this-command 'find-tag)))
((use-region-p) (indent-rigidly (region-beginning) (region-end) indent-count))
((or (= 0 (current-column)) (= ?\s (char-before))) (funcall indent))
(t (call-interactively autocomplete))))
(defun generic-smart-tab-key ()
"Indent when at left margin or right of whitespace, autocomplete elsewhere"
(interactive)
(smart-tab-key 'indent-for-tab-command 'dabbrev-expand 1))
(defun python-smart-tab-key ()
"Indent when at left margin or right of whitespace, autocomplete elsewhere"
(interactive)
(smart-tab-key 'indent-for-tab-command 'dabbrev-expand 1))
Add a write-file
wrapper which disables Ivy’s automagic directory completion.
(defun my:save-as ()
"Calls write-file, doesn't let Ivy autoselect directories upon slash."
(interactive)
(let
((ivy-magic-slash-non-match-action nil))
(call-interactively 'write-file)))
Evaluate the trailing sexp and replace it with results of evaluation. Handy for inline math.
(defun my:eval-and-replace-sexp ()
"Evaluate the previous sexp and replace it with result."
(interactive)
(backward-kill-sexp)
(condition-case nil
(prin1 (eval (read (current-kill 0)))
(current-buffer))
(error (message "Invalid expression")
(insert (current-kill 0)))))
Refresh agenda after TODO change. This let’s queued entries to be shown quickly when the ORDERED property is set to t for a heading.
(defun my:org-agenda-todo-and-redo ()
"Call org-agenda-todo interactively, then org-agenda-redo"
(interactive)
(call-interactively 'org-agenda-todo)
(org-agenda-redo))
Modify org-agenda-switch-to to respect the ‘pop-up-buffers custom variable. It will open a new frame or window when opening TODO items in agenda views.
(defun my:org-agenda-switch-to (&optional delete-other-windows)
"Like normal org-agenda-switch-to, but respect pop-up-buffer by
using display-buffer in place of pop-to-buffer."
(interactive)
(if (and org-return-follows-link
(not (org-get-at-bol 'org-marker))
(org-in-regexp org-bracket-link-regexp))
(org-open-link-from-string (match-string 0))
(let* ((marker (or (org-get-at-bol 'org-marker)
(org-agenda-error)))
(buffer (marker-buffer marker))
(pos (marker-position marker)))
(unless buffer (user-error "Trying to switch to non-existent buffer"))
(display-buffer buffer) ;; <----------------------- MODIFIED LINE BJG
(when delete-other-windows (delete-other-windows))
(widen)
(goto-char pos)
(when (derived-mode-p 'org-mode)
(org-show-context 'agenda)
(run-hooks 'org-agenda-after-show-hook)))))
Toggle center-alignment & word-wrap (good for reading) in unison. Sometimes it’s needed to keep tables formatted, which are too wide to fit in the normal fill-column but are fine in export.
(defvar my:org-wrapped t)
(make-variable-buffer-local 'my:org-wrapped)
(defun my:org-toggle-wrap ()
(interactive)
(if my:org-wrapped
(progn
(olivetti-mode -1)
(visual-line-mode -1))
(progn
(olivetti-mode 1)
(visual-line-mode 1)))
(setq my:org-wrapped (not my:org-wrapped)))
Ignore dependencies when I explicitly want to set status. I have dependency checking enabled only so that irrelevant goals are not cluttering my todo list. See org-agenda-dim-blocked-tasks
.
(defun my:org-todo ()
"Like org-todo, but with `org-enforce-todo-dependencies' temporarily bound to nil"
(interactive)
(let ((org-enforce-todo-dependencies nil))
(call-interactively #'org-todo)))
Function to interactively switch the currently clocked task. Modified from org-ql’s github.
(defun my:org-clock-return-to-task ()
"Clock-in to another task that's been worked on today."
(interactive)
(let ((clocked-tasks (org-ql-select (org-agenda-files)
'(clocked :on today)
:action (lambda ()
(propertize (org-get-heading t)
'marker (copy-marker (point)))))))
(ivy-read
"Return to Task: "
clocked-tasks
:action
(lambda (headline)
(let* ((marker (get-text-property 0 'marker headline))
(buffer (and (markerp marker) (marker-buffer marker))))
(when buffer
(with-current-buffer buffer
(goto-char marker)
(org-show-entry)
(org-clock-in)
(message (concat "Working on " headline)))))))))
Be smart about home/end on headings.
(customize-set-variable 'org-special-ctrl-a/e t)
Add a function to quickly insert TODO items as subheadings at the bottom of the current heading.
(defun my:org-insert-todo-bottom-current-heading ()
(interactive)
(save-excursion
(org-insert-heading-respect-content t) ; new heading immediately after this one as a sibling
(org-demote) ; demote it -- it becomes the last child of original
(let
((priority (ivy-read "Priority: " '("A" "B" "C" "D" "E")))
(summary (read-string "Task Description: "))
(timestamp (format-time-string "[%Y-%m-%d %a %H:%M]")))
(insert "TODO [#" priority "] " summary)
(insert "\n:LOGBOOK:\n- State \"TODO\" from " timestamp "\n:END:"))))
Function to open the directory for attachments of the current heading
(defun my:open-org-attach-dir (&optional arg)
"Browse attachment dir at point in dired. With prefix, open in system file browser."
(interactive "P")
(let ((dir (org-attach-dir)))
(if arg
(if (on-windows-p)
(w32-shell-execute "open " dir)
(shell-command (concat "xdg-open '" dir "'")))
(dired dir))))
Make sure Swiper query replace tries to replace all occurrences.
(defun my:swiper-query-replace-all ()
(interactive)
(save-excursion
(execute-kbd-macro (kbd "M-<")) ;; <- (call-interactively 'ivy-beginning-of-buffer doesn't work?
(swiper-query-replace)))
Describe symbol with completing read; ivy in my case. This code is from xiongtx on the Slime github.
(defun my:slime-read-from-minibuffer (prompt &optional initial-value history)
"Completing-read a string from the minibuffer, prompting with prompt."
(interactive)
(let ((minibuffer-setup-hook (slime-minibuffer-setup-hook)))
(completing-read prompt (slime-simple-completions (or initial-value ""))
nil nil nil
(or history 'slime-minibuffer-history))))
(defun my:slime-read-symbol-name (prompt &optional query)
"Either read a symbol name or choose the one at point.
The user is prompted if a prefix argument is in effect, if there is no
symbol at point, or if QUERY is non-nil."
(cond ((or current-prefix-arg query (not (slime-symbol-at-point)))
(my:slime-read-from-minibuffer prompt (slime-symbol-at-point)))
(t (slime-symbol-at-point))))
(defun my:slime-describe-symbol (symbol-name)
"Describe the symbol at point."
(interactive (list (my:slime-read-symbol-name "Describe symbol: ")))
(when (not symbol-name)
(error "No symbol given"))
(slime-eval-describe `(swank:describe-symbol ,symbol-name)))
(defun my:slime-eval-region-or-sexp ()
"If region is active, send it. Else, send last sexp."
(interactive)
(if (use-region-p)
(call-interactively 'slime-eval-region)
(slime-eval-last-expression)))
A function to eval and run the deftest at point.
(defun my:cider-eval-and-run-test-at-point ()
"Evaluate the current defun (should be a test) and run it."
(interactive)
(cider-eval-defun-at-point)
(cider-test-run-test))
Function to quickly check my emails.
(defun my:check-emails ()
(interactive)
(notmuch-search "date:3M.."))
Function to toggle hide/show for all nodes in buffer
(defun my:hs-toggle-hiding-all ()
(interactive)
(if (hs-already-hidden-p)
(hs-show-all)
(hs-hide-all)))
Alias some basic functionality to names that may have different backends during experimentation.
(defalias 'my:find-text 'swiper-isearch)
(defalias 'my:find-this-text 'swiper-thing-at-point)
(defalias 'my:find-buffer 'ivy-switch-buffer)
(defalias 'my:run-program 'counsel-linux-app)
(defalias 'my:find-file 'counsel-find-file)
(defalias 'my:terminal-emulator (if (equal system-type 'windows-nt) 'eshell 'vterm))
(general-auto-unbind-keys)
(defhydra my:music-hydra ()
"Music Controls"
("SPC" (my:emms-do-then-show 'emms-pause) "Toggle Play/Pause")
("<right>" (my:emms-do-then-show 'emms-next) "Next Track")
("<left>" (my:emms-do-then-show 'emms-previous) "Previous Track")
("r" (my:emms-do-then-show 'emms-random) "Random Track")
("m" (my:emms-do-then-show 'emms) "Start EMMS")
("s" emms-show "Show Current Track")
("a" emms-show-all "Show All Track Info")
("<escape>" nil "Quit"))
(defhydra my:frame-hydra ()
"Moving the frame."
("<right>" my:fancy-move-frame-right "Move the frame right.")
("<left>" my:fancy-move-frame-left "Move the frame left.")
("<up>" my:fancy-move-frame-up "Move the frame up.")
("<down>" my:fancy-move-frame-down "Move the frame down.")
("S-<right>" my:fancy-resize-frame-bigger-horizontally "Widen the frame.")
("S-<left>" my:fancy-resize-frame-smaller-horizontally "Skinny the frame.")
("S-<up>" my:fancy-resize-frame-bigger-vertically "Tallen the frame.")
("S-<down>" my:fancy-resize-frame-smaller-vertically "Shorten the frame.")
("<escape>" nil "Quit"))
(defhydra my:window-hydra ()
"Moving the window."
("<right>" buf-move-right "Move the window right.")
("<left>" buf-move-left "Move the window left.")
("<up>" buf-move-up "Move the window up.")
("<down>" buf-move-down "Move the window down.")
("S-<right>" my:fancy-resize-window-bigger-horizontally "Widen the window.")
("S-<left>" my:fancy-resize-window-smaller-horizontally "Skinny the window.")
("S-<up>" my:fancy-resize-window-bigger-vertically "Tallen the window.")
("S-<down>" my:fancy-resize-window-smaller-vertically "Shorten the window.")
("<escape>" nil "Quit"))
(general-define-key :keymaps 'emms-playlist-mode-map
"SPC" (ilambda () (my:emms-do-then-show 'emms-pause))
"<right>" (ilambda () (my:emms-do-then-show 'emms-next))
"<left>" (ilambda () (my:emms-do-then-show 'emms-previous))
"r" (ilambda () (my:emms-do-then-show 'emms-random))
"m" (ilambda () (my:emms-do-then-show 'emms))
"s" 'emms-show
"a" 'emms-show-all)
(setq my:shortcut-leader "<f12>")
(general-define-key :prefix my:shortcut-leader
;; Double tap
my:shortcut-leader 'org-capture
;; ? means help
"?" 'helm-descbinds
;; "o"-> Org global shortcuts
"odd" 'my-org-journal-open-today
"odt" 'my-org-journal-open-tomorrow
"ody" 'my-org-journal-open-yesterday
"oa" 'org-agenda
"ot" 'org-todo-list
"oc" 'my:org-concept-open
"os" 'my:org-search-concept
"oo" 'my:org-occur-in-agenda-files
"ov" 'my:voice-notes
"or" 'my:org-refile-transient
"o SPC" 'my:org-toggle-wrap
"oli" 'my:org-insert-link-to-scanned-document
"oj" 'my:open-journal
;; "v"-> VC (Magit) global shortcuts
"vs" 'magit-status
"vb" 'magit-blame
"vd" 'magit-diff
;; "m" -> Music
"m" 'my:music-hydra/body
;; "e" -> Email
"ee" 'my:check-emails
"es" 'counsel-notmuch
"em" 'mu4e
;; "l" -> language modes
"lm" 'markdown-mode
"lp" 'python-mode
"le" 'emacs-lisp-mode
"lc" 'c-mode
"lo" 'org-mode
;; "c" -> Calendar/Clocking
"cs" 'org-caldav-sync
"cf" 'cfw:open-org-calendar
"ci" 'org-clock-in
"co" 'org-clock-out
"cq" 'org-clock-cancel
"cr" 'my:org-clock-return-to-task
"c SPC" 'my:org-clock-new-task-silent
;; "s" -> system
"sx" 'my:run-program
"sl" 'counsel-locate
"sb" 'my:battery-check
"sr" 'my:read-pdf-from-scanner/scanimage
;; t -> translate
"t" 'google-translate-at-point
;; "i" -> input methods
"ie" (ilambda () (set-input-method nil) (message "English"))
"ic" (ilambda () (set-input-method 'chinese-tonepy) (message "Chinese (pinyin)"))
;; "d" -> debug
"dbf" 'debug-on-entry
"dbv" 'debug-on-variable-change
"dbnf" 'cancel-debug-on-entry
"dbnv" 'cancel-debug-on-variable-change
;; "w" -> window management
"wb" 'balance-windows
"wu" 'winner-undo
"wr" 'winner-redo
"w RET" 'delete-other-windows
"ww" 'my:window-hydra/body
;; "f" -> frame management
"f" 'my:frame-hydra/body
;; "p" -> python
"pp" 'run-python
"pd" 'my:simple-pdb
;; "g" -> GPT
"ga" 'gptel-quick ; GPT Ask question, no context
"gi" 'gptel-insert ; GPT Insert
"ge" 'gptel-rewrite ; GPT Edit
"gc" 'my:gptel-new-conversation ; GPT Conversation
)
(general-define-key
;; Controlling emacs
"M-x" 'counsel-M-x
"<escape>" 'execute-command-toggle
"S-<escape>" 'execute-command-toggle-ignore-region
"H-<print>" 'describe-bindings
"H-<menu>" 'my:toggle-use-frames
"C-x C-z" nil ; Was suspend-frame. I've never hit this intentionally
"C-x C-c" nil ; Was save-buffers-kill-terminal. Again, I've never done this intentionally.
;; Window navigation
"H-<right>" 'windmove-right
"H-<left>" 'windmove-left
"H-<down>" 'windmove-down
"H-<up>" 'windmove-up
"<S-f8>" 'delete-window
"<H-f8>" 'my:kill-current-buffer
"<f8>" 'delete-window
"<H-S-f8>" 'kill-buffer-and-window
;; Workspaces (virtual desktops/saved window configs/etc)
"S-<left>" 'tab-bar-switch-to-prev-tab
"S-<right>" 'tab-bar-switch-to-next-tab
"S-<up>" 'tab-bar-new-tab
"C-S-<up>" 'tab-bar-undo-close-tab
"S-<down>" 'tab-bar-close-tab
"C-S-<left>" (ilambda () (tab-bar-move-tab -1))
"C-S-<right>" (ilambda () (tab-bar-move-tab 1))
"C-S-SPC" 'tab-bar-rename-tab
;; Window splitting
; names seem wrong, but I imagine dpad right to mean "send focus right", etc.
"H-s <left>" 'split-window-right
"H-s <right>" 'split-window-left
"H-s <down>" 'split-window-above
"H-s <up>" 'split-window-below
; Window resizing
"H-S-<up>" 'my:fancy-resize-window-bigger-vertically
"H-S-<right>" 'my:fancy-resize-window-bigger-horizontally
"H-S-<down>" 'my:fancy-resize-window-smaller-vertically
"H-S-<left>" 'my:fancy-resize-window-smaller-horizontally
;; Frame manipulations
"<H-f11>" 'toggle-frame-fullscreen
"H-t" 'my:set-window-opacity
;; Keyboard macros
"H-*" 'kmacro-start-macro
"<f1>" (ilambda () (my:run-or-save-macro "f1"))
"<f2>" (ilambda () (my:run-or-save-macro "f2"))
"<f3>" (ilambda () (my:run-or-save-macro "f3"))
"<f4>" (ilambda () (my:run-or-save-macro "f4"))
"C-<f1>" (ilambda () (my:run-macro "f1"))
"C-<f2>" (ilambda () (my:run-macro "f2"))
"C-<f3>" (ilambda () (my:run-macro "f3"))
"C-<f4>" (ilambda () (my:run-macro "f4"))
;; File operations
"<f5>" 'my:find-file
"<f6>" 'save-buffer
"<f7>" 'my:save-as
"H-<f5>" 'my:revert-buffer-no-prompt
"S-<f6>" (ilambda () (call-with-prefix-arg 4 'save-some-buffers))
;; OS Utilities
"H-f" 'counsel-ag
"H-F" (ilambda () (call-with-prefix-arg 'counsel-ag))
"<f9>" 'my:terminal-emulator
;; Text navigation
"<home>" 'smart-beginning-of-line
"M-<up>" 'backward-paragraph
"M-<down>" 'forward-paragraph
"H-a" 'my:find-text
"H-A" 'my:find-this-text
"H-M-a" 'my:find-this-text
"H-[" 'my:goto-previous-change
"H-]" 'my:goto-next-change
"H-g" 'my:goto-line
"H-p" 'isearch-forward-regexp
;; Text selection and editing
"C-<delete>" 'smart-batch-delete
"<insert>" 'smart-copy-paste
"<delete>" 'smart-cut-or-delete
"C-SPC" 'smart-set-mark
"H-r" 'anzu-query-replace-regexp
"H-y" 'yas-insert-snippet
"H-u" 'counsel-unicode-char
"H-i" 'emojify-insert-emoji
"C-<return>" 'newline-after-current
"M-<return>" 'newline-before-current
"C-M-<return>" 'newline-before-and-after-current
"C-t" 'indent-according-to-mode
"C-z" 'undo-tree-undo
"C-S-z" 'undo-tree-redo
"H-z" 'undo-tree-visualize
"C-c a" 'mark-whole-buffer
"M-Q" 'unfill-paragraph
"H-q" 'iedit-mode
"H-\\" 'my:eval-and-replace-sexp
;; Bookmarks
"H-SPC" 'bm-toggle
"H-S-SPC" 'my:annotated-bookmark
"H-}" 'bm-next
"H-{" 'bm-previous
"<left-margin> <mouse-1>" 'bm-toggle-mouse
;; Buffer navigation
"H-b" 'my:find-buffer
"H-B" 'counsel-locate
"<prior>" 'previous-appropriate-buffer
"<next>" 'next-appropriate-buffer
"C-H-<left>" 'buf-move-left
"C-H-<right>" 'buf-move-right
"C-H-<up>" 'buf-move-up
"C-H-<down>" 'buf-move-down
"C-<up>" 'my:scroll-down-some-lines
"C-<down>" 'my:scroll-up-some-lines
"C-<left>" 'my:scroll-left-some-lines
"C-<right>" 'my:scroll-right-some-lines
;; screen recording and screenshots
"M-<print>" 'camcorder-mode
;; Quick access to toggle play/pause
"<pause>" (ilambda () (my:emms-do-then-show 'emms-pause))
;; Other
"H-=" (ilambda () (text-scale-increase 1))
"H--" (ilambda () (text-scale-decrease 1))
;; Auto Yasnippet
"H-W" 'aya-create
"H-w" 'aya-expand)
(general-define-key :keymaps 'isearch-mode-map
"<backspace>" 'isearch-del-char
"<escape>" 'isearch-exit
"<C-escape>" 'isearch-abort
"<return>" 'isearch-repeat-forward
"S-<return>" 'isearch-repeat-backward
"<right>" 'isearch-yank-word-or-char
"<left>" 'isearch-del-char
"<down>" 'isearch-repeat-forward
"<up>" 'isearch-repeat-backward)
(general-define-key :keymaps 'ivy-minibuffer-map
"S-<return>" 'ivy-immediate-done)
(general-define-key :keymaps 'camcorder-moode-map
"M-<print>" 'camcorder-stop)
(general-define-key :keymaps '(query-replace-map multi-query-replace-map)
"<up>" 'backup
"<down>" 'skip
"<escape>" 'exit
"<return>" 'act
"<insert>" 'edit)
(general-define-key :keymaps 'slime-mode-map
"C-x C-r" 'slime-macroexpand-1
"C-\\" 'my:slime-eval-region-or-sexp
"H-h" (ilambda () (call-interactively 'slime-documentation))
"TAB" (gen-smart-tab-key 'indent-for-tab-command 'auto-complete 2)
"H-h" 'common-lisp-hyperspec
"H-d" 'my:slime-describe-symbol)
(general-define-key :keymaps 'lisp-mode-map
"M-<right>" 'forward-list
"M-<down>" 'down-list
"M-<left>" 'backward-list
"M-<up>" 'up-list)
(general-define-key :keymaps 'nov-mode-map
"<home>" nil
"<end>" nil)
(general-define-key :keymaps 'swiper-map
"H-r" 'my:swiper-query-replace-all)
(general-define-key :keymaps 'c-mode-map
"<tab>" 'c-smart-tab-key
"TAB" 'c-smart-tab-key
"C-t" 'c-indent-line-or-region
"C-r" 'align-current)
(general-define-key :keymap js-mode-map
"C-;" 'comment-or-uncomment-region)
(general-define-key :keymaps 'emacs-lisp-mode-map
"C-\\" 'eval-region)
(general-define-key :keymaps 'emmet-mode-keymap
"TAB" 'emmet-expand-yas) ;; my tab key management is getting out of hand & I need to do something about it.
(general-define-key :keymaps 'c-mode-map :prefix "H-c"
"1" 'c-insert-region-heading
"2" 'c-insert-function-skeleton
"3" 'c-insert-forced-todo
"4" 'c-insert-debugging-printf
"5" 'c-insert-ternary-for-boolean-to-string
"6" 'c-insert-todo-comment
"f" 'c-insert-if-block
"o" 'c-insert-for-block
"w" 'c-insert-while-block
"d" 'c-insert-do-while-block
"F" 'c-insert-preproc-if-block
"D" 'c-insert-preproc-ifdef-block
"N" 'c-insert-preproc-ifndef-block
"I" 'c-insert-preproc-include-block)
(general-define-key :keymaps 'python-mode-map
"H-/" 'counsel-jedi
"C-c C-c" 'python-shell-send-defun)
(general-define-key :keymaps 'prog-mode-map
"H-;" 'my:comment-region-or-line
"H-:" 'my:comment-region-or-line-keep-clone
"H-e o" 'poporg-dwim
"H-e s" 'my:edit-as-source
"<backtab>" 'my:hs-toggle-hiding-all
"<tab>" 'hs-toggle-hiding)
(general-define-key :keymaps 'auto-highlight-symbol-mode-map
"s-<up>" 'ahs-backward
"s-<down>" 'ahs-forward)
(general-define-key :keymaps 'org-agenda-mode-map
"p" 'org-agenda-priority
"t" 'my:org-agenda-todo-and-redo
"T" 'org-todo-yesterday
"s" 'org-agenda-schedule
"M-<up>" 'backward-paragraph
"M-<down>" 'forward-paragraph
"<RET>" 'my:org-agenda-switch-to)
(general-define-key :keymaps 'shell-mode-map
"C-p" 'comint-previous-input
"C-n" 'comint-next-input
"C-M-l" nil)
(general-define-key :keymaps 'flyspell-mode-map
"H-c" 'flyspell-auto-correct-word)
(general-define-key :keymaps 'pdf-view-mode-map
"H-a" 'isearch-forward) ;; pdf-tools has specific support for this search method
(general-define-key :keymaps '(org-mode-map org-agenda-mode-map)
"S-<left>" 'tab-bar-switch-to-prev-tab ;; take this back
"S-<right>" 'tab-bar-switch-to-next-tab
"S-<up>" 'tab-bar-new-tab
"C-S-<up>" 'tab-bar-undo-close-tab
"S-<down>" 'tab-bar-close-tab
"C-S-<left>" (ilambda () (tab-bar-move-tab -1))
"C-S-<right>" (ilambda () (tab-bar-move-tab 1))
"C-S-SPC" 'tab-bar-rename-tab
"C-c C-t" 'my:org-todo
"H-x" 'org-babel-execute-buffer
"H-." (ilambda () (progn
(org-insert-time-stamp nil t t)
(newline)))
"<H-M-return>" 'org-babel-demarcate-block
"<print>" 'ros
"S-<insert>" 'org-download-clipboard
"H-;" 'my:comment-region-or-line
"H-t" 'my:org-insert-todo-bottom-current-heading)
(general-define-key :keymaps 'dired-mode-map
"o" 'dired-omit-mode
"p" (ilambda () (emms-play-dired) (my:voice-note-make-timestamp))
"i" 'dired-subtree-cycle
"e" 'dired-toggle-read-only)
(general-define-key :keymaps 'notmuch-show-mode-map
"v" (ilambda () (call-process
"thunderbird" nil nil nil
(notmuch-show-get-filename))))
(general-define-key :keymaps 'copilot-mode-map
"<tab>" 'copilot-accept-completion)
(general-define-key :keymaps 'gptel-mode-map
"C-k" 'my:gptel-reset-conversation)
(general-define-key :keymaps 'cider-mode-map
"C-c C-t t" 'my:cider-eval-and-run-test-at-point)
(customize-set-variable 'exwm-input-global-keys
`((,(kbd "H-<left>") . windmove-left)
(,(kbd "H-s <up>") . split-window-below)
(,(kbd "H-<right>") . windmove-right)
(,(kbd "H-<down>") . windmove-down)
(,(kbd "H-<up>") . windmove-up)
(,(kbd "<H-f8>") . my:kill-current-buffer)
(,(kbd "S-<F8>") . delete-window)
(,(kbd "S-<F8>") . delete-window)
(,(kbd "H-b") . my:find-buffer)
(,(kbd "H-S-<up>") . ,(ilambda () (enlarge-window (if (e27-p) 1 5)))) ;; Emacs 27 is fast at this
(,(kbd "H-S-<right>") . enlarge-window-horizontally)
(,(kbd "H-S-<down>") . ,(ilambda () (shrink-window (if (e27-p) 1 5)))) ;; Emacs 27 is fast at this
(,(kbd "H-S-<left>") . shrink-window-horizontally)
(,(kbd "H-s <down>") . split-window-above)
(,(kbd "H-s <left>") . split-window-right)
(,(kbd "H-s <right>") . split-window-left)
(,(kbd "H-c") . exwm-input-release-keyboard)
(,(kbd "H-l") . exwm-input-grab-keyboard)
(,(kbd "H-e") . exwm-edit--compose)))
(customize-set-variable 'exwm-input-simulation-keys
`((,(kbd "H-a") . ,(kbd "C-f"))
(,(kbd "<insert>") . ,(kbd "C-c"))
(,(kbd "S-<insert>") . ,(kbd "C-v"))))
Org agenda keymap modifications. Changing the keymap itself doesn’t seem to work.
(add-hook 'org-agenda-mode-hook
(lambda ()
(local-set-key (kbd "b")
(ilambda ()
(if (eq org-agenda-dim-blocked-tasks 't)
(setq org-agenda-dim-blocked-tasks 'invisible)
(setq org-agenda-dim-blocked-tasks 't))
(org-agenda-redo 't)))))
Clear out some keys from the vterm-map that I just never actually need in the terminal.
(when (boundp 'vterm-mode-map)
(mapcar (lambda (key)
(define-key vterm-mode-map (kbd key) nil))
'("<f1>" "<f2>" "<f3>" "<f4>" "<f5>" "<f6>"
"<f7>" "<f8>" "<f9>" "<f10>" "<f11>" "<f12>"
"<prior>" "<next>")))
(message "SECTION: Utility.")
This section defines general purpose code snippets, which are used throughout the rest of the file.
(defmacro my:recenter-on-jump (&rest body)
"If, during the evaulation of body, point moves past the window's limits,
in either direction, recenter the buffer in the window."
`(let*
((last-pt (window-end))
(first-pt (window-start)))
,@body
(when (or (> (point) last-pt) (< (point) first-pt))
(recenter))))
(defmacro my:retain-position-from-symbol-start (&rest body)
"Any change of point in this macro body will be readjusted to retain
the starting distance from the beginning of the current symbol. Most
reasonable use is when jumping between occurrences of the same symbol."
`(let*
((pos (point))
(offset (save-excursion
(when (not (looking-at "\\_<"))
(search-backward-regexp "\\_<"))
(- pos (point)))))
,@body
(when (not (looking-at "\\_<"))
(search-backward-regexp "\\_<"))
(right-char offset)))
(defmacro my:do-with-silent-bell (&rest body)
"Evaluate body with the system bell silenced. Note that any changes
to ring-bell-function during body will be lost."
`(let
((ring-bell-function-backup ring-bell-function))
(setq ring-bell-function nil)
,@body
(setq ring-bell-function ring-bell-function-backup)))
General functions.
(defun match-strings-all (&optional string)
"Return the list of all expressions matched in last search.
STRING is optionally what was given to `string-match'.
From https://www.emacswiki.org/emacs/ElispCookbook#toc36"
(let ((n-matches (1- (/ (length (match-data)) 2))))
(mapcar (lambda (i) (match-string i string))
(number-sequence 0 n-matches))))
(defun my:kill-current-buffer ()
"kill the current buffer."
(interactive)
(kill-buffer (current-buffer)))
(defun my:keyboard-escape-quit ()
"Just silence the bell"
(interactive)
(my:do-with-silent-bell
(keyboard-escape-quit)))
(defun my:line-word-char-count (&optional start end)
"Returns formatted string with number of lines, words
and characters in region or whole buffer."
(interactive)
(let ((n 0)
(start (if mark-active (region-beginning) (point-min)))
(end (if mark-active (region-end) (point-max))))
(save-excursion
(goto-char start)
(while (< (point) end) (if (forward-word 1) (setq n (1+ n)))))
(format "[ L%d W%d C%d ] " (count-lines start end) n (- end start))))
(defun my:line-to-top-of-window ()
"Shift current line to the top of the window- i.e. zt in Vim"
(interactive)
(set-window-start (selected-window) (point)))
(defun my:purge-buffers ()
"Kill all buffers which aren't being shown."
(interactive)
(mapcar 'kill-buffer (remove-if '(lambda (x) (with-current-buffer x (get-buffer-window))) (buffer-list))))
(defun my:set-window-opacity (percent)
"Set window opacity. Prefix arg is opacity in percent."
(interactive "p")
(set-frame-parameter (selected-frame) 'alpha percent))
Set window size from elisp.
(defun set-window-width (cols)
"Set the selected window's width."
(window-resize (selected-window) (- cols (window-width)) t))
(defun set-window-height (rows)
"Set the selected window's height."
(window-resize (selected-window) (- rows (window-height))))
Marries a buffer to a window.
(define-minor-mode sticky-buffer-mode
"Make the current window always display this buffer."
nil " sticky" nil
(set-window-dedicated-p (selected-window) sticky-buffer-mode))
This is a purely TODO item. Previous version had some code to monitor a buffer and alert upon new appearances of a particular regex.
Preload swiper with thing at point.
;; https://github.com/abo-abo/swiper/issues/1068
(defun ivy-with-thing-at-point (cmd)
(let ((ivy-initial-inputs-alist
(list
(cons cmd (thing-at-point 'symbol)))))
(funcall cmd)))
;; Example 2
(defun swiper-thing-at-point ()
(interactive)
(ivy-with-thing-at-point 'swiper))
Working with Seafile conflicts.
(defvar my:seafile-conflict-fname-regex "\\(.*\\) (SFConflict \\([^[:space:]]+\\) \\([^[:space:]]+\\))\\(.*\\)")
(defun my:delete-current-file-kill-buffer ()
"Delete file associated with current buffer, then kill the buffer"
(interactive)
(let
((file (buffer-file-name)))
(if (not file)
(error "No file associated with buffer!")
(if (yes-or-no-p (concat "Delete file? "))
(progn
(delete-file file)
(kill-buffer))))))
(defun my:fname-seafile-conflict-p (filename)
"Is this filename a Seafile conflict file?
Return fnamepart, user, date, and extension if so.
'somefilename (SFConflict [email protected] 1900-02-12).org' -> '(somefilename [email protected] 1900-02-12 .org"
(if (string-match my:seafile-conflict-fname-regex filename)
(rest (match-strings-all filename))))
(defun my:seafile-conflict-source-fname (filename)
"Return the path to the original file from which this conflict was created."
(let*
((parts (my:fname-seafile-conflict-p filename))
(fname (and parts (nth 0 parts)))
(ext (and parts (nth 3 parts))))
(concat fname ext)))
(defun my:seafile-list-conflicting-files (path)
"Search a path recursively for Seafile conflict files, and return any hits."
(directory-files-recursively path my:seafile-conflict-fname-regex))
(defun my:delete-conflict-file (conflict-file &optional prompt)
"Delete a conflict file -- optionally prompt the user."
(if (or (not prompt) (yes-or-no-p (concat "Source file deleted for \"" conflict-file "\", delete conflict file? ")))
(delete-file conflict-file)))
(defun my:merge-seafile-conflict (source conflict &optional prompt)
"For now, just opens both."
(if (or (not prompt) (y-or-n-p (concat "Merge conflict for \"" source "\"? ")))
(progn
(find-file source)
(split-window-left)
(find-file conflict))))
(defun my:seafile-merge-conflicts (path)
"Interactively merge conflicting files in a given path."
(interactive "DDirectory:")
(let*
((conflict-file (completing-read "Choose conflict to merge: " (my:seafile-list-conflicting-files path)))
(source-file (my:seafile-conflict-source-fname conflict-file)))
(if (and (file-exists-p conflict-file)
(file-exists-p source-file))
(my:merge-seafile-conflict source-file conflict-file)
(my:delete-conflict-file conflict-file t))))
(defun my:org-merge-conflict ()
"Interactively merge "
(interactive)
(my:seafile-merge-conflicts my:org-file-tree-base-path))
ros from LionxyML.
(defun ros ()
(interactive)
(let ((filename
(concat "./"
(file-name-nondirectory buffer-file-name)
"_"
(format-time-string "%Y%m%d_%H%M%S")
".png")))
(call-process "scrot" nil nil nil "-s" filename)
(when (file-exists-p filename)
(insert (concat "#+ATTR_ORG: :width 400\n[[" filename "]]")))
(org-display-inline-images t t)))
Let it hook into dired-mode
(add-hook 'dired-mode-hook 'org-download-enable)
Attach the ‘normal’ org way
(customize-set-variable 'org-download-method 'attach)
Looks for org-mode link at point, attempts to open it in KolourPaint, then refreshes images after exiting.
(defun my:org-edit-sketch (sketch-name)
(start-process "sketch-process" nil "kolourpaint" (concat (file-name-directory (buffer-file-name)) "sketch-" sketch-name ".png")))
(org-link-set-parameters "sketch" :follow 'my:org-edit-sketch)
Add a command for quickly scanning multi-page PDFs.
Notes on getting these to work on Linux:
- Have these working with an Epson ES-400, using document feeder and full-duplex scanning correctly
- utsushi is a pain to install and a pain to keep working. Try scanimage first. The latter works okay on Ubuntu, but I had no such luck on Archlinux.
- Imagemagick’s default settings aren’t very practical. You need to remember to enable PDF mode (security risk due to historical bugs in ghostscript), and need to remember to grow the disk cache limits.
Under ubuntu, the imagemagick settings can be edited with “sudo nano /etc/ImageMagick-6/policy.xml”
- Change: <policy domain=”coder” rights=”none” pattern=”PDF” />
- To: <policy domain=”coder” rights=”read|write” pattern=”PDF” />
- Change: <policy domain=”resource” name=”disk” value=”1GiB”/>
- To: <policy domain=”resource” name=”disk” value=”80GiB”/>
- (or something that feels okay to you)
If you don’t change the disk limit, ImageMagick will start truncating your PDFs. It spits out a warning, but emacs won’t show it. Make this a big value to avoid data loss!
TODO: OCR?
(defcustom my:preferred-scanner nil
"Name of scanner used in the call to utsushi.")
(defcustom my:scanned-document-destination nil
"Default directory for storing scanned documents.")
(defun my:utsushi-list-devices ()
"Get a list of all devices utsushi can see."
(split-string (shell-command-to-string "utsushi list")))
(defun my:scanimage-list-devices ()
"Get a list of all devices utsushi can see."
(split-string (shell-command-to-string "scanimage -L")))
(defun my:read-pdf-from-scanner/utsushi ()
"Read a multipage duplex PDF from the automatic document feeder. Uses the utsushi program directly
because the SANE backend for my scanner does not seem to be working correctly yet.
Prompts for save location."
(interactive)
(let*
((capture-dir (read-directory-name "Save Scan Inside: " my:scanned-document-destination))
(basename (read-string "File Name (no extension or date): "))
(document-date (org-read-date nil nil nil "Document Date: "))
(captured-path (concat (file-name-as-directory capture-dir) document-date "-" basename ".pdf"))
(file-already-exists (file-exists-p captured-path))
(scanner-list (my:utsushi-list-devices))
(scanner (or (and my:preferred-scanner (memq my:preferred-scanner scanner-list))
(concat "\"" (completing-read "Choose scanner: " scanner-list) "\""))))
(when (and (y-or-n-p (concat "Scanning to \"" captured-path "\", continue? "))
(or (not file-already-exists)
(yes-or-no-p (concat captured-path " already exists -- sure you want to overwrite? "))))
(shell-command-to-string
(concat
"tmpfileraw=$(mktemp)\n"
"tmpfilebig=$(mktemp)\n"
"mkdir -p `dirname \"" captured-path "\"`\n"
"utsushi scan " scanner " $tmpfileraw --no-interface --image-format=TIFF --resolution=250 --duplex\n"
"convert tiff:$tmpfileraw -fuzz 1% -trim -density 250 +repage pdf:$tmpfilebig\n"
"gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/printer -dNOPAUSE -dQUIET -dBATCH -sOutputFile=\"" captured-path "\" $tmpfilebig"))
(find-file-other-window captured-path))))
(defun my:read-pdf-from-scanner/scanimage ()
"Read a multipage duplex PDF from the automatic document feeder. Uses the scanimage program. Prompts for save location."
(interactive)
(let*
((capture-dir (read-directory-name "Save Scan Inside: " my:scanned-document-destination))
(basename (read-string "File Name (no extension or date): "))
(document-date (org-read-date nil nil nil "Document Date: "))
(captured-path (concat (file-name-as-directory capture-dir) document-date "-" basename ".pdf"))
(file-already-exists (file-exists-p captured-path))
(scanner my:preferred-scanner) ;; scanimage -L is VERY slow...10 seconds or more
;(scanner-list (my:scanimage-list-devices))
;(scanner (or (and my:preferred-scanner (memq my:preferred-scanner scanner-list))
; (concat "\"" (completing-read "Choose scanner: " scanner-list) "\"")))
)
(when (and (y-or-n-p (concat "Scanning to \"" captured-path "\", continue? "))
(or (not file-already-exists)
(yes-or-no-p (concat captured-path " already exists -- sure you want to overwrite? "))))
(let*
((temp-dir (concat (string-trim (shell-command-to-string "mktemp -d")) "/"))
(script-path (concat temp-dir "scan-cmds.sh"))
(script-body (concat
"tmpdir=\"" temp-dir "\"\n"
"mkdir -p \"$tmpdir/raws\"\n"
"mkdir -p `dirname \"" captured-path "\"`\n"
"sudo scanimage -d \"" scanner "\" --format tiff --mode Color --resolution 300 --batch=\"$tmpdir/raws/p-%04d.tiff\" --source 'ADF Duplex'\n"
"magick convert \"$tmpdir/raws/p-*.tiff\" -fuzz 1% -trim -compress jpeg -quality 70 +repage \"$tmpdir/combined.pdf\"\n"
"cp \"$tmpdir/combined.pdf\" \"" captured-path "\"\n"
))
(cmd (concat "bash \"" script-path "\" && rm -r \"" temp-dir "\"")))
(message "Using temp diectory at %s" temp-dir)
(with-temp-file script-path
(insert script-body))
(message "%s" cmd)
(message "%s" (shell-command-to-string cmd)))
(find-file-other-window captured-path))))
(defun my:org-insert-link-to-scanned-document (&optional prefix)
"At point, insert link to the last modified PDF file in `my:scanned-document-destination'. With
prefix argument, select from a list of all scanned documents, sorted by recency of modification."
(interactive "P")
(let*
((raw-file-list (shell-command-to-string
(concat "find \"" my:scanned-document-destination "\" -type f -exec ls -1t \"{}\" +;")))
(file-list (split-string raw-file-list "\n")))
(org-insert-link
nil
(if prefix
(ivy-read
"Insert link to: "
file-list)
(first file-list))
(read-string "Description: "))))
Run voicenotes2org
, and open my unfiled voice notes file.
(defun my:file-voice-notes ()
"Kick off transcription of any new voice-notes, and open the unfiled notes file."
(interactive)
(async-shell-command "voicenotes2org")
(find-file "/sync/documents/org/voice-notes/unfiled-notes.org"))
Small function that creates and shows a new EMMS buffer with tracks from my voice notes directory (synced from my phone).
(defun my:voice-notes ()
"Open voice notes in EMMS buffer for processing."
(interactive)
(split-window-below)
(emms-play-directory "/sync/voice-notes")
(emms)
(emms-playlist-sort-by-file-mtime))
This function, when called from a dired buffer, attempts to check the filename under point against my voice note naming regex. If it matches, then it creates an inactive timestamp that corresponds to the filename and pushes that onto the kill-ring. I use this for transcribing and categorizing voice notes.
(defun my:voice-note-make-timestamp ()
"In dired buffer, with point over a voice note, create an org timestamp and push it onto the kill-ring."
(interactive)
(let ((fname (dired-get-filename)))
(when (string-match ".*My recording \\([[:digit:]]+\\)-\\([[:digit:]]+\\)-\\([[:digit:]]+\\) \\([[:digit:]]+\\)-\\([[:digit:]]+\\) \\(..\\).*\\.wav" fname)
(let*
((year (match-string 1 fname))
(month (match-string 2 fname))
(day (match-string 3 fname))
(hour (match-string 4 fname))
(minute (match-string 5 fname))
(ampm (match-string 6 fname))
(hour (if (string= ampm "PM") (number-to-string (+ 12 (string-to-number hour))) hour))
(normed-timestr (concat year "-" month "-" day " " hour ":" minute))
(time (apply 'encode-time (parse-time-string normed-timestr))))
(kill-new (format-time-string "[%Y-%m-%d %a %H:%M]" time))
(message "Timestamp pushed onto kill-ring.")))))
Add an org-link type that opens a webpage archive in Firefox. Also, add a function which downloads the webpage and all dependencies using wget.
(defun my:org-slurp-webpage ()
(interactive)
(let*
((shortname (read-string "Name for Archived Page:"))
(url (read-string "URL to Archive:"))
(cachefname (concat (file-name-directory (buffer-file-name)) "cache-" shortname))
(linktarget (concat "./cache-" shortname "/" url)))
(start-process "slurp-process" nil "wget" "--page-requisites"
"--convert-links"
"--no-parent"
"--html-extension"
"--directory-prefix" cachefname
url)
(insert (concat "[[pagecache:" linktarget "][" shortname "]]"))))
(defun my:org-open-slurped-webpage (name)
(start-process "firefox-process" nil "firefox" (concat name)))
(org-link-set-parameters "pagecache" :follow 'my:org-open-slurped-webpage)
Get battery life
(defun my:battery-percent ()
(string-to-number (battery-format "%p" (funcall battery-status-function))))
(defun my:battery-percent ()
100)
(defun my:battery-message ()
(battery-format " %t (%p%%%%) " (funcall battery-status-function)))
(defun my:battery-check ()
"Echo battery level."
(interactive)
(message (my:battery-message)))
Open a CMD shell buffer on WSL.
(defun my:open-cmd-shell-buffer ()
(interactive)
(process-send-string (get-buffer-process "*shell*") "cmd.exe"))
From within WSL
, call waf
on a product name using MSYS2 bash
as
packaged by the Window Git
distribution.
It’s shell hell folks.
(defun my:wsl-waf ()
(interactive)
(let ((shell-file-name "/c/Program Files/Git/bin/bash.exe"))
(compile (concat "./waf " (read-string "Product Name: ")))))
Reopen current file as sudo.
(defun my:reopen-sudo ()
"Reopen current file as sudo."
(interactive)
(find-file (concat "/sudo::" (buffer-file-name))))
Copy current buffer’s path to clipboard. Taken from StackOverflow.
(defun my:copy-buffer-filename-as-kill ()
"Put the current file name on the clipboard"
(interactive)
(let ((filename (if (equal major-mode 'dired-mode)
default-directory
(buffer-file-name))))
(when filename
(with-temp-buffer
(insert filename)
(clipboard-kill-region (point-min) (point-max)))
(message filename))))
Gather number at point, interpret it as seconds since Unix epoch, and echo the formatted datetime.
(defun my:echo-datetime-at-point ()
"Message integer at point as a Unix timestamp."
(interactive)
(message (concat
(format-time-string "%D %T Local" (seconds-to-time (thing-at-point 'number)) nil)
(format-time-string " (%D %T UTC)" (seconds-to-time (thing-at-point 'number)) t))))
Insert current time as unix epoch.
(defun my:insert-epoch-time ()
"Insert current seconds since epoch at point."
(interactive)
(insert (number-to-string (time-to-seconds))))
Increment the decimal number at point, modified from Emacs Wiki. Took the “simple” solution and added prefix arg support. The advanced alternative listed there is prone to creating leading zeros on decrement (100->099), which is bad news in languages like C and Python 2, where leading zero implies an octal literal.
(defun my:increment-number-at-point (&optional arg)
(interactive "p*")
(skip-chars-backward "0-9")
(or (looking-at "[0-9]+")
(error "No number at point"))
(replace-match (number-to-string (+ (string-to-number (match-string 0))
(if arg arg 1)))))
Emacs defines commands both 'toggle-debug-on-error
and 'toggle-debug-on-quit
, but debugging on a specific message is only supported by a variable. Add 'toggle-debug-on-message
, which clears the current trigger message if it is set, and otherwise, prompts the user to enter a regex.
(defun toggle-debug-on-message (regex)
"Set a message to debug on, or clear it."
(interactive "sRegex Trigger: ")
(if debug-on-message
(setq debug-on-message nil)
(setq debug-on-message regex)))
Quickly clear out byte-compiled elisp files.
(defun my:clear-byte-compiled-init-files ()
"Clean up *.elc files in init dir."
(interactive)
(shell-command-to-string "find ~/.emacs.d/ -name \"*.elc\" -type f | xargs rm -f"))
(message "SECTION: User Interface Settings")
This section defines code which modifies how emacs displays buffers and interacts with the user.
Make sure emacsclient doesn’t open in the background.
(add-hook 'server-switch-hook
(lambda ()
(raise-frame)
(select-frame-set-input-focus (selected-frame))))
Allow file management in counsel’s file-find
;; https://github.com/abo-abo/swiper/wiki/Copy,-move-and-delete-file-actions-for-counsel-find-file
(defun reloading (cmd)
(lambda (x)
(funcall cmd x)
(ivy--reset-state ivy-last)))
(defun given-file (cmd prompt) ; needs lexical-binding
(lambda (source)
(let ((target
(let ((enable-recursive-minibuffers t))
(read-file-name
(format "%s %s to:" prompt source)))))
(funcall cmd source target 1))))
(defun confirm-delete-file (x)
(dired-delete-file x 'confirm-each-subdirectory))
(ivy-add-actions
'counsel-find-file
`(("c" ,(given-file #'copy-file "Copy") "copy")
("d" ,(reloading #'confirm-delete-file) "delete")
("m" ,(reloading (given-file #'rename-file "Move")) "move")))
(ivy-add-actions
'counsel-projectile-find-file
`(("c" ,(given-file #'copy-file "Copy") "copy")
("d" ,(reloading #'confirm-delete-file) "delete")
("m" ,(reloading (given-file #'rename-file "Move")) "move")
("b" counsel-find-file-cd-bookmark-action "cd bookmark")))
Show current/total matches in isearch
(global-anzu-mode +1)
Show whitespace
(global-whitespace-mode 0)
(setq whitespace-style '(face trailing))
Use visual bell instead of audible, modified from code by Miles Bader. Code mirror on GitHub here.
(defun echo-area-flash (color &optional duration message)
"Flash a colorful message in the echo area."
(setq duration (or duration 0.1))
(setq message (or message ""))
(unless (memq this-command '(keyboard-quit keyboard-escape-quit))
(message (propertize
(concat
(propertize
"x"
'display
`(space :align-to (- right ,(+ 2 (length message)))))
message)
'face `(:background ,color :foreground "white" :weight bold)))
(sit-for duration)
(message "")))
(defun echo-area-bell ()
"Flash the some color in the echo area."
(echo-area-flash "red" nil "*DING* "))
(defun ding ()
(echo-area-flash "hotpink"))
(setq ring-bell-function 'echo-area-bell)
Get rid of blinking cursor
(setq default-cursor-type 'box)
(blink-cursor-mode -1)
Display trailing whitespace
(setq-default show-trailing-whitespace t)
Highlight body of parens
(setq show-paren-style 'expression)
(show-paren-mode 1)
Use a consistent window name. This helps with AutoHotkey recognition on Windows, in addition to being a little more usable in Linux.
(setq frame-title-format (if (equal system-type 'windows-nt) "emacs-nt" "emacs"))
Make sure we push any kill-ring data into OS clipboard.
(setq save-interprogram-paste-before-kill t)
Highlight the current line
(global-hl-line-mode)
When buffer is not modified in emacs, automatically reload it when its file changes on disk. Also allow dired, etc, to autorevert.
(global-auto-revert-mode)
(customize-set-variable 'global-auto-revert-non-file-buffers t)
Tweak the text shown when hiding regions of a buffer.
(setq hide-region-before-string "hidden-region-->")
(setq hide-region-after-string "<--hidden-region")
Make sure that diff-hl-mode is showing changes between now and the last commit, only.
(global-diff-hl-amend-mode 1)
Fall back to frame-moving if window-moving doesn’t cut it.
(setq framemove-hook-into-windmove t)
Remove flashy splash stuff
(setq inhibit-splash-screen t)
(setq inhibit-startup-echo-area-message t)
(setq inhibit-startup-screen t)
(setq inhibit-startup-message t)
Show prefix keys in the echo area much faster.
(setq echo-keystrokes 0.001)
Indicate tabs.
(standard-display-ascii ?\t "→ ")
And never insert tabs.
(customize-set-variable 'indent-tabs-mode nil)
Highlight some common tags.
(font-lock-add-keywords 'prog-mode
'(("\\<\\(DEBUG\\)" 1 font-lock-warning-face prepend)
("\\<\\(FIXME\\)" 1 font-lock-warning-face prepend)
("\\<\\(TODO\\)" 1 font-lock-warning-face prepend)
("\\<\\(JIRA\\)" 1 font-lock-warning-face prepend)))
Clear the minibuffer.
(delete-minibuffer-contents)
Move the cursor to corner of display, except on Windows. On that platform, this makes placing the window via click and drag almost impossible. The window repeatedly moves the cursor away from the window, while the mouse is being clicked…causing the window to continually jump right with it.
(unless-on-windows
(mouse-avoidance-mode 'none))
Advise delete-window to deal with single-window frames, single-window tabs, and single-tab frames.
- If the window is the last window in its tab, and also the last tab in the frame, then delete the frame.
- If the window is the last window in its tab, but there are more tabs in the frame, then just close the tab.
- If tab bar mode is disabled and the window is the last one in the frame, then delete the frame.
- In any other case, allow delete-window to actually delete the window.
Modified from frame-cmds.el.
(defadvice delete-window (around delete-frame-if-one-win activate)
"If WINDOW is the only one in its frame, then `delete-frame' too."
(with-selected-window
(or (ad-get-arg 0) (selected-window))
(if (one-window-p t)
(if (and (boundp 'tab-bar-mode) tab-bar-mode)
(if (> (length (tab-bar-tabs)) 1)
(tab-bar-close-tab)
(delete-frame))
(delete-frame))
ad-do-it)))
Give visual feedback on tab creation/close.
(add-hook 'tab-bar-tab-pre-close-functions
(lambda (idx last-tab)
(ding)))
(add-hook 'tab-bar-tab-post-open-functions
(lambda (tab)
(ding)))
Use icons in Ovy (switch buffer, etc)
(all-the-icons-ivy-setup)
Tell Olivetti to use a sane default width.
(custom-set-variables
'(olivetti-body-width 130)
'(olivetti-minimum-body-width 130))
Always open shell buffers in the current window.
(add-to-list 'display-buffer-alist
'("^\\*shell\\*$" . (display-buffer-same-window)))
Run with scissors.
(setq disabled-command-function nil)
Run faster with scissors.
(setq confirm-kill-processes nil)
Use ivy-posframe by default, exempt swiper. Based on the documentation.
DISABLED: Too buggy for now.
(setq ivy-posframe-display-functions-alist '((swiper-isearch . nil) (t . ivy-posframe-display-at-frame-center))) (setq ivy-posframe-height-alist '((swiper-isearch . 15) (t . 35)) ivy-posframe-parameters '((internal-border-width . 1) (left-fringe . 8) (right-fringe . 8) (alpha . 70 ))) (setq ivy-posframe-width 130) (ivy-posframe-mode 1)
This code adds a strongly customized header and mode line.
TODO: Serious cleanup and reorg needed. Works well, but the code is nasty.
(defface my-header-line-face nil "Face of header line.")
(defface my-header-line-inactive-face nil "Face of header line (inactive).")
(defface mode-line-buffer-name-face nil "Face of buffer name in mode line.")
(defface mode-line-buffer-name-inactive-face nil "Face of buffer name in mode line (inactive).")
(defface mode-line-notification-face nil "Face of mode line notifications.")
(defface mode-line-happy-notification-face nil "Face of happy mode line notifications.")
(defface mode-line-notification-inactive-face nil "Face of mode line notifications (inactive).")
(customize-set-variable 'mode-line-in-non-selected-windows nil)
(defmacro my-header-line ()
`(list
'(:eval
(let*
((mface (if (window-has-focus)
'my-header-line-face
'my-header-line-inactive-face)))
(propertize
(concat
" "
(if (buffer-file-name)
(buffer-file-name)
(buffer-name))
(mode-line-fill mface (if (window-has-focus)
0;34
0)))
'face mface)))))
(setq-default header-line-format (my-header-line))
(setq header-line-format (my-header-line))
(defvar my-selected-window nil)
(add-hook 'post-command-hook
(lambda ()
;; (when (not (minibuffer-selected-window))
(setq my-selected-window (selected-window))))
(defun window-has-focus ()
(eq
(selected-window)
my-selected-window))
(defun debug-window-focus ()
(interactive)
(format "%s & %s & %s & %s & %s" mode-line-frame-identification (frame-selected-window) (get-buffer-window) (selected-frame) (window-frame (get-buffer-window))))
(defmacro mode-line-notification-entry (check text help-echo)
`'(:eval (when ,check
(let*
((mface (if (window-has-focus)
'mode-line-notification-face
'mode-line-notification-inactive-face))
(bface (if (window-has-focus)
'my-header-line-face
'my-header-line-inactive-face)))
(concat
(propertize ,text
'face mface
'help-echo ,help-echo)
(propertize " "
'face bface))))))
(defmacro mode-line-happy-notification-entry (check text help-echo)
`'(:eval (when ,check
(let*
((mface (if (window-has-focus)
'mode-line-happy-notification-face
'mode-line-notification-inactive-face))
(bface (if (window-has-focus)
'my-header-line-face
'my-header-line-inactive-face)))
(concat
(propertize ,text
'face mface
'help-echo ,help-echo)
(propertize " "
'face bface))))))
(defmacro mode-line-status-entry (check text help-echo)
`'(:eval (when ,check
(let*
((mface (if (window-has-focus)
'mode-line-buffer-name-face
'mode-line-buffer-name-inactive-face))
(bface (if (window-has-focus)
'my-header-line-face
'my-header-line-inactive-face)))
(concat
(propertize ,text
'face mface
'help-echo ,help-echo)
(propertize " "
'face bface))))))
(defmacro mode-line-buffer-name-entry (format-str help-echo)
`'(:eval
(let*
((mface (if (window-has-focus)
'mode-line-buffer-name-face
'mode-line-buffer-name-inactive-face)))
(propertize ,format-str
'face mface
'help-echo ,help-echo))))
(defmacro mode-line-str-dflt (body)
`'(:eval
(let*
((mface (if (window-has-focus)
'my-header-line-face
'my-header-line-inactive-face)))
(propertize
,body
'face mface))))
(defun mode-line-fill (face reserve)
"Return empty space using FACE and leaving RESERVE space on the right."
(unless reserve
(setq reserve 20))
(when (and window-system (eq 'right (get-scroll-bar-mode)))
(setq reserve (- reserve 3)))
(propertize " "
'display `((space :align-to (- (+ right right-fringe right-margin) ,reserve)))
'face face))
(setq-default mode-line-format
(list
; buffer name field
'(:eval
(let*
((mface (if (window-has-focus)
'mode-line-buffer-name-face
'mode-line-buffer-name-inactive-face)))
(propertize " %b " 'face mface)))
; buffer position field
(mode-line-str-dflt " %p (%l,%c) ")
; major mode field
(mode-line-buffer-name-entry " %m " buffer-file-coding-system)
(mode-line-str-dflt " ")
;;; MODE SPECIFIC AREAS
; text mode: Show word, letter, char count
'(:eval (when (eq major-mode 'text-mode)
(mode-line-str-dflt
(if transient-mark-mode
(my:line-word-char-count (point) (mark))
(my:line-word-char-count)))))
;;; STATUSES: less important stuff
(mode-line-status-entry (/= text-scale-mode-amount 0) (format " ±%d " text-scale-mode-amount) "Font scale")
(mode-line-status-entry buffer-read-only " RO " "Buffer is read-only")
(mode-line-status-entry vc-mode (concat " VC:" vc-mode " ") vc-mode)
(mode-line-status-entry isearch-mode (concat " search: " isearch-string " ") "isearch in progress")
(mode-line-status-entry (and (>= 20 (my:battery-percent))
(< (my:battery-percent) 100))
(my:battery-message) "Battery")
;;; NOTIFICATIONS: IMPORTANT STUFF
(mode-line-happy-notification-entry real-auto-save-mode " AS " "Autosaving Changes")
(mode-line-notification-entry (and (buffer-modified-p)
(buffer-file-name)
(not (bound-and-true-p real-auto-save-mode)))
" !! " "Buffer has been modified")
(mode-line-notification-entry (buffer-narrowed-p) " >< " "Buffer is narrowed")
(mode-line-notification-entry (bound-and-true-p realgud-short-key-mode) " DBG " "Debugger is active.")
;; (mode-line-notification-entry (< (my:battery-percent) 20) (my:battery-message) "Battery")
;; '(:eval (debug-window-focus))
;; '(:eval mode-line-frame-identification)
(mode-line-str-dflt
(let*
((gap (if (window-has-focus)
8
0)))
(mode-line-fill mface gap)))
'(:eval
(when (window-has-focus)
(mode-line-buffer-name-entry (format-time-string " %I:%M%p ") "")))))
When my:use-frames is set, configure emacs to use popup frames. Also, assume that we’re using StumpWM & load it.
(defun my:toggle-use-frames ()
(interactive)
(setq my:use-frames (not my:use-frames)
pop-up-frames my:use-frames)
(if my:use-frames
(message "Using frames.")
(message "Using windows.")))
(when my:use-frames
(progn
(require 'stumpwm-mode)
(add-to-list 'default-frame-alist '(alpha . (90 . 90)))
(setq pop-up-frames t)))
This function produces a closure which helps in calculating easing offsets.
(defun make-easing-calculator (easing-period-seconds reset-period-seconds &optional throttle-period-seconds max-step-size power)
"Returns a closure which yields an eased value."
(lexical-let ((start-time nil)
(last-time nil)
(max-step-size (or max-step-size 1.0))
(easing-period-seconds easing-period-seconds)
(reset-period-seconds reset-period-seconds)
(power (or power 1))
(throttle-period-seconds throttle-period-seconds))
(lambda ()
(let* ((now (float-time))
(in-progress (and last-time (< (- now last-time) reset-period-seconds)))
(duration (if in-progress (- now start-time) 0.0))
(speed (min 1.0 (/ duration easing-period-seconds)))
(speed (expt speed power))
(distance (* speed max-step-size)))
;; First event, set start time
(when (not in-progress)
(setq start-time now))
;; Skip update if too fast
(let ((act-now (or (not throttle-period-seconds)
(not in-progress)
(and last-time
(>= (- now last-time) throttle-period-seconds)))))
(when act-now
(setq last-time now)
distance))))))
Functions for moving frames with an eased speed.
(defun my:fancy-move-frame-right ()
(interactive)
(fancy-move-frame :right))
(defun my:fancy-move-frame-left ()
(interactive)
(fancy-move-frame :left))
(defun my:fancy-move-frame-up ()
(interactive)
(fancy-move-frame :up))
(defun my:fancy-move-frame-down ()
(interactive)
(fancy-move-frame :down))
(setq move-frame-easing-calc (make-easing-calculator 0.75
0.25
(/ 1.0 45)
(/ (display-pixel-width) 15)
3))
(defun fancy-move-frame (direction)
"Move frame with easing. Use with a repeatable keybinding."
(let ((distance (funcall move-frame-easing-calc)))
(when distance
(setq distance (max distance 1))
(case direction
(:right (move-frame distance 0))
(:left (move-frame (- distance) 0))
(:down (move-frame 0 distance))
(:up (move-frame 0 (- distance)))))))
(defun move-frame (delta-x delta-y)
(let* ((pos (frame-position))
(x (car pos))
(y (cdr pos))
(new-x (round (+ x delta-x)))
(new-x (max 0 (min (display-pixel-width) new-x)))
(new-y (round (+ y delta-y)))
(new-y (max 0 (min (display-pixel-height) new-y))))
(set-frame-position nil new-x new-y)))
Functions for resizing frames with an eased speed.
(defun my:fancy-resize-frame-bigger-horizontally ()
(interactive)
(fancy-resize-frame :bigger-horizontally))
(defun my:fancy-resize-frame-smaller-horizontally ()
(interactive)
(fancy-resize-frame :smaller-horizontally))
(defun my:fancy-resize-frame-bigger-vertically ()
(interactive)
(fancy-resize-frame :bigger-vertically))
(defun my:fancy-resize-frame-smaller-vertically ()
(interactive)
(fancy-resize-frame :smaller-vertically))
(setq resize-frame-easing-calc (make-easing-calculator 0.75
0.25
(/ 1.0 45)
8
3))
(defun fancy-resize-frame (direction)
"Resize frame with easing. Use with a repeatable keybinding."
(let ((distance (funcall resize-frame-easing-calc)))
(when distance
(setq distance (max distance 1))
(case direction
(:bigger-horizontally (resize-frame distance 0))
(:smaller-horizontally (resize-frame (- distance) 0))
(:bigger-vertically (resize-frame 0 (* 0.5 distance)))
(:smaller-vertically (resize-frame 0 (* 0.5 (- distance))))))))
(defun resize-frame (delta-x delta-y)
(let* ((x (frame-width))
(y (frame-height))
(new-x (round (+ x delta-x)))
(new-x (max 0 (min (display-pixel-width) new-x)))
(new-y (round (+ y delta-y)))
(new-y (max 0 (min (display-pixel-height) new-y))))
(set-frame-size nil new-x new-y)))
Functions for resizing windows with eased speed.
(defun my:fancy-resize-window-bigger-horizontally ()
(interactive)
(fancy-resize-window :bigger-horizontally))
(defun my:fancy-resize-window-smaller-horizontally ()
(interactive)
(fancy-resize-window :smaller-horizontally))
(defun my:fancy-resize-window-bigger-vertically ()
(interactive)
(fancy-resize-window :bigger-vertically))
(defun my:fancy-resize-window-smaller-vertically ()
(interactive)
(fancy-resize-window :smaller-vertically))
(setq resize-window-easing-calc (make-easing-calculator 0.75
0.25
(/ 1.0 15)
20
3))
(defun fancy-resize-window (direction)
"Resize window with easing. Use with a repeatable keybinding."
(let ((distance (funcall resize-window-easing-calc)))
(when distance
(setq distance (round (max distance 1)))
(case direction
(:bigger-horizontally (shrink-window (- distance) t))
(:smaller-horizontally (shrink-window distance t))
(:bigger-vertically (shrink-window (- distance)) nil)
(:smaller-vertically (shrink-window distance))))))
Tell the uniquify
package how to rename buffers.
(custom-set-variables
'(uniquify-buffer-name-style 'post-forward nil (uniquify)))
Initialize the scratch buffer to org-mode
.
(custom-set-variables
'(initial-major-mode (quote org-mode))
'(initial-scratch-message
"This buffer is for text that is not saved, and for Lisp evaluation.
To create a file, visit it with \\[find-file] and enter text in its buffer.
#+BEGIN_SRC emacs-lisp
#+END_SRC
"))
DEACTIVATED: Optionally, scroll on a per-pixel basis, like it’s 1999 (rather than 1989). Based on advice on StackOverflow.
(pixel-scroll-mode) (setq pixel-dead-time 0) ; Never go back to the old scrolling behaviour. (setq pixel-resolution-fine-flag t) ; Scroll by number of pixels instead of lines (t = frame-char-height pixels). (setq mouse-wheel-scroll-amount '(1)) ; Distance in pixel-resolution to scroll each mouse wheel event. (setq mouse-wheel-progressive-speed nil) ; Progressive speed is too fast for me.
Don’t do special processing for tall lines by default. Way too much cursor lag.
(setq-default auto-window-vscroll nil)
When emacs is fullscreened on very large displays, the minibuffer can be so far away from the point of visual focus that I don’t even notice when it’s been unexpectedly activated (y-or-n-p type of questions). So, toggle the frame background color whenever the minibuffer activates.
(defun my:minibuffer-focus-background-color ()
"If minibuffer is active, change the frame background color."
(unless (bound-and-true-p executing-kbd-macro)
(if (minibufferp)
(set-background-color "#110022")
(set-background-color "Black"))))
(defun my:minibuffer-reset-background-color ()
"Unchange the frame background color."
(unless (bound-and-true-p executing-kbd-macro)
(set-background-color "Black")))
(add-hook 'minibuffer-setup-hook #'my:minibuffer-focus-background-color)
(add-hook 'minibuffer-exit-hook #'my:minibuffer-reset-background-color)
(message "SECTION: Theme")
Load my theme.
(add-to-list 'custom-theme-load-path "~/.emacs.d/emacs-config/themes/")
(load-theme 'bgutter t)
(message "SECTION: File Associations")
(add-to-list 'auto-mode-alist '("\\.plt\\'" . gnuplot-mode))
(add-to-list 'auto-mode-alist '("\\.m$" . octave-mode ))
B2C, T2C, and F2C files
(define-generic-mode
'2c-mode ; name of the mode to create
'("*") ; comments start with '!!'
'("c_dep") ; keywords
nil ; special words
'("\\.t2c\\'" "\\.b2c\\'" "\\.f2c\\'") ; files for which to activate this mode
'((lambda () (run-hooks 'prog-mode-hook))) ; other functions to call
"A mode for B2C, T2C, & F2C files") ; doc string for this mode
Monkey-C Mode
(define-generic-mode
'monkey-c-mode
'("//")
'("using" "as" "class" "extends" "function" "hidden" "return")
'("initialize" "onUpdate")
'("\\.mc\\'")
'((lambda () (run-hooks 'prog-mode-hook)))
"A mode for Monkey-C")
(message "SECTION: Mode Management")
Handle which modes/settings are triggered where.
Truncate lines, don’t wrap.
(setq-default truncate-lines t)
Full Undo/Redo history as a tree
(global-undo-tree-mode 1)
Don’t use lockfiles....causes issues in SeaFile.
(setq create-lockfiles nil)
Track recent files – quite a lot of them.
(recentf-mode 1)
(setq recentf-max-menu-items 2000)
(setq recentf-max-saved-items 2000)
Manage workspaces.
- Shift + left/right switches between tabs.
- Shift + up clones current tab to the right
- Shift + down deletes current tab and moves focus left
(tab-bar-mode)
(custom-set-variables
'(tab-bar-close-button-show nil)
'(tab-bar-close-tab-select 'left)
'(tab-bar-new-tab-choice nil)
'(tab-bar-new-tab-to 'right)
'(tab-bar-show 1)
'(tab-bar-tab-hints t))
Help me with my keys
(customize-set-variable 'which-key-idle-delay 0.25)
(which-key-mode)
Enable counsel and ivy modes.
(counsel-mode)
(ivy-mode)
Hide the toolbar, menu bar, and scroll bars.
(tool-bar-mode -1)
(menu-bar-mode 0)
(scroll-bar-mode -1)
Use yasnippet everywhere.
(yas-global-mode t)
Use ido by default.
(ido-mode)
Let ido match strings flexibly.
(customize-set-variable 'ido-enable-flex-matching t)
Always open files in the current window.
(custom-set-variables
'(ido-default-buffer-method (quote selected-window))
'(ido-default-file-method (quote selected-window)))
Use pdf-tools for viewing PDF documents, rather than docview
(unless-on-windows
(pdf-tools-install t))
Emojis everywhere (except for some places).
(add-hook 'after-init-hook #'global-emojify-mode)
(customize-set-variable 'emojify-inhibit-major-modes
'(dired-mode doc-view-mode debugger-mode pdf-view-mode image-mode help-mode ibuffer-mode magit-popup-mode magit-diff-mode ert-results-mode compilation-mode proced-mode mu4e-headers-mode org-agenda-mode))
(defun my:emojify-inhibit-fix-org-drawers (text beg end)
"Since org-mode now uses lower-case :begin:, :end:, etc tags, some of them are
now being rendered as Emojis. Filter this case out."
(and (equal major-mode 'org-mode) (member (downcase text) '(":begin:" ":end:"))))
(add-to-list 'emojify-inhibit-functions 'my:emojify-inhibit-fix-org-drawers)
Unicode setup
(unicode-fonts-setup)
If I trigger a self-insert key when region is active, I want the selection to be replaced by that text.
(delete-selection-mode)
Add the ability to undo/redo window arrangement changes.
(winner-mode 1)
The hook prog-mode-hook
is triggered in all programming major-modes.
(add-hook 'prog-mode-hook
'(lambda ()
(flyspell-prog-mode)
(display-line-numbers-mode 1)
(auto-highlight-symbol-mode)
(hs-minor-mode)
(hs-hide-all)
(rainbow-delimiters-mode)))
(add-hook 'text-mode-hook
(lambda ()
(flyspell-mode)
(visual-line-mode)
(variable-pitch-mode t)
(text-scale-increase 1)))
(add-hook 'org-mode-hook
(lambda ()
(variable-pitch-mode -1)
(org-autolist-mode)
;(auto-fill-mode)
;(visual-line-mode nil)
;(setq truncate-lines t)
(olivetti-mode)
(text-scale-increase 0)))
Use employer’s preferred C format…
(add-hook 'c-mode-common-hook
'(lambda ()
(set 'c-basic-offset 4)
(set 'c-electric-flag nil)
(c-set-style "whitesmith")
(when (locate-dominating-file default-directory ".ccls-root")
(lsp))
(setq comment-start "// ")
(setq comment-end "")))
(add-hook 'octave-mode-hook
(lambda ()
(local-set-key (kbd "C-\\") 'octave-send-region-or-line)))
(add-hook 'compilation-mode-hook
(lambda ()
(visual-line-mode)
(local-set-key (kbd "H-]") 'next-error)
(local-set-key (kbd "H-[") 'previous-error)))
(add-hook 'shell-mode-hook
(lambda ()
(setq show-trailing-whitespace nil)
(visual-line-mode t)))
(when (boundp 'vterm-mode-hook)
(add-hook 'vterm-mode-hook
(lambda ()
(setq show-trailing-whitespace nil))))
(defun my/vterm-execute-current-line ()
"Insert text of current line in vterm and execute. https://www.reddit.com/r/emacs/comments/op4fcm/send_command_to_vterm_and_execute_it/"
(interactive)
(require 'vterm)
(let ((command (buffer-substring
(save-excursion
(beginning-of-line)
(point))
(save-excursion
(end-of-line)
(point)))))
(let ((buf (current-buffer)))
(unless (get-buffer vterm-buffer-name)
(vterm))
(display-buffer vterm-buffer-name t)
(switch-to-buffer-other-window vterm-buffer-name)
(vterm--goto-line -1)
(message command)
(vterm-send-string command)
(vterm-send-return)
(switch-to-buffer-other-window buf)
)))
(defun my/python-interrupt()
(interactive)
(let ((proc (ignore-errors
(python-shell-get-process-or-error))))
(when proc
(interrupt-process proc))))
(add-hook 'python-mode-hook
(lambda ()
;(jedi:setup)
(olivetti-mode)
(local-set-key (kbd "<tab>") 'python-smart-tab-key)
(local-set-key (kbd "TAB") 'python-smart-tab-key)
(local-set-key (kbd "C-t") 'indent-for-tab-command)
(local-set-key (kbd "<backspace>") 'backward-delete-char-untabify)
(local-set-key (kbd "C-\\") 'run-python-in-shell)
(local-set-key (kbd "C-r") 'align-current)
(local-set-key (kbd "C-|") 'python-shell-send-buffer)
(local-set-key (kbd "C-d") 'my/python-interrupt)))
(add-hook 'ruby-mode-hook
(lambda ()
(local-set-key (kbd "<tab>") 'generic-smart-tab-key)
(local-set-key (kbd "TAB") 'generic-smart-tab-key)
(local-set-key (kbd "C-t") 'indent-for-tab-command)))
(add-hook 'scheme-mode-hook
(lambda ()
(local-set-key (kbd "<tab>") 'generic-smart-tab-key)
(local-set-key (kbd "TAB") 'generic-smart-tab-key)
(local-set-key (kbd "C-t") 'indent-for-tab-command)))
(add-hook 'emacs-lisp-mode-hook
(lambda ()
(local-set-key (kbd "<tab>") 'generic-smart-tab-key)
(local-set-key (kbd "TAB") 'generic-smart-tab-key)
(local-set-key (kbd "C-t") 'indent-for-tab-command)))
(customize-set-variable 'cider-clojure-cli-global-aliases "-A:fig")
(add-hook 'term-mode-hook
(lambda ()
(display-line-numbers-mode -1)
(setq show-trailing-whitespace nil)
(term-pager-toggle)))
Use emmet in any web-dev modes.
(add-hook 'html-mode-hook 'emmet-mode)
(add-hook 'css-mode-hook 'emmet-mode)
Add a margin between columns, and center content.
(custom-set-variables '(csv-align-padding 7)
'(csv-align-style 'centre))
Whenever we start csv-mode
, freeze the first row & auto-align fields.
(add-hook 'csv-mode-hook 'csv-align-mode)
(add-hook 'csv-mode-hook 'csv-header-line)
(add-hook 'csv-mode-hook (lambda () (visual-line-mode 0)))
Assume sqlite.
(add-hook 'sql-mode-hook
(lambda ()
(sql-set-product 'sqlite)))
(message "SECTION: Package Configurations")
Ensure that org-mode starts up using indentation.
(setq org-startup-indented t)
Mode hooks
(add-hook 'org-mode-hook
(lambda ()
(text-scale-increase 0)
(org-bullets-mode 1)))
If this is a journal or concept file in my org files directory, rename the buffer using some special rules.
- journal.org in journal dir gets renamed like “journal [2024-1-12]”
- concept.org in concept dir gets renamed like “concept [relative/concept/path]”
(defun my:extract-date-from-journal-path (path)
"Extracts date in the form of year, month, day from journal.org path"
(when (string-match ".*/\\s-*\\([[:digit:]]+\\)\\s-*/\\s-*\\([[:digit:]]+\\)\\s-*/\\s-*\\([[:digit:]]+\\)\\s-*/.*" path)
`((year . ,(match-string 1 path))
(month . ,(match-string 2 path))
(day . ,(match-string 3 path)))))
(defun my:rename-org-buffer-name-in-org-dir-as-needed ()
(let ((filename (buffer-file-name)))
(if (and filename my:org-file-tree-base-path)
(cond
;; Handle journal files
((file-in-directory-p filename (concat my:org-file-tree-base-path "/journal/"))
(let ((date-fields (my:extract-date-from-journal-path filename)))
(rename-buffer (concat "journal ["
(alist-get 'year date-fields) "-"
(alist-get 'month date-fields) "-"
(alist-get 'day date-fields)
"]")
t)))
;; Handle concept files
((file-in-directory-p filename (concat my:org-file-tree-base-path "/concepts/"))
(let*
((concept-relpath (file-relative-name filename (concat my:org-file-tree-base-path "/concepts/")))
(concept-clean-relpath (replace-regexp-in-string "/concept\\.org\\'" "" concept-relpath)))
(rename-buffer (concat "concept ["
concept-clean-relpath
"]")
t)))))))
(add-hook 'org-mode-hook 'my:rename-org-buffer-name-in-org-dir-as-needed)
Unhide areas when they’re edited.
(setq org-catch-invisible-edits 'show-and-error)
Colorize quote blocks like we do source blocks.
(customize-set-variable 'org-fontify-quote-and-verse-blocks t)
Allow images to be visually resized with #+ATTR keywords.
(customize-set-variable 'org-image-actual-width nil)
Adjust emphasis elements a bit.
(customize-set-variable 'org-emphasis-alist
'(("*" bold)
("/" italic)
("_" underline)
("=" org-verbatim verbatim)
("~" org-code verbatim)
("+"
(:strike-through t))
("%" highlight verbatim)))
Adjust how columns are shown when enabled.
(customize-set-variable 'org-columns-default-format "%25ITEM %TODO %3PRIORITY %TAGS")
Use a simple unicode bullet.
(customize-set-variable 'org-bullets-bullet-list '("●"))
Any TODO which is tagged :noagenda: shouldn’t be in the agenda.
(customize-set-variable 'org-agenda-hide-tags-regexp "noagenda")
Set our org-modules.
(customize-set-variable 'org-modules '(org-bbdb org-bibtex org-docview org-gnus org-habit org-info org-irc org-mhe org-rmail org-w3m org-collector))
Make easy template work again. Though the new expansion method looks pretty cool. Discussion here.
(when (version<= "9.2" (org-version))
(require 'org-tempo))
Use minted code sections in PDF export, and fixup the margins a bit. Some code from the GNU Mailing list.
(setq org-latex-listings 'minted
org-latex-default-figure-position "H"
org-latex-packages-alist '(("" "minted")
("margin=1.5cm" "geometry" nil)
("" "parskip")
("" "booktabs")
("" "float"))
org-latex-pdf-process
'("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
"pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))
(setq org-latex-minted-options
'(("fontfamily" "zi4")))
;;'(("linenos")
;; ("numbersep" "5pt")
;; ("frame" "none") ; box frame is created by the mdframed package
;; ("framesep" "2mm")
;; ("fontfamily" "zi4") ; required only when using pdflatex
;; ; instead of xelatex
;; ;; minted 2.0 specific features
;; ("breaklines") ; line wrapping within code blocks
;; ))
(customize-set-variable 'org-latex-minted-langs
'((emacs-lisp "common-lisp")
(cc "c++")
(cperl "perl")
(shell-script "bash")
(caml "ocaml")
(sqlite "sql")
(ipython "python")))
Don’t add a table of contents upon export.
(setq org-export-with-toc nil)
Be consistent with caption placement.
(customize-set-variable 'org-latex-caption-above nil)
Use two column layouts with 8pt font for reports and article document classes.
(setq org-latex-classes
'(("article" "\\documentclass[8pt,twocolumn]{article}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}"))
("report" "\\documentclass[8pt,twocolumn]{report}"
("\\part{%s}" . "\\part*{%s}")
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
("book" "\\documentclass[11pt]{book}"
("\\part{%s}" . "\\part*{%s}")
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))))
Define org todo keywords.
Current Tags:
tag | Use |
---|---|
TODO | Items which are queued to be completed by me. |
BACKLOG | Items which are queued to be completed, by someone – maybe me. |
ASSIGNED | Items which are queued to be completed by someone else. |
NEXT | Items which are considered to be in-progress. Max 3-5 items at any point in time. |
WAIT | Items which are blocked by an external party. |
DONE | Items which have been completed as described. |
CANCELLED | Items which will not be completed. |
HIATUS | “Soft-cancelled”. Items which were originally TODO’d, but for some reason, may not ever be done. |
MIGRATED | Items which have been made redundant by newer entries. |
Deprecated Tags:
tag | Use |
---|---|
IN_PROGRESS | Previous name for NEXT |
PENDING | Previous name for WAIT |
(setq-default org-todo-keywords
'((sequence "TODO(t!)" "ASSIGNED(a!)" "BACKLOG(b!)" "IN_PROGRESS(i!)" "NEXT(n!)" "PENDING(p!)" "WAIT(w!)" "|" "DONE(d!)" "CANCELLED(c!)" "HIATUS(h!)" "MIGRATED(m!)")))
And their faces.
(setq-default org-todo-keyword-faces
'(( "TODO" . (:foreground "white" :background "darkorchid4" :weight bold ))
( "ASSIGNED" . (:foreground "white" :background "blue" :weight bold ))
( "BACKLOG" . (:foreground "white" :background "darkorchid4" :weight bold ))
( "IN_PROGRESS" . ( :background "deeppink3" :weight bold ))
( "NEXT" . ( :background "deeppink3" :weight bold ))
( "HIATUS" . (:foreground "white" :slant italic))
( "DONE" . (:foreground "forst green" :weight bold ))
( "CANCELLED" . (:foreground "light gray" :slant italic))
( "FAILED" . (:foreground "white" :background "red" :weight bold ))
( "MIGRATED" (:foreground "light gray" :slant italic))
( "PENDING" (:foreground "light gray" :slant italic))
( "WAIT" ( :background "Red" :weight bold ))))
And their priorities:
priority | Meaning |
---|---|
A | This is critical and urgent |
B | This is very important and/or time sensitive |
C | Needs to happen in the near future. |
D | Would be cool to do this. |
E | Just an idea. Whatever. |
(setq org-highest-priority ?A)
(setq org-lowest-priority ?E)
(setq org-default-priority ?B)
(setq org-priority-faces '((?A . (:foreground "white" :background "dark red" :weight bold))
(?B . (:foreground "white" :background "dark green" :weight bold))
(?C . (:foreground "green" :weight bold))
(?D . (:foreground "yellow"))
(?E . (:foreground "gray"))))
Uncomplicate agenda fonts.
(setq org-agenda-deadline-faces '((1.0 . org-upcoming-deadline)
(0.0 . (:foreground "light pink" :slant italic))))
Show state changes in org-agenda log mode.
(setq org-agenda-log-mode-items '(closed clock state))
Log into drawer (LOGBOOK)
(setq org-log-into-drawer t)
Open agenda in current window.
(setq org-agenda-window-setup 'current-window)
Mark items closed, as many times as needed.
(setq org-log-done 'time)
(setq org-log-reschedule 'time)
(setq org-log-redeadline 'time)
(setq org-closed-keep-when-no-todo t)
Hide items scheduled in future from org-todo list.
(setq org-agenda-todo-ignore-scheduled 'future)
Unclutter the agenda display.
(customize-set-variable 'org-agenda-prefix-format
'((agenda . " %i %?-12t% s")
(todo . " %i %-12:c")
(tags . " ")
(search . " %i %-12:c")))
Set a default category name, don’t use filename.
(setq-default org-category "_")
I want to be able to have multiple agenda windows.
(customize-set-variable 'org-agenda-sticky t)
Add custom agenda command, use some awesome code from this blog and this StackOverflow user.
(defun air-org-skip-subtree-if-priority (priority)
"Skip an agenda subtree if it has a priority of PRIORITY.
PRIORITY may be one of the characters ?A, ?B, or ?C."
(let ((subtree-end (save-excursion (org-end-of-subtree t)))
(pri-value (* 1000 (- org-lowest-priority priority)))
(pri-current (org-get-priority (thing-at-point 'line t))))
(if (= pri-value pri-current)
subtree-end
nil)))
(defun air-org-skip-subtree-if-habit ()
"Skip an agenda entry if it has a STYLE property equal to \"habit\"."
(let ((subtree-end (save-excursion (org-end-of-subtree t))))
(if (string= (org-entry-get nil "STYLE") "habit")
subtree-end
nil)))
(defun air-org-skip-subtree-unless-habit ()
"Skip an agenda entry if it has a STYLE property equal to \"habit\"."
(let ((subtree-end (save-excursion (org-end-of-subtree t))))
(if (string= (org-entry-get nil "STYLE") "habit")
nil
subtree-end)))
(defun cmp-date-property (prop)
"Compare two `org-mode' agenda entries, `A' and `B', by some date property.
If a is before b, return -1. If a is after b, return 1. If they
are equal return t."
(lexical-let ((prop prop))
#'(lambda (a b)
(let* ((a-pos (get-text-property 0 'org-marker a))
(b-pos (get-text-property 0 'org-marker b))
(a-date (or (org-entry-get a-pos prop)
(format "<%s>" (org-read-date t nil "now"))))
(b-date (or (org-entry-get b-pos prop)
(format "<%s>" (org-read-date t nil "now"))))
(cmp (compare-strings a-date nil nil b-date nil nil))
)
(if (eq cmp t) nil (signum cmp))
))))
(defun zin/org-agenda-skip-tag (tag &optional others)
"Skip all entries that correspond to TAG.
If OTHERS is true, skip all entries that do not correspond to TAG.
https://emacs.stackexchange.com/questions/26351/custom-sorting-for-agenda"
(let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))
(current-headline (or (and (org-at-heading-p)
(point))
(save-excursion (org-back-to-heading)))))
(if others
(if (not (member tag (org-get-tags-at current-headline)))
next-headline
nil)
(if (member tag (org-get-tags-at current-headline))
next-headline
nil))))
(setq org-agenda-custom-commands
`(("x" "Combined Task List"
(
(todo "WAIT"
((org-agenda-skip-function '(zin/org-agenda-skip-tag "NOAGENDA"))
(org-agenda-overriding-header "Blocked Tasks:")))
(alltodo ""
((org-agenda-skip-function '(or (org-agenda-skip-entry-if 'todo '("WAIT" "ASSIGNED" "BACKLOG"))
(zin/org-agenda-skip-tag "NOAGENDA")
(zin/org-agenda-skip-tag "UNFILED")))
;;(org-agenda-skip-if nil '(scheduled deadline))))
(org-agenda-overriding-header "Current and Unscheduled Tasks:")))
(todo "ASSIGNED"
((org-agenda-skip-function '(zin/org-agenda-skip-tag "NOAGENDA"))
(org-agenda-overriding-header "Delegated Tasks:")))
(todo "BACKLOG"
((org-agenda-skip-function '(zin/org-agenda-skip-tag "NOAGENDA"))
(org-agenda-overriding-header "Tasks To Delegate:")))
(tags "UNFILED"
((org-agenda-overriding-header "Unfiled Captures:")))))))
Don’t show blocked tasks (ORDERED property set to t)
(setq org-enforce-todo-dependencies t
org-agenda-dim-blocked-tasks 'invisible)
Hide tags in agenda
(customize-set-variable 'org-agenda-remove-tags t)
Show past week and next two in agenda view.
(add-hook 'org-agenda-mode-hook
(lambda ()
(setq org-agenda-span 21
org-agenda-start-on-weekday nil
org-agenda-start-day "-7d")))
Enable many different languages in org-babel. Remember to reload org whenever these change (C-c C-c on the file header).
(with-eval-after-load 'org
(progn
(org-babel-do-load-languages
'org-babel-load-languages
'((python . t)
(emacs-lisp . t)
(shell . t)
(lisp . t)
(gnuplot . t)
(elasticsearch . t)
(R . t)
(C . t)
(http . t)
(ruby . t)
(sqlite . t)
(plantuml . t)
(jupyter . t)))))
Plantuml blocks also need some special attention to get working. Hope this is consistent across distros; this is the correct value for Ubuntu/WSL Xenial.
(customize-set-variable 'org-plantuml-jar-path "/usr/share/plantuml/plantuml.jar")
Display images after each eval
(add-hook 'org-babel-after-execute-hook 'org-display-inline-images 'append)
Track boldness, italics for many lines.
(setcar (nthcdr 4 org-emphasis-regexp-components) 10)
(org-set-emph-re 'org-emphasis-regexp-components org-emphasis-regexp-components)
Map file extensions to external programs which open them. org-mode uses this when following file links.
(setq org-file-apps
`((auto-mode . ,(if (on-windows-p) "%s" 'emacs))
("\\.pdf" . ,(if (on-windows-p) "%s" 'emacs))
("\\.xlsx" . ,(if (on-windows-p) "%s" 'emacs))
("\\.docx" . ,(if (on-windows-p) "%s" 'emacs))
("\\.ods" . "libreoffice %s")))
Live fearlessly.
(setq org-confirm-babel-evaluate nil)
Apparently org block switches are a thing that exist, and have nothing to do with org block header arguments…? You need them to enable line numbers in exported blocks, but the current implementation of switches is…partial.
Tobias posted this code on the Emacs StackExchange which implements the buffer-global property syntax for switches. It’s like this:
#+PROPERTY: switches: -n
(require 'cl-lib)
(require 'org)
(require 'ox)
(require 'org-element)
(defvar org-src-switch-alist nil
"Alist mapping an org source code switches to its argument parser (which can be nil) and its interpreter.")
(setq org-src-switches-alist
'(("-n" org-src-parse-number (lambda (num) (list :number-lines (cons 'new (or num 0)))))
("+n" org-src-parse-number (lambda (num) (list :number-lines (cons 'continued (or num 0)))))
("-r" nil (:retain-labels nil))
("-i" nil (:preserve-indent t))
("-l" org-src-parse-quoted-string (lambda (str) (assert (stringp str)) (list :label-fmt str)))
("-k" nil (:use-labels t))))
(defun org-src-parse-number ()
"Parse number at point and return it as the only element of a list.
If parsing fails the return value is (nil)."
(list
(when (looking-at "[0-9]")
(read (current-buffer)))))
(defun org-src-parse-quoted-string ()
"Parse quoted string at point and return it as the only element of a list.
If parsing fails the return value is (nil)."
(list
(when (looking-at "\"")
(read (current-buffer)))))
(defun org-src-switch-parse (str)
"Parse org mode source block switches in string STR."
(let ((sw-re (regexp-opt (mapcar 'car org-src-switches-alist)))
ret)
(with-temp-buffer
(insert str)
(goto-char (point-min))
(while (progn
(skip-syntax-forward " ")
(looking-at sw-re))
(goto-char (match-end 0))
(let* ((sw (assoc-string (match-string 0) org-src-switches-alist))
(parser (cadr sw))
(interpreter (nth 2 sw))
args)
(skip-syntax-forward " ")
(when parser
(setq args (funcall parser)))
(setq ret
(append
ret
(if (functionp interpreter)
(apply interpreter args)
interpreter))))))
ret))
(defun org-src-switch-propagate (keyword-element)
(let ((value
(and (string-equal (org-element-property :key keyword-element) "PROPERTY")
(org-element-property :value keyword-element))))
(when (and value (string-match "switches:" (downcase value)))
(setq value (org-src-switch-parse (substring value (match-end 0))))
(let ((parent (org-element-property :parent keyword-element)))
(when (and
(eq (org-element-type parent) 'section)
(eq (org-element-type (org-element-property :parent parent)) 'org-data))
(setq parent (org-element-property :parent parent)))
(org-element-map
parent
'(src-block example-block)
`(lambda (bl)
(cl-loop for pair on ',value by 'cddr
do (org-element-put-property bl (car pair) (cadr pair)))))))))
(defun org-element-parse-buffer-ad (info)
"Filter the INFO returned by `org-element-parse-buffer' for global switches
and apply them to source code blocks."
(org-element-map info 'keyword #'org-src-switch-propagate)
info)
(advice-add #'org-element-parse-buffer :filter-return #'org-element-parse-buffer-ad)
When editing source blocks in indirect buffers, open them in the current window. Do not mess with the window layout.
(customize-set-variable 'org-src-window-setup 'current-window)
Provide a function to generate a session name for source blocks the current buffer.
(defun my:org-buffer-babel-session-name ()
"Generate a session name based on the current buffer file name."
(let* ((filename (buffer-file-name))
(session-name (replace-regexp-in-string "[^a-zA-Z0-9]" "_" filename)))
session-name))
Function to fix formatting in an org block.
Borrowed from this Stack Overflow answer.
(defun my:indent-org-block-automatically ()
(interactive)
(when (org-in-src-block-p)
(org-edit-special)
(indent-region (point-min) (point-max))
(org-edit-src-exit)))
Function to automatically format all babel blocks in a buffer.
(defun my:indent-all-emacs-lisp-blocks-automatically ()
(interactive)
(save-excursion
(goto-char (point-min))
(let ((prev-point nil))
(while (not (equal (point) prev-point))
(setf prev-point (point))
(org-babel-next-src-block)
(when (equal (first (org-babel-get-src-block-info)) "emacs-lisp")
(my:indent-org-block-automatically))))))
Sort org-habits by priority. See StackOverflow.
(defun hw-org-agenda-sort-habits (a b)
"Sort habits first by user priority, then by schedule+deadline+consistency."
(let ((ha (get-text-property 1 'org-habit-p a))
(hb (get-text-property 1 'org-habit-p b)))
(when (and ha hb)
(let ((pa (org-get-priority a))
(pb (org-get-priority b)))
(cond ((> pa pb) +1)
((< pa pb) -1)
((= pa pb) (org-cmp-values a b 'priority)))))))
(setq org-agenda-cmp-user-defined 'hw-org-agenda-sort-habits
org-agenda-sorting-strategy '((agenda time-up user-defined-down habit-down)
(todo priority-down category-keep)
(tags priority-down category-keep)
(search category-keep)))
Customize the habit display a bit.
(custom-set-variables
'(org-habit-completed-glyph 88)
'(org-habit-following-days 0)
'(org-habit-graph-column 90)
'(org-habit-preceding-days 0)
'(org-habit-today-glyph 124))
Defcustoms for various personal definitions.
(defconst my:org-file-tree-base-path
(expand-file-name "org"
(if (on-windows-p)
(getenv "USERPROFILE")
"/sync/documents"))
"Base directory of my org directory structure.")
(defun my:org-scraps-path ()
(concat my:org-file-tree-base-path "/scraps/scraps.org"))
(defun my:org-dream-journal-path ()
(concat my:org-file-tree-base-path "/concepts/dreams/journal/concept.org"))
(defun my:incomprehensible-voice-notes-path ()
(concat my:org-file-tree-base-path "/voice-notes/incomprehensible.org"))
(defun my:unfiled-notes-path ()
(concat my:org-file-tree-base-path "/voice-notes/unfiled-notes.org"))
(defun my:generate-gpt-conversation-file-name ()
(concat my:org-file-tree-base-path "/llm-history/" (format-time-string "%Y/%m/%d/%I-%M-%S-%p") "/chat.org"))
Make sure org-attach places files in the org-attachments directory
(customize-set-variable 'org-attach-id-dir (concat my:org-file-tree-base-path "/attachments/"))
Add function to rebuild org file list. Call it once.
(defun my-org-update-agenda-index()
(interactive)
(setq org-agenda-files
(seq-filter
(lambda (x) (not (my:fname-seafile-conflict-p x)))
(directory-files-recursively my:org-file-tree-base-path ".*\\.org$"))))
(when my:org-file-tree-base-path
(my-org-update-agenda-index))
Add function to interactively select an existing concept file.
(defun my-org-list-concepts ()
(seq-filter (lambda (path)
(and (file-directory-p path)
(file-exists-p (concat path "/concept.org"))))
(directory-files-recursively (concat my:org-file-tree-base-path "/concepts/") ".*" t)))
(defun my-org-find-concept ()
(call-interactively
(lambda (choice)
(interactive
(list (ivy-read "Existing Concepts: "
(my-org-list-concepts))))
(concat choice "/concept.org"))))
(defun my:org-concept-open ()
(interactive)
(find-file (my-org-find-concept)))
Add function to insert link to existing concept.
(defun my:org-insert-concept-link ()
(interactive)
(let
((file (my-org-find-concept)))
(org-insert-link nil file)))
Add function to search existing concepts, journals, etc.
(if (on-windows-p)
(defun my:org-search-concept ()
(interactive)
(my:org-occur-in-agenda-files t))
(defun my:org-search-concept ()
(interactive)
(counsel-ag "" my:org-file-tree-base-path)))
Add function to run org-occur-in-agenda-files on thing at point without prefix, or, interactively without prefix.
(defun my:org-occur-in-agenda-files (prefix-arg)
(interactive "P")
(if prefix-arg
(call-interactively 'org-occur-in-agenda-files)
(org-occur-in-agenda-files (thing-at-point 'word))))
Functions for creating and opening daily journal pages.
(defvar my:journal-expansion-time nil
"Temporarily bound to a date when the journal yasnippet should expand to dates other than today.")
(defun my-org-journal-current-day-path ()
"Get the path to today's journal file."
(my-org-journal-path (current-time)))
(defun my-org-journal-path (time)
"Get the path to some day's journal file, given time (compatible with (current-time))."
(let*
((journal-root (concat my:org-file-tree-base-path "/journal/"))
(filename "journal.org")
(path (concat journal-root (format-time-string "%Y/%m/%e/" time) filename)))
path))
(defun my-org-journal-make-if-missing (&optional time)
"Unless already present, create a journal ORG file for today from the existing template."
(interactive)
(setq time (or time (current-time)))
(let
'(path (my-org-journal-path time))
(progn
(save-excursion
(unless (file-exists-p path)
(progn
(make-directory (file-name-directory path) t)
(find-file path)
(let ((my:journal-expansion-time time))
(yas-expand-snippet (yas-lookup-snippet "Journal Page Template" 'org-mode)))
(save-buffer)
(kill-buffer)
(my-org-update-agenda-index))))
path)))
(defun my-org-journal-open-today ()
"Open today's journal file, creating it if missing."
(interactive)
(let ((time (current-time)))
(my-org-journal-make-if-missing time)
(find-file (my-org-journal-path time))))
(defun my-org-journal-open-yesterday ()
"Open yesterday's journal file, creating it if missing."
(interactive)
(let ((time (time-subtract (current-time) (seconds-to-time (* 60 60 24)))))
(my-org-journal-make-if-missing time)
(find-file (my-org-journal-path time))))
(defun my-org-journal-open-tomorrow ()
"Open tomorrow's journal file, creating it if missing."
(interactive)
(let ((time (time-add (current-time) (seconds-to-time (* 60 60 24)))))
(my-org-journal-make-if-missing time)
(find-file (my-org-journal-path time))))
(defun my:get-journal-date (&optional buffer)
"Get the datetime associated with a journal buffer."
(with-current-buffer (or buffer (current-buffer))
(let ((date-str (plist-get (org-export-get-environment) :date)))
(when date-str
(encode-time (mapcar (lambda (x) (or x 0)) (parse-time-string (car date-str))))))))
(defun my:list-journal-pages (&optional newest-first)
"Return a sorted alist mapping datetime to journal page path."
(let* ((journal-root (concat my:org-file-tree-base-path "/journal/"))
(filename "^journal.org$")
(raw-list (directory-files-recursively journal-root filename))
(journal-alist (mapcar (lambda (fname)
(let* ((components (split-string (file-relative-name fname (concat my:org-file-tree-base-path "/journal/")) "/"))
(year (string-to-number (first components)))
(month (string-to-number (second components)))
(day (string-to-number (third components))))
(cons (encode-time (list 0 0 0 day month year 0 -1 0))
fname)))
raw-list)))
(sort journal-alist (lambda (a b)
(if newest-first
(not (time-less-p (car a) (car b)))
(time-less-p (car a) (car b)))))))
(defun my:open-journal ()
"Interactively open a journal file"
(interactive)
(let ((pages (my:list-journal-pages t)))
(find-file (completing-read "Journal: " (mapcar #'cdr pages)))))
(defun my:visit-previous-journal-page ()
"Open the newest journal page before current."
(interactive)
(let* ((pages (my:list-journal-pages))
(curtime (my:get-journal-date))
(prev-page (cdar (last (seq-filter (lambda (pair)
(time-less-p (first pair) curtime))
pages)))))
(if prev-page
(find-file prev-page)
(progn (ding)
(message "Oldest journal page!")))))
(defun my:visit-next-journal-page ()
"Open the oldest journal page after current."
(interactive)
(let* ((pages (my:list-journal-pages))
(curtime (my:get-journal-date))
(next-page (cdr (second (seq-filter (lambda (pair)
(not (time-less-p (first pair) curtime)))
pages)))))
(if next-page
(find-file next-page)
(progn (ding)
(message "Newest journal page!")))))
(defun my:journal-buffer-p ()
"Is the current buffer assocted with a journal file?"
(let ((fname (buffer-file-name)))
(when fname
(string= (file-name-nondirectory fname) "journal.org"))))
Advise org-save-all-org-buffers to autocommit any changes in my org folder
(defun my:org-autocommit-get-message ()
"Return a string for an org autocommit commit message.
I use YYYY-MM-DD HH:MM:SS:ms"
(prin1-to-string
(string-join
(list
(format-time-string "%Y-%m-%d %I:%M:%S:%3N")
""
(shell-command-to-string (concat "cd " my:org-file-tree-base-path " && git status --porcelain")))
"\n")))
(defun my:org-autocommit ()
"Automatically add/update files in the org dir."
(interactive)
(when my:commit-org
(shell-command (concat "cd " my:org-file-tree-base-path " && git add -A && git commit -m " (my:org-autocommit-get-message)))))
(defadvice org-save-all-org-buffers
(after my:org-save-all-org-buffers-autocommit activate)
(my:org-autocommit))
Define org-capture templates.
Code | Purpose |
---|---|
j | Journal Entries. Writes a timestamped heading under the “Thoughts” section of today’s journal.org |
u | Unscheduled/unplanned TODO items, written to today’s journal.org. Prompts for priority. |
c | Concept-based TODO items, written to that concept’s concept.org file. Prompts for concept and priority |
d | Dream journal entries. Writes a timestamped heading to the dreams/journal concept file. |
p | Writing prompts and scraps. Writes timestamped section to the writing scraps file. |
(setq org-capture-templates
`(("j"
"journal"
entry
(file+headline my-org-journal-make-if-missing "Thoughts")
"** %U
%?"
:empty-lines 1)
("i"
"inbox"
entry
(file ,(my:unfiled-notes-path))
"** TODO %?
:LOGBOOK:
- State \"TODO\" from %U
:END:"
:empty-lines 1)
("w"
"Ad Hoc Task"
entry
(file+headline my-org-journal-make-if-missing "Ad Hoc Tasks")
"** %? :%^{Type|talking|texting|meetings|coding|planning|downtime|clerical|tooling|analysis}:"
:empty-lines 1
:clock-in t
:clock-keep t)
("y"
"Work Log - Silent"
entry
(file+headline my-org-journal-make-if-missing "Ad Hoc Tasks")
"** %?"
:empty-lines 1
:clock-in t
:clock-keep t)
("u"
"unscheduled task"
entry
(file+headline my-org-journal-make-if-missing "Action Items")
"** TODO [#%^{Priority|C|A|B|D|E}] %?
:LOGBOOK:
- State \"TODO\" from %U
:END:"
:empty-lines 1)
("c"
"Concept Task"
entry
(file+headline my-org-find-concept "Action Items")
"** TODO [#%^{Priority|C|A|B|D|E}] %?
:LOGBOOK:
- State \"TODO\" from %U
:END:"
:empty-lines 1)
("d"
"Dream Journal"
entry
(file+headline ,(my:org-dream-journal-path) "Dream Log")
"** %U\n%?"
:prepend t
:empty-lines 1)
("p"
"Writing Prompt"
plain
(file+headline ,(my:org-scraps-path) "Scraps")
"%U\n%?"
:prepend t
:empty-lines 1)))
Add a function to rapidly clock into a new task without any information.
(defun my:org-clock-new-task-silent ()
"Create new ad hoc task & clock in -- don't do anything interactively."
(interactive)
(org-capture nil "y")
(org-capture-finalize))
Set the refile targets to be all org files in my tree, and show the full path.
(setq org-refile-targets '((nil :maxlevel . 9)
(org-agenda-files :maxlevel . 9)))
(setq org-outline-path-complete-in-steps nil)
(setq org-refile-use-outline-path 'full-file-path)
I want new items placed at the top of headings.
(setq org-reverse-note-order t)
Refile a heading to a file+heading+datetree, using the date of the first timestamp in the heading as the date.
This is copied from Alphapapa’s unpackaged.el.
(defun unpackaged/org-refile-to-datetree-using-ts-in-entry (which-ts file &optional subtree-p)
"Refile current entry to datetree in FILE using timestamp found in entry.
WHICH should be `earliest' or `latest'. If SUBTREE-P is non-nil,
search whole subtree."
(interactive (list (intern (completing-read "Which timestamp? " '(earliest latest)))
(read-file-name "File: " (concat org-directory "/") nil 'mustmatch nil
(lambda (filename)
(string-suffix-p ".org" filename)))
current-prefix-arg))
(require 'ts)
(let* ((sorter (pcase which-ts
('earliest #'ts<)
('latest #'ts>)))
(tss (unpackaged/org-timestamps-in-entry subtree-p))
(ts (car (sort tss sorter)))
(date (list (ts-month ts) (ts-day ts) (ts-year ts))))
(unpackaged/org-refile-to-datetree file :date date)))
(defun unpackaged/org-timestamps-in-entry (&optional subtree-p)
"Return timestamp objects for all Org timestamps in entry.
If SUBTREE-P is non-nil (interactively, with prefix), search
whole subtree."
(interactive (list current-prefix-arg))
(save-excursion
(let* ((beg (org-entry-beginning-position))
(end (if subtree-p
(org-end-of-subtree)
(org-entry-end-position))))
(goto-char beg)
(cl-loop while (re-search-forward org-tsr-regexp-both end t)
collect (ts-parse-org (match-string 0))))))
(cl-defun unpackaged/org-refile-to-datetree (file &key (date (calendar-current-date)) entry)
"Refile ENTRY or current node to entry for DATE in datetree in FILE."
(interactive (list (read-file-name "File: " (concat org-directory "/") nil 'mustmatch nil
(lambda (filename)
(string-suffix-p ".org" filename)))))
;; If org-datetree isn't loaded, it will cut the tree but not file
;; it anywhere, losing data. I don't know why
;; org-datetree-file-entry-under is in a separate package, not
;; loaded with the rest of org-mode.
(require 'org-datetree)
(unless entry
(org-cut-subtree))
;; Using a condition-case to be extra careful. In case the refile
;; fails in any way, put cut subtree back.
(condition-case err
(with-current-buffer (or (org-find-base-buffer-visiting file)
(find-file-noselect file))
(org-datetree-file-entry-under (or entry (car kill-ring)) date)
(save-buffer))
(error (unless entry
(org-paste-subtree))
(message "Unable to refile! %s" err))))
Refiling utility based on this StackOverflow question.
(defun jay/refile-to (file headline)
"Move current headline to specified location"
(let ((pos (save-excursion
(find-file file)
(org-find-exact-headline-in-buffer headline))))
(org-refile nil nil (list headline file nil pos))))
Custom refile functions.
(defmacro my:define-refiler (fname filename-form &optional headline-option)
"Macro which defines a function which refiles the heading under point to
the filename to which `filename-form' evaluates. `fname' will be the name of
that function.
If `headline-option' is 'datetree, then content will be refiled into a datetree
in that file. Heading will not be prompted."
`(defun ,fname ()
(interactive)
(let ((target-file-path ,filename-form)
(target-headline (and (not ,headline-option)
(ivy-read "Target Heading:" '("Thoughts" "Action Items")))))
;; Create heading if missing in that file
(when target-headline
(save-excursion
(find-file target-file-path)
(unless (org-find-exact-headline-in-buffer target-headline)
(end-of-buffer)
(newline)
(insert (concat "* " target-headline)))))
;; Do the refile
(pcase ,headline-option
('datetree (save-excursion
(unpackaged/org-refile-to-datetree-using-ts-in-entry 'earliest target-file-path t)))
((pred stringp) (progn
(org-mark-ring-push)
(jay/refile-to target-file-path ,headline-option)
(org-mark-ring-goto)))
(- (progn
(org-mark-ring-push)
(jay/refile-to target-file-path target-headline)
(org-mark-ring-goto)))))))
(my:define-refiler my:org-refile-as-concept (my-org-find-concept))
(my:define-refiler my:org-refile-as-journal (my-org-journal-make-if-missing))
(my:define-refiler my:org-refile-as-dream (my:org-dream-journal-path) 'datetree)
(my:define-refiler my:org-refile-as-scrap (my:org-scraps-path) "Scraps")
(my:define-refiler my:org-refile-as-incomprehensible (my:incomprehensible-voice-notes-path) 'datetree)
(defun my:org-refile-transient ()
"Wrapper for org-refile with some personal shortcuts and behaviors."
(interactive)
(let
((choice (read-multiple-choice "Refile Heading As..."
'((?c "Concept")
(?j "Journal")
(?s "Scrap")
(?d "Dream")
(?i "Incomprehensible")
(?o "Other")))))
(case (first choice)
(?c (my:org-refile-as-concept))
(?j (my:org-refile-as-journal))
(?s (my:org-refile-as-scrap))
(?d (my:org-refile-as-dream))
(?i (my:org-refile-as-incomprehensible))
(?o (org-refile)))))
Always save all files after a refile. A little surprised the files aren’t saved automatically. From StackOverflow.
(advice-add 'org-refile :after
(lambda (&rest _)
(org-save-all-org-buffers)))
Use IPython, and use the included initialization script.
(setq python-shell-interpreter "ipython"
python-shell-interpreter-args (concat "--simple-prompt -i "
(expand-file-name "~/.emacs.d/emacs-config/emacs.ipy")))
Use Jedi, but make it a little less invasive.
(setq jedi:complete-on-dot nil)
Just another Microsoft Windows hack. Without this, PDB doesn’t show any output until the process exits.
(when-on-windows
(setenv "PYTHONUNBUFFERED" "TRUE"))
Set the snippet dir
(setq yas-snippet-dirs '("~/.emacs.d/emacs-config/snippets"))
(yas-reload-all)
Configure calDAV server. Defcustoms used so that I can occasionally share this file on GitHub without needing to sanitize as much.
(defcustom my:org-ical-timezone nil "Timezone used for org-caldav")
(defcustom my:org-caldav-url nil "URL used by org-caldav")
(defcustom my:org-caldav-calendar-id nil "ID used by org-caldav")
(if (and my:org-caldav-calendar-id my:org-ical-timezone my:org-caldav-url)
(setq org-caldav-url my:org-caldav-url
org-caldav-calendar-id my:org-caldav-calendar-id
org-caldav-inbox (concat my:org-file-tree-base-path "/calendar.org")
org-icalendar-timezone my:org-ical-timezone
org-caldav-files '()))
Include recentf in ivy-switch-buffer
(setq ivy-use-virtual-buffers t)
Change the theme a bit – for whatever reason this wasn’t defcustom’d.
(setq-default cfw:org-face-agenda-item-foreground-color "Pink")
Use the default Roswell implementation, if it exists. Else, SBCL.
To install Roswell on Arch:
yay -S roswell
ros install sbcl
ros install slime
(if (file-directory-p "~/.roswell")
(progn
(load (expand-file-name "~/.roswell/helper.el"))
(setq inferior-lisp-program "ros dynamic-space-size=22768 -Q run"))
(setq inferior-lisp-program "sbcl"))
Show information about symbol at point in echo area.
(add-to-list 'slime-contribs 'slime-autodoc)
(add-hook 'slime-mode-hook
(lambda ()
(slime-autodoc-mode)
(set-up-slime-ac)
(auto-complete-mode)))
Per the quickstart
(emms-standard)
(emms-default-players)
Add function which calls some function (presumably related to music playback), then echos the current track.
(defun my:emms-do-then-show (func)
"Call func interactively, then show current track."
(call-interactively func)
(sit-for 0.01) ;; else EMMS will say nothing is playing.
(emms-show))
Jump through all occurrences in buffer.
(setq ahs-default-range 'ahs-range-whole-buffer)
Only search code.
(setq ahs-inhibit-face-list '(font-lock-comment-delimiter-face
font-lock-comment-face
font-lock-doc-face
font-lock-doc-string-face
font-lock-string-face))
Set delay for AHS highlighting.
(customize-set-variable 'ahs-idle-interval 0.25)
Newest emails first.
(customize-set-variable 'notmuch-search-oldest-first nil)
(setq send-mail-function 'smtpmail-send-it)
Auto-update proced
buffers.
(custom-set-variables
'(proced-auto-update-flag t)
'(proced-auto-update-interval 1))
Always recenter when jumping between bookmarks.
(customize-set-variable 'bm-recenter t)
Highlighting is done by line.
(customize-set-variable 'bm-highlight-style 'bm-highlight-only-line)
Tell clhs.el
to read my local copy of the CLHS.
(defcustom my:website-archive-root nil "Root directory of my website archives.")
(customize-set-variable 'common-lisp-hyperspec-root
(concat my:website-archive-root "clhs"))
Don’t need to see trailing whitespace here – I won’t be fixing it.
(add-hook 'eww-mode-hook
(lambda ()
(setq show-trailing-whitespace nil)
(olivetti-mode 1)))
Add fence regex for Python block strings.
(add-to-list 'fence-edit-blocks
'("\"\"\"" "\"\"\"" 1))
Don’t CD whenever I start debugging – I’ll do that manually.
(customize-set-variable 'gud-chdir-before-run nil)
Install foot gun
(customize-set-variable 'realgud-safe-mode nil)
Add some code to launch realgud & avoid the excessive window changes.
Call something like realgud:pdb, but, with the following effects;
- Don’t let realgud make ANY changes to window or buffer layout.
- Force -in-srcbuf? to t so that realgud doesn’t aggressively steal focus into the command buffer.
- Unset the process’ query on kill flag, because yes I want to kill it.
- Display the command buffer in a new, non-focus window, like any other package in Emacs would.
With any luck, focus will now be in your original source buffer, but with debugging overlays and shortkeys.
(defmacro my:call-realgud (&rest forms)
`(let
((process-buffer (save-window-excursion
,@forms)))
(with-current-buffer process-buffer
(realgud-cmdbuf-info-in-srcbuf?= t))
(set-process-query-on-exit-flag (get-buffer-process process-buffer) nil)
(display-buffer process-buffer)))
Launch PDB for the current file. Without prefix arg, the command to start PDB will be the last one used. If this is the first call to PDB, we’ll guess. With prefix arg, it allows realgud to prompt for input.
(defun my:simple-pdb (arg)
"Just debug."
(interactive "P")
(my:call-realgud
(realgud:pdb
(cond
;; Given an prefix arg -- don't tell realgud what to do, it will ask
(arg
nil)
;; There's no history -- just guess
((null (cdr realgud:pdb-minibuffer-history))
(pdb-suggest-invocation "pdb"))
;; No prefix arg and history available -- run last command
(t
(car realgud:pdb-minibuffer-history))))))
Advise realgud:cmd-quit such that it cleans up after itself. When the debugger quits, kill the command buffer, and delete its window.
(defadvice realgud:cmd-quit (after my:cleanup-command-buf activate)
"Find the command buffer associated with the current buffer, assert that
its process is dead, and kill both it and its window."
(let*
((cmdbuf (realgud-get-cmdbuf))
(window (and cmdbuf (get-buffer-window cmdbuf))))
(when cmdbuf
(kill-buffer cmdbuf))
(when window
(delete-window window))))
Adjust autosave interval.
(customize-set-variable 'real-auto-save-interval 0.25)
Use fish shell, if it is available.
(when (executable-find "fish")
(customize-set-variable 'vterm-shell "fish"))
Kill buffers when the shell exits.
(customize-set-variable 'vterm-kill-buffer-on-exit t)
(require 'mu4e) ;(require 'mu4e-views) (setq mail-user-agent 'mu4e-user-agent) (setq mu4e-sent-folder "/Sent" mu4e-drafts-folder "/Drafts" mu4e-trash-folder "/Trash")
Jump to end of buffer after every chunk is inserted.
(add-hook 'gptel-post-stream-hook ;; never runs for some reason?
(lambda ()
(end-of-buffer)))
(add-hook 'gptel-post-response-hook
(lambda (a b)
(end-of-buffer)))
(message "SECTION: Monkey Patching")
This advice modifies isearch such that it will always auto-wrap whenever a match is not found between point and the end of the buffer.
(defadvice isearch-search (after isearch-no-fail activate)
"Autowrap searches."
(unless isearch-success
(ad-disable-advice 'isearch-search 'after 'isearch-no-fail)
(ad-activate 'isearch-search)
(isearch-repeat (if isearch-forward 'forward))
(ad-enable-advice 'isearch-search 'after 'isearch-no-fail)
(ad-activate 'isearch-search)))
This advice causes isearch, the split-window-*, compilation mode, etc, functions to recenter the buffer. This means less time spent searching for the cursor, without having a distracting cursor design.
(defun my:maybe-recenter ()
"Recenter, unless it will cause an error in C-code. See recenter definition in window.c"
(interactive)
(when (equal (window-buffer) (current-buffer))
(recenter)))
(defadvice isearch-forward
(after isearch-forward-recenter activate)
(my:maybe-recenter))
(defadvice isearch-repeat-forward
(after isearch-repeat-forward-recenter activate)
(my:maybe-recenter))
(defadvice isearch-repeat-backward
(after isearch-repeat-backward-recenter activate)
(my:maybe-recenter))
(defadvice isearch-printing-char
(after isearch-printing-char-then-recenter activate)
(my:maybe-recenter))
(defadvice split-window-below
(after split-window-below-recenter activate)
(my:maybe-recenter))
(defadvice split-window-left
(after split-window-left-recenter activate)
(my:maybe-recenter))
(defadvice split-window-right
(after split-window-right-recenter activate)
(my:maybe-recenter))
(defadvice split-window-above
(after split-window-above-recenter activate)
(my:maybe-recenter))
(defadvice compile-goto-error
(after compile-goto-error-then-recenter activate)
(my:maybe-recenter))
If I want to quit a search, let me. What a weird default.
(customize-set-variable 'search-nonincremental-instead nil)
Whenever a file in version control is opened, enable diff-hl mode.
(add-hook 'find-file-hook
(lambda ()
(when (vc-backend (buffer-file-name))
(diff-hl-mode)
(diff-hl-update))))
Whenever a file is saved, make sure diff-hl is force updated. TODO: This probably shouldn’t be needed?
(add-hook 'after-save-hook
(lambda ()
(when (or
(and (boundp 'diff-hl-mode) diff-hl-mode)
(and (boundp 'diff-hl-amend-mode) diff-hl-amend-mode))
(diff-hl-update))))
Yes, I do want to create the directories....
(defadvice find-file (before make-directory-maybe (filename &optional wildcards) activate)
"Create parent directory if not exists while visiting file."
(unless (file-exists-p filename)
(let ((dir (file-name-directory filename)))
(unless (file-exists-p dir)
(make-directory dir)))))
Magit doesn’t play nicely with Gerrit by default. Workaround from StackOverflow. Modified to specify remote branch as well (master, features/xyz, etc).
(defun magit-push-to-gerrit (source dest)
"Push an arbitrary branch or commit to refs/for/someting on gerrit. The source and dest are read in the minibuffer."
(interactive
(let ((source (magit-read-local-branch-or-commit "Push"))
(dest (magit-read-remote-branch "To")))
(list source dest)))
(magit-git-command-topdir (concat "git push origin " source ":refs/for/" (string-join (remove "origin" (split-string dest "/")) "/"))))
(transient-append-suffix 'magit-push "m" '("g" "Push to gerrit" magit-push-to-gerrit))
When killing emacs, ask to save the desktop. I don’t want this done by default, and I want to load the saved desktop only manually (so, no desktop-save-mode
).
2023-4-2: Disabled to get fg daemon working correctly, and I don’t use this anyway
;(add-hook 'kill-emacs-hook (ilambda () (when (y-or-n-p "Save Desktop?") (desktop-save-in-desktop-dir))))
Tweak org-cycle
such that it has a fourth state – subtree without drawers – after the children state.
(defun org-cycle-internal-local ()
"Do the local cycling action."
(let ((goal-column 0) eoh eol eos has-children children-skipped struct)
;; First, determine end of headline (EOH), end of subtree or item
;; (EOS), and if item or heading has children (HAS-CHILDREN).
(save-excursion
(if (org-at-item-p)
(progn
(beginning-of-line)
(setq struct (org-list-struct))
(setq eoh (point-at-eol))
(setq eos (org-list-get-item-end-before-blank (point) struct))
(setq has-children (org-list-has-child-p (point) struct)))
(org-back-to-heading)
(setq eoh (save-excursion (outline-end-of-heading) (point)))
(setq eos (save-excursion (org-end-of-subtree t t)
(when (bolp) (backward-char)) (point)))
(setq has-children
(or
(save-excursion
(let ((level (funcall outline-level)))
(outline-next-heading)
(and (org-at-heading-p t)
(> (funcall outline-level) level))))
(and (eq org-cycle-include-plain-lists 'integrate)
(save-excursion
(org-list-search-forward (org-item-beginning-re) eos t))))))
;; Determine end invisible part of buffer (EOL)
(beginning-of-line 2)
(while (and (not (eobp)) ;This is like `next-line'.
(get-char-property (1- (point)) 'invisible))
(goto-char (next-single-char-property-change (point) 'invisible))
(and (eolp) (beginning-of-line 2)))
(setq eol (point)))
;; Find out what to do next and set `this-command'
(cond
;; Empty entry case
((= eos eoh)
;; Nothing is hidden behind this heading
(unless (org-before-first-heading-p)
(run-hook-with-args 'org-pre-cycle-hook 'empty))
(org-unlogged-message "EMPTY ENTRY")
(setq org-cycle-subtree-status nil)
(save-excursion
(goto-char eos)
(outline-next-heading)
(when (org-invisible-p) (org-flag-heading nil))))
;; Show Children Case
((and (or (>= eol eos)
(not (string-match "\\S-" (buffer-substring eol eos))))
(or has-children
(not (setq children-skipped org-cycle-skip-children-state-if-no-children))))
;; Entire subtree is hidden in one line: children view
(unless (org-before-first-heading-p)
(run-hook-with-args 'org-pre-cycle-hook 'children))
(if (org-at-item-p)
(org-list-set-item-visibility (point-at-bol) struct 'children)
(org-show-entry)
(org-with-limited-levels (org-show-children))
(org-show-set-visibility 'canonical)
;; Fold every list in subtree to top-level items.
(when (eq org-cycle-include-plain-lists 'integrate)
(save-excursion
(org-back-to-heading)
(while (org-list-search-forward (org-item-beginning-re) eos t)
(beginning-of-line 1)
(let* ((struct (org-list-struct))
(prevs (org-list-prevs-alist struct))
(end (org-list-get-bottom-point struct)))
(dolist (e (org-list-get-all-items (point) struct prevs))
(org-list-set-item-visibility e struct 'folded))
(goto-char (if (< end eos) end eos)))))))
(org-unlogged-message "CHILDREN")
(save-excursion
(goto-char eos)
(outline-next-heading)
(when (org-invisible-p) (org-flag-heading nil)))
(setq org-cycle-subtree-status 'children)
(unless (org-before-first-heading-p)
(run-hook-with-args 'org-cycle-hook 'children)))
;; Show subtree content case
((or children-skipped
(and
(eq last-command this-command)
(eq org-cycle-subtree-status 'subtree-headings)))
;; We just showed the children, or no children are there,
;; now show everything.
(unless (org-before-first-heading-p)
(run-hook-with-args 'org-pre-cycle-hook 'subtree))
(org-flag-region eoh eos nil 'outline)
(org-unlogged-message "SUBTREE")
(setq org-cycle-subtree-status 'subtree)
(unless (org-before-first-heading-p)
(run-hook-with-args 'org-cycle-hook 'subtree)))
;; Show Subtree Headings Case
((and (or children-skipped (eq last-command this-command))
(eq org-cycle-subtree-status 'children))
;; We just showed the children, or no children are there,
;; now show everything.
(unless (org-before-first-heading-p)
(run-hook-with-args 'org-pre-cycle-hook 'subtree))
(org-kill-note-or-show-branches)
(org-unlogged-message "SUBTREE-HEADINGS")
(setq org-cycle-subtree-status 'subtree-headings)
(unless (org-before-first-heading-p)
(run-hook-with-args 'org-cycle-hook 'subtree)))
;; Hide everything case
(t
;; Default action: hide the subtree.
(run-hook-with-args 'org-pre-cycle-hook 'folded)
(org-flag-region eoh eos t 'outline)
(org-unlogged-message "FOLDED")
(setq org-cycle-subtree-status 'folded)
(unless (org-before-first-heading-p)
(run-hook-with-args 'org-cycle-hook 'folded))))))
(message "SECTION: OS Integration")
Support for using EXWM (Emacs X-Window Manager). :) Though, I’ve switched to StumpWM for the time being.
(defun my:launch-de()
"Modified from code here: http://doc.rix.si/cce/cce-exwm.html"
(use-package exwm)
(use-package exwm-config)
;; (use-package exwm-systemtray)
(use-package exwm-edit)
(use-package clipmon)
(clipmon-mode-start)
(exwm-enable)
(defun exwm-rename-buffer ()
(interactive)
(exwm-workspace-rename-buffer
(concat exwm-class-name ":"
(if (<= (length exwm-title) 50) exwm-title
(concat (substring exwm-title 0 49) "...")))))
;; Add these hooks in a suitable place (e.g., as done in exwm-config-default)
(add-hook 'exwm-update-class-hook 'exwm-rename-buffer)
(add-hook 'exwm-update-title-hook 'exwm-rename-buffer)
(setq window-divider-default-right-width 4)
(window-divider-mode t))
;; (exwm-systemtray-enable))
;(setq exwm-manage-configurations '((t char-mode t))))
(unless-on-windows
(use-package exwm)
(use-package exwm-config)
(exwm-config-example))
(message "SECTION: Notes & Vestigial Code")
Notes are addressed to myself & my own use-cases.
Emacs for Windows works extremely well, but still have constant hangs and crashes. Using Emacs for Linux in WSL (on Windows >=10) solves most of these issues (while creating a few others..).
- Install the Microsoft Ubuntu image. There’s a third-party Arch image which works for the most part, but it’s been less stable. The Ubuntu image is secure, verified, and maintained by Microsoft.
- Make paths more MSYS/MINGW/GitForWindows compatible with
ln -s /mnt/c /c
. - Install
fish
,build-essential
, and whatever other system apps I use. - Build emacs from the git mirror, unless you want a very old build. Not needed if using the Arch image. Make sure to use the GTK and ImageMagick switches. At time of writing, enabling XWidgets causes a compilation error due to missing typenames (something related to Lisp/JS interop?). Enabling cairo seems to be fine.
- Configure
xmodmap
like in Linux, as discussed in the Key Mappings section. - Install
fonts-noto
andfonts-liberation
,silversearcher-ag
, and whatever else is needed. - Install
vcxsrv
on Windows and run it. - Mount network shares like
sudo mount -t drvfs '\\blah.com\SHARE' /mnt/blah
. Save any recurring mounts in ~/mount-drives.sh. TODO: fstab? - Kindly ask IT, just one more time, to let you install native Linux. :)
#!/bin/bash
export DISPLAY=localhost:0
export GDK_SCALE=0.5
export GDK_DPI_SCALE=2
xmodmap ~/.Xmodmap
xset r rate 200 60
bash ~/mount-drives.sh # drvfs commands, though tbh fstab would probably be better.
source activate py36_64 # Use the python interpreter that I control, not the system's.
#sudo service dbus start # DON'T do this, it doesn't set environment variables right in WSL
export $(dbus-launch) # For whatever reason, systemd doesn't work like this does
export $(gnome-keyring-daemon) # run secret-tool store --label="Test" test test once to trigger wallet creation
emacs --debug-init
Emacs can take some time to start up. Especially if you’re loading over around 500 *.org files every time....
Write this to ~/.config/systemd/user/emacs.service
, then use emacs via emacsclient
for (basically) instant startups. You’ll need to change the path to emacs
and emacsclient
if you didn’t compile it yourself – they should match src_sh{which emacs}.
Don’t forget to src_sh{systemctl enable –user emacs} and src_sh{systemctl start –user emacs}.
[Unit] Description=Emacs text editor Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/ [Service] Type=forking ExecStart=/usr/local/bin/emacs --daemon ExecStop=/usr/local/bin/emacsclient --eval "(kill-emacs)" Environment=SSH_AUTH_SOCK=%t/keyring/ssh Restart=on-failure [Install] WantedBy=default.target
Now, to edit a file with emacs
from the command line, you’ll want to use emacsclient -c
. It’s faster to use an alias.
Bash:
alias e="emacsclient -c"
Fish:
alias e="emacsclient -c"
In Bash and similar shells:
export ALTERNATE_EDITOR=""
export EDITOR="emacsclient -c"
export VISUAL="emacsclient -c -a emacs"
In Fish:
set -Ux EDITOR "emacsclient -c"
set -Ux VISUAL "emacsclient -c -a emacs"
Substitute -c
for -t
if you like that sort of thing.
Add an SSH config file so that Github can get a different ID_RSA than whatever the machine is already configured for.
At ~/.ssh/config
Host github.com
User git
IdentityFile ~/.ssh/id_rsa_gh
Also remember to set the upstream URL to SSH if cloned as HTTPS:
git remote set-url origin [email protected]:bgutter/dotemacs.git
The rest should be obvious. Get creds from github, etc.
These notes aren’t even actually related to Emacs. Just some notes that I frequently find that I need while using emacs to accomplish other tasks.
Find all processes listening on a specific port
sudo ss -lptn 'sport = :9000'
Run these commands in eshell
This command clones one filesystem to another on Windows.
robocopy C:/path/to/source D:/path/to/dest /e /copyall /mir /log+:robolog.txt
Do this if you don’t have many persmissions – it skips owner info, ACL, etc:
robocopy C:/path/to/source D:/path/to/dest /e /copy:DAT /mir /log+:robolog.txt
Commit skipping hooks
git commit --no-verify
Make sure to install virtualenv first
pip install virtualenv
Replace python3.8 with whichever installed version you prefer to use
python -m virtualenv --python=python3.8 /path/to/env
Run this to link a new project or repo into Quicklisp, making it quickload-able
ln -s /path/to/folder/containing/my-project ~/quicklisp/local-projects/my-project
The directory should contain the ASD file, and that ASD file should just contain the project’s defsystem.
Follow these instructions – don’t bother trying to use pacman or aptitude.
Summarized from Clojure Deps and CLI…
A minimal modern Clojure project is structured like this:
- Project folder like
my-project
- Dependencies file at
my-project/deps.edn
- Code in
my-project/src/app.clj
Here’s an example deps.edn
with a single dependency from the reference page.
{:deps
{clojure.java-time/clojure.java-time {:mvn/version "1.1.0"}}}
Here’s an example hello.clj
, also from the reference site.
(ns hello
(:require [java-time.api :as t]))
(defn time-str
"Returns a string representation of a datetime in the local time zone."
[instant]
(t/format
(t/with-zone (t/formatter "hh:mm a") (t/zone-id))
instant))
(defn run [opts]
(println "Hello world, the time is" (time-str (t/instant))))
You would run it like this:
cd my-project
clj -X hello/run
Open a file in a project, then run cider-jack-in
.
This is different from creating a normal Clojure project because you need to use a namespace folder, and it needs to use underscores.
From the ClojureScript guide:
hello-world # Our project folder
├─ src # The CLJS source code for our project
│ └─ hello_world # Our hello_world namespace folder
│ └─ core.cljs # Our main file
├─ cljs.jar # (Windows only) The standalone Jar you downloaded earlier
└─ deps.edn # (macOS/Linux only) A file for listing our dependencies
Content of deps.edn
:
{:deps {org.clojure/clojurescript {:mvn/version "1.11.54"}}}
Content of core.cljs
:
(ns hello-world.core)
(println "Hello world!")
And run it like this:
clj -M --main cljs.main --compile hello-world.core --repl
This will open the site in a browser window.
Continuing from the ClojureScript guide:
Add a dependency on React to deps.edn
:
{:deps {org.clojure/clojurescript {:mvn/version "1.11.54"}
cljsjs/react-dom {:mvn/version "16.2.0-3"}}}
Require it in your core.cljs
file, and, use the APIs:
(ns hello-world.core
(:require react-dom))
(.render js/ReactDOM
(.createElement js/React "h2" nil "Hello, React!")
(.getElementById js/document "app"))
You can run the app with the same command as before.
This is from the Reagent Github:
Add both react and reagent dependencies to deps.edn
:
{:deps {org.clojure/clojurescript {:mvn/version "1.11.54"}
cljsjs/react {:mvn/version "16.2.0-3"}
cljsjs/react-dom {:mvn/version "16.2.0-3"}
reagent/reagent {:mvn/version "1.2.0"}}}
Do something like this in your main clojure file:
(ns hello-world.core
(:require react-dom
[reagent.core :as r]
[reagent.dom :as rd]))
(defn child [name]
[:p "Hi, I am " name])
(defn childcaller []
[child "Foo Bar"])
(rd/render [childcaller]
(.-body js/document))
Run the app as usual
Summarized from the Figwheel tutorial.
Inside of deps.edn
, place a dependency on figwheel-main
, and also
specify your source and target paths.
{:deps {com.bhauman/figwheel-main {:mvn/version "0.2.18"}}
:paths ["src" "target"]}
Assuming your code is in src/hello/cruel_world.cljs
, and it looks something like this:
(ns hello.cruel-world)
(defn what-kind? []
"Cruel")
(js/console.log (what-kind?))
…you can then run your app under Figwheel with the following command:
clojure -M -m figwheel.main --compile hello.cruel-world --repl
This means to run the Figwheel main function, compiling your app’s namespace, and also open a REPL for us to work in.
Compiled assets will be placed into /target
.
Still working from the Figwheel tutorial…
Builds are defined in *.cljs.edn files in the project root folder, next to deps.edn
Put a file like cruel.cljs.edn
with the following content:
{:main hello.cruel-world}
Into your project like this:
hello-cljs/
├── cruel.cljs.edn
├── deps.edn
└── src
└── hello
└── cruel_world.cljs
Now, you want to run it like this instead:
clojure -M -m figwheel.main --build cruel --repl
rm -rf target/public
clojure -M -m figwheel.main --optimizations advanced --build-once cruel
First, make sure you are using a build file.
Then, add a resources/
directory to :paths
in deps.edn
:
{:deps {com.bhauman/figwheel-main {:mvn/version "0.2.18"}
com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}}
:paths ["src" "target" "resources"]}
Inside the resources folder, make a dedicated subfolder for css. Then add this CSS resource path to the build’s metadata in your build.edn (for example, cruel.cljs.edn
):
^{:css-dirs ["resources/public/css"]}
{:main hello.cruel-world}
Add your index.html
and style.css
into the project structure like this:
hello-cljs/
├── cruel.cljs.edn
├── deps.edn
├── resources/
| └── public/
| ├── index.html
| └── css/
| └── style.css
└── src/
└── hello/
└── cruel_world.cljs
style.css
can contain whatever you want. index.html
should look like this:
<!DOCTYPE html>
<html>
<head>
<!-- this refers to resources/public/css/style.css -->
<link href="css/style.css" rel="stylesheet" type="text/css">
</head>
<body>
<div id="app"></div>
<!-- this refers to target/public/cljs-out/cruel-main.js -->
<script src="cljs-out/cruel-main.js"></script>
</body>
</html>
Figwheel lets you define a Ring handler function so you can start on a Clojure backend for your Clojurescript application.
From the figwheel docs:
Create a new clojure file in your source directory which will include backend (Clojure, not Clojurescript) code. Here’s a minimal example:
(ns hello-world.app-server)
(defn handler [req]
{:status 404
:headers {"Content-Type" "text/html"}
:body "Yep the server failed to find it."})
Here’s an example using a framework – compojure:
(ns hello-world.app-server
(:require
[compojure.core :refer [defroutes GET]]
[compojure.route :as route]
[hiccup.page :refer [html5 include-js include-css]]))
(defn index-html []
(html5
[:head
[:meta {:charset "UTF-8"}]
[:meta {:name "viewport"
:content "width=device-width, initial-scale=1"}]
(include-css "/css/style.css")]
[:body
[:div {:id "app"}]
(include-js "/cljs-out/dev-main.js")]))
(defroutes handler
(GET "/" [] (index-html))
(route/not-found "<h1>Page not found</h1>"))
Whatever you do, save that to src/hello_world/app_server.clj
, then make sure to point to it in the build file (like the cruel.cljs.edn
from earlier):
^{:css-dirs ["resources/public/css"]
:ring-handler hello-world.app-server/handler}
{:main hello.cruel-world}
Figwheel will hot reload your backend just like your frontend.
Call cider-jack-in-cljs
from a buffer in the project.