Managing a plain emacs config that got closer and closer to what you get ootb with doom emacs was beginning to feel tedious, so this is an attempt to for a setup with ~feature parity on top of doom emacs. It was probably due a clean up after all these years.
First, install doom emacs. I have since moved on to doing this with nix and nix-darwin.
git clone --depth 1 https://github.com/doomemacs/doomemacs ~/.emacs.d
~/.emacs.d/bin/doom install
Then clone the config.
cd && git clone [email protected]:torgeir/.emacs.d.git ~/.doom.d
Put ~/.doom.d/bin
on path for the terminal shortcuts e
and et
to open emacsclient and emacsclient in the terminal.
The built in emacs help is fantastic. The doom one on SPC h d
is even better! Here’s a few examples.
- Browse examples of useful doom apis
- Lookup help for doom modules
- Search for in the doom .emacs.d folder
SPC h d h
to access documentationSPC h d
followed byC-h
invokes a fuzzy search of keys available in the formerly pressed binding. This also works withSPC
followed byC-h
.K
on a module to view its documentationgd
on a module to browse its directory
Type SPC m g g
to run consult-org-heading
, then C-c C-;
to embark-export
.
setq
sets a variable in the current buffersetq-default
sets a default value for a variable for all bufferssetq-local
sets a buffer local variable, it needs to be buffer local first(make-variable-buffer-local '...)
When doom sync -u
fails for some packages, try deleting the cloned repos before running it again.
E.g. rm -rf ~~/.config/doom-local/straight/repos/{magit,transient,with-editor}
This config uses the :config literal
that tangles this file to config.el
on save. The +literate-config-file is set in init.el
and points to this file.
- https://github.com/zzamboni/dot-doom/blob/master/doom.org
- https://abdelhakbougouffa.pro/posts/config/
I usually navigate this with c-c c-n
or c-c c-p
, or mgg
.
The madness that is dynamic is too much for anyone, really.
;;; $DOOMLOCALDIR/config.el -*- lexical-binding: t; -*-
This is tangled to packages.el
on save.
(package! nerd-icons-dired)
(package! d2-mode)
(package! llm)
(package! ellama)
(package! pulsar)
(package! copilot
:recipe (:host github :repo "copilot-emacs/copilot.el" :files ("*.el" "dist")))
(package! tide :disable t)
(package! catppuccin-theme)
(package! magit-delta)
(package! elfeed-tube)
(package! evil-cleverparens)
(package! transpose-frame)
(package! highlight-symbol)
(package! olivetti)
(package! calendar-norway)
(package! spray)
(package! dired-subtree)
(package! org-ai :recipe (:host github :repo "rksm/org-ai"))
(package! mastodon :recipe (:host codeberg :repo "martianh/mastodon.el"))
(package! hnreader)
(package! reddigg)
(package! org-appear :recipe (:host github :repo "awth13/org-appear"))
(package! remark-mode)
(package! command-log-mode) ;; C-c o
;; needed for wn program on a mac?
(package! wordnut :pin "feac531404041855312c1a046bde7ea18c674915")
;; needed for wn program on a mac?
(package! synosaurus :pin "14d34fc92a77c3a916b4d58400424c44ae99cd81")
(package! recursion-indicator)
(package! org-alert)
(package! ellama)
(package! spacious-padding)
(package! mu4e-alert)
You can try packages without loading them permanently by calling m-x
straight-use-package
or (call-interactively ‘straight-use-package).
I can’t remember this, so here’s a function
(defun t/try ()
(interactive)
(call-interactively 'straight-use-package))
Ignore some of the cached emacs files in recent files
(after! recentf
(add-to-list 'recentf-exclude "\.emacs\.d/\.local"))
(let ((email (getenv "USER_EMAIL"))
(email-2 (getenv "USER_EMAIL_2")))
(when (not email) (error "No USER_EMAIL set?"))
(when (not email-2) (error "No USER_EMAIL_2 set?"))
(setq user-full-name "Torgeir Thoresen"
user-mail-address email
user-mail-address-2 email-2))
(defun t/1p (item &optional args)
"Lookup 1p item. On linux, sign in manually first."
(let ((args (or args "--fields label=password")))
(if is-mac
(with-temp-buffer
(if (zerop (call-process-shell-command (format "op item get %s %s" item args) nil t))
(replace-regexp-in-string (rx "\n" eos) "" (buffer-string))
(error "1p: looking up item failed.")))
(let* ((pass (read-passwd "1p master password: "))
(session-token nil)
(ret nil))
(setq session-token (with-temp-buffer
(if (zerop (call-process-shell-command (format "echo -n %s | op signin --raw" pass) nil t))
(replace-regexp-in-string (rx "\n" eos) "" (buffer-string))
(error "1p: auth failed."))))
(with-temp-buffer
(if is-linux
(call-process-shell-command (format "op --session %s item get %s %s" session-token item args) nil t))
(replace-regexp-in-string (rx "\n" eos) "" (buffer-string)))))))
Needed to do this to make emacs discover 1p SSH_AGENT_SOCK
set in .zprofile. Or run this command from the terminal
doom env -a ^SSH_ -a ^GPG
[2023-10-07 Sat] On mac this still needs [email protected] [2024-01-13 Lør] Fixed by patching gnupg
Prerequisits, import and trust key ultimately
gpg --batch --import
# <enter>
# <paste key>
# c-d
gpg --list-keys
gpg --edit-key 922E681804CA8D82F1FAFCB177836712DAEA8B95
# gpg> trust
# gpg> 5
(defun t/gpg ()
(interactive)
(start-process-shell-command
"gpg:agent"
nil
(format
"gpg-connect-agent updatestartuptty /bye > /dev/null && \
$(gpgconf --list-dirs libexecdir)/gpg-preset-passphrase -c -P '%s' \
$(gpg --fingerprint --with-keygrip [email protected] | awk '/Keygrip/ {print $3}' | tail -n 1)"
(t/1p "keybase.io" "--format json | jq -j '.fields[] | select(.id == \"password\") | .value'")))
(let ((p (start-process-shell-command "gpg:test" nil "gpg -q --batch -d ~/.authinfo.gpg 2>&1 1>/dev/null")))
(set-process-sentinel p (lambda (p event) (message "%s %s" p event)))))
(setq org-directory (expand-file-name "~/Dropbox/org/")
org-agenda-files '("~/Dropbox/org")
org-archive-location "%s_archive.gpg::") ; so files are encrypted automatically
My old collection of more or less useful defuns.
(progn
(defconst is-win (featurep :system 'windows))
(defconst is-cygwin (featurep :system 'windows))
(defconst is-mac (featurep :system 'macos))
(defconst is-linux (featurep :system 'linux))
(defun t/user-file (path) (concat (expand-file-name "~/") path))
(defun t/user-emacs-file (path) (concat doom-user-dir path))
(defun t/user-dropbox-folder (path) (expand-file-name (concat "~/Dropbox" (if is-mac " (Personal)" "") "/" path)))
(load! (concat doom-user-dir "t-defuns.el")))
A small overlay map that exposes a set of key bindings until you press q, or something else not in the keymap.
(defun t/micro-state (quit key fn &rest bindings)
"Micro state that temporarily overlays a new key map, kinda like hydra"
(let ((keymap (make-sparse-keymap)))
(while key
(bind-key key fn keymap)
(setq key (pop bindings)
fn (pop bindings)))
(lambda ()
(interactive)
(let ((exit (set-temporary-overlay-map keymap t (lambda () (when quit (quit-window))))))
(when quit
(bind-key "q" (cmd! (funcall exit)) keymap))))))
And one that enters a mode
, then turns on the keymap. It turns mode
off again if you hit a key not in the map.
(defun t/micro-state-in-mode (mode after key fn &rest bindings)
"Micro state that toggles mode and temporarily overlays a new key map, kinda like hydra"
(let ((keymap (make-sparse-keymap)))
(while key
(bind-key key fn keymap)
(setq key (pop bindings)
fn (pop bindings)))
(lambda ()
(interactive)
(funcall mode)
(set-temporary-overlay-map keymap t (lambda nil
(funcall mode -1)
(when after (after)))))))
Some commands are useful from within the minibuffer. This needs enable-recursive-minibuffers
, see below.
(after! vertico
(map! :map (vertico-map
minibuffer-local-map
read--expression-map)
:g "C-k" 'kill-line
:g "M-SPC" 'doom/leader))
When you change your mind and need to do something first, after you already started a command that opens the minibuffer. Cancel them with C-]
.
(setq enable-recursive-minibuffers t)
And a slightly fancier indicator than (minibuffer-depth-indicate-mode)
(use-package! recursion-indicator
:config
(recursion-indicator-mode))
Move ~~/.authinfo.gpg~ to the front. It is originally behind the macos keychain that doom puts in there.
(after! auth-source (setq auth-sources (nreverse auth-sources)))
Defaults
(let ((h (* 4 60 60)))
(setq auth-source-do-cache t
auth-source-cache-expiry h
password-cache t
password-cache-expiry h))
(after! epa
(setq-default epa-file-encrypt-to '("[email protected]"))
;; https://irreal.org/blog/?p=11827
(fset 'epg-wait-for-status 'ignore))
Wait just long enough.
(setq which-key-idle-delay 0.5
which-key-idle-secondary-delay 0.05)
Reset <a href=”file:~/.config/emacs/modules/config/default/config.el::(map! ”” #’drag-stuff-up”>drag stuff on meta arrows, m-left/right
is too engrained to move between words.
;; TODO kjører ikke på linux?
(add-hook! 'doom-after-init-hook :append
(defun t/unbind-drag-stuff ()
(interactive)
(map! :g "M-<left>" nil
:g "M-<right>" nil)))
On load theme
(defun t/doom-load-theme-hook (&optional &rest _)
"This is unused atm, no longer needed the highlight indent guides mode stuff."
(interactive))
(advice-remove 'load-theme 't/doom-load-theme-hook)
(advice-add 'load-theme :after 't/doom-load-theme-hook)
Opt-in to emojis instead 🚀
(add-hook! 'doom-first-buffer-hook
(defun t/after-first-buffer-hook ()
(global-emojify-mode -1)))
Soft wrap everywhere
(add-hook! 'doom-after-init-hook
(defun t/after-init-hook ()
(setq truncate-lines t)
(global-visual-line-mode 0)
(global-hl-line-mode -1)))
Programming modes
(add-hook! '(prog-mode-hook text-mode-hook conf-mode-hook)
(defun t/prog-mode-hook ()
(interactive)))
(after! whitespace
(add-to-list 'whitespace-style 'trailing))
(add-hook!
'(prog-mode-hook org-mode-hook)
(defun t/set-whitespace-style ()
(interactive)
(setq whitespace-style '(face tabs trailing lines ;; space-mark spaces
space-before-tab newline indentation
empty space-after-tab tab-mark
newline-mark missing-newline-at-eof))))
(after! emmet-mode
(add-to-list 'emmet-jsx-major-modes 'typescript-ts-mode)
(add-to-list 'emmet-jsx-major-modes 'tsx-ts-mode))
I spent so much time with vim, I will probably never give it up.
Useful for C-e
followed by C-x C-e
to eval an s-expression. Makes cleverparens
nav commands like L
and H
move across sexps
(setq evil-move-beyond-eol t)
Don’t use zz and zq for org src editing
(after! evil-collection
(add-to-list 'evil-collection-key-blacklist "ZZ")
(add-to-list 'evil-collection-key-blacklist "ZQ"))
Fine undo
(after! evil
(setq evil-want-fine-undo t))
(defun t/indent-after-paste (fn &rest args)
(evil-start-undo-step)
(let* ((u-prefix (t/prefix-arg-universal?))
(current-prefix-arg (unless u-prefix current-prefix-arg))
(args (if u-prefix (list nil) args)))
(apply fn args)
(if u-prefix
(indent-region (region-beginning) (region-end))))
(evil-end-undo-step))
(advice-add 'yank :around #'t/indent-after-paste)
(advice-add 'evil-paste-before :around #'t/indent-after-paste)
(advice-add 'evil-paste-after :around #'t/indent-after-paste)
I use SPC w h
instead of SPC w C-h
to move to the left window. C-h
is more useful as embark-prefix-help-command
, which this falls back to, like in all other keymaps
(map! :after evil :map evil-window-map "C-h" nil)
Try e.g. SPC C-h
to browse all available commands with vertico.
(map! :n "g-" #'evil-numbers/dec-at-pt
:n "g+" #'evil-numbers/inc-at-pt)
Some macros I once used.
This one makes camelCaseWords into to snake_case_words. Run it with @c
(evil-set-register ?c [?: ?s ?/ ?\\ ?\( ?\[ ?a ?- ?z ?0 ?- ?9 ?\] ?\\ ?\) ?\\ ?\( ?\[ ?A ?- ?Z ?0 ?- ?9 ?\] ?\\ ?\) ?/ ?\\ ?1 ?_ ?\\ ?l ?\\ ?2 ?/ ?g])
(after! evil-goggles
(setq evil-goggles-duration 0.2
evil-goggles-enable-delete t
evil-goggles-enable-change t)
(evil-goggles-use-diff-refine-faces)
(pushnew! evil-goggles--commands
'(evil-cp-delete-line
:face evil-goggles-delete-face
:advice evil-goggles--generic-blocking-advice)))
A useful macro one for testing stuff out
(defmacro comment (&rest ignore)
nil)
(comment
(funcall (t/micro-state nil "m" (cmd! (message "1")))))
Type qq
to record a macro to q
. Move to where you want the number and press C-x C-k C-i
. Move to the next line start to make the macro repeatble. Type q
. Undo. Select the list and hit @q
.
- one
- two
- three
(map!
:g "C-," #'embark-act ; global
:map org-mode-map "C-," #'embark-act
:map minibuffer-mode-map "C-," #'embark-act)
Prevent embark-export
, C-e
, from being “popupized” by doom’s :ui popup
and its (popup +all)
setting.
(set-popup-rule! "^*Embark" :ignore t)
You can use C-SPC
to preview candidates.
Embark improves prefix help commands, e.g. C-c C-h
, by showing auto complete that is fuzzy searchable.
Sometimes its useful not to close it. Hit q
after opening it to embark-toggle-quit
before e.g. running k
to kill a buffer. Or use this with m-x
(after! embark
(defun embark-act-noquit ()
"Run action but don't quit the minibuffer afterwards."
(interactive)
(let ((embark-quit-after-action nil))
(embark-act))))
Add a mapping to kill buffers like vterm without all the nagging.
(map! :map embark-buffer-map "D" #'t/volatile-kill-buffer-and-window)
C-a c-k
is so engrained in my fingers, I need it everywhere. C-a
seems to work out of the box.
(after! vertico
(map! :map vertico-map
:g "C-k" 'kill-line))
Exclude stuff from +default/search-project
by placing excludes in ~/.rgignore
Disable eldoc on the modeline, makes it so eldoc only appears on SPC h .
, i.e. on m-x eldoc-doc-buffer
(add-hook! '(web-mode js-mode rjsx-mode typescript-mode typescript-tsx-mode)
(defun t/eldoc-only-in-buffer ()
(interactive)
(setq eldoc-message-function (defun t-void (&optional one two) nil))))
Fix issue where org-eldoc-get-src-lang
is not defined?
(add-hook! 'org-mode-hook (defun t/fix-missing-definition-org-eldoc-get-src-lang ()
(interactive)
(require 'org-eldoc)))
A tuned version of Prot’s and Kristoffer Balintona’s vertico, maginalia and orderless setup
Some examples and explanations
- m-x: name= ^[m]
- contains chars of name in word in order AND starts with regex m
- m-x: Buffer= e nm=
- contains chars of Buffer in word in order AND contains e AND contains chars of nm in word in order (e.g. like in u<nm>ark)
- SPC s p: #defun#j gjp, ha,
- rg search for defun, in-emacs matching for long words that have leading inner words starting with g j and p in order, and have leading inner words starting with h and a
(after! orderless
(setq marginalia-max-relative-age 0)
(progn
(setq orderless-matching-styles
'(orderless-literal
;; orderless-initialism
;; orderless-regexp
;; orderless-flex
))
(setq orderless-style-dispatchers
'(initialism-dispatcher ;; suffix search with =
flex-dispatcher ;; suffix search with .
regexp-dispatcher ;; suffix search with ~
or-regexp ;; regex search with foo|bar
))
(defun regexp-dispatcher (pattern _index _total)
"Matches regexp."
(when (string-suffix-p "~" pattern)
`(orderless-regexp . ,(substring pattern 0 -1))))
(defun flex-dispatcher (pattern _index _total)
"Matches using any group in any order."
(when (string-suffix-p "." pattern)
`(orderless-flex . ,(substring pattern 0 -1))))
(defun or-regexp (pattern index _total)
"foo|bar"
(cond
((string-suffix-p "|" pattern)
`(orderless-regexp . ,(concat "\\(" (concat (s-replace "|" "\\|" (substring pattern 0 -1)) "\\)"))))
((string-match-p "|" pattern)
`(orderless-regexp . ,(concat "\\(" (concat (s-replace "|" "\\|" pattern) "\\)"))))))
(defun literal-dispatcher (pattern _index _total)
"Literal style dispatcher using the equals sign as a suffix."
(when (string-suffix-p "=" pattern)
`(orderless-literal . ,(substring pattern 0 -1))))
;;;###autoload
(defun initialism-dispatcher (pattern _index _total)
"Matches leading on words in order
E.g.
#fun#gjp, ha,
(defun t/js2-get-json-path (&optional hardcoded-array-index))
^^^^^ ^ ^ ^ ^ ^
#fun#gjp, hi,
Would not match the above as no leading words start h then another word starting with i
"
(when (string-suffix-p "," pattern)
`(orderless-strict-initialism . ,(substring pattern 0 -1))))
(defun orderless-strict-initialism (component)
"Match a COMPONENT as a strict initialism, optionally ANCHORED.
The characters in COMPONENT must occur in the candidate in that
order at the beginning of subsequent words comprised of letters.
Only non-letters can be in between the words that start with the
initials.
If ANCHORED is `start' require that the first initial appear in
the first word of the candidate. If ANCHORED is `both' require
that the first and last initials appear in the first and last
words of the candidate, respectively."
(orderless--separated-by
'(seq (zero-or-more alpha) word-end (zero-or-more (not alpha)))
(cl-loop for char across component collect `(seq word-start ,char))))))
Iterate through CamelCase words
(global-subword-mode 1)
npm install -g prettier
The built in apheleia
is enough, don’t need eglot
formatting as well. It messes up prettier.
(setq +format-with-lsp nil)
(after! dired
(setq dired-listing-switches "-aBhl --group-directories-first")
(add-hook 'dired-mode-hook (defun t/dired-truncate-lines ()
(interactive)
(visual-line-mode -1)
(toggle-truncate-lines 1)))
(add-hook 'dired-mode-hook 'nerd-icons-dired-mode)
(add-hook 'dired-mode-hook 'dired-subtree-toggle)
(add-hook 'dired-mode-hook 'dired-hide-details-mode)
(add-hook 'dired-mode-hook 'dired-async-mode)
)
(defun t/dired-subtree-tab ()
(interactive)
(cond
((and (t/prefix-arg-universal?)
(dired-subtree--is-expanded-p)) (t/dired-close-recursively))
((t/prefix-arg-universal?) (t/dired-open-recursively))
(t (t/dired-subtree-toggle))))
(after! (:or dired)
;; prevent kill all dired buffers on q
(map! :map dired-mode-map :ng "q" 't/volatile-kill-buffer)
(map! :map dired-mode-map :ng "Q" 'evil-record-macro)
(map!
:map (dired-mode-map)
"<return>" (cmd! (if (t/prefix-arg-universal?)
(call-interactively 'dired-find-file)
(let ((split-window-preferred-function 'ignore))
(call-interactively 'dired-find-file))))
"C-k" 'dired-kill-subdir
"<tab>" 't/dired-subtree-tab
:n "<tab>" 't/dired-subtree-tab
"<backspace>" 'dired-kill-subdir
"M-<down>" (cmd! (dired-find-alternate-file))
"M-<up>" (cmd! (find-alternate-file ".."))))
(after! dired
(require 'nerd-icons-dired)
(advice-add 'dired-subtree-toggle :around #'nerd-icons-dired--refresh-advice))
(defvar t-sidebar-buffer-prefix ":")
;; TODO hackery to be able to tweak display-buffer-alist even with doom's set-popup-rule!
(advice-add #'set-popup-rule! :after
(defun t/add-display-buffer-alist (fn &rest args)
(add-to-list 'display-buffer-alist
`(,(concat "^" t-sidebar-buffer-prefix)
(display-buffer-in-side-window)
(side . left)
(window-height . fit-window-to-buffer)
(body-function . (lambda (window) (set-window-dedicated-p window t)))
(window-parameters . ((no-other-window . t)))))))
(defun t-toggle-sidebar ()
(interactive)
(let* ((sidebar-project (replace-regexp-in-string (expand-file-name "~") "~" (t/project-root)))
(sidebar-name (concat t-sidebar-buffer-prefix sidebar-project))
(sidebar-buffer (get-buffer sidebar-name))
(sidebar-displayed (and sidebar-buffer (get-buffer-window sidebar-buffer))))
(if sidebar-displayed
(delete-window (get-buffer-window sidebar-buffer))
(when (not sidebar-buffer)
(with-current-buffer (dired-noselect sidebar-project)
;; unadvertise buffer so dired does not consider it on subsequent dired-jum
(dired-unadvertise (dired-current-directory))
(rename-buffer sidebar-name)))
(pop-to-buffer sidebar-name))))
(comment
(setq display-buffer-alist
(assoc-delete-all "^:" display-buffer-alist))
)
(comment
(while (not (equal (dired-current-directory) (t/project-root)))
(progn (dired-up-directory) (dired-subtree-cycle) (revert-buffer))))
Doom doesnt use the customize interface. It is useful nonetheless for experimenting with face colors etc
(set-popup-rule! "^*Customize" :ignore t)
Make s-s
save in customize
. Look up the function of a button using describe-text-properties
on a button, like the “Apply and Save”
(map! :map custom-mode-map
"s-s" 'Custom-save)
Zoom to the previewed org subtree when jumping between headings with consult-org-heading
.
(add-hook! 'consult-after-jump-hook :append
(defun t/after-consult-jump ()
""
;; org
(when (eq major-mode 'org-mode)
(when (org-at-heading-p)
(outline-hide-sublevels (org-outline-level)))
(org-show-subtree))
;; always
(recenter)))
(after! evil
(defun t/mc-skip-prev ()
(interactive)
(evil-multiedit-toggle-or-restrict-region)
(evil-multiedit-match-and-prev))
(defun t/mc-skip-next ()
(interactive)
(evil-multiedit-toggle-or-restrict-region)
(evil-multiedit-match-and-next)))
Make cursor follow matches so m-n
or m-p
can be used to skip matches easily, depending on what direction you are moving in. R
marks all occurrences from visual.
(after! evil
(setq evil-multiedit-follow-matches t)
(map!
:after evil
:mode evil-multiedit-mode
;; for some reason m-j does not work, use m-n and m-p instead
:n "M-n" #'t/mc-skip-next
:n "M-p" #'t/mc-skip-prev
;; don't clash with ~evil-cp-delete-sexp~, require visual mode for multi edit
:mode emacs-lisp-mode
:v "M-d" 'evil-multiedit-match-symbol-and-next))
;; test
;; test test
;; test
Restores a lost multiedit selection.
(map!
:g "C-M-r" 'evil-multiedit-restore)
Multiedit calls iedit which is missing all-caps in emacs 29.
(when (version< "29.0" emacs-version)
(defun all-caps (smtn)
(upper smtn)))
(defun t/font-spec (f &optional s weight)
(font-spec :family f
:size (or s 20)
:weight (or weight 'regular)
:slant 'normal
:width 'normal))
(setq t-fonts `((:face ,"IosevkaTermCurlySlab Nerd Font")))
(defun t/cycle-fonts (&optional font-spec)
(interactive)
(setq t-fonts (nconc (last t-fonts) (butlast t-fonts)))
(let* ((spec (or font-spec (car t-fonts)))
(f (plist-get spec :face))
(s (plist-get spec :size))
(w (plist-get spec :weight)))
(message "Font: %s, size: %s, weight: %s" f s w)
(setq doom-font (t/font-spec f s w)
doom-variable-pitch-font (t/font-spec "IosevkaEtoile Nerd Font" 19 w)
doom-big-font (t/font-spec f 28)
doom-font-increment 2)
(doom/reload-font)
f))
(t/cycle-fonts)
Remember to run
(nerd-icons-install-fonts)
(call-interactively 'describe-font)
or
fc-list
Navigate flymake and flycheck errors
(map!
:leader
(:prefix-map ("e" . "errors")
(:when t
:desc "Toggle flycheck" "t" #'flycheck-mode
:desc "List errors" "l" (cmd! (cond
((and (boundp 'flycheck-mode) flycheck-mode) (flycheck-list-errors))
(t (flymake-show-buffer-diagnostics))))
:desc "Jump to next error" "n" (cmd! (cond
((and (boundp 'flycheck-mode) flycheck-mode) (flycheck-next-error))
(t (flymake-goto-next-error))))
:desc "Jump to previous error" "N" (cmd! (cond
((and (boundp 'flycheck-mode) flycheck-mode) (flycheck-previous-error))
(t (flymake-goto-prev-error)))))))
(after! flymake
(map!
:map flymake-diagnostics-buffer-mode-map
:n "C-p" (cmd! (let ((p (point))
(b (current-buffer)))
(previous-line)
(flymake-goto-diagnostic (point))
(pop-to-buffer b)
(goto-char p)
(previous-line) ;; again??
))
:n "C-n" (cmd! (let ((p (point))
(b (current-buffer)))
(next-line)
(flymake-goto-diagnostic (point))
(pop-to-buffer b)
(goto-char p)
(next-line) ;; again??
))))
(after! (eglot flycheck)
(push 'eglot flycheck-checkers)
(delq! 'eglot flycheck-checkers))
Ignore some extra folders from projectile
(after! projectile
(add-to-list 'projectile-globally-ignored-directories "^build$")
(add-to-list 'projectile-globally-ignored-directories "^target$")
(add-to-list 'projectile-globally-ignored-directories "^\\.log$"))
(map!
:leader "1" '+workspace/switch-to-0
:leader "2" '+workspace/switch-to-1
:leader "3" '+workspace/switch-to-2
:leader "4" '+workspace/switch-to-3
:leader "5" '+workspace/switch-to-4
:leader "6" '+workspace/switch-to-5
:leader "7" '+workspace/switch-to-6
:leader "8" '+workspace/switch-to-7
:leader "9" '+workspace/switch-to-8
:leader "0" '+workspace/switch-to-final
:leader "-" '+workspace/switch-to)
And fix super
navigation across modes that steal SPC
.
(map!
"s-1" '+workspace/switch-to-0
"s-2" '+workspace/switch-to-1
"s-3" '+workspace/switch-to-2
"s-4" '+workspace/switch-to-3
"s-5" '+workspace/switch-to-4
"s-6" '+workspace/switch-to-5
"s-7" '+workspace/switch-to-6
"s-8" '+workspace/switch-to-7
"s-9" '+workspace/switch-to-8
"s-0" 'doom/reset-font-size)
Be explicit about when deleting workspaces
(after! (:and evil persp-mode)
(define-key! persp-mode-map
[remap delete-window] #'delete-window
[remap evil-window-delete] #'delete-window))
(map!
:map doom-leader-workspace-map
:leader :desc "Other workspace" "TAB '" '+workspace/other
:leader :desc "New workspace" "TAB w" '+workspace/new-named
:leader :desc "Next workspace" "TAB n" '+workspace:switch-next
:leader :desc "Previous workspace" "TAB p" '+workspace:switch-previous
:leader :desc "Swap next" "TAB j" '+workspace/swap-right
:leader :desc "Swap previous" "TAB k" '+workspace/swap-left)
;; like tmux window nav
(map!
;; make room under c-b
:gnm "C-b" nil
:gm :desc "Next workspace" "C-b C-n" '+workspace:switch-next
:gm :desc "Previous workspace" "C-b C-p" '+workspace:switch-previous
:map (magit-mode-map vterm-mode-map)
:gnm "C-b" nil
:gn :desc "Next workspace" "C-b C-n" '+workspace:switch-next
:gn :desc "Previous workspace" "C-b C-p" '+workspace:switch-previous)
(map!
:desc "Goto workspace" "s-t" '+workspace/switch-to
:desc "Rename workspace" "s-r" '+workspace/rename)
Make tab accept the current suggestion.
(after! company
(map! :map company-active-map
"<tab>" 'company-complete-selection
;; and c-e and right arrow like in zsh-autosuggest
"C-e" 'company-complete-selection
"<right>" 'company-complete-selection))
(after! tramp
(setq tramp-default-method "ssh"
tramp-verbose 1
tramp-default-remote-shell "/bin/bash"
tramp-connection-local-default-shell-variables
'((shell-file-name . "/bin/bash")
(shell-command-switch . "-c")))
(connection-local-set-profile-variables 'tramp-connection-local-default-shell-profile
'((shell-file-name . "/bin/bash")
(shell-command-switch . "-c"))))
Recentf cleanup logs a lot of error messages, like described here
(after! tramp
;; https://discourse.doomemacs.org/t/recentf-cleanup-logs-a-lot-of-error-messages/3273/4
(advice-add 'doom--recentf-file-truename-fn :override
(defun my-recent-truename (file &rest _args)
(if (or (not (file-remote-p file)) (equal "sudo" (file-remote-p file 'method)))
(abbreviate-file-name (file-truename (tramp-file-local-name file)))
file))))
Editorconfig is extremely slow, e.g. when using doom/sudo-find-file
to open, say, /etc/systemd/system/
. This fixes that.
(after! tramp
(setq tramp-ignored-file-name-regexp ".editorconfig"))
Add for Github codespaces over ssh, for tramp editing, e.g. with C-x C-f /ghcs:codespace-name:/path/to/file
Thanks to https://blog.sumtypeofway.com/posts/emacs-config.html for this one
(after! tramp
(let ((ghcs (assoc "ghcs" tramp-methods))
(ghcs-methods '((tramp-login-program "gh")
(tramp-login-args (("codespace") ("ssh") ("-c") ("%h")))
(tramp-remote-shell "/bin/sh")
(tramp-remote-shell-login ("-l"))
(tramp-remote-shell-args ("-c")))))
;; just for debugging the methods
(if ghcs (setcdr ghcs ghcs-methods)
(push (cons "ghcs" ghcs-methods) tramp-methods))))
The above needs the following feature in the codespace
{
"features": {
"ghcr.io/devcontainers/features/sshd:1": {
"version": "latest"
}
}
}
There’s a lot of good doom themes. I tuned doom-one a little, darkening some of the colors even more. Its in themes/t-doom-one-theme.el.
(setq *t-themes* '(doom-feather-dark
doom-flatwhite
t-doom-one
catppuccin
doom-vibrant)
doom-theme (car *t-themes*)
t-system-theme-dark 't-doom-one
t-system-theme-dark 'catppuccin
t-system-theme-light 'doom-flatwhite)
Cycle through nice ones.
(defun t/cycle-theme ()
"Cycle through the themes of `*t-themes*`."
(interactive)
(setq *t-themes*
(if (t/prefix-arg-universal?)
(append (list (car (reverse *t-themes*))) (butlast *t-themes*))
(append (cdr *t-themes*) (list (car *t-themes*)))))
(let ((theme (car *t-themes*)))
(load-theme theme t)
(setq doom-theme theme)
(message "Theme: %s" theme)))
Bind it to SPC t t
. To cycle the other way around do SPC u
SPC t t
(map! :leader "t t" #'t/cycle-theme)
And cycle between the selected t-system-theme-dark
and t-system-theme-light
when the system appearance is changed on macos.
(advice-remove 't/toggle-system-appearence :after)
(advice-add 't/toggle-system-appearence :after 't/load-system-theme)
Determines the style of line numbers in effect. If set to nil
, line numbers are disabled. For relative line numbers, set this to relative
. Off by default, relative
in programming modes. Toggle them with SPC t l
.
(setq display-line-numbers-type nil)
(setq-hook! 'prog-mode-hook display-line-numbers-type 'relative)
Set across all real buffers.
(comment
(progn
(t/in-all-buffers (lambda (b) (setq display-line-numbers 'relative)))
(t/in-all-buffers (lambda (b) (setq display-line-numbers nil)))))
(add-hook! '(prog-mode-hook css-mode-hook html-mode-hook) 'rainbow-mode)
(add-hook! '(prog-mode-hook css-mode-hook html-mode-hook) 'show-paren-mode)
(custom-set-faces!
'(show-paren-match :background nil :foreground "yellow" :weight bold)
'(rainbow-delimiters-depth-1-face :foreground "DeepPink4" :overline nil :underline nil)
'(rainbow-delimiters-depth-2-face :foreground "DeepPink3" :overline nil :underline nil)
'(rainbow-delimiters-depth-3-face :foreground "DeepPink2" :overline nil :underline nil)
'(rainbow-delimiters-depth-4-face :foreground "DeepPink1" :overline nil :underline nil)
'(rainbow-delimiters-depth-5-face :foreground "maroon4" :overline nil :underline nil)
'(rainbow-delimiters-depth-6-face :foreground "maroon3" :overline nil :underline nil)
'(rainbow-delimiters-depth-7-face :foreground "maroon2" :overline nil :underline nil)
'(rainbow-delimiters-depth-8-face :foreground "maroon1" :overline nil :underline nil)
'(rainbow-delimiters-depth-9-face :foreground "VioletRed3" :overline nil :underline nil)
'(rainbow-delimiters-depth-10-face :foreground "VioletRed2" :overline nil :underline nil)
'(rainbow-delimiters-depth-11-face :foreground "VioletRed1" :overline nil :underline nil)
'(rainbow-delimiters-unmatched-face :foreground "Red" :overline nil :underline nil))
(let ((tr 99))
(t/transparency tr)
(comment
(advice-remove #'load-theme :after)
(advice-remove #'load-theme :before)
)
(advice-add #'doom/reload-theme :after (cmd! (t/transparency tr))))
Show the buffer and the file
(setq frame-title-format "%b (%f)")
(use-package! spacious-padding
:defer t
:config (spacious-padding-mode))
Scroll most recently used window when using c-m-v
and c-m-S-v
.
(setq other-window-scroll-default #'get-lru-window)
If say a single dired window is visible and it is dedicated, allow splitting, else never allow splitting.
(setq split-window-preferred-function 'split-window-sensibly)
;;(setq split-window-preferred-function
;; (lambda (ignored-window)
;; (if (= 1 (length (window-list)))
;; (split-window-right)
;; nil)))
(setq-default window-combination-resize t)
If there is no window in the direction you move, send the keypress for the direction instead hjkl
.
(map! :after evil
:map evil-window-map
"s" (t/micro-state
nil
"<left>" (cmd! (cond
((and (window-in-direction 'right) (window-in-direction 'left)) (evil-resize-window (- (window-width) 8) t))
((window-in-direction 'left) (evil-resize-window (+ (window-width) 8) t))
((window-in-direction 'right) (evil-resize-window (- (window-width) 8) t))
(t (execute-kbd-macro "h"))))
"<right>" (cmd! (cond
((and (window-in-direction 'right) (window-in-direction 'left)) (evil-resize-window (+ (window-width) 8) t))
((window-in-direction 'right) (evil-resize-window (+ (window-width) 8) t))
((window-in-direction 'left) (evil-resize-window (- (window-width) 8) t))
(t (execute-kbd-macro "l"))))
"<up>" (cmd! (cond
((and (window-in-direction 'up) (window-in-direction 'down)) (evil-resize-window (+ (window-height) 4)))
((window-in-direction 'down) (evil-resize-window (- (window-height) 4)))
((window-in-direction 'up) (evil-resize-window (+ (window-height) 4)))
(t (execute-kbd-macro "k"))))
"<down>" (cmd! (cond
((and (window-in-direction 'up) (window-in-direction 'down)) (evil-resize-window (- (window-height) 4)))
((window-in-direction 'up) (evil-resize-window (- (window-height) 4)))
((window-in-direction 'down) (evil-resize-window (+ (window-height) 4)))
(t (execute-kbd-macro "j"))))))
;; TODO
(defvar +messages--auto-tail-enabled nil)
(defun +messages--auto-tail-a (&rest arg)
"Make *Messages* buffer auto-scroll to the end after each message."
(let* ((buf-name (buffer-name (messages-buffer)))
;; Create *Messages* buffer if it does not exist
(buf (get-buffer-create buf-name)))
;; Activate this advice only if the point is _not_ in the *Messages* buffer
;; to begin with. This condition is required; otherwise you will not be
;; able to use `isearch' and other stuff within the *Messages* buffer as
;; the point will keep moving to the end of buffer :P
(when (not (string= buf-name (buffer-name)))
;; Go to the end of buffer in all *Messages* buffer windows that are
;; *live* (`get-buffer-window-list' returns a list of only live windows).
(dolist (win (get-buffer-window-list buf-name nil :all-frames))
(with-selected-window win
(goto-char (point-max))))
;; Go to the end of the *Messages* buffer even if it is not in one of
;; the live windows.
(with-current-buffer buf
(goto-char (point-max))))))
(defun +messages-auto-tail-toggle ()
"Auto tail the '*Messages*' buffer."
(interactive)
(if +messages--auto-tail-enabled
(progn
(advice-remove 'message '+messages--auto-tail-a)
(setq +messages--auto-tail-enabled nil)
(message "+messages-auto-tail: Disabled."))
(advice-add 'message :after '+messages--auto-tail-a)
(setq +messages--auto-tail-enabled t)
(message "+messages-auto-tail: Enabled.")))
Some of these, like SPC j c
works across windows when prefixed with C-u
or SPC u
.
(map!
:leader
(:prefix-map ("j" . "jump")
(:when t
:desc "Jump to window" "W" #'ace-window
:desc "Jump to word" "w" #'avy-goto-word-1
:desc "Jump to line" "l" #'avy-goto-line
:desc "org: Jump to header" "h" #'avy-org-goto-heading-timer
:desc "Jump to char" "c" #'avy-goto-char-2
:desc "Jump to char" "C" #'avy-goto-char)))
(after! (avy evil-integration)
(defun t/setup-avy (&optional frame)
(interactive)
(setq avy-keys '(?j ?f ?d ?k ?s ?a)
avy-timeout-seconds 0.2
;;avy-all-windows 'all-frames
avy-all-windows nil
avy-case-fold-search nil
avy-highlight-first t
avy-style 'at-full
avy-background t)
(let* ((b "#222") (f "DeepPink1"))
(set-face-attribute 'avy-background-face nil :foreground b)
(set-face-attribute 'avy-lead-face nil :background b :foreground f :weight 'bold)
(set-face-attribute 'avy-lead-face-0 nil :background b :foreground f :weight 'bold)
(set-face-attribute 'avy-lead-face-1 nil :background b :foreground f :weight 'bold)
(set-face-attribute 'avy-lead-face-2 nil :background b :foreground f :weight 'bold)))
(t/setup-avy)
;;Also after creating a new frame when emacs is in daemon mode
(add-hook! 'doom-load-theme-hook :append #'t/setup-avy))
Use paredit bindings. Make `'
a pair in emacs lisp mode.
(after! smartparens
(sp-local-pair 'emacs-lisp-mode "`" "'" :when '(sp-in-docstring-p))
(add-hook! (clojure-mode emacs-lisp-mode cider-repl-mode) :append #'smartparens-strict-mode)
(sp-use-paredit-bindings))
And add some extra pairs for org mode.
(after! smartparens
(sp-with-modes 'org-mode
(sp-local-pair "`" "'" :when '(sp-in-docstring-p))
(sp-local-pair "*" "*" :actions '(insert wrap) :unless '(sp-point-after-word-p sp-point-at-bol-p) :wrap "C-*" :skip-match 'sp--org-skip-asterisk)
(sp-local-pair "_" "_" :unless '(sp-point-after-word-p) :post-handlers '(("[d1]" "SPC")))
(sp-local-pair "/" "/" :unless '(sp-point-after-word-p) :post-handlers '(("[d1]" "SPC")))
(sp-local-pair "~" "~" :unless '(sp-point-after-word-p) :post-handlers '(("[d1]" "SPC")))
(sp-local-pair "<" ">" :unless '(sp-point-after-word-p) :post-handlers '(("[d1]" "SPC")))
(sp-local-pair "=" "=" :unless '(sp-point-after-word-p) :post-handlers '(("[d1]" "SPC")))
(sp-local-pair "«" "»")))
Smartparens-mode paredit bindings in org mode messes up M-up
and M-down
, bring them back.
(add-hook! 'org-mode-hook
(defun t/org-mode-hook ()
(map!
:map evil-motion-state-local-map
"M-<up>" 'org-metaup
"M-<down>" 'org-metadown
"M-S-<right>" 'org-shiftmetaright
"M-S-<left>" 'org-shiftmetaleft)))
Don’t create cache files
(add-hook! 'org-mode-hook (defun t/org-disable-auto-save-mode () (interactive) (auto-save-mode -1)))
Bring back C-k
in the minibuffer. Overrides +evil-bindings.el.
(map! :map (evil-ex-completion-map evil-ex-search-keymap)
:gi "C-k" #'kill-line)
(define-key!
:keymaps +default-minibuffer-maps
"C-k" #'kill-line)
Support wrapping sexps by holding super, both in normal mode and insert mode, from the front and the back of expressions.
(map! :map smartparens-mode-map
;; literally S-s-8 on a norwegian mac keyboard
:n "s-(" (cmd! (evil-emacs-state nil)
(sp-wrap-with-pair "\(")
(evil-normal-state nil))
:i "s-(" (cmd! (sp-wrap-with-pair "\("))
;; literally S-s-MetaRight-8 on my norwegian mac keyboard
:n "s-{" (cmd! (evil-emacs-state nil)
(sp-wrap-with-pair "\{")
(evil-normal-state nil))
:i "s-{" (cmd! (sp-wrap-with-pair "\{"))
;; literally S-MetaRight-8 on my norwegian mac keyboard
:n "s-[" (cmd! (evil-emacs-state nil)
(sp-wrap-with-pair "\[")
(evil-normal-state nil))
:i "s-[" (cmd! (sp-wrap-with-pair "\["))
;; literally S-s-9 on a norwegian mac keyboard
:n "s-)" (cmd! (evil-emacs-state nil)
(backward-sexp)
(sp-wrap-with-pair "\(")
(forward-sexp)
(evil-normal-state nil))
:i "s-)" (cmd! (backward-sexp)
(sp-wrap-with-pair "(")
(forward-sexp))
;; literally S-s-MetaRight-9 on my norwegian mac keyboard
:n "s-}" (cmd! (evil-emacs-state nil)
(backward-sexp)
(sp-wrap-with-pair "\{")
(forward-sexp)
(evil-normal-state nil))
:i "s-}" (cmd! (backward-sexp)
(sp-wrap-with-pair "\{")
(forward-sexp))
;; literally S-MetaRight-9 on my norwegian mac keyboard
:n "s-]" (cmd! (evil-emacs-state nil)
(backward-sexp)
(sp-wrap-with-pair "\[")
(forward-sexp)
(evil-normal-state nil))
:i "s-]" (cmd! (backward-sexp)
(sp-wrap-with-pair "\[")
(forward-sexp)))
[[file:~/.config/emacs/modules/config/default/config.el::dolist (brace ‘(“(” “{” “\[“)][Override this to always expand braces]].
(after! smartparens
(sp-pair "{" nil :post-handlers '(("||\n[i]" "RET") ("| " " ")))
(sp-pair "(" nil :post-handlers '(("||\n[i]" "RET") ("| " " ")))
(sp-pair "[" nil :post-handlers '(("||\n[i]" "RET"))))
A really global global writeroom mode. The function is redefined such that if writeroom-major-modes is nil, writeroom-mode is activated in ALL buffers.
(setq writeroom-major-modes nil)
(defun turn-on-writeroom-mode ()
(interactive)
(when (or (not writeroom-major-modes)
(apply 'derived-mode-p writeroom-major-modes))
(writeroom-mode 1)))
The doom default text scale of 2 is a bit heavy
(setq +zen-text-scale 0)
Bring back text zoom in writeroom mode, moving away toggle mode-line, awkwardly bound to s-?
. Give it an even more awkward binding.
(map! :map writeroom-mode-map
"s-?" (cmd! (text-scale-increase 1))
"s-:" 'writeroom-toggle-mode-line)
And screens are big, so a bit more space for text is nice.
(defun t/sidebar-frac (&optional ignore)
(let* ((w-px (frame-pixel-width (selected-frame)))
(h-px (frame-pixel-height (selected-frame)))
(w (frame-width (selected-frame))))
;; noisy
;; (message "w: %s, w-px: %s, h-px: %s" w w-px h-px)
(cond
((< w-px h-px) (/ (float 1) 3))
((> w 200) (/ (float 2) 5))
((and (> w 160) (> w-px 1440)) (/ (float 3) 7))
(t (/ (float 2) 5)))))
(after! writeroom-mode
(setq writeroom-width (t/sidebar-frac)))
(after! olivetti
(setq olivetti-minimum-body-width 90)
(setq-default olivetti-body-width (floor (* (frame-width (selected-frame)) (t/sidebar-frac))))
(add-to-list 'window-size-change-functions 'olivetti-set-window t))
Adjust margins equally across modes.
(map! :map evil-window-map
"M" (t/micro-state
nil
"<left>" (cmd! (cond
((and (boundp 'writeroom-mode) writeroom-mode) (writeroom-decrease-width))
((and (boundp 'olivetti-mode) olivetti-mode) (olivetti-shrink))
(t (t/margins-global-decrease))))
"<right>" (cmd! (cond
((and (boundp 'writeroom-mode) writeroom-mode) (writeroom-increase-width))
((and (boundp 'olivetti-mode) olivetti-mode) (olivetti-expand))
(t (t/margins-global-increase))))))
Show workspace in modeline, adjust bar width, moar iconz, truncate path.
(defun t/doom-modeline-mode-hook (&optional &rest ignore)
(interactive)
(setq doom-modeline-persp-name t
doom-modeline-persp-icon t
;; doom-modeline-height (* 2 (font-get (or (and doom-big-font-mode doom-big-font) doom-font) :size))
;; doom-feather-dark-padded-modeline t
doom-themes-padded-modeline t
doom-modeline-bar-width 4
doom-modeline-github t
doom-modeline-repl t
doom-modeline-battery t
display-time-24hr-format t
;; it needs padding to the right
display-time-string-forms '(dayname " " day "/" month " ")
doom-modeline-major-mode-icon t
doom-modeline-major-mode-color-icon t
doom-modeline-buffer-file-name-style 'truncate-upto-root)
(use-package! mu4e-alert
:after mu4e
:init (setq doom-modeline-mu4e nil)
:config (mu4e-alert-enable-mode-line-display))
(after! doom-modeline
(set-face-attribute 'doom-modeline-persp-name nil :foreground "DeepPink2" :weight 'bold :italic nil)
(display-battery-mode)
(display-time-mode)
(doom-modeline-github-timer)))
(t/doom-modeline-mode-hook)
(add-hook! 'doom-load-theme-hook :append #'t/doom-modeline-mode-hook)
Read more on seagle0128/doom-modeline
(doom-modeline 'main)
You could add your own segments to something like this.
(doom-modeline-def-modeline 't-modeline
'(bar window-number modals matches buffer-info-simple)
'(media-info major-mode time))
Running it creates the function
(doom-modeline-format--t-modeline)
(doom-modeline-set-modeline 't-modeline)
mode-line-format
To set it by default (setf (default-value 'mode-line-format) ...)
is used
(after! doom-modeline
(doom-modeline-def-segment tasks
"Display # of tasks not refiled. Use (nerd-icons-insert-faicon) to look up icons."
(concat
(doom-modeline-spc)
(when-let ((icon (doom-modeline-icon 'faicon "nf-fae-checklist_o" "🗉" "")))
(concat
(doom-modeline-display-icon icon)
(doom-modeline-vspc)
(with-current-buffer "tasks.org"
(let ((count 0))
;; for each heading
(org-map-entries
(lambda (&optional heading)
(when (not (org-entry-is-done-p))
(setq count (1+ count))))
;; all headline
t
;; in file
'file)
;; needs to be string
(format "%s" count)))
(doom-modeline-vspc)
)))))
It has 3 parts, the left, the separator and the right.
(defun t/around-doom-modeline-format--main (fn)
(interactive)
(let ((res (funcall fn)))
(list
(nth 0 res)
(nth 1 res)
(cons '(:eval (doom-modeline-segment--tasks))
(nth 2 res)))))
(advice-remove 'doom-modeline-format--main 't/around-doom-modeline-format--main)
(advice-add 'doom-modeline-format--main :around 't/around-doom-modeline-format--main)
Fix +lookup/dictionary-definition
so that it adheres to display-buffer-alist
.
(set-popup-rule! "^\\*osx-dictionary" :side 'right :size 0.5 :vslot 2)
(setq osx-dictionary-generate-buffer-name-function
(lambda (&rest args)
(pop-to-buffer osx-dictionary-buffer-name)
osx-dictionary-buffer-name))
(after! ielm
(add-hook 'inferior-emacs-lisp-mode-hook 'evil-cleverparens-mode))
Highlight dotfiles that are sourced from the shell in sh-mode
based on their file location.
(add-to-list 'auto-mode-alist (cons (concat "^" (t/user-file "dotfiles") "/" "[^.]") 'sh-mode))
(add-to-list 'auto-mode-alist (cons (concat "^" (t/user-file "Projects/dotfiles") "/" "[^.]") 'sh-mode))
- Doom editor keybindings
- +evil-bindings.el
- evil commands
(map! :after markdown-mode
:map evil-markdown-mode-map
:i "M-b" nil
:map markdown-mode-map
:i "M-b" 'backward-word
:i "M-f" 'forward-word
"M-p" 'backward-paragraph
"M-n" 'forward-paragraph)
(map!
;; resize fonts
:n "s-0" nil
:g "s-0" #'doom/reset-font-size
:g "s-+" #'doom/increase-font-size
:g "s--" #'doom/decrease-font-size
:n "C-+" (cmd! (text-scale-increase 1))
:n "C--" (cmd! (text-scale-decrease 1))
;; and on linux?
"s-?" (cmd! (text-scale-increase 1))
"s-_" (cmd! (text-scale-decrease 1))
"s-=" (cmd! (text-scale-set 0))
;; split windows
"s-d" #'t/split-window-right-and-move-there-dammit
"s-D" #'t/split-window-below-and-move-there-dammit
;; move around with opt+cmd, like in ye olde iterm
"s-M-<up>" 'evil-window-up
"s-M-<right>" 'evil-window-right
"s-M-<down>" 'evil-window-down
"s-M-<left>" 'evil-window-left
;; resize frame
"C-s-<left>" 't/decrease-frame-width
"C-s-<right>" 't/increase-frame-width
"C-s-<down>" 't/increase-frame-height
"C-s-<up>" 't/decrease-frame-height
;; move like history in the terminal
"M-n" 'forward-paragraph
"M-p" 'backward-paragraph
;; g = global
:g "M-y" 'consult-yank-from-kill-ring
;; i = insert
:i "C-d" #'delete-char
:i "C-k" #'evil-delete-line
:i "C-p" #'previous-line
:i "C-n" #'next-line
;; mark all like on macos
"s-a" 'mark-whole-buffer
;; skip between buffers
"s-k" 'previous-buffer
"s-j" 'next-buffer
;; skip between windows like on macos
"s->" 'next-multiframe-window
"s-<" 'previous-multiframe-window
;; beginning and end of line like macos
"s-<left>" 't/smart-beginning-of-line
"s-<right>" 'end-of-line
;; complete with similar words in buffer
"C-." 't/hippie-expand-no-case-fold
;; beginning
"C-a" 't/smart-beginning-of-line
;; m = motion
:m "C-e" 'end-of-line
;; more file commands like on macos
"s-q" 'save-buffers-kill-emacs
"s-n" 'make-frame
"s-s" 'save-buffer
"s-w" #'t/delete-frame-or-hide-last-remaining-frame
;; op -- :leader :desc "Toggle treemacs" "f L" #'+treemacs/toggle
:leader :desc "Open folder" "p o" #'t/open-in-desktop
:leader :desc "Toggle directory sidebar" "f l" #'t-toggle-sidebar
:leader :desc "Toggle directory sidebar, follow" "f L" 't/dired-locate
:leader (:prefix ("o" . "open")
(:prefix-map
("c" . "Consume")
(:when t
:desc "nrk.no" "n" (cmd! (t/eww-readable "https://www.nrk.no/nyheter/" 't/clean-nrk-buffer))
:desc "hackernews" "h" (cmd! (+workspace-switch "*hn*" t)
(hnreader-news))
:desc "rss" "r" #'=rss
:desc "mail" "m" (cmd! (t/gpg) (=mu4e))
:desc "music" "M" (cmd! (+workspace-switch "*emms*")
(emms-cache-set-from-mpd-all)
(emms-smart-browse))
:desc "mastodon" "d" (cmd! (+workspace-switch "*mastodon*" t)
(mastodon))
:desc "gnus" "g" (cmd! (+workspace-switch "*gnus*" t)
(gnus)))))
:leader :desc "Calendar" "o C" #'calendar
:leader :desc "Browse" "o e" #'eww
:leader :desc "Www" "o w" #'eww
:leader :desc "Music" "o m" (t/micro-state
nil
"+" 't/music-volume-up
"-" 't/music-volume-down
"H" 't/music-prev
"h" 't/music-seek-backward
"l" 't/music-seek-forward
"L" 't/music-next
"p" 't/music-play-pause
"b" 't/music-browse
"s" 't/music-stop)
:leader :desc "Show home" "o h" #'(lambda () (interactive) (find-file (t/user-dropbox-folder "org/home.org.gpg")))
:leader :desc "Show da" "o d" #'(lambda () (interactive) (find-file (t/user-dropbox-folder "org/da.org.gpg")))
:leader :desc "Open Intellij" "o i" #'t/open-in-intellij
:leader :desc "Browse at point" "o b" #'t/browse-url-at-point
:leader :desc "Browse chrome url" "o B" #'t/browse-chrome-url-in-eww
:leader :desc "Search the web" "s w" #'consult-web-search
:leader :desc "Search marks" "s M" #'evil-show-marks
:leader :desc "Search registers" "s R" #'evil-show-registers
:leader :desc "Toggle copilot" "t c" #'copilot-mode
:leader :desc "Fill column indicator" "t C" #'display-fill-column-indicator-mode
:leader :desc "Toggle Big mode" "t B" #'doom-big-font-mode
:leader :desc "Toggle dedication" "t d" #'t/toggle-dedicated-window
:leader :desc "Toggle emoji" "t e" #'global-emojify-mode ; :rocket:
:leader :desc "Debug on error" "t D" #'toggle-debug-on-error
:leader :desc "Cycle fonts" "t f" #'t/cycle-fonts
:leader :desc "Toggle focus mode" "t F" #'focus-mode
:leader :desc "Toggle idle highlight" "t h" #'t-idle-highlight-mode
:leader :desc "Toggle highlight line" "t H" #'hl-line-mode
:leader :desc "Toggle variable pitch" "t v" (defun t/variable-pitch-mode (&optional turn-on)
"https://www.reddit.com/r/DoomEmacs/comments/l9jy0h/how_does_variablepitchmode_work_and_why_does_it/."
(interactive)
(if (or turn-on (derived-mode-p 'solaire-mode))
(progn
(solaire-mode -1)
(variable-pitch-mode 1))
(progn
(variable-pitch-mode nil)
(call-interactively 'solaire-mode))))
:leader :desc "Toggle visual linemode""t V" #'visual-line-mode
:leader :desc "Toggle truncate" "t u" #'toggle-truncate-lines
:leader :desc "Toggle margins" "t M" #'t/margins-global
:leader :desc "Toggle olivetti" "t o" #'olivetti-mode
:leader :desc "Toggle transparency" "t T" #'t/transparency
:leader :desc "Reading" "r" #'t/start-spray-micro-state
:leader :desc "Show whitespace" "t w" #'whitespace-mode
:leader :desc "Toggle writeroom" "t z" #'global-writeroom-mode
:leader :desc "Flip frame" "w f" #'rotate-frame
:leader :desc "Delete window or frame or hide" "w d" #'t/delete-window-or-frame-or-hide
:leader :desc "Delete buffer and window" "w K" #'t/volatile-kill-buffer-and-window
:leader :desc "Winner redo" "w R" #'winner-redo
:leader :desc "Rotate frame" "w r" (cmd!
(if (t/prefix-arg-universal?)
(rotate-frame-anticlockwise)
(rotate-frame-clockwise)))
:leader :desc "Projectile dired" "p d" #'t/projectile-dired
:leader :desc "Projectile magit" "p g" #'t/projectile-magit-status
:leader :desc "Projectile pulls" "p P" #'t/projectile-visit-git-link-pulls
:leader :desc "Scratch buffer" "b s" #'doom/open-scratch-buffer
:leader :desc "Previous occurrence" "h p" #'highlight-symbol-prev
:leader :desc "Previous occurrence" "h N" #'highlight-symbol-prev
:leader :desc "Next occurrence" "h n" #'highlight-symbol-next)
Hide the last frame on os x instead of nuking it
(map! :leader "q f" 't/delete-frame-or-hide-last-remaining-frame)
That’s irritating. Prevent drag-stuff-mode from messing things up
(map!
:after drag-stuff-mode
:map drag-stuff-mode-map
"<M-up>" #'drag-stuff-up ;; messes up org mode
"<M-down>" #'drag-stuff-down ;; messes up org mode
;; :ni "<M-left>" #'evil-backward-word-begin
;; :ni "<M-right>" #'evil-forward-word-begin
)
Popup bindings on a norwegian keyboard
(map! :g "C-*" #'+popup/raise
:g "C-x p" #'+popup/other
:leader "ø" #'+popup/toggle
:map org-mode-map
:g "C-*" #'+popup/raise
:g "C-ø" #'+popup/toggle)
(set-popup-rule! "^*Summary" :side 'bottom :size 0.5)
(set-popup-rule! "^*Article" :side 'bottom :size 0.5)
(setq gnus-select-method '(nntp "news.gmane.io")) ; A A
One help shortcut, everywhere.
(map! :leader :n "h h" #'helpful-at-point)
Keep them on the side for some more room.
(set-popup-rule! "^*info" :side 'right :width 82)
(set-popup-rule! "^*help" :side 'right :width 82)
(set-popup-rule! "^*eglot-help" :side 'right :width 82)
(set-popup-rule! "^*cider-doc" :side 'right :width 82)
Make helpful buffers more navigable by removing doom popup’s dedication. This makes q
fall back to the previous help buffer after a help link click that made you navigate to the next help topic.
(advice-add
#'push-button
:after (defun t/keep-help-buffers-around (&optional arg)
(set-window-dedicated-p (selected-window) nil)
(set-window-parameter (selected-window) 'no-delete-other-windows nil)))
(after! info
(map!
:map Info-mode-map
"M-n" #'forward-paragraph
"M-p" #'backward-paragraph))
Motion keys for info mode.
(after! evil
(after! info
(evil-define-key 'normal Info-mode-map (kbd "H") 'Info-history-back)
(evil-define-key 'normal Info-mode-map (kbd "L") 'Info-history-forward)
(unbind-key (kbd "h") 'Info-mode-map)
(unbind-key (kbd "l") 'Info-mode-map)))
(after! org
(add-hook! 'org-mode-hook 'evil-cleverparens-mode)
(defun t/open-prev-heading ()
(interactive)
(let ((was-narrowed (buffer-narrowed-p)))
(when was-narrowed (widen))
(when (org-at-heading-p)
(outline-hide-sublevels (org-outline-level)))
(org-previous-visible-heading 1)
(outline-show-subtree)
(when was-narrowed (org-narrow-to-subtree))
(recenter-top-bottom 0)
(progn ;; hack to make eldoc pop up
(evil-previous-line)
(evil-next-line)
(evil-forward-word-begin))))
(defun t/open-next-heading ()
(interactive)
(let ((was-narrowed (buffer-narrowed-p)))
(when was-narrowed (widen))
(when (org-at-heading-p)
(outline-hide-sublevels (org-outline-level)))
(org-next-visible-heading 1)
(outline-show-subtree)
(eldoc-print-current-symbol-info)
(when was-narrowed (org-narrow-to-subtree))
(recenter-top-bottom 0)
(progn ;; hack to make eldoc pop up
(evil-previous-line)
(evil-next-line)
(evil-forward-word-begin))))
;; like in normal org, not like in doom
(map! :after evil-org
:map evil-org-mode-map
:ni "C-<return>" #'org-insert-heading-respect-content
;; bring back deleting characters from insert in org mode
:i "C-d" nil
:map org-mode-map
:ni "C-c C-p" #'t/open-prev-heading
:ni "C-c C-n" #'t/open-next-heading)
;; Include gpg files in org agenda
(unless (string-match-p "\\.gpg" org-agenda-file-regexp)
(setq org-agenda-file-regexp
(replace-regexp-in-string "\\\\\\.org" "\\\\.org\\\\(\\\\.gpg\\\\)?"
org-agenda-file-regexp)))
(defun t/org-capture-chrome-link-template (&optional &rest args)
"Capture current frontmost tab url from chrome."
(concat "* TODO %? :url:\n\n" (t/grab-chrome-url)))
(defun t/org-capture-link-template (&optional &rest args)
"Capture url."
(concat "* TODO %? %^G\n\nLink:\n - "
(cond
((equal major-mode 'mu4e-view-mode) (concat "mu4e:msgid:" (plist-get (mu4e-message-at-point) :message-id)))
((equal major-mode 'mu4e-headers-mode) (concat "mu4e:msgid:" (plist-get (mu4e-message-at-point) :message-id)))
((equal major-mode 'elfeed-show-mode) (elfeed-entry-link elfeed-show-entry))
((equal major-mode 'elfeed-search-mode) (s-join "\n - " (cl-loop for feed in (elfeed-search-selected)
collect (elfeed-entry-link feed))))
((equal major-mode 'eww-mode) (concat "%a"))
((equal major-mode 'org-mode) (concat "%a"))
(t (get-text-property (point) 'shr-url)))))
(setq org-tags-column -60
org-hide-emphasis-markers t ; hide symbols like ~ and / when wrapped around text
org-support-shift-select t ; shift can be used to mark multiple lines
org-special-ctrl-k t ; don't clear tags, etc
org-special-ctrl-a/e t ; don't move past ellipsis on c-e
org-id-link-to-org-use-id t ; create link if it doesnt exist, or when org-capture -ing (needs %a in template)
org-attach-directory (t/user-dropbox-folder "/org/attachments")
org-attach-id-to-path-function-list '(org-attach-id-ts-folder-format ;; saner attachment folder structure
org-attach-id-uuid-folder-format)
org-goto-interface 'outline-path-completion ;; more useful c-c c-j
org-id-method 'ts
org-agenda-skip-scheduled-if-done t
org-default-notes-file (t/user-dropbox-folder "/org/home.org.gpg")
org-log-done 'time ; log when todos are completed
org-log-redeadline 'time ; log when deadline changes
org-log-reschedule 'time ; log when schedule changes
org-reverse-note-order t ; newest notes first
org-return-follows-link t ; go to http links in browser
org-todo-keywords '((sequence "TODO(t)" "STARTED(s)" "NEXT(n)" "|" "DONE(d)" "CANCELLED(c)"))))
Use os support if it exists.
(setq image-use-external-converter t
org-image-actual-width (list (float 0.5) (float 0.5)))
(add-hook! 'org-mode-hook (defun t/variable-pitch-mode-some-buffers ()
(interactive)
(let ((bn (buffer-name)))
(when (or (s-ends-with? "posts.org" bn)
(s-equals? "*ChatGPT*" bn))
(olivetti-mode 1)
(t/variable-pitch-mode 1)))))
(add-hook 'org-ai-mode-hook (defun t/org-ai-mode-hook ()
(interactive)
(advice-add
'org-ctrl-c-ctrl-c
:after
(defun t/org-ai-ctrl-c (&optional &rest any)
(when (s-equals? "*ChatGPT*" (buffer-name))
(end-of-buffer))))))
Make it possible to use the header argument :async true
for async execution of begin_src code blocks.
(after! org
(require 'ob-async))
Org agenda customizations
(defun t/org-agenda-todo-type (name)
`((org-agenda-remove-tags t)
(org-agenda-sorting-strategy '(tag-up priority-down))
(org-agenda-todo-keyword-format "")
(org-agenda-overriding-header ,name)))
(defun t/org-agenda-day (tags)
(list tags `((org-agenda-span 'day)
(org-agenda-tag-filter-preset ,tags))))
(defun t/org-agenda-pri (header tags)
(list tags `((org-agenda-overriding-header ,header)
(org-agenda-skip-function '(or (org-agenda-skip-entry-if 'todo 'done)
(and (org-agenda-skip-entry-if 'notregexp "\\[#A\\]")
(org-agenda-skip-entry-if 'notregexp "\\[#B\\]")
(org-agenda-skip-entry-if 'notregexp "\\[#C\\]")))))))
(defun t/org-agenda-not-pri (header tags skip)
(list tags `((org-agenda-overriding-header ,header)
(org-agenda-skip-function '(or (org-agenda-skip-entry-if 'regexp "\\[#A\\]")
(org-agenda-skip-entry-if 'regexp "\\[#B\\]")
(org-agenda-skip-entry-if 'regexp "\\[#C\\]")
(org-agenda-skip-if nil (quote ,skip)))))))
(defun t/org-agenda-todos (header tags)
(t/org-agenda-not-pri header tags '(scheduled deadline)))
(defun t/org-agenda-todos-scheduled (header tags)
(t/org-agenda-not-pri header tags '(notscheduled deadline)))
(defun t/org-day-summary (&rest tags)
`((agenda ,@(t/org-agenda-day (string-join tags "|")))
(tags ,@(t/org-agenda-pri "Pri" (string-join tags "|")))
(tags-todo ,@(t/org-agenda-todos "Todo" (string-join tags "|")))
(tags-todo ,@(t/org-agenda-todos-scheduled "Scheduled todo" (string-join tags "|")))))
(defun t/org-agenda-read ()
`(tags-todo "book|read|pocket" ((org-agenda-overriding-header "Read"))))
(defun t/org-done-today (tag)
`(tags ,(format "%s+CLOSED>=\"<today>\"" tag) ((org-agenda-overriding-header "\nCompleted today\n"))))
;; and some custom agenda shortcuts using them
(setq org-agenda-custom-commands
`(("n" "Agenda and all TODOs" ((agenda "") (alltodo "")))
("m" tags-todo "serie|film")
("e" tags-todo "emacs")
("r" ,@(t/org-agenda-read))
("v" tags-todo "video")
("T" alltodo)
("C" todo "DONE" ,(t/org-agenda-todo-type "DONE"))
("t" todo "TODO" ,(t/org-agenda-todo-type "TODO"))
("b" todo "STARTED" ,(t/org-agenda-todo-type "STARTED"))
("c" todo "CANCELLED" ,(t/org-agenda-todo-type "CANCELLED"))
("w" "work" ,(append (t/org-day-summary "+bekk" "+da")
`((tags "+someday+da")
(tags "+someday+bekk")
,(t/org-done-today "+work"))))
("h" "home" ,(append (t/org-day-summary "+home-emacs-someday")
`(,(t/org-agenda-read)
(tags-todo "+someday-work" ((org-agenda-overriding-header "Someday")))
,(t/org-done-today "+home"))))))
(defun t/org-clock-start (&optional &rest args)
(interactive)
(when (not (featurep 'org-pomodoro))
(require 'org-pomodoro))
(org-todo "STARTED"))
(defun t/org-clock-stop (&optional &rest args)
(interactive)
(when (not (featurep 'org-pomodoro))
(require 'org-pomodoro))
(when (not (org-pomodoro-active-p))
(org-clock-jump-to-current-clock)
(org-todo)))
(advice-remove 'org-clock-in 't/org-clock-start)
(advice-remove 'org-clock-out 't/org-clock-stop)
(advice-add 'org-clock-in :after 't/org-clock-start)
(advice-add 'org-clock-out :after 't/org-clock-stop)
Setup alert.el
to notify also on macos.
(setq alert-default-style (if is-mac 'osx-notifier 'libnotify))
Alert a 5 minutes before schedules or deadlines, keep it going for 10. Capture the first time string as the date like suggested in the readme.
(use-package! org-alert
:init
(setq org-alert-interval (* 5 60)
org-alert-notify-cutoff 5
org-alert-notify-after-event-cutoff 5
org-alert-time-match-string "\\(?:SCHEDULED\\|DEADLINE\\):.*?<.*?\\([0-9]\\{2\\}:[0-9]\\{2\\}\\).*>")
:config
(org-alert-enable))
Extensions of some of the Doom org mode map bindings.
Heading and item bindings
C-ret
- new below, insert mode, same level
C-S-ret
- new above, insert mode, same level
M-ret
- new heading, normal mode, same level
M-S-ret
- todo below, normal mode, same level
C-M-ret
- heading below, normal mode, level down
SPC-m-h
- heading from text
SPC-m-i
- item from text
SPC g a
seems more reasonable than SPC g G
. Localleader in doom is bound to SPC m
. This also enables searching across all agenda files using SPC g A
.
(map! :map org-mode-map
:localleader "g a" #'consult-org-agenda
:localleader "g A" (cmd! (consult-org-heading t 'agenda-with-archives)))
Widen
(map!
:map org-mode-map
:localleader :desc "Widen" "s w" 'widen
:localleader :desc "Narrow to subtree" "s n" 'org-narrow-to-subtree)
Save from agenda
(map! :after org-agenda
:map (evil-org-agenda-mode-map org-super-agenda-header-map)
:g "h" nil
:g "j" nil
:g "k" nil
:g "l" nil
:m "H" #'org-agenda-earlier
:m "L" #'org-agenda-later
:m "d" #'org-agenda-day-view
:m "w" #'org-agenda-week-view
:m "y" #'org-agenda-year-view
:m "m" #'org-agenda-month-view
"s-s" #'org-save-all-org-buffers)
(after! org
(set-face-attribute 'org-todo nil :foreground "#94fFe4" :weight 'bold))
(use-package! org-appear
:hook (org-mode . org-appear-mode)
:config
(setq org-appear-autoemphasis t
org-appear-autosubmarkers t
org-appear-autolinks nil)
;; for proper first-time setup, `org-appear--set-elements'
;; needs to be run after other hooks have acted.
(run-at-time nil nil #'org-appear--set-elements))
Make org handle links load links that start with
eww:
eshell
man:
vterm:
(add-hook! 'org-mode-hook
(defun t/load-org-links ()
(interactive)
(require 'ol)
(require 'ol-eshell)
(require 'ol-man)
(require 'ol-eww)
(defun t/org-vterm-open (url _)
"Open URL with vterm in the current buffer."
(let ((current-prefix-arg 1))
(call-interactively '+vterm/toggle)
(term-send-raw-string (concat url "\C-m"))))
(org-link-set-parameters "vterm" :follow 't/org-vterm-open)))
Save org mode buffers after refile.
(defadvice org-refile (after t/after-org-refile activate)
(org-save-all-org-buffers))
(after! evil
(when (boundp 'org-evil-table-mode-map)
(map!
:map org-evil-table-mode-map
"M-S-<left>" 'org-table-delete-column
"M-S-<right>" 'org-table-insert-column)))
Allow ox-hugo
to copy webp
(after! ox-hugo
(add-to-list 'org-hugo-external-file-extensions-allowed-for-copying "webp"))
(after! org
(with-eval-after-load 'org-capture
(defun org-hugo-new-subtree-post-capture-template ()
"Returns `org-capture' template string for new Hugo post.
See `org-capture-templates' for more information.
https://ox-hugo.scripter.co/doc/org-capture-setup/"
(let* ((title (read-from-minibuffer "Post Title: "))
(fname (org-hugo-slug title)))
(mapconcat #'identity
`(,(concat "* TODO " title)
":PROPERTIES:"
,(concat ":EXPORT_FILE_NAME: " fname)
":END:" "%?\n")
"\n")))))
Remove the s
mapping for source code blocks.
(after! org
(setq org-structure-template-alist (remove '("s" "src") org-structure-template-alist)))
Replace it with ss
(its faster than the default ~s ~) so we can add some more along side it.
(after! org
(add-to-list 'org-structure-template-alist (cons "ss" "src"))
(add-to-list 'org-structure-template-alist (cons "se" "src emacs-lisp"))
(add-to-list 'org-structure-template-alist (cons "sp" "src python"))
(add-to-list 'org-structure-template-alist (cons "sn" "src nix"))
(add-to-list 'org-structure-template-alist (cons "sj" "src javascript"))
(add-to-list 'org-structure-template-alist (cons "sh" "src sh"))
(add-to-list 'org-structure-template-alist (cons "aI" "ai :image :size 512x512"))
(add-to-list 'org-structure-template-alist (cons "ai" "ai"))
(add-to-list 'org-structure-template-alist (cons "d" "description")))
If you need to remove one, do this
(comment
(setq org-structure-template-alist (assoc-delete-all "sh" org-structure-template-alist)))
Don’t popupize the org code block editor with doom’s popup framework, so it opens split wherever it fits like it is by default.
(after! org
(set-popup-rule! "^*Org Src" :ignore t))
(after! org
(setq org-capture-templates
`(("t" "Task" entry (file+olp org-default-notes-file "tasks") "* TODO %? \n\n%i\n\n" :prepend t :empty-lines-after 1)
("d" "Da" entry (file+olp ,(t/user-dropbox-folder "org/da.org.gpg") "Tasks") "* TODO %? \n\n%i" :prepend t :empty-lines-after 1)
("b" "Bekk" entry (file+olp ,(t/user-dropbox-folder "org/bekk.org.gpg") "Tasks") "* TODO %? \n\n%i" :prepend t :empty-lines-after 1)
("f" "File/item (or elfeed)" entry (file+olp org-default-notes-file "Tasks") "* TODO %? %^G\n\n%i%a\n\n" :prepend t :empty-lines-after 1)
("l" "Link (eww, mu4e, etc)" entry (file+olp org-default-notes-file "Tasks") (function t/org-capture-link-template) :prepend t :empty-lines-after 1)
("c" "Chrome location" entry (file+olp org-default-notes-file "Tasks") (function t/org-capture-chrome-link-template) :prepend t :empty-lines-after 1)
("p" "Post" entry (file+olp "~/Code/posts/content-org/blog.org" "Drafts") (function org-hugo-new-subtree-post-capture-template)))))
(after! evil
(evil-define-text-object evil-org-outer-subtree (count &optional beg end type)
"An Org subtree. Uses code from `org-mark-subtree`"
:type line
(save-excursion
;; get to the top of the tree
(org-with-limited-levels
(cond ((org-at-heading-p) (beginning-of-line))
((org-before-first-heading-p) (user-error "Not in a subtree"))
(t (outline-previous-visible-heading 1))))
(cl-decf count)
(when count (while (and (> count 0) (org-up-heading-safe)) (cl-decf count)))
;; extract the beginning and end of the tree
(let ((element (org-element-at-point)))
(list (org-element-property :end element)
(org-element-property :begin element))))))
(after! evil
(evil-define-text-object evil-org-inner-subtree (count &optional beg end type)
"An Org subtree, minus its header and concluding line break. Uses code from `org-mark-subtree`"
:type line
(save-excursion
;; get to the top of the tree
(org-with-limited-levels
(cond ((org-at-heading-p) (beginning-of-line))
((org-before-first-heading-p) (user-error "Not in a subtree"))
(t (outline-previous-visible-heading 1))))
(cl-decf count)
(when count (while (and (> count 0) (org-up-heading-safe)) (cl-decf count)))
;; extract the beginning and end of the tree
(let* ((element (org-element-at-point))
(begin (save-excursion
(goto-char (org-element-property :begin element))
(next-line)
(point)))
(end (save-excursion
(goto-char (org-element-property :end element))
(backward-char 1)
(point))))
(list end begin)))))
(after! evil
(evil-define-text-object evil-org-outer-item (count &optional beg end type)
:type line
(let* ((struct (org-list-struct))
(begin (org-list-get-item-begin))
(end (org-list-get-item-end (point-at-bol) struct)))
(if (or (not begin) (not end))
nil
(list begin end)))))
(after! evil
(evil-define-text-object evil-org-inner-item (count &optional beg end type)
(let* ((struct (org-list-struct))
(begin (progn (goto-char (org-list-get-item-begin))
(forward-char 2)
(point)))
(end (org-list-get-item-end-before-blank (point-at-bol) struct)))
(if (or (not begin) (not end))
nil
(list begin end)))))
(define-key evil-outer-text-objects-map "h" 'evil-org-outer-subtree)
(define-key evil-inner-text-objects-map "h" 'evil-org-inner-subtree)
(define-key evil-outer-text-objects-map "*" 'evil-org-outer-subtree)
(define-key evil-inner-text-objects-map "*" 'evil-org-inner-subtree)
(define-key evil-outer-text-objects-map "i" 'evil-org-outer-item)
(define-key evil-inner-text-objects-map "i" 'evil-org-inner-item)
(define-key evil-outer-text-objects-map "-" 'evil-org-outer-item)
(define-key evil-inner-text-objects-map "-" 'evil-org-inner-item)
(after! org
(setq org-pomodoro-format "%s"
org-pomodoro-play-sounds nil
org-pomodoro-length 25
org-pomodoro-short-break-length 5
org-pomodoro-long-break-length 10
org-pomodoro-long-break-frequency 4))
Clock in like SPC m c i
.
(map! :map org-mode-map
:localleader
(:prefix ("c" . "clock")
"p" #'org-pomodoro))
Clock in like SPC m c i
.
(map! :map org-mode-map
:localleader
(:prefix ("c" . "clock")
"p" #'org-pomodoro))
Default openai language model.
(setq-default *t-gpt-models* "gpt-4o-mini")
(use-package! org-ai
:hook (org-mode . org-ai-mode)
:config
(add-to-list 'org-ai-chat-models *t-gpt-models* t)
(setq-default org-ai-default-chat-model *t-gpt-models*))
(set-popup-rule! "^\\*ChatGPT" :size 0.45 :side 'right :quit 'other)
(defun t/chatgpt-prompt (prompt)
"Pop open an org mode buffer with the selection region and the given prompt
prepended."
(interactive)
(t/chatgpt-buffer (region-beginning) (region-end) prompt))
(defun t/chatgpt-buffer (beg end &optional prompt)
"Pop open an org mode buffer with the selection region and an optional prompt
prepended."
(interactive (list (and (mark t) (region-beginning))
(and (mark t) (region-end))))
(let ((active-region (when (region-active-p)
(buffer-substring beg end)))
(major-mode-name (symbol-name major-mode)))
(with-current-buffer (pop-to-buffer "*ChatGPT*")
(erase-buffer)
(org-mode)
(olivetti-mode)
(insert
"#+begin_ai"
"\n"
"[SYS]: You are a helpful, expert programming assistant, that lives inside of emacs. Respond in a way that is useful for a developer. Keep responses brief, don't extensively explain why something works, focus on how."
"\n\n"
"[ME]: \n#+end_ai")
(move-end-of-line 0)
(evil-insert 0)
(save-excursion
(when prompt (insert "#" prompt))
(when active-region (insert "\n\n" active-region))))))
(defun t/chatgpt-send ()
(interactive)
(with-current-buffer (pop-to-buffer "*ChatGPT*")
(call-interactively 'org-ctrl-c-ctrl-c)))
(map! :leader
(:prefix
("o" . "open")
(:prefix-map
("G" . "chatgpt")
(:when t
:desc "ask" "a" #'t/chatgpt-buffer
:desc "fix" "f" (cmd! (t/chatgpt-prompt "Why doesn't this code work?"))
:desc "explain" "e" (cmd! (t/chatgpt-prompt "What does this code do?") (t/chatgpt-send))
:desc "gen tests" "t" (cmd! (t/chatgpt-prompt "Write a test for this code") (t/chatgpt-send))
:desc "optimize" "o" (cmd! (t/chatgpt-prompt "Refactor this code for speed and tell me what you changed and why it's faster") (t/chatgpt-send))
:desc "refactor" "r" (cmd! (t/chatgpt-prompt "Refactor this code and tell me what you changed and why it's better") (t/chatgpt-send))
:desc "summarize" "s" (cmd! (t/chatgpt-prompt "Summarize this text:") (t/chatgpt-send))))))
STARTED chatgpt-shell
(setq chatgpt-shell-model-version *t-gpt-models*
chatgpt-shell-openai-key
(lambda ()
(auth-source-pick-first-password :host "api.openai.com")))
(after! mastodon
(setq mastodon-instance-url "https://fosstodon.org"
mastodon-active-user "@[email protected]")
(set-popup-rule! "^*mastodon" :ignore t)
(map! :map mastodon-mode-map
:n "q" #'+workspace/kill
:n "j" (cmd!
(mastodon-tl--goto-next-item)
(let ((current-prefix-arg '(4)))
(call-interactively 'recenter-top-bottom)))
:n "k" (cmd!
(mastodon-tl--goto-prev-item)
(let ((current-prefix-arg '(4)))
(call-interactively 'recenter-top-bottom)))))
I never really got into this.
(defun t/spray-micro-state (&optional after)
(t/micro-state-in-mode
'spray-mode
after
"s" 'spray-slower
"f" 'spray-faster
"SPC" 'spray-start/stop
"b" 'spray-backward-word
"w" 'spray-forward-word
"<left>" 'spray-backward-word
"<right>" 'spray-forward-word))
(defun t/start-spray-micro-state (&optional on-exit)
(interactive)
(let ((map (make-sparse-keymap)))
(bind-key (kbd "<wheel-right>") 'mwheel-scroll map)
(bind-key (kbd "<wheel-left>") 'mwheel-scroll map)
(bind-key (kbd "<wheel-up>") 'mwheel-scroll map)
(bind-key (kbd "<wheel-down>") 'mwheel-scroll map)
(bind-key "n" (lambda ()
(interactive)
(condition-case nil
(scroll-up-command)
(error
(cond
((eq major-mode 'elfeed-show-mode) (elfeed-show-next))
((eq major-mode 'mu4e-view-mode) (mu4e-view-headers-next)))))) map)
(bind-key "p" (lambda ()
(interactive)
(condition-case nil
(scroll-down-command)
(error
(cond
((eq major-mode 'elfeed-show-mode) (elfeed-show-prev))
((eq major-mode 'mu4e-view-mode) (mu4e-view-headers-prev)))))) map)
(bind-key "s" (cmd!
(when (eq major-mode 'elfeed-show-mode)
(let ((shr-inhibit-images t)) (elfeed-show-refresh)))
(funcall (t/spray-micro-state))) map)
(bind-key "S" (cmd! (call-interactively 'ellama-summarize)) map)
(bind-key "r" (cmd! (call-interactively 'eww-readable)) map)
(bind-key "i" (cmd!
(setq shr-inhibit-images (not shr-inhibit-images))
(when (eq major-mode 'elfeed-show-mode)
(elfeed-show-refresh))
(when (eq major-mode 'eww-mode)
(call-interactively 't/eww-toggle-images))) map)
(bind-key "l" (cmd! (call-interactively 'link-hint-open-link)) map)
(bind-key "v" (cmd! (call-interactively 't/variable-pitch-mode)) map)
(bind-key "o" (cmd! (call-interactively 'olivetti-mode)) map)
(set-temporary-overlay-map map t on-exit))
(when (not (minibuffer-window-active-p (selected-window)))
(message "(n)ext page, (p)rev page, (i)mages, (r)eadability, (s)pray mode, (S)ummarize, (o)livetti, (v)ariable pitch")))
(map! :leader :desc "Toggle spray" "t s" (t/spray-micro-state))
(after! spray
(setq spray-wpm 720
spray-height nil)
(add-hook 'spray-mode-hook #'t/spray-mode-hook)
(defun t/spray-mode-hook ()
(setq-local spray-margin-top (truncate (/ (window-height) 2.5)))
(setq-local spray-margin-left (truncate (/ (window-width) 3.5)))
(set-face-foreground 'spray-accent-face
(face-foreground 'font-lock-keyword-face))))
An elisp web browser.
This makes RET
on a url open eww
. You can still open an external browser with SPC u RET
. Some urls, like github, open in the external browser.
(setq blacklisted-eww-url-parts '("localhost"
"slack.com"
"github.com"
"openai.com"
"twitter.com"
"googleusercontent.com")
browse-url-browser-function
(lambda (url &optional _new-window)
(let* ((parsed-url (url-generic-parse-url url))
(host (url-host parsed-url)))
(message "browse url: %s" parsed-url)
(cond
((-any-p (lambda (url-part) (and host (s-contains? url-part host))) blacklisted-eww-url-parts)
(browse-url-default-browser url))
((and host (or (s-contains-p "youtube.com" host) (s-contains-p "youtu.be" host)))
(elfeed-tube-fetch url)
(run-at-time "0 sec" nil 't/start-spray-micro-state))
(t (eww-browse-url url))))))
Make SPC s o
open in eww first, then use &
to go to the default browser if needed.
(setq +lookup-open-url-fn #'eww)
(after! evil
;; the original way
;;(setf (alist-get 'size (display-buffer-assq-regexp "*eww*" display-buffer-alist nil)) 0.6)
;; the doom way
(set-popup-rule! "^\\*eww*" :side 'right :size 0.7 :vslot 10))
Enter readable mode automatically, normally available from pressing R
in eww mode.
(add-hook 'eww-after-render-hook (cmd! (call-interactively 'eww-readable)))
(add-hook 'eww-after-render-hook 'olivetti-mode)
(add-hook 'eww-after-render-hook 't/variable-pitch-mode)
(add-hook 'eww-after-render-hook 't/start-spray-micro-state)
Eww functions that directly enter the eww readability mode after loading a given url
(defun t/eww-readable-after-render (status url buffer fn)
(eww-render status url nil buffer)
(switch-to-buffer buffer)
(eww-readable)
(let ((content (buffer-substring-no-properties (point-min) (point-max))))
(read-only-mode 0)
(erase-buffer)
(insert content)
(beginning-of-buffer)
(when fn (funcall fn))))
(defun t/eww-readable (url &optional fn)
(interactive "sEnter URL: ")
(let ((buffer (get-buffer-create "*eww*")))
(with-current-buffer buffer
(autoload 'eww-setup-buffer "eww")
(eww-setup-buffer)
(url-retrieve url 't/eww-readable-after-render (list url buffer fn)))))
(after! shr
;; don't truncate lines in
(defun shr-fill-text (text) text)
(defun shr-fill-lines (start end) nil)
(defun shr-fill-line () nil)
;; not too large images
(setq shr-use-fonts nil
shr-max-image-proportion 0.6
shr-ignore-cache t))
Some useful eww keybindings
(after! eww
(defun t/eww-hook ()
(map!
:map evil-normal-state-local-map
"q" 'quit-window
"S-TAB" 'shr-previous-link
"TAB" 'shr-next-link
"R" 'eww-readable
"M-p" 'backward-paragraph
"M-n" 'forward-paragraph
"s-l" 'eww
"s" (t/spray-micro-state))))
(add-hook 'eww-mode-hook #'t/eww-hook)
(use-package! hnreader
:commands (hnreader-news)
:config
(set-popup-rule! "^*HN" :ignore t))
(use-package! reddigg
:commands (reddigg-view-main)
:config
(progn
(set-popup-rule! "^*reddigg" :ignore t)
(setq reddigg-subs '(doomemacs emacs minilab homelab homeserver orgmode sffpc archlinux))))
A custom function to fetch a clean view of the current news from nrk.no
(defun t/clean-nrk-buffer ()
(flush-lines "^$")
;; clean up lines beginning with dates, e.g. 20. sept...
(beginning-of-buffer)
(flush-lines "^[0-9][0-9]\.")
;; clean up lines beginning with -
(beginning-of-buffer)
(t/cleanup-buffer-whitespace-and-indent)
(while (re-search-forward "*" nil t)
;; change * to -
(replace-match "\n-")
;; highlight the line
(add-text-properties (point-at-bol) (point-at-eol) '(face outline-4)))
(beginning-of-buffer)
;; kill more lines with dates
(while (re-search-forward "^[0-9][0-9]\." nil t)
(when (string-match-p "^[0-9][0-9]\. [jfmasond]" (thing-at-point 'line))
(beginning-of-line) (kill-line) (forward-line) (join-line)))
;; remove leading line
(beginning-of-buffer)
(kill-line)
;;(darkroom-mode)
(read-only-mode)
(funcall (t/micro-state (t/prefix-arg-universal?)
"n" (cmd! nil
(evil-search "^-" t t)
(evil-ex-nohighlight)
(recenter nil))
"p" (cmd! nil
(evil-search "^-" nil t)
(evil-ex-nohighlight)
(recenter nil))
"s" (t/spray-micro-state))))
(after! eglot
(setq eglot-connect-timeout (* 60 20)
;; don't block while waiting, defaults to 3
eglot-sync-connect nil))
(after! eglot
;; (setq eglot-server-programs
;; (cl-remove-if (lambda (c) (eq (car c) 'nix-mode)) eglot-server-programs))
(add-to-list 'eglot-server-programs '(nix-mode . ("nil"))))
(after! nix-mode
(add-hook! 'nix-mode-hook 'eglot-ensure))
Move around like in org, collapsing what is moved away from, expanding what is moved to.
(map! :map markdown-mode-map
"C-c C-p" (cmd!
(outline-show-all)
(outline-hide-body)
(markdown-outline-previous)
(outline-show-entry))
"C-c C-n" (cmd!
(outline-show-all)
(outline-hide-body)
(markdown-outline-next)
(outline-show-entry)))
(after! markdown-mode
(add-hook! 'markdown-mode-hook (defun t/markdown-toggle-pretty ()
(interactive)
;; TODO
;;(markdown-toggle-url-hiding)
;;(markdown-toggle-markup-hiding)
;;(markdown-toggle-inline-images)
)))
npm install -g bash-language-server
Adapt cleverparens keys that clash with my M-[hjkl] bindings in ~/.skhdrc
(after! evil
(map! :map evil-cleverparens-mode-map
"C-M-h" 'evil-cp-beginning-of-defun
"C-M-l" 'evil-cp-end-of-defun
"C-M-k" 'evil-cp-drag-backward
"C-M-j" 'evil-cp-drag-forward))
(after! clojure-mode
(add-hook! '(clojure-mode-hook
clojurec-mode-hook
clojurescript-mode-hook) 'evil-cleverparens-mode)
(map! :map clojure-mode-map "DEL" #'sp-backward-delete-char))
Holding alt for moving between sexps feel right
(after! clojure-mode
(map! :map clojure-mode-map
:i "M-<right>" #'evil-cp-forward-sexp
:i "M-<left>" #'evil-cp-backward-sexp)
(map! :map clojurec-mode-map
:i "M-<right>" #'evil-cp-forward-sexp
:i "M-<left>" #'evil-cp-backward-sexp)
(map! :map clojurescript-mode-map
:i "M-<right>" #'evil-cp-forward-sexp
:i "M-<left>" #'evil-cp-backward-sexp))
Doom removes cider auto completion, bring it back, by adding it to the front of completion-at-point-functions
.
(after! clojure-mode
(remove-hook! 'cider-mode-hook '+clojure--cider-disable-completion)
(add-hook! 'clojure-mode-hook :append
(defun t/enable-cider-autocomplete-again ()
(interactive)
(add-hook 'completion-at-point-functions #'cider-complete-at-point -99 t))))
;; TODO bind M-SPC in minibuffer-mode-map
(map! :map (minibuffer-mode-map emacs-lisp-mode-map)
:localleader :desc "Eval and replace" "e R" #'t/eval-and-replace)
(after! evil
(add-hook 'emacs-lisp-mode-hook #'evil-cleverparens-mode))
Show containing parens, when the cursor is inside theme.
(define-advice show-paren-function (:around (fn) fix)
"Highlight enclosing parens."
(cond ((looking-at-p "\\s(") (funcall fn))
(t (save-excursion
(ignore-errors (backward-up-list))
(funcall fn)))))
Highlight terraform plans in terraform-mode
based on their file name.
(add-to-list 'auto-mode-alist (cons (concat "^" (t/user-file "Downloads/") "tf_plan_.*") 'terraform-mode))
(after! terraform-mode
(defun t-tf-plan-hook ()
(interactive)
(flycheck-mode -1)
(when (s-contains-p "tf_plan_" buffer-file-name)
(beginning-of-buffer)
(evil-search "^───" t t)
(call-interactively 'evil-scroll-line-to-top)))
(add-hook! 'terraform-mode-hook #'terraform-format-on-save-mode)
(add-hook! 'terraform-mode-hook #'t-tf-plan-hook))
(defun t/tf-grep ()
(interactive)
(+vertico/project-search
nil
"^\\(\\<resource\\>\\|\\<output\\>\\|\\<variable\\>\\|\\<module\\>\\|\\<variable\\>\\)#\\(\"[^\"]+\"\\)~ \\(\"[^\"]+\"\\)?~ {"
))
case $(uname) in
Darwin)
brew install hashicorp/tap/terraform-ls
;;
Linux)
false
;;
esac
(after! eglot
(add-to-list 'eglot-server-programs '((hcl-mode terraform-mode) . ("terraform-lsp"))))
This does not work well with eglot
(after! eglot
;;(-find (lambda (c) (eq (car c) 'kotlin-mode)) eglot-server-programs)
(setq eglot-server-programs
(cl-remove-if (lambda (c) (eq (car c) 'kotlin-mode)) eglot-server-programs)))
wget https://github.com/fwcd/kotlin-language-server/releases/download/1.3.1/server.zip -O ~/bin/server.zip
cd ~/bin/
rm -rf server
unzip server.zip
ln -sf server/bin/kotlin-language-server .
Create a venv in venv.
python3 -m venv ~/.doom.d/test-files/venv
(add-to-list 'auto-mode-alist
(cons (concat "^" (t/user-emacs-file "test-files/*"))
(defun t/activate-pyenv-test-files ()
(pyvenv-activate (t/user-emacs-file "test-files/venv")))))
Install a language server in the venv.
case $(uname) in
Darwin)
#pip install python-lsp-server
pip install 'python-lsp-server[all]'
;;
Linux)
false
;;
esac
Need C-c C-s
in org mode.
(after! pyenv-mode
(map! :map 'pyenv-mode-map "C-c C-s" nil))
(use-package! remark-mode
:commands remark-mode
:init
(set-popup-rule! "*remark browser*" :ttl nil))
Prerequisites
npm install -g typescript-language-server typescript
Remove what the doom (javascript :lsp)
module sets up for web mode typescript
(setq auto-mode-alist (assoc-delete-all "\\.tsx?\\'" auto-mode-alist))
(setq auto-mode-alist (assoc-delete-all "\\.tsx\\'" auto-mode-alist))
(add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-ts-mode) t)
(add-to-list 'auto-mode-alist '("\\.tsx\\'" . tsx-ts-mode) t)
Get rid of tide
, it keeps starting tsserver
. I don’t want it.
(after! tide
(when (featurep 'evil-collection-tide) (unload-feature 'evil-collection-tide))
(when (featurep 'company-tide) (unload-feature 'company-tide))
(when (featurep 'tide) (unload-feature 'tide)))
Remove what eglot-server-programs
contains, that is the typescript-language-server
setup without inlay hints
(after! eglot
;; get rid of the old configuration
;; remove configuration where the first item is js-mode in the nested list
;; could not find a simpler to remove the default configuration than this
(setq eglot-server-programs
(cl-remove-if (lambda (el) (and
(listp (car el))
(listp (caar el))
(equal (caaar el) 'js-mode)))
eglot-server-programs))
;; insert the new configuration that sets options to include inlay hints
;; https://github.com/joaotavora/eglot/discussions/1266
;; https://www.reddit.com/r/emacs/comments/11bqzvk/comment/jg0hlm4
(let ((inlay-opts '("typescript-language-server" "--stdio"
:initializationOptions
(:preferences
(:includeInlayParameterNameHints "all"
:includeInlayParameterNameHintsWhenArgumentMatchesName t
:includeInlayVariableTypeHintsWhenTypeMatchesName t
:includeInlayPropertyDeclarationTypeHints t
:includeInlayFunctionLikeReturnTypeHints t
:includeInlayFunctionParameterTypeHints t
:includeInlayEnumMemberValueHints t
:includeInlayVariableTypeHints t)))))
(add-to-list
'eglot-server-programs
;; stole this from the original eglot-server-programs
`((;; this messes up json-mode which inherits javascript-mode
;;(js-mode :language-id "javascript")
(js-ts-mode :language-id "javascript")
(tsx-ts-mode :language-id "typescriptreact")
(typescript-ts-mode :language-id "typescript")
(typescript-mode :language-id "typescript"))
.
,inlay-opts))))
(defun t/eglot-organize-imports ()
(interactive)
(if (derived-mode-p 'rjsx-mode
'typescript-ts-base-mode)
(seq-do
(lambda (kind)
(interactive)
(ignore-errors
(eglot-code-actions (buffer-end 0)
(buffer-end 1) kind t)))
;; https://github.com/typescript-language-server/typescript-language-server#code-actions-on-save
(list
"source.addMissingImports.ts"
"source.fixAll.ts"
"source.removeUnused.ts"
"source.addMissingImports.ts"
"source.removeUnusedImports.ts"
"source.sortImports.ts"
"source.organizeImports.ts"
))
(funcall-interactively #'eglot-code-action-organize-imports)))
(defun t-ts-mode-hook ()
(add-hook 'before-save-hook #'t/eglot-organize-imports -100 t)
(electric-indent-mode -1)
(lsp!))
(add-hook! '(rjsx-mode-hook typescript-ts-mode-hook tsx-ts-mode-hook) #'t-ts-mode-hook)
Adapt what nodejs-repl
does using ts-node
.
npm install -g ts-node
You need a tsconfig.json
for the following to work.
cat <<EOF > $DOOMDIR/test-files/tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"jsx": "react",
"esModuleInterop": true
}
}
EOF
Then make doom understand typescript, e.g. when you select something and go SPC o r
.
(defun t/ts-node-repl ()
(interactive)
(pop-to-buffer
(make-comint "ts-node repl" "ts-node")))
(set-repl-handler! '(typescript-ts-mode tsx-ts-mode) #'t/ts-node-repl)
Or when you eval
something when the repl is open, e.g. g r a p
(eval around paragraph). For multiline stuff type .editor
into the repl before running the command, finish it with C-c C-d
(set-eval-handler! '(typescript-ts-mode tsx-ts-mode)
'((:command . "ts-node")
(:exec . "%c %s")
(:description . "Run ts-node script")))
(eval +overlay)
works in elisp, but is not that good for js/ts, so limit it to use the popup instead.
(setq +eval-popup-min-lines 0)
(map!
:after rjsx-mode
:map rjsx-mode-map :g "M-<return>" #'eglot-code-actions)
(map!
:after typescript-ts-mode
:map (typescript-ts-mode-map tsx-ts-mode-map)
:g "M-<return>" #'eglot-code-actions)
Larger compilation window
(set-popup-rule! "^\\*rustic-compilation" :size 0.5 :side 'bottom)
Emacs server setup.
e
and et
on the command line target the running emacs server instance, to quickly open a file or folder.
I also use this from Alfred, as a quick way of capturing from anywhere.
/etc/profiles/per-user/torgeir/bin/emacsclient -e '(progn (select-frame-set-input-focus (selected-frame)) (org-capture))'
This is paired with the bash function vterm_set_directory that updates the current working directory for emacs as the vterm path changes.
(setq vterm-shell "/usr/bin/env zsh")
(after! vterm
;; https://github.com/akermu/emacs-libvterm#how-can-i-get-the-directory-tracking-in-a-more-understandable-way
;; see dotfiles/source/functions
(add-to-list
'vterm-eval-cmds
'("update-pwd" (lambda (path) (setq default-directory path))))
(add-to-list
'vterm-eval-cmds
'("magit-diff" (lambda (path)
(let ((default-directory path))
(call-interactively' magit-diff)))))
(add-to-list
'vterm-eval-cmds
'("magit-log" (lambda (path)
(let ((default-directory path))
(call-interactively' magit-log)))))
(add-to-list
'vterm-eval-cmds
'("magit-status" (lambda (path)
(let ((default-directory path))
(call-interactively' magit-status))))))
Make pasting from consult-yank-from-kill-ring
actually insert results in vterm
.
(defun my/insert-for-yank-vterm-shim (orig-fun &rest args)
(if (eq major-mode 'vterm-mode)
(let ((inhibit-read-only t))
(apply #'vterm-insert args))
(apply orig-fun args)))
(advice-add #'+default/newline :around #'my/insert-for-yank-vterm-shim)
(advice-add #'insert-char-preview :around #'my/insert-for-yank-vterm-shim)
(advice-add #'insert-for-yank :around #'my/insert-for-yank-vterm-shim)
Keep evil insert mode cursor after shell commands have run.
(advice-add #'vterm--redraw :after (lambda (&rest args) (evil-refresh-cursor evil-state)))
(advice-add #'vterm--delayed-redraw :after (lambda (&rest args) (evil-refresh-cursor evil-state)))
The black in the terminal, e.g. when prefixing commands with a #
, is a little to similar to my theme background. Brighten it slightly.
(after! vterm
(set-face-attribute 'vterm-color-black nil :background "#595B6E" :foreground "#454758"))
This also fixes the color to be the one tmux uses with the catppuccin theme.
Some keybindings are so engrained I can’t live without them.
(after! vterm
(map! :map vterm-mode-map
;; (vterm-send-key KEY &optional SHIFT META CTRL)
;; undo like in the terminal
:i "C-_" (cmd! (vterm-send-key "_" t nil t))
:i "M-<return>" (cmd! (vterm-send-key "<return>" nil t nil))
:i "C-<return>" (cmd! (vterm-send-key "<return>" nil nil t))
:i "M-<up>" (cmd! (vterm-send-key "<up>" nil t nil))
:i "M-<right>" (cmd! (vterm-send-key "<right>" nil t nil))
:i "M-<down>" (cmd! (vterm-send-key "<down>" nil t nil))
:i "M-<left>" (cmd! (vterm-send-key "<left>" nil t nil))
:i "M-d" (cmd! (vterm-send-key "d" nil t nil))
:i "M-D" (cmd! (vterm-send-key "D" nil t nil))
;; send c-up c-down for dir stack zsh navigation
:i "C-<up>" (cmd! (vterm-send-key "<up>" nil nil t))
:i "C-<down>" (cmd! (vterm-send-key "<down>" nil nil t))
:i "s-<up>" (cmd! (vterm-send-key "<up>" nil nil t))
:i "s-<down>" (cmd! (vterm-send-key "<down>" nil nil t)))
(map! :map vterm-mode-map
:m "C-a" (cmd! (vterm-send-key "a" nil nil t))
:m "M-<backspace>" (cmd! (vterm-send-key "w" nil nil t))
:i "M-<backspace>" (cmd! (vterm-send-key "w" nil nil t))
:i "C-h" (cmd! (vterm-send-key "h" nil nil t))
))
Remove C-l
in normal mode, so that recenter-top-bottom
works. Bring it back in insert mode.
(after! vterm
(map! :map vterm-mode-map "C-l" nil)
(map! :map vterm-mode-map :i "C-l" 'vterm-clear))
First esc sends escape, second exits to emacs normal mode
(after! vterm
(comment
;; TODO remove
(defun t/vterm-escape-second-time-around ()
(interactive)
(if (eq last-command 't/vterm-escape-second-time-around)
(call-interactively 'evil-force-normal-state)
(call-interactively 'vterm-send-escape)))
(map! :map vterm-mode-map
:i "<escape>" 't/vterm-escape-second-time-around))
;; this is good enough?
(map! :map vterm-mode-map
:i "M-<escape>" 'vterm-send-escape)
(map! :map vterm-mode-map
:i "M-:" 'eval-expression))
Allow vim-like copy from tmux to emacs kill ring
(after! vterm
(setq vterm-enable-manipulate-selection-data-by-osc52 t))
Use it with ^x^e
(after! vterm
;; ^xe edit-command-line in zshrc
;; ^x^e edit-command-line in zshrc
(map! :map vterm-mode-map
:i "C-x e" (cmd!
(vterm-send-key "x" nil nil t)
(vterm-send-key "e" nil nil nil))
:i "C-x C-e" (cmd!
(vterm-send-key "x" nil nil t)
(vterm-send-key "e" nil nil t))))
Hack to make C-c C-c
accept, and C-c C-k
exit, when running ^x^e
in a vterm terminal. Forces save upon accepting the content and cleans up the hanging “Waiting for Emacs…” when we return to vterm by sending C-l
.
(after! sh-script
(map! :map sh-mode-map :g "C-c C-c" nil)
(add-hook! 'sh-mode-hook
(defun t/sh-mode-server-hook ()
(interactive)
;; when is visiting a window that belongs to an emacsclient
(when server-clients
(map! :map (evil-insert-state-local-map evil-normal-state-local-map)
:g "C-c C-k" 'server-edit-abort
:g "C-c C-c" (defun t/server-edit ()
(interactive)
(call-interactively 'save-buffer)
(server-edit)
(run-at-time "0 sec" nil (cmd! (term-send-raw-string "\C-l")))))))))
Make s-ret
(super+enter) create a vterm terminal window inside emacs.
(map! :gn [s-return]
(defun t/vterm-here ()
(interactive)
(if (eq major-mode 'dired-mode)
(let* ((selected-dir (dired-get-marked-files t current-prefix-arg))
(selected-path (concat default-directory (car selected-dir)))
(default-directory
(if (file-directory-p selected-path)
selected-path
(file-name-directory selected-path))))
(+vterm/here t))
(+vterm/here t))))
Goes great with [[file:~/.config/dotfiles/skhdrc::cmd - return \[][these lines from ~/.skhdrc]], that make super+enter create a terminal from other apps.
(map! :map comint-mode-map
:n "C-d" (cmd! (call-interactively 'evil-scroll-down))
:i "C-d" #'t/volatile-kill-buffer-and-window)
(after! cider
(map! :map cider-repl-mode-map
:n "C-d" (cmd! (call-interactively 'evil-scroll-down))
:i "C-d" #'t/volatile-kill-buffer-and-window))
which-key
messes with evil movement in vterm
, e.g. when attemting to jump somewhere and perform an action on an evil text object, like yiw
. This delays it long enough so you can finish your movement command before it kicks in, preventing it from interfering, only when in vterm-mode
:
(after! which-key
(defun t/delayed-which-key (_ _)
"Suggested in https://github.com/justbur/emacs-which-key/issues/243"
(cond
((eq major-mode 'vterm-mode) 2)
(t nil)))
(add-hook! 'which-key-delay-functions #'t/delayed-which-key))
(add-hook! 'vterm-mode-hook (defun t/vterm-mode-hook ()
(interactive)
(global-emojify-mode -1)
(eros-mode -1)))
By entering vterm-copy-mode
before running the search vterm will be prevented from resetting the cursor, so the jump with consult-line
can be allowed.
(defun t/around-vterm (fn)
"Enter `vterm-copy-mode' before jumping with `consult-line' so that the cursor is not reset when the match is chosen in consult. Restore normal vterm mode by hitting `<return>' after."
(interactive)
(if (not (eq major-mode 'vterm-mode))
(funcall fn)
(progn
(vterm-copy-mode)
(funcall fn))))
(advice-remove '+default/search-buffer 't/around-vterm)
(advice-add '+default/search-buffer :around 't/around-vterm)
Sometimes you need both changes.
(after! ediff
(defun t/bind-ediff-use-both ()
(define-key ediff-mode-map "d" 't/ediff-use-both))
(add-hook! 'ediff-keymap-setup-hook #'t/bind-ediff-use-both))
Useful magit keybindings:
S-SPC
- preview commit
gj
- next and preview
j
- next
magit-log-arguments
and the like are not ment to be used like a list you add args to, instead set options in the magit transient buffer by toggling them and saving it with c-x c-s
.
(after! magit
(setq git-commit-summary-max-length 72 ;; like github
magit-display-buffer-function 'magit-display-buffer-same-window-except-diff-v1)
(defun t/commit-truncate ()
(visual-line-mode -1)
(toggle-truncate-lines 1))
(add-hook! '(magit-log-mode-hook magit-status-mode-hook) 't/commit-truncate)
(defun t/commit-mode-hook ()
(add-to-list 'whitespace-style 'trailing)
(whitespace-mode 1)
(t/commit-truncate))
(add-hook! 'git-commit-mode-hook 't/commit-mode-hook)
(set-popup-rule! "^magit:" :ignore t)
(set-popup-rule! "^magit-revision" :side 'right :size 0.5))
Extend leader map with gn
and gN
, for navigating hunks, the g] and g[ bindings never made sense to me. And gca
for amending.
(map!
:after git-gutter
:leader
(:prefix-map
("g" . "git")
(:when (modulep! :ui vc-gutter)
:desc "Stage hunk" "s" (cmd! (let ((git-gutter:ask-p nil))
(git-gutter:stage-hunk)))
:desc "Jump to next hunk" "n" (cmd! (call-interactively 'git-gutter:next-hunk)
(call-interactively 'evil-scroll-line-to-center))
:desc "Jump to previous hunk" "N" (cmd! (call-interactively 'git-gutter:previous-hunk)
(call-interactively 'evil-scroll-line-to-center)))))
(map!
:after magit
:leader
(:prefix-map
("g" . "git")
(:when (modulep! :tools magit)
:desc "Diff dwim" "d" #'magit-diff-dwim
:desc "Ediff dwim" "e" #'magit-ediff-dwim
:desc "Visit pulls" "p" #'t/visit-git-link-pulls
:desc "Push" "P" #'magit-push
(:prefix ("c" . "create")
:desc "Ammend" "a" #'magit-commit-amend
:desc "Instant fixup" "F" #'magit-commit-instant-fixup))))
I have been trying to get used to magit in evil mode for a while now. But the magit-process-buffer keybinding is crazy on a norwegian keyboard, so this brings back the binding from the emacs mode magit.
(map!
:map magit-status-mode-map
:desc "Show process buffer" :n "$" #'magit-process-buffer)
Colorz.
(after! magit
(set-face-attribute 'magit-diff-hunk-heading nil :background "#513d5b" :foreground "#07010E")
(set-face-attribute 'magit-diff-hunk-heading-highlight nil :background "#ED60BA" :foreground "#01010E" :weight 'bold)
(set-face-attribute 'magit-diff-revision-summary nil :inherit 'magit-diff-hunk-heading :foreground "#ED60BA"))
Make the hostnames personal
and work
from ~/.ssh/config
resolve to github.com, so that commands like SPC g o o
opens github.
(after! browse-at-remote
(add-to-list 'browse-at-remote-remote-type-regexps '(:host "^personal$" :type "github" :actual-host "github.com"))
(add-to-list 'browse-at-remote-remote-type-regexps '(:host "^work$" :type "github" :actual-host "github.com")))
Improved diffs with magit-delta-mode
. Show the themes with delta --show-syntax-themes --dark | grep -i theme
.
(setq magit-delta-default-dark-theme "DarkNeon")
(defun magit-log-propertize-keywords-conventional-commits (_rev msg)
(let ((boundary 0))
(when (string-match "^\\(?:squash\\|fixup\\)! " msg boundary)
(setq boundary (match-end 0))
(magit--put-face (match-beginning 0) (1- boundary)
'magit-keyword-squash msg))
(when magit-log-highlight-keywords
;; Case [...]
(while (string-match "\\[[^[]*?]" msg boundary)
(setq boundary (match-end 0))
(magit--put-face (match-beginning 0) boundary
'magit-keyword msg))
;; Conventional commits
(while (string-match "^\\(?:feat\\|fix\\|chore\\|docs\\|style\\|refactor\\|perf\\|test\\)\\(?:\\(?:[(].*[)]\\)\\|\\(?:!\\)\\)?:" msg boundary)
(setq boundary (match-end 0))
(magit--put-face (match-beginning 0) boundary
'magit-keyword msg))))
msg)
(advice-add #'magit-log-propertize-keywords :override #'magit-log-propertize-keywords-conventional-commits)
(defun t/artist-mode ()
(interactive)
(if (and (boundp 'artist-mode)
artist-mode)
(progn
(artist-mode-off)
(evil-normal-state))
(progn
(switch-to-buffer "*scratch*")
(evil-insert-state)
(artist-mode t))))
(after! artist
(add-hook! 'artist-mode-hook
(defun t/artist-mode-hook ()
(map!
:map evil-insert-state-local-map "q" 'artist-mode-off
:map evil-normal-state-local-map "q" 'artist-mode-off))))
(map!
:leader
(:prefix-map
("z" . "misc")
(:prefix
("z" . "artist")
(:when t
:desc "Enable" "t" 't/artist-mode
:desc "Draw: pen" "p" 'artist-select-op-pen-line
:desc "Draw: line" "l" 'artist-select-op-line
:desc "Draw: rectangle" "r" 'artist-select-op-rectangle
:desc "Draw: circle" "c" 'artist-select-op-circle
:desc "Draw: ellips" "e" 'artist-select-op-ellipse
:desc "Draw: square" "s" 'artist-select-op-square))))
(after! elfeed
(setq rmh-elfeed-org-files '("~/Dropbox/org/feeds.org")
rmh-elfeed-org-auto-ignore-invalid-feeds t
rmh-elfeed-org-ignore-tag "ARCHIVE"
elfeed-db-directory (t/user-dropbox-folder "Apps/elfeed/")
elfeed-goodies/entry-pane-position 'right
elfeed-search-filter "@2-week-ago -youtube -news -tech +unread")
(add-hook! 'elfeed-db-update-hook 'elfeed-db-save))
Switch around the refresh mappings, for more useful defaults.
(after! elfeed
(map!
:map elfeed-search-mode-map
:n "S" (cmd! (call-interactively 'ellama-summarize-webpage)
(with-current-buffer "*elfeed-search*"
(elfeed-search-untag-all-unread)))
:n "!" #'elfeed-search-untag-all-unread
:n "?" #'elfeed-search-tag-all-unread
;; switcharoo
:n "gR" #'elfeed-search-update--force
:n "gr" #'elfeed-search-fetch
:map elfeed-show-mode-map
:n "gr" #'elfeed-show-refresh
))
Also allow refresh when viewing a post. E.g. to show with images again after toggling them off.
(after! elfeed
(map!
:map elfeed-show-mode-map
:n "gr" #'elfeed-show-refresh
))
(after! elfeed
(defun t/toggle-elfeed-tag (tag)
(interactive "sTag: ")
(when tag
(setq elfeed-search-filter
(cond
((s-contains? (concat "+" tag) elfeed-search-filter)
(replace-regexp-in-string (concat "\\+" tag) (concat "-" tag) elfeed-search-filter))
((s-contains? (concat "-" tag) elfeed-search-filter)
(replace-regexp-in-string (concat "-" tag) (concat "+" tag) elfeed-search-filter))
(t (concat elfeed-search-filter " +" tag))))
(elfeed-search-update :force)))
(map!
:map elfeed-search-mode-map
:localleader "t" (cmd!
(let* ((items '(("a" "adressa")
("d" "dev")
("f" "fun")
("i" "diy")
("n" "news")
("p" "photo")
("r" "read")
("s" "stories")
("t" "tech")
("u" "unread")
("y" "youtube"))))
(funcall (t/micro-state nil
"a" (cmd! (t/toggle-elfeed-tag "adressa"))
"d" (cmd! (t/toggle-elfeed-tag "dev"))
"f" (cmd! (t/toggle-elfeed-tag "fun"))
"i" (cmd! (t/toggle-elfeed-tag "diy"))
"n" (cmd! (t/toggle-elfeed-tag "news"))
"p" (cmd! (t/toggle-elfeed-tag "photo"))
"r" (cmd! (t/toggle-elfeed-tag "read"))
"s" (cmd! (t/toggle-elfeed-tag "stories"))
"t" (cmd! (t/toggle-elfeed-tag "tech"))
"u" (cmd! (t/toggle-elfeed-tag "unread"))
"y" (cmd! (t/toggle-elfeed-tag "youtube"))))
(message (s-join ", "
(seq-map (lambda (item)
(let ((index (string-match (car item) (cadr item))))
(concat (substring (cadr item) 0 index)
(concat "(" (car item) ")")
(substring (cadr item) (1+ index)))))
items)))))))
(after! elfeed
(add-hook 'elfeed-search-mode-hook
(defun t/hook-elfeed-search-mode-hook ()
(show-paren-mode -1)
(visual-line-mode -1))))
Make reading smoother. Turn on olivetti-mode
to center content. Wrap lines. Bind n
, p
for nav, that skips to the next item on reaching the end.
(after! elfeed
(add-hook 'elfeed-show-mode-hook
(defun t/hook-elfeed-show-mode-hook ()
(t/variable-pitch-mode 1)
(olivetti-mode 1)
(t/start-spray-micro-state (lambda () (equal major-mode 'elfeed-show-mode))))))
Show the mouseover text for xkcd comics by moving to the image and fetching the 'shr-alt
text property that holds the mouse over text. Insert it in the buffer on the next tick, to wait for the image to appear first.
(after! elfeed
(add-hook 'elfeed-show-mode-hook
(defun t/elfeed-show-xkcd-mouseover-hook ()
(run-at-time "5 sec" nil
(cmd!
(when (and
elfeed-show-entry
(s-equals-p "xkcd.com" (car (elfeed-entry-id elfeed-show-entry))))
(save-excursion
(forward-line)
(read-only-mode -1)
(when-let ((text (get-text-property (point) 'shr-alt)))
(goto-char (point-max))
(insert "\n\n")
(insert text))
(read-only-mode 1))))))))
Auto tagging of some types of subs.
(after! elfeed
(add-hook 'elfeed-new-entry-hook
(elfeed-make-tagger :feed-url "youtube\\.com"
:add '(video youtube))))
(after! elfeed
(add-hook 'elfeed-new-entry-hook
(elfeed-make-tagger :before "2 weeks ago"
:remove 'unread)))
(after! elfeed
(set-face-attribute 'elfeed-search-tag-face nil :foreground (face-attribute 'font-lock-type-face :foreground))
(set-face-attribute 'elfeed-search-title-face nil :bold nil :foreground (face-attribute 'font-lock-comment-face :foreground))
(set-face-attribute 'elfeed-search-unread-title-face nil :bold t :foreground (face-attribute 'font-lock-keyword-face :foreground))
(copy-face 'elfeed-search-tag-face 'elfeed-hl-face)
(set-face-attribute 'elfeed-hl-face nil :bold t))
(after! elfeed
;; this is the start of *elfeed-entry-<title>* names of youtube buffers without an elfeed entry
(set-popup-rule! "^\\*elfeed-entry-<" :side 'right :size 0.6 :select t :vslot 10)
(set-popup-rule! "^\\*elfeed-entry*" :side 'right :size 0.6 :select t :vslot 5))
Extracted youtube feeds like this
curl https://www.youtube.com/@$1 | grep -ioE “<link [^>]+>” | rg rss | sed -E ‘s#.*href=”([^”]+)”.*#\1#’)
Elfeed textual youtube support
(use-package! elfeed-tube
:commands (elfeed-tube-setup)
:init
(setq elfeed-tube-auto-save-p nil)
(setq elfeed-tube-auto-fetch-p t))
(after! elfeed
(add-hook! 'elfeed-show-mode-hook 'elfeed-tube-setup))
Inspiration https://gist.github.com/alphapapa/80d2dba33fafcb50f558464a3a73af9a
(after! elfeed
(defun t-elfeed-print-entry (&optional entry)
"Customize what heading goes where, elfeed-search-print-entry--default."
(let* ((tags (or (elfeed-entry-tags entry) ""))
(date (elfeed-search-format-date (elfeed-entry-date entry)))
(feed (elfeed-entry-feed entry))
(feed-title
(when feed
(or (elfeed-meta feed :title) (elfeed-feed-title feed))))
(title (or (elfeed-meta entry :title) (elfeed-entry-title entry) ""))
(title-faces (elfeed-search--faces (elfeed-entry-tags entry))))
(insert (propertize (elfeed-format-column date 10 :left) 'face 'elfeed-search-date-face) " ")
(when feed-title
(insert (propertize (elfeed-format-column feed-title 18 :left)
'face
'elfeed-search-feed-face) " "))
(insert (propertize (elfeed-format-column title 80 :left)
'face
(if (and (member 'unread (append tags))
(member 'read (append tags)))
'elfeed-hl-face
title-faces)
'kbd-help title) " ")
(insert (propertize (elfeed-format-column tags 30 :right)
'face 'elfeed-search-date-face) " ")))
(setq elfeed-search-print-entry-function 't-elfeed-print-entry))
(after! elfeed
(defun t/show-elfeed-heading-in-minibuffer ()
(interactive)
(let ((selected (car (elfeed-search-selected))))
(when-let ((feed (and selected (elfeed-entry-feed selected))))
(message "%s: %s"
(propertize (elfeed-feed-title feed) 'face 'elfeed-search-feed-face)
(propertize (elfeed-entry-title selected) 'face 'elfeed-hl-face)))))
(defun t/setup-elfeed-heading-in-minibuffer ()
(interactive)
(add-hook! 'post-command-hook :local 't/show-elfeed-heading-in-minibuffer))
(add-hook! 'elfeed-search-mode-hook 't/setup-elfeed-heading-in-minibuffer))
Ready some fonts that stand out.
(after! calendar
(copy-face font-lock-comment-face 'calendar-week-face)
(copy-face font-lock-string-face 'calendar-today-face)
(set-face-attribute 'holiday nil :foreground "VioletRed1" :weight 'bold :background nil)
(set-face-attribute 'calendar-today-face nil :weight 'bold :background nil)
(set-face-attribute 'calendar-week-face nil :foreground "VioletRed4"))
Weeks on start on monday in Norway, and weeks have numbers. I also like holidays.
(after! calendar
(setq calendar-date-style 'iso
calendar-week-start-day 1
calendar-mark-holidays-flag t
calendar-today-marker 'calendar-today-face
calendar-intermonth-header '(propertize "w" 'font-lock-face 'calendar-week-face)
calendar-intermonth-text '(propertize
(format "%2d" (car
(calendar-iso-from-absolute
(calendar-absolute-from-gregorian
(list month day year)))))
'font-lock-face
'calendar-week-face)))
Mark today when scrolling past it.
(after! calendar
(add-hook 'calendar-today-visible-hook 'calendar-mark-today))
Translate days and seasons to norwegian.
(after! calendar
(add-hook 'calendar-initial-window-hook
(defun t/calendar-initial-window-hook ()
(require 'calendar-norway)
(setq calendar-day-header-array ["sø" "ma" "ti" "on" "to" "fr" "lø"]
calendar-day-name-array ["Søndag" "Mandag" "Tirsdag" "Onsdag" "Torsdag" "Fredag" "Lørdag"]
solar-n-hemi-seasons '("Vårjevndøgn" "Sommersolverv" "Høstjevndøgn" "Vintersolherv")
calendar-holidays (append
calendar-norway-raude-dagar
calendar-norway-andre-merkedagar
calendar-norway-dst
'((holiday-fixed 3 17 "St. Patricksdag")
(holiday-fixed 10 31 "Halloween")
(holiday-float 11 4 4 "Thanksgiving"))))
(calendar-redraw))))
Evil like navigation.
(after! calendar
(add-hook! 'calendar-mode-hook
(defun t/calendar-mode-hook ()
(map!
:map calendar-mode-map
:m "H" #'calendar-scroll-left
:m "L" #'calendar-scroll-right))))
(after! re-builder
(setq reb-re-syntax 'rx)
(defvar t-regex-mode nil "reb-mode on or not"))
(defun t/toggle-regex-mode ()
(interactive)
(if t-regex-mode (reb-quit) (re-builder))
(setq t-regex-mode (not t-regex-mode)))
Settings from modules/email/mu4e/README.org
Inspiration:
- https://pragmaticemacs.wordpress.com/2016/03/22/migrating-from-offlineimap-to-mbsync-for-mu4e/
- https://macowners.club/posts/email-emacs-mu4e-macos/
- https://www.reddit.com/r/emacs/comments/8q84dl/tip_how_to_easily_manage_your_emails_with_mu4e/
- https://www.reddit.com/r/emacs/comments/bfsck6/mu4e_for_dummies/
- https://www.djcbsoftware.nl/code/mu/mu4e/Multiple-accounts.html
- https://f-santos.gitlab.io/2020-04-24-mu4e.html
- https://www.erichgrunewald.com/posts/setting-up-gmail-in-doom-emacs-using-mbsync-and-mu4e/
- https://blog.leonardotamiano.xyz/posts/mu4e-setup/
(after! mu4e
(set-popup-rule! "^*mu4e-main" :ignore t)
(set-popup-rule! "^*mu4e-draft" :ignore nil :side 'right :size 0.8)
(set-popup-rule! "^*mu4e-headers" :ignore t :side 'right :size 0.8 :vslot 5 :select t)
(set-popup-rule! "^*mu4e-article" :ignore t :side 'right :size 0.8 :vslot 10 :select t)
(add-hook! 'mu4e-main-mode-hook
(defun t/mu4e-main-mode-hook ()
(interactive)
(visual-line-mode -1)))
(setq mu4e-maildir "~/.maildir"
mu4e-mu-version "1.12.5"
+mu4e-workspace-name "*email*"
+mu4e-alert-bell-cmd nil ;; no sounds
mu4e-split-view 'vertical
mu4e-update-interval (* 5 60)
mu4e-context-policy nil
mu4e-attachment-dir "~/Desktop"
mu4e-mu-binary (executable-find "mu")
mu4e-get-mail-command (concat (executable-find "mbsync") " -a")
mu4e-headers-fields '((:account-stripe . 1)
(:human-date . 8)
(:from . 22)
(:flags . 6)
(:subject)
(:to . 25))
;; rename files when moving - needed for mbsync:
mu4e-change-filenames-when-moving t
mu4e-maildir-shortcuts '(("/gmail/INBOX" . ?g)
("/gmail/[Gmail]/Sent Mail" . ?G)
("/junk/INBOX" . ?j)
("/junk/[Gmail]/Sent Mail" . ?J)))
(add-to-list 'mu4e-bookmarks '(:name "Junk" :query "maildir:/junk/INBOX" :key ?j))
(add-to-list 'mu4e-bookmarks '(:name "Gmail" :query "maildir:/gmail/INBOX" :key ?g))
(setq mail-user-agent 'mu4e-user-agent)
(setq +mu4e-gmail-accounts `((,user-mail-address . "/gmail")
(,user-mail-address-2 . "/junk")))
(set-email-account!
"gmail"
`((mu4e-sent-folder . "/gmail/[Gmail]/Sent Mail")
(mu4e-drafts-folder . "/gmail/[Gmail]/Drafts")
(mu4e-trash-folder . "/gmail/[Gmail]/Trash")
(mu4e-compose-signature . "\nT")
(user-full-name . ,user-full-name)
(user-mail-address . ,user-mail-address)
(message-sendmail-extra-arguments . ("--read-envelope-from" "--account=gmail"))
(org-msg-signature . "\n\n#+begin_signature\n--\n\nT\n#+end_signature"))
t)
(set-email-account!
"junk"
`((mu4e-sent-folder . "/junk/[Gmail]/Sent Mail")
(mu4e-drafts-folder . "/junk/[Gmail]/Drafts")
(mu4e-trash-folder . "/junk/[Gmail]/Trash")
(mu4e-compose-signature . "\ntorg")
(user-full-name . "torg")
(user-mail-address . ,user-mail-address-2)
(message-sendmail-extra-arguments . ("--read-envelope-from" "--account=junk"))
(org-msg-signature . "\n\n#+begin_signature\n--\n\ntorg\n#+end_signature"))
t)
(setq sendmail-program (executable-find "msmtp")
send-mail-function #'smtpmail-send-it
message-sendmail-f-is-evil t
message-send-mail-function #'message-send-mail-with-sendmail))
(after! mu4e
(add-hook 'mu4e-compose-mode-hook 'turn-off-auto-fill))
(after! mu4e
(add-hook! 'mu4e-headers-mode-hook :append
(defun t/mu4e-headers-mode-hook ()
(visual-line-mode -1)
(show-paren-mode -1))))
(after! mu4e
(add-hook 'mu4e-compose-mode-hook
(defun t/mu-add-cc-and-bcc ()
"My Function to automatically add Cc & Bcc: headers."
(save-excursion (message-add-header "Cc:\n"))
(save-excursion (message-add-header "Bcc:\n")))))
Get rid of consecutive newlines and clean the buffer up. Related struggles.
(after! mu4e
(add-hook! 'mu4e-view-rendered-hook :append
(defun t/mu4e-rendered-mode-hook ()
(evil-normal-state)
(t/start-spray-micro-state)
(with-current-buffer "*mu4e-article*"
(t/remove-consecutive-newlines)
(olivetti-mode)))))
(after! mu4e
(require 'mu4e-contrib)
(setq mu4e-html2text-command 'mu4e-shr2text)
(setq shr-color-visible-luminance-min 60)
(setq shr-color-visible-distance-min 5)
(setq shr-use-colors nil)
(advice-add #'shr-colorize-region :around (defun shr-no-colourise-region (&rest ignore))))
Sometimes mu4e
cannot be found, so SPC o C m
does not launch it. Try *Doom env from terminal, including SSH_* and GPG_* env vars and try launching =mu4e
again.
The subfolder gmail
is what makes the mu4e setting above need /gmail/
in their path
mkdir -p ~/.maildir/{gmail,junk}
- On arch or nix on mac:
/etc/ssl/certs/ca-certificates.crt
- On plainmacos:
Keychain Access -> System Roots -> Certificates -> select all -> Shift+cmd+E
to ~~/.maildir/certificates/certificates.pem~
Gmail setting to remove deleted emails from inbox and move them in [Gmail]/Trash
.
Go to Gmail IMAP/POP settings (in normal view; the options are not available in HTML view) and set “When a message is marked as deleted” to “Move to trash” or “Immediately delete.” Also set “Auto-Expunge off.”
brew install msmtp
Create .msmtprc
config for sending email
(after! f
(f-write-text
(concat "defaults
auth on
port 587
protocol smtp
tls on
tls_starttls on
account gmail
host smtp.gmail.com
from " (getenv "USER_EMAIL") "
user " (replace-regexp-in-string "@gmail.com" "" (getenv "USER_EMAIL")) "
passwordeval \"gpg -q --for-your-eyes-only --no-tty --logger-file /dev/null --batch -d ~/.authinfo.gpg | awk '/machine smtp\\.gmail\\.com login "
(replace-regexp-in-string "\\." "\\\\." (getenv "USER_EMAIL"))
" port 587/ {print $NF}'\"
account junk
host smtp.gmail.com
from " (getenv "USER_EMAIL_2") "
user " (replace-regexp-in-string "@gmail.com" "" (getenv "USER_EMAIL_2")) "
passwordeval \"gpg -q --for-your-eyes-only --no-tty --logger-file /dev/null --batch -d ~/.authinfo.gpg | awk '/machine smtp\\.gmail\\.com login "
(replace-regexp-in-string "\\." "\\\\." (getenv "USER_EMAIL_2"))
" port 587/ {print $NF}'\"
")
'utf-8 (t/user-file ".msmtprc")))
brew install isync
(after! f
(f-write-text
(concat "
# gmail ========================
IMAPAccount gmail
Host imap.gmail.com
User " (getenv "USER_EMAIL") "
PassCmd \"gpg -q --for-your-eyes-only --no-tty --logger-file /dev/null --batch -d ~/.authinfo.gpg | awk '/machine imap\\.gmail\\.com login "
(replace-regexp-in-string "\\." "\\\\." (getenv "USER_EMAIL"))
" port 993/ {print $NF}'\"
Port 993
SSLType IMAPS
SSLVersions TLSv1.2
AuthMechs PLAIN
SystemCertificates no
CertificateFile /etc/ssl/certs/ca-certificates.crt
IMAPStore gmail-remote
Account gmail
MaildirStore gmail-local
SubFolders Verbatim
Path ~/.maildir/gmail/
Inbox ~/.maildir/gmail/INBOX
Channel gmail
Far :gmail-remote:
Near :gmail-local:
Patterns * ![Gmail]* \"[Gmail]/Sent Mail\" \"[Gmail]/Trash\"
Create Near
Sync All
Expunge Both
SyncState *
# junk =========================
IMAPAccount junk
Host imap.gmail.com
User " (getenv "USER_EMAIL_2") "
PassCmd \"gpg -q --for-your-eyes-only --no-tty --logger-file /dev/null --batch -d ~/.authinfo.gpg | awk '/machine imap\\.gmail\\.com login "
(replace-regexp-in-string "\\." "\\\\." (getenv "USER_EMAIL_2"))
" port 993/ {print $NF}'\"
Port 993
SSLType IMAPS
SSLVersions TLSv1.2
AuthMechs PLAIN
SystemCertificates no
CertificateFile /etc/ssl/certs/ca-certificates.crt
IMAPStore junk-remote
Account junk
MaildirStore junk-local
SubFolders Verbatim
Path ~/.maildir/junk/
Inbox ~/.maildir/junk/INBOX
Channel junk
Far :junk-remote:
Near :junk-local:
Patterns * ![Gmail]* \"[Gmail]/Sent Mail\" \"[Gmail]/Trash\"
Create Near
Sync All
Expunge Both
SyncState *")
'utf-8 (t/user-file ".mbsyncrc")))
Then sync it with mbsync -aV
.
First time mu
initialization
(shell-command-to-string
(concat "mu init -m ~/.maildir"
" --my-address " (getenv "USER_EMAIL")
" --my-address " (getenv "USER_EMAIL_2")
" && mu index"))
(after! mu4e
(map! :map mu4e-headers-mode-map
:n "*" #'mu4e-headers-mark-for-something
:n "u" #'mu4e-headers-mark-for-read
:n "U" #'mu4e-headers-mark-for-unread
:n "!" #'mu4e-headers-mark-for-unmark
:n "?" #'mu4e-mark-unmark-all))
Had to locate the mu4e installed nix package
fd / mu4e
and add it to the load path of emacs manually, something like this
(add-to-list ‘load-path ”nix/store/g119kihhviy5c4krzr8h30wakzldab3d-mu-1.10.8-mu4e/share/emacs/site-lisp/mu4e”)
Useful elisp I committed, or decided to work on.
(after! org
(use-package! ox-gfm
:commands org-export-dispatch
:load-path (lambda () (t/user-emacs-file "site-lisp/ox-gfm/"))))
(use-package! consult-async
:commands consult-web-search
:load-path (lambda () (t/user-emacs-file "site-lisp/consult-async/")))
;; don't use this for large files, e.g. like 15MB, it really brings emacs to a stall
(use-package! nxml-eldoc
:commands turn-on-nxml-eldoc
:load-path (lambda () (t/user-emacs-file "site-lisp/nxml-eldoc/")))
(use-package! json-path-eldoc
:commands turn-on-json-path-eldoc
;; TODO too slow on large files, use treesitter?
;;:init (add-hook! 'json-mode-hook 'turn-on-json-path-eldoc)
:load-path (lambda () (t/user-emacs-file "site-lisp/json-path-eldoc/")))
(defun t/json-mode-hook ()
(interactive)
(eldoc-mode -1))
(add-hook! 'json-mode-hook #'t/json-mode-hook)
(remove-hook! 'json-mode-hook #'+electric--init-json-mode-h)
A little minor mode to highlight the symbol at point or the selected region across all emacs buffers.
(defvar t-idle-highlight-idle-time 0.3
"Time before idle highlight kicks in.")
(defvar t-idle-highlight-flash-duration 1.5
"Time of idle highlight flash duration.")
(defvar t-idle-highlight--timer nil
"Idle timer that triggers the highlight when user is idle.")
(define-minor-mode t-idle-highlight-mode
"Minor mode that will highlight symbol at point when emacs is idle."
:init-value nil
:global t
:lighter " t-idle-highlight"
(if t-idle-highlight-mode
(progn
(when (not (featurep 'highlight-symbol))
(require 'highlight-symbol))
(add-hook 'pre-command-hook 't/unhighlight-in-all-buffers)
(setq t-idle-highlight--timer
(run-with-idle-timer
t-idle-highlight-idle-time
t
't/highlight-in-all-buffers)))
(progn
(remove-hook 'pre-command-hook 't/unhighlight-in-all-buffers)
(when t-idle-highlight--timer
(cancel-timer t-idle-highlight--timer)
(setq t-idle-highlight--timer nil)
(t/unhighlight-in-all-buffers)))))
(defface t-idle-highlight-face
'((t (:weight bold)))
"Idle highlighting face.")
(defface t-idle-highlight-face-loud
'((t (:weight bold :background "#f0a" :foreground "#dadada")))
"Idle highlighting face, loud.")
(defun t/flash-in-all-buffers ()
"Highlight symbol at point across all buffers temporarily."
(interactive)
(t/highlight-in-all-buffers 't-idle-highlight-face-loud)
(run-at-time (format "%d sec" t-idle-highlight-flash-duration) nil 't/unhighlight-in-all-buffers))
(defun t/highlight-with-all-buffers (fn)
(if-let ((symbol
(if (use-region-p)
(buffer-substring-no-properties (region-beginning) (region-end))
(thing-at-point 'symbol t))))
(save-excursion
(let ((tail (buffer-list)))
(while tail
(let ((buffer (car tail)))
(set-buffer buffer)
(funcall fn symbol)
(setq tail (cdr tail))))))))
(defun t/highlight-in-all-buffers (&optional face)
"Highlight symbol at point in all buffers."
(interactive)
(t/highlight-with-all-buffers
(lambda (symbol)
(highlight-symbol-add-symbol-with-face symbol (or face 't-idle-highlight-face)))))
(defun t/unhighlight-in-all-buffers ()
"Remove all symbol highlights in all buffers."
(interactive)
(t/highlight-with-all-buffers
(lambda (symbol)
(highlight-symbol-remove-all))))
Extend evil to flash highlights on SPC u *
(after! evil
(map! :map evil-motion-state-map
"*" (cmd! (when (not (featurep 'highlight-symbol))
(require 'highlight-symbol))
(if (t/prefix-arg-universal?)
(t/flash-in-all-buffers)
(call-interactively 'evil-ex-search-word-forward)))))
(when is-linux
(defun compat-assoc (&optional a b c) nil))
(when is-linux
(defun compat-assoc-delete-all (&optional a b c) nil))
(add-hook 'find-file-hook
(defun t/in-every-file ()
;;(when (string= (file-name-extension buffer-file-name) "ts") (typescript-mode))
))
Useful e.g. to make dired act like a directory tree sidebar
(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)
(setq window-size-fixed (if sticky-buffer-mode 'width nil)))
(defun t/gha ()
(interactive)
(let ((current-prefix-arg 1))
;; (call-interactively '+vterm/here)
(call-interactively '+vterm/toggle))
(term-send-raw-string "gh run watch\C-m"))
(comment
(progn
(require 'xref)
(let ((l (xref-location-marker
(xref-make-file-location
(t/user-emacs-file "test-files/index.js")
27
11))))
(xref--show-pos-in-buf l (marker-buffer l))))
)
Check if configuration affecting sway is changed, if so reload it
(add-hook! 'after-save-hook
(defun t/reload-sway-after-save ()
(when
(or (s-ends-with? "i3status-rust/config.toml" (buffer-file-name))
(s-ends-with? "config/sway/config" (buffer-file-name)))
(call-process-shell-command "swaymsg reload"))))
(doom-mark-buffer-as-real-h)
can make scratch buffer navigable with s-j/k
June 18, 2021 at 12:12PM https://ift.tt/36jQLT2
deno-fmt is function that formats the current buffer on save with deno fmt. The package also exports a minor mode that applies (deno-fmt) on save. Feel free to replace typescript-mode / js2-mode in the following with your TypeScript/JavaScript mode of choice.
(after! eglot
(defclass eglot-deno (eglot-lsp-server) ()
:documentation "A custom class for deno lsp.")
(cl-defmethod eglot-initialization-options ((server eglot-deno))
"Passes through required deno initialization options"
(list :enable t
:lint t)))
(after! emms
(setq emms-player-list (list 'emms-player-mpd))
(add-to-list 'emms-info-functions (list 'emms-info-mpd))
(emms-cache-set-from-mpd-all))
Then type C-ret
to add something to the playlist.
(after! emms
(setq emms-volume-change-amount 10
emms-volume-change-function 'emms-volume-mpd-change)
(defun t/music-volume-up () (interactive) (emms-volume-raise))
(defun t/music-volume-down () (interactive) (emms-volume-lower))
(defun t/music-browse () (interactive) (emms-smart-browse))
(defun t/music-stop () (interactive) (emms-stop))
(defun t/music-seek-backward () (interactive) (emms-seek-backward))
(defun t/music-seek-forward () (interactive) (emms-seek-forward))
(defun t/music-play-pause () (interactive) (if emms-player-playing-p
(emms-pause)
(emms-start)))
(defun t/music-prev () (interactive) (emms-previous))
(defun t/music-next () (interactive) (emms-next))
(defun t/music-browse () (interactive) (emms-smart-browse)))
(use-package! copilot
:bind (:map copilot-completion-map
("<tab>" . 'copilot-accept-completion)
("TAB" . 'copilot-accept-completion)
("C-TAB" . 'copilot-accept-completion-by-word)
("C-<tab>" . 'copilot-accept-completion-by-word)))
Unload module
(when (featurep 'nav-flash)
(unload-feature 'nav-flash))
(use-package! pulsar
:commands pulsar-global-mode
:init
(setq pulsar-pulse t)
(setq pulsar-delay 0.055)
(setq pulsar-iterations 10)
(setq pulsar-face 'pulsar-magenta)
(setq pulsar-highlight-face 'pulsar-yellow)
(add-hook! '(imenu-after-jump-hook
better-jumper-post-jump-hook
counsel-grep-post-action-hook
consult-after-jump-hook
dumb-jump-after-jump-hook)
#'pulsar-pulse-line)
(add-hook 'doom-switch-window-hook #'pulsar-pulse-line)
;; `org'
(add-hook 'org-follow-link-hook
(cmd!
(run-at-time 0.1 nil #'pulsar-pulse-line)))
;; `saveplace'
(advice-add #'save-place-find-file-hook :after #'pulsar-pulse-line)
;; `evil'
(advice-add #'evil-window-top :after #'pulsar-pulse-line)
(advice-add #'evil-window-middle :after #'pulsar-pulse-line)
(advice-add #'evil-window-bottom :after #'pulsar-pulse-line)
;; Bound to `ga' for evil users
(advice-add #'what-cursor-position :after #'pulsar-pulse-line)
:config
(pulsar-global-mode 1))
(setq alert-default-style (if is-mac 'osx-notifier 'libnotify))
(defun t/ellama-code-ask (prompt)
(interactive "sPrompt: ")
(let* ((snippet (when (region-active-p)
(buffer-substring-no-properties (region-beginning) (region-end))))
(json (json-serialize (list
'model "codellama"
'prompt
(if snippet
(concat "# " prompt "\n\n" snippet)
prompt)))))
(t/async-shell-command
"ChatGPT"
(format
"curl --no-buffer -s http://torgnix:11434/api/generate --data-binary @- <<EOF | jq --unbuffered -jrc '.response'
%s
EOF" (replace-regexp-in-string "`" "\\\\`" json)) ;; bash cant handle ` in EOF?
)
(pop-to-buffer "*ChatGPT*")))
(defun t/ellama-review-code ()
(interactive)
(t/ellama-code-ask
(concat
"Review the following code and make concise suggestions. "
(if (t/prefix-arg-universal?) (read-string "Be specific: ") ""))))
(use-package! ellama
:config
(setq torgnix-providers
(list
(make-llm-ollama
:scheme "http"
:host "torgnix"
:port 11434
:chat-model "llama3"
:embedding-model "llama3")
(make-llm-ollama
:scheme "http"
:host "torgnix"
:port 11434
:chat-model "wizard-vicuna-uncensored:13b"
:embedding-model "wizard-vicuna-uncensored:13b")))
(setq ellama-providers nil)
(add-to-list 'ellama-providers `("torgnix llama" . ,(car torgnix-providers)))
(add-to-list 'ellama-providers `("torgnix wizard" . ,(cadr torgnix-providers)))
(setq ellama-provider (car torgnix-providers))
(setq ellama-keymap-prefix "C-c")
(ellama-setup-keymap)
(set-popup-rule! "^ellama"
:side 'right
:size 0.7
:vslot 2
:select t)
(map! :leader
(:prefix
("o" . "open")
(:prefix-map
("g" . "ollama")
(:when t
:desc "code-complete" "c c" 'ellama-code-complete
:desc "code-add" "c a" 'ellama-code-add
:desc "code-edit" "c e" 'ellama-code-edit
:desc "code-improve" "c i" 'ellama-code-improve
:desc "code-review" "c r" 'ellama-code-review
;; summarize
:desc "summarize" "s s" 'ellama-summarize
:desc "summarize-webpage" "s w" 'ellama-summarize-webpage
;; session
:desc "load-session" "s l" 'ellama-load-session
:desc "session-rename" "s r" 'ellama-session-rename
:desc "session-remove" "s d" 'ellama-session-remove
:desc "session-switch" "s a" 'ellama-session-switch
;; improve
:desc "improve-wording" "i w" 'ellama-improve-wording
:desc "improve-grammar" "i g" 'ellama-improve-grammar
:desc "improve-conciseness" "i c" 'ellama-improve-conciseness
;; make
:desc "make-list" "m l" 'ellama-make-list
:desc "make-table" "m t" 'ellama-make-table
:desc "make-format" "m f" 'ellama-make-format
;; ask
:desc "ask-about" "a a" 'ellama-ask-about
:desc "chat" "a i" 'ellama-chat
:desc "ask-line" "a l" 'ellama-ask-line
:desc "ask-selection" "a s" 'ellama-ask-selection
;; text
:desc "translate" "t t" 'ellama-translate
:desc "translate-buffer" "t b" 'ellama-translate-buffer
:desc "complete" "t c" 'ellama-complete
:desc "chat-translation-enable" "t e" 'ellama-chat-translation-enable
:desc "chat-translation-disable" "t d" 'ellama-chat-translation-disable
;; define
:desc "define-word" "d w" 'ellama-define-word
;; context
:desc "context-add-buffer" "x b" 'ellama-context-add-buffer
:desc "context-add-file" "x f" 'ellama-context-add-file
:desc "context-add-selection" "x s" 'ellama-context-add-selection
:desc "context-add-info-node" "x i" 'ellama-context-add-info-node
;; provider
:desc "provider-select" "p s" 'ellama-provider-select
)))))
(after! ellama
(defun ellama-summarize-webpage (url)
"Summarize webpage fetched from URL."
(interactive
(list
(if-let ((url (or (and (fboundp 'thing-at-point) (thing-at-point 'url))
(shr-url-at-point nil)
(org-element-property :raw-link (org-element-context))
(and (equal major-mode 'elfeed-show-mode)
(elfeed-entry-link elfeed-show-entry))
(and (equal major-mode 'elfeed-search-mode)
(elfeed-entry-link (car (elfeed-search-selected)))))))
url
(read-string "Enter URL you want to summarize: "))))
(let ((buffer-name (url-retrieve-synchronously url t)))
;; (display-buffer buffer-name)
(with-current-buffer buffer-name
(goto-char (point-min))
(or (search-forward "<!DOCTYPE" nil t)
(search-forward "<html" nil))
(beginning-of-line)
(kill-region (point-min) (point))
(shr-insert-document (libxml-parse-html-region (point-min) (point-max)))
(goto-char (point-min))
(or (search-forward "<!DOCTYPE" nil t)
(search-forward "<html" nil))
(beginning-of-line)
(kill-region (point) (point-max))
(ellama-summarize)))))
(after! workspaces
(advice-add '+workspace/kill-session :before 'eglot-shutdown-all))
(defun t/+workspace-new (name &rest buffers)
(interactive)
(+workspace-switch name t)
(dolist (buffer (reverse buffers))
(find-file buffer)))
(defun t/default-workspaces-init ()
(interactive)
(t/+workspace-new "doom" doom-user-dir)
(t/+workspace-new "dot" "~/Projects/dotfiles")
(t/+workspace-new "nix-darwin" "~/.config/nix-darwin/" "~/.config/nix-darwin/flake.nix" "~/.config/nix-darwin/home/default.nix")
(t/+workspace-new "org" (format "%s/home.org.gpg" (car org-agenda-files))))
(defun t/default-workspaces ()
(interactive)
(if (equal '("main") (+workspace-list-names))
(t/default-workspaces-init)
(message "Workspaces exist, bailing: %s" (+workspace-list-names))))