This file is a literate programming document written with org-mode and org-mode-babel which contains the configuration I use for development.
This buys a small improvement in performance, but every little bit helps.
;; -*- lexical-binding: t; -*-
The following configuration have been moved to early-init.el
for
performance reasons, and are therefore not tangled here.
Emacs will run garbage collection after `gc-cons-threshold’ bytes of consing. The default value is 800,000 bytes, or ~ 0.7 MiB. By increasing to maximum we reduce the number of pauses due to garbage collection during setup.
(setq gc-cons-threshold most-positive-fixnum ; 2^61 bytes
gc-cons-percentage 0.6)
(ignore-errors
(tool-bar-mode -1))
(ignore-errors
(scroll-bar-mode -1))
(setq visible-bell nil)
(setq ring-bell-function 'ignore)
(setq initial-scratch-message nil)
(setq message-log-max 512)
(setq debug-on-error t)
Emacs resizes the (GUI) frame when your newly set font is larger (or smaller) than the system default. This seems to add 0.4-1s to startup.
(setq frame-inhibit-implied-resize t)
This doesn’t seem to persist from early-init.el
and seems
to have a big impact on performance, especially in large Org
files.
(blink-cursor-mode 0)
(setq visible-cursor nil)
(defvar my--gc-cons-threshold 16777216)
At the end of setup this value should be returned to the default, once startup has completed.
(add-hook 'emacs-startup-hook
(lambda ()
(setq gc-cons-threshold my--gc-cons-threshold
gc-cons-percentage 0.1)))
I want to make sure any minibuffer operations
won’t trigger gc, so tools like flx
won’t pause.
(defun my/minibuffer-setup-hook ()
(setq gc-cons-threshold most-positive-fixnum))
(defun my/minibuffer-exit-hook ()
;; Defer it so that commands launched immediately after will enjoy the
;; benefits.
(run-at-time
1 nil (lambda () (setq gc-cons-threshold my--gc-cons-threshold))))
(add-hook 'minibuffer-setup-hook #'my/minibuffer-setup-hook)
(add-hook 'minibuffer-exit-hook #'my/minibuffer-exit-hook)
(defun dotfiles--gc-on-last-frame-out-of-focus ()
"GC if all frames are inactive."
(if (seq-every-p #'null (mapcar #'frame-focus-state (frame-list)))
(garbage-collect)))
(add-function :after after-focus-change-function
#'dotfiles--gc-on-last-frame-out-of-focus)
Emacs consults this variable every time a file is read or library loaded, or when certain functions in the file API are used (like expand-file-name or file-truename).
It does so to check if a special handler is needed to read the file, but none of these handlers are necessary for startup, so it is generally safe to disable it temporarily.
The disable step is being handled in early-init.el
using the
following:
;; copy to custom var
(defvar my--file-name-handler-alist file-name-handler-alist)
;; set original to nil
(setq file-name-handler-alist nil)
And here is the hook to re-set the value:
;; Restore after startup
(add-hook 'emacs-startup-hook
(lambda ()
(setq file-name-handler-alist my--file-name-handler-alist)))
(if (functionp 'json-serialize)
(message "Native JSON is available")
(message "Native JSON is *not* available"))
(if (and (fboundp 'native-comp-available-p)
(native-comp-available-p))
(setq comp-deferred-compilation t)
(message "Native complation is *not* available"))
Adding timestamps to the messages so we can see whether anything is causing emacs to block for a significant amount of time.
;;; timestamps in *Messages*
(defun current-time-microseconds ()
(let* ((nowtime (current-time))
(now-ms (nth 2 nowtime)))
(concat (format-time-string "[%Y-%m-%dT%T" nowtime) (format ".%d] " now-ms))))
(defadvice message (before test-symbol activate)
(if (not (string-equal (ad-get-arg 0) "%s%s"))
(let ((deactivate-mark nil)
(inhibit-read-only t))
(with-current-buffer "*Messages*"
(goto-char (point-max))
(if (not (bolp))
(newline))
(insert (current-time-microseconds))))))
Moved to early-init.el
.
(setq byte-compile-warnings nil)
Moved to early-init.el
.
(fset 'yes-or-no-p 'y-or-n-p)
Moved to early-init.el
.
(setq confirm-kill-emacs 'y-or-n-p)
Moved to early-init.el
.
(setq save-interprogram-paste-before-kill t)
(setq require-final-newline t)
Moved to early-init.el
.
(setq cursor-in-non-selected-windows nil)
Moved to early-init.el
.
(setq highlight-nonselected-windows t)
(setq-default indent-tabs-mode nil)
Moved to early-init.el
.
(setq select-enable-clipboard t)
Moved to early-init.el
.
(delete-selection-mode 1)
I prefer the *Help*
buffer to gain focus when it opens
so I can hit q
to close it and go back to where I was.
Moved to early-init.el
.
(setq help-window-select t)
This is useful during the start-up process, but becomes a nuisance once we’re in edit mode.
(add-hook 'emacs-startup-hook
(lambda ()
(setq debug-on-error nil)))
(setq read-process-output-max (* 1024 1024)) ;; 1mb
Note: moved to early-init
(set-charset-priority 'unicode)
(setq locale-coding-system 'utf-8) ; pretty
(set-terminal-coding-system 'utf-8-unix) ; pretty
(set-keyboard-coding-system 'utf-8) ; pretty
(set-selection-coding-system 'utf-8) ; please
(prefer-coding-system 'utf-8) ; with sugar on top
(setq default-process-coding-system '(utf-8-unix . utf-8-unix))
(setenv "INSIDE_EMACS" "1")
(defun my/configure-path ()
(let ((path (shell-command-to-string ". ~/.zshrc; echo -n $PATH")))
(setenv "PATH" path)
(setq exec-path
(append
(split-string-and-unquote path ":")
exec-path))))
(add-hook 'after-init-hook 'my/configure-path)
direnv
is a great tool for managing local environment during
development. This package integrates direnv
with Emacs so that
programs started from within emacs, such as inferior shells,
linters, compilers, and test runners, will be looked up in the
correct $PATH
, and will be started with the correct environment
variables set.
(use-package direnv
:delight
:config
(add-hook 'emacs-startup-hook (direnv-mode)))
(use-package exec-path-from-shell
:if (memq window-system '(mac ns))
:delight
:config
(with-eval-after-load 'exec-path-from-shell-initialize))
(use-package use-package-ensure-system-package
:delight)
(add-to-list 'exec-path (expand-file-name "~/.asdf/shims"))
(setq ns-use-native-fullscreen t)
This stops new windows (frames) opening when calling emacs from the terminal with a filename
(setq ns-pop-up-frames nil)
(setq ns-use-srgb-colorspace t)
My personal theme: https://github.com/OldhamMade/leiptr-them
(use-package leiptr-theme
:straight (leiptr :type git :host github :repo "OldhamMade/leiptr-theme")
:init (load-theme 'leiptr t))
This has been set in early-init.el
, but repeated here for completeness.
(set-face-attribute 'default nil :font "SFMono Nerd Font:pixelsize=10:weight=normal:slant=normal:width=normal:spacing=100:scalable=true:hinting=true")
(use-package unicode-fonts
:ensure t
:delight
:config
(when (member "Apple Color Emoji" (font-family-list))
(set-fontset-font t 'symbol "Apple Color Emoji" nil 'prepend))
(unicode-fonts-setup))
(use-package mode-line-bell
:defer
:delight
:config
(setq mode-line-bell-flash-time 0.4)
(add-hook 'emacs-startup-hook 'mode-line-bell-mode))
I’d like to use more “jump” commands, but I rely on arrow keys too much. This should hopefully remove that reliance.
(use-package annoying-arrows-mode
:defer
:delight
:config
(add-hook 'emacs-startup-hook 'global-annoying-arrows-mode))
(use-package popup
:defer
:delight)
(use-package helpful
:defer
:delight
:bind (("C-h f" . helpful-callable)
("C-h v" . helpful-variable)
("C-h k" . helpful-key)))
(setq mac-command-modifier 'alt
mac-option-modifier 'meta
mac-command-modifier 'hyper
mac-right-option-modifier nil)
(bind-keys*
;; undo/redo handled by alternative package
; ("H-z" . undo)
; ("H-Z" . redo)
;; moving around
("<next>" . (lambda () (interactive)
(condition-case nil (scroll-up)
(end-of-buffer (goto-char (point-max))))))
("<prior>" . (lambda () (interactive)
(condition-case nil (scroll-down)
(beginning-of-buffer (goto-char (point-min))))))
;; Select all
("H-a" . mark-whole-buffer)
;; cut
("H-x" . kill-region)
;; copy
("H-c" . kill-ring-save)
;; paste
("H-v" . yank)
;; open
("H-o" . find-file)
;; save
("H-s" . save-buffer)
;; close
("H-w" . (lambda ()
(interactive)
(my-kill-buffer
(current-buffer))))
;; quit
("H-q" . save-buffers-kill-emacs)
;; minimise
("H-m" . iconify-frame)
;; hide
("H-h" . ns-do-hide-emacs)
;; jump to beginning of line
("H-<left>" . beginning-of-line)
;; jump to end of line
("H-<right>" . end-of-line)
)
(use-package general
:delight)
(use-package free-keys
:defer
:delight)
which-key
is a minor mode for Emacs that displays the key bindings
following your currently entered incomplete command (a prefix) in
a popup. For example, after enabling the minor mode if you enter
C-x
and wait for the default of 1 second the minibuffer will
expand with all of the available key bindings that follow C-x
(or
as many as space allows given your settings).
I’m using which-key
to try and remove my reliance on custom
Hydras with H-<key>
bindings.
(use-package which-key
:delight
:config
(setq which-key-idle-delay .4
which-key-side-window-location 'bottom
which-key-side-window-max-height 0.25)
(which-key-mode 1))
hercules.el
lets us call any group of related command
sequentially with no prefix keys, while showing a handy
which-key
-style popup to remember the bindings for those
commands.
I’m using this to remove my reliance on custom Hydras from my previous config.
(use-package hercules
:defer
:delight)
amx
is an alternative interface for M-x in Emacs. It provides
several enhancements over the ordinary execute-extended-command,
such as prioritizing your most-used commands in the completion
list and showing keyboard shortcuts, and it supports several
completion systems for selecting commands, such as ido and ivy.
(use-package amx
:defer
:delight)
Whenever I do searches I prefer the fuzzy-matching style,
similar to fzf
on the commandline. flx
provides similar
functionality in emacs.
(use-package flx
:defer
:delight)
ivy
is a generic completion mechanism for Emacs. While it operates
similarly to other completion schemes such as icomplete-mode, Ivy
aims to be more efficient, smaller, simpler, and smoother to use
yet highly customizable.
Counsel takes this further, providing versions of common Emacs commands that are customised to make the best use of ivy.
And Swiper is an alternative to isearch that uses ivy to show an overview of all matches.
I’m trialing Ivy/Counsel/Swiper as a replacement for
ido
+ smex
and isearch
.
(use-package ivy
:delight
:defer
:init
(ivy-mode 1)
:bind
(("C-x C-b" . ivy-switch-buffer)
("C-x b" . ivy-switch-buffer)
("C-c i r" . ivy-resume))
:config
(setq ivy-use-virtual-buffers t
enable-recursive-minibuffers t
ivy-height 10
ivy-wrap t
ivy-extra-directories nil
;; disable ^ prefix
ivy-initial-inputs-alist nil
;; enable fuzzy matches eveywhere
ivy-re-builders-alist
'((swiper . ivy--regex-plus)
(t . ivy--regex-fuzzy)) ;; fuzzy-search everywhere
ivy-count-format "(%d/%d) ")
;; Use C-j for immediate termination with the current value, and RET
;; for continuing completion for that directory. This is the ido
;; behaviour.
;; TODO: Remove me, to get used to proper ivy usage
(general-define-key
:keymaps 'ivy-minibuffer-map
"C-j" 'ivy-immediate-done
"RET" 'ivy-alt-done))
(use-package counsel
:delight
:defer
:after (ivy)
:bind
((:map counsel-describe-map ("M-." . counsel-find-symbol))
("C-x C-f" . counsel-find-file)
("C-M-f" . counsel-rg)
("C-M-r" . counsel-recentf)
("C-x m" . counsel-mark-ring))
:init
(require 'amx)
(counsel-mode)
:config
(setq counsel-find-file-ignore-regexp (regexp-opt '("./" "..")))
(setq counsel-fzf-cmd "fd -H | fzf -f \"%s\"")
(add-to-list 'ivy-re-builders-alist '(counsel-ag-function . ivy--regex))
(add-to-list 'ivy-re-builders-alist '(counsel-fzf-function . ivy--regex))
(add-to-list 'ivy-sort-functions-alist '(counsel-fzf-function . nil)))
(use-package swiper
:delight
:defer
:general
("C-s" 'swiper)
:init
(setq ivy-display-style 'fancy))
ivy-rich
is a more friendly interface for ivy, providing inline help
and other “rich” data.
(use-package ivy-rich
:defer
:delight
:after (ivy counsel)
:config
(ivy-rich-mode 1))
Automagically interact with “projects”; git, mercurial, bazaar, and darcs repos are seen as projects by default.
(use-package projectile
:delight
:custom
(projectile-enable-caching t)
:config
(defun get-projectile-root ()
"Return path `matcha-projectile' can print in heading."
(if (projectile-project-p)
(file-name-nondirectory
(directory-file-name
(file-name-directory (projectile-project-root))))
"Not in Project"))
)
(add-hook 'emacs-startup-hook (lambda () (projectile-mode +1)))
(use-package counsel-projectile
:delight
:after
(counsel projectile)
:init
(setq projectile-completion-system 'ivy
projectile-switch-project-action 'projectile-vc)
:config
(general-define-key
:keymaps 'projectile-mode-map
"C-c p" 'projectile-command-map)
(counsel-projectile-mode))
(general-def
:keymaps 'projectile-command-map
"A" 'projectile-add-known-project
"K" 'projectile-remove-known-project
"DEL" 'projectile-cleanup-known-projects)
This stops the cursor entering the prompt text in the minibuffer when using shortcuts such as CTRL-A.
(setq minibuffer-prompt-properties
'(read-only t point-entered minibuffer-avoid-prompt face minibuffer-prompt))
We can make the minibuffer much more useful by enabling recursive usage. This means that when the minibuffer is active we can still call commands that require the minibuffer.
(setq enable-recursive-minibuffers t)
With this setting enabled, it’s easy to lose track of whether we’re in a recursive minibuffer or not. We display the recursion level in the minibuffer to avoid confusion.
(minibuffer-depth-indicate-mode 1)
When selecting a file to visit, // in the path will mean / (root) and ~ will mean $HOME regardless of preceding text
(setq file-name-shadow-tty-properties '(invisible t))
Dim the part of the path that will be replaced.
(file-name-shadow-mode 1)
(use-package mini-modeline
:init
(setq mini-modeline-r-format
(list
; Modified?
'(:eval (when (buffer-modified-p)
(propertize "*"
'help-echo "Buffer has been modified"
'face 'font-lock-warning-face)))
; Read only?
'(:eval (when buffer-read-only
(propertize "!"
'help-echo "Buffer is read-only"
'face 'font-lock-type-face)))
; Current filename
'(:eval (propertize " %b" 'help-echo (buffer-file-name)))
; Current git branch
;'(:eval (propertize '(vc-mode vc-mode)
; 'face 'git-commit-comment-file-face))
'(vc-mode vc-mode)
'(:eval (propertize projectile--mode-line
'help-echo "Current project"
'face 'font-lock-keyword-face))
" "
; Current line and column
(propertize "%l:%c" 'help-echo "Line and column index")
; Total lines
'(:eval (propertize (format "[%s]" (or my/mode-line-buffer-line-count "?"))
'help-echo "Total lines"
'face 'compilation-line-number))
))
:config
(mini-modeline-mode t)
:custom
(mini-modeline-echo-duration 3)
(mini-modeline-right-padding 1)
(mini-modeline-enhance-visual nil)
:custom-face
(mini-modeline-face-attr `(:background ,(face-attribute 'default :background)))
;(mini-modeline-mode-line ((t (:background "#FFFFFF" :box nil :height 0.1))))
;(mini-modeline-mode-line-inactive ((t (:background "#EEEEEE" :box nil :height 0.1))))
)
(defun my/load-uniquify ()
(require 'uniquify)
(setq uniquify-buffer-name-style 'forward))
(add-hook 'emacs-startup-hook 'my/load-uniquify)
(defvar my/mode-line-buffer-line-count nil)
(make-variable-buffer-local 'my/mode-line-buffer-line-count)
(defun my/mode-line-count-lines ()
(setq my/mode-line-buffer-line-count (int-to-string (count-lines (point-min) (point-max)))))
(add-hook 'after-init-hook 'my/mode-line-count-lines)
(add-hook 'find-file-hook 'my/mode-line-count-lines)
(add-hook 'after-save-hook 'my/mode-line-count-lines)
(add-hook 'after-revert-hook 'my/mode-line-count-lines)
(add-hook 'dired-after-readin-hook 'my/mode-line-count-lines)
Note: using :hooks
keyword causes issues because this is a minor-mode.
(use-package highlight-indentation
:defer
:delight
:hook ((prog-mode sass-mode yaml-mode) . highlight-indentation-mode)
:config
(set-face-background 'highlight-indentation-face "#222"))
I want to see the paren matches, but I don’t want to be too distracted by them.
(setq show-paren-delay 0
show-paren-style 'parenthesis)
(set-face-background 'show-paren-match "#456")
(set-face-foreground 'show-paren-match "#cde")
(set-face-attribute 'show-paren-match nil :weight 'extra-bold)
(show-paren-mode t)
Note: using :hooks
keyword causes issues because this is a minor-mode.
(use-package rainbow-delimiters
:defer
:delight
:hook ((org-mode prog-mode sass-mode) . rainbow-delimiters-mode))
Rainbow identifiers subtly changes the look of variables, to make them a little easier to visually search
(use-package rainbow-identifiers
:delight
:defer
:config
(add-hook 'prog-mode-hook (lambda ()
(unless (eq major-mode 'js2-mode)
(rainbow-identifiers-mode)))))
(setq whitespace-style '(face lines-tail
trailing space-before-tab
indentation empty space-after-tab))
(setq nobreak-char-display 0)
(dolist (hook '(prog-mode-hook
text-mode-hook))
(add-hook hook
(lambda () (setq show-trailing-whitespace t))))
(defun my/load-whitespace ()
(require 'whitespace)
(setq whitespace-line-column 80) ;; limit line length
(whitespace-mode +1))
(add-hook 'prog-mode-hook 'my/load-whitespace)
(add-hook 'web-mode-hook 'my/load-whitespace)
(use-package rainbow-mode
:delight
:defer
:hook (sass-mode css-mode emacs-lisp-mode))
(use-package volatile-highlights
:delight
:defer
:config (add-hook 'emacs-startup-hook (lambda ()(volatile-highlights-mode t))))
(use-package hl-todo
:defer
:delight
:hook (emacs-startup . global-hl-todo-mode))
Wait until emacs has loaded, then enable syntax highlighting everywhere
(add-hook 'emacs-startup-hook
(lambda () (global-font-lock-mode 1)))
(add-hook 'emacs-startup-hook
(lambda () (global-prettify-symbols-mode +1)))
I was previously using cursor-chg
to change the cursor color
dynamically, but I found that it can cause some serious lag while
typing, to the point where I would be waiting for a second or two
for the sentence I’d just written to display at all.
This is a small snippet I found which works the same way but without the performance penalty.
Display the cursor as grey for read-only buffers, red when in overwrite mode, or white otherwise.
(setq my/set-cursor-color-color "")
(setq my/set-cursor-color-buffer "")
(defun my/set-cursor-color-according-to-mode ()
"change cursor color according to some minor modes."
;; set-cursor-color is somewhat costly, so we only call it when needed:
(let ((color
(if buffer-read-only
"#BBB"
(if overwrite-mode
"#C00"
"#FFF"))))
(unless (and
(string= color my/set-cursor-color-color)
(string= (buffer-name) my/set-cursor-color-buffer))
(set-cursor-color (setq my/set-cursor-color-color color))
(setq my/set-cursor-color-buffer (buffer-name)))))
(add-hook 'post-command-hook 'my/set-cursor-color-according-to-mode)
(add-hook 'emacs-startup-hook
(lambda ()
(setq recentf-max-menu-items 100
recentf-max-saved-items 100)
(recentf-mode 1)
))
(use-package super-save
:delight
:defer
:hook (emacs-startup . super-save-mode)
:init
(setq super-save-auto-save-when-idle t ;; autosave to the real file
super-save-idle-duration 5 ;; autosave idle wait
auto-save-default nil) ;; disable autosave to backup file
)
(setq auto-save-file-name-transforms `((".*" ,"~/.emacs.d/auto-backup/" t))
backup-directory-alist '(("." . "~/.emacs.d/auto-backup/")) ; don't litter my fs tree
backup-by-copying t ; don't clobber symlinks
delete-old-versions t
kept-new-versions 6
kept-old-versions 2
version-control t) ; use versioned backups
(setq create-lockfiles nil)
(setq require-final-newline t)
(use-package shrink-whitespace
:delight
:defer
:general
("<S-backspace>" #'shrink-whitespace))
(defun copy-file-name-to-clipboard ()
"Copy the current buffer file name to the clipboard."
(interactive)
(let ((filename (if (equal major-mode 'dired-mode)
default-directory
(buffer-file-name))))
(when filename
(kill-new filename)
(message "Copied buffer file name '%s' to the clipboard." filename))))
(use-package reveal-in-osx-finder
:delight
:defer
:general
("H-O" #'reveal-in-osx-finder))
Switch to text-mode
once startup has completed.
(setq initial-major-mode 'fundamental-mode)
(add-hook 'emacs-startup-hook
(lambda ()
(setq initial-major-mode 'text-mode)))
(defun new-empty-buffer ()
"Create a new buffer called untitled(<n>)"
(interactive)
(let ((newbuf (generate-new-buffer-name "untitled")))
(switch-to-buffer newbuf)))
(general-define-key "H-n" 'new-empty-buffer)
(use-package persistent-scratch
:delight
:config
(setq persistent-scratch-save-file (expand-file-name "~/Dropbox/.emacs.persist/.scratch"))
:hook (emacs-startup . persistent-scratch-setup-default))
(defun my/set-scratch-as-text ()
(with-current-buffer (get-buffer "*scratch*")
(let ((mode "text-mode"))
(message "Setting scratch to text-mode")
(funcall (intern mode)))))
(defadvice persistent-scratch-restore (after advice-persistent-scratch-restore activate)
(my/set-scratch-as-text))
;; yas-reload-all unfortunately triggers `persistent-scratch-setup-default`
;; again, resetting the scratch to fundamental-mode, so advising here too.
;; (defadvice yas-reload-all (after advice-yas-reload-all activate)
;; (my/set-scratch-as-text))
(setq bury-buffer-names '("*scratch*" "*Messages*" "*dashboard*"))
(defun kill-buffer-query-functions-maybe-bury ()
"Bury certain buffers instead of killing them."
(if (member (buffer-name (current-buffer)) bury-buffer-names)
(progn
(kill-region (point-min) (point-max))
(bury-buffer)
nil)
t))
(add-hook 'kill-buffer-query-functions 'kill-buffer-query-functions-maybe-bury)
(defun my-kill-buffer (buffer)
"Protect some special buffers from getting killed."
(interactive (list (current-buffer)))
(if (member (buffer-name buffer) bury-buffer-names)
(call-interactively 'bury-buffer buffer)
(kill-buffer buffer)))
(defun my/kill-all-buffers-except-current ()
"Kill all buffers except current buffer."
(interactive)
(let ((current-buf (current-buffer)))
(dolist (buffer (buffer-list))
(set-buffer buffer)
(unless (eq current-buf buffer)
(kill-buffer buffer)))))
(general-define-key (kbd "C-x K") 'my/kill-all-buffers-except-current)
(defun copy-full-path-to-kill-ring ()
"copy buffer's full path to kill ring"
(interactive)
(when buffer-file-name
(kill-new (file-truename buffer-file-name))))
(defun describe-variable-short (var)
(interactive "vVariable: ")
(message (format "%s: %s" (symbol-name var) (symbol-value var))) )
(defun get-buffer-path ()
"print the buffer path in the mini buffer"
(interactive)
(when buffer-file-name
(kill-new (file-truename buffer-file-name))
(message (format "Path: %s (copied to kill-ring)" (file-truename buffer-file-name)))
))
;; fix syntax highlighting ((
(use-package ibuffer-vc
:hook
(ibuffer-hook . (lambda ()
(ibuffer-vc-set-filter-groups-by-vc-root)
(unless (eq ibuffer-sorting-mode 'filename/process)
(ibuffer-do-sort-by-filename/process))))
:config
(define-ibuffer-column size-h
(:name "Size" :inline t)
(cond
((> (buffer-size) 1000000) (format "%7.1fM" (/ (buffer-size) 1000000.0)))
((> (buffer-size) 1000) (format "%7.1fk" (/ (buffer-size) 1000.0)))
(t (format "%8d" (buffer-size)))))
(setq ibuffer-formats
'((mark modified read-only vc-status-mini " "
(name 18 18 :left :elide)
" "
(size-h 9 -1 :right)
" "
(mode 16 16 :left :elide)
" "
filename-and-process)
(mark modified read-only vc-status-mini " "
(name 18 18 :left :elide)
" "
(size-h 9 -1 :right)
" "
(mode 16 16 :left :elide)
" "
(vc-status 16 16 :left)
" "
filename-and-process)))
)
(use-package buffer-move
:defer
:delight
:general
(:keymaps 'global
"<H-M-up>" 'buf-move-up
"<H-M-down>" 'buf-move-down
"<H-M-left>" 'buf-move-left
"<H-M-right>" 'buf-move-right))
(global-subword-mode 1)
M-g
, the goto-map
, is somewhat limited. Since we have avy
and it’s friends, let’s add further options.
(general-define-key
:keymaps 'goto-map
"<up>" 'beginning-of-buffer
"<down>" 'end-of-buffer
"<left>" '("previous points" . pop-global-mark)
"." '("previous M-. tag" . pop-tag-mark)
)
And now let’s bind C-;
, a more comfortable key combo which is
generally unused, to goto-map
.
(general-define-key
"C-;" (general-simulate-key "M-g"))
C-a
and C-e
normally moves the cursor to the beginning/end of
the line unconditionally.
mwim
is more useful, as it moves to the first non-whitespace
character if we’re already at the beginning of the line. Repeated
use of C-a
toggles between these two positions.
C-e
will toggle to the end of the line ignoring comments, or to
the true end of the line.
(use-package mwim
:commands (mwim-beginning mwim-end)
:delight
:general
(:keymaps 'override
"C-a" #'mwim-beginning
"C-e" #'mwim-end
"H-<left>" #'mwim-beginning
"H-<right>" #'mwim-end))
(use-package avy
:defer
:delight
:general
("C-." #'avy-goto-char)
(:keymaps 'goto-map
";" 'avy-goto-char
"C-;" 'avy-goto-char-2
"c" 'avy-goto-char-timer
"w" 'avy-goto-word-1
"l" 'avy-goto-line
)
:config (setq avy-all-windows nil))
(use-package goto-last-change
:delight
:defer
:general
(:keymaps 'goto-map
"-" 'goto-last-change))
Smart Scan will try to infer the symbol your point is on and let you jump to other, identical, symbols elsewhere in your current buffer with a single key stroke.
Use M-n
and M-p
move between symbols, and M-'
to replace all
symbols in the buffer matching the one under point.
(use-package smartscan
:defer
:delight
:hook (emacs-startup . (lambda () (smartscan-mode 1))))
(use-package undo-fu
:straight (undo-fu :type git :host gitlab :repo "ideasman42/emacs-undo-fu")
:defer
:delight
:init
(global-unset-key (kbd "H-z"))
(global-unset-key (kbd "H-Z"))
:general
("H-z" #'undo-fu-only-undo)
("H-Z" #'undo-fu-only-redo))
(use-package undo-fu-session
:straight (undo-fu-session :type git :host gitlab :repo "ideasman42/emacs-undo-fu-session")
:delight
:after undo-fu
:init
(setq undo-fu-session-directory (expand-file-name "~/Dropbox/.emacs.persist/.undohist")
undo-fu-session-incompatible-files
'("COMMIT_EDITMSG"
"NOTES_EDITMSG"
"MERGE_MSG"
"TAG_EDITMSG"
"\\.gpg\\'"
"/tmp"
file-remote-p)))
Unfill adds the inverse of fill-paragraph/-region.
(use-package unfill
:delight
:defer)
(electric-indent-mode +1)
(setq-default tab-width 4)
Allows editing with multiple points on the screen.
(use-package multiple-cursors
:demand
:delight
:general
(:prefix-map 'my/mc-map
"n" #'mc/mark-next-like-this
"p" #'mc/mark-previous-like-this
"j" #'mc/skip-to-next-like-this
"-" #'mc/skip-to-previous-like-this
"a" #'mc/mark-all-like-this
"N" #'mc/mark-next-symbol-like-this
"P" #'mc/mark-previous-symbol-like-this
"A" #'mc/mark-all-symbols-like-this
"." #'mc/mark-all-dwim
"1" #'mc/insert-numbers
"L" #'mc/insert-letters
"l" #'mc/edit-lines
"s" #'mc/sort-regions
"r" #'mc/reverse-regions
)
:config
(hercules-def
:toggle-funs #'my/mc-mode
:keymap 'my/mc-map
:transient t)
(general-define-key
:keymaps 'mc/keymap
"<return>" nil)
(general-define-key
(kbd "C-c m") #'my/mc-mode))
ace-mc
makes it really easy to add and remove multiple cursors
using ace jump mode.
(use-package ace-mc
:after (multiple-cursors)
:delight
:general ("C-c C-m" #'ace-mc-add-multiple-cursors))
Smart region guesses what you want to select by one command:
- If you call this command multiple times at the same position, it expands the selected region (with `er/expand-region’).
- Else, if you move from the mark and call this command, it selects the region rectangular (with `rectangle-mark-mode’).
- Else, if you move from the mark and call this command at the same column as mark, it adds a cursor to each line (with `mc/edit-lines’).
(use-package smart-region
:delight
:after (multiple-cursors)
:general ("C-\\" #'smart-region)
:hook (emacs-startup . smart-region-on))
(save-place-mode 1)
(setq save-place-forget-unreadable-files nil)
I use expand region a lot. M-[
feels like a good binding, with
the mental connection of “open” (expand) and conversely M-]
as
“close” (contract).
(use-package expand-region
:defer
:delight
:general
(:keymaps 'global
"M-[" #'er/expand-region
"M-]" #'er/contract-region))
FIXME: elixir mode should have expansions similar to ruby-mode ;:config ;(er/enable-mode-expansions ‘elixir-mode ‘er/add-ruby-mode-expansions)
Emacs 24.4+ comes with electric-pair-mode
which matches autopair
in terms of functionality.
I disable it in the minibuffer as it usually just gets in the way there.
(electric-pair-mode t)
(add-hook 'minibuffer-setup-hook (lambda () (electric-pair-mode -1)))
(add-hook 'minibuffer-exit-hook (lambda () (electric-pair-mode t)))
Add/Change/Delete pairs based on expand-region
(use-package embrace
:delight
:defer
:general
("C-'" #'embrace-commander)
:hook
(ruby-mode . embrace-ruby-mode))
(use-package move-text
:delight
:defer
:hook (emacs-startup . move-text-default-bindings))
crux
bundles a few useful interactive commands to enhance your
overall Emacs experience.
(use-package crux
:delight
:defer
:commands
(crux-duplicate-current-line-or-region
crux-smart-kill-line
crux-rename-file-and-buffer
crux-kill-other-buffers
crux-capitalize-region
crux-upcase-region
crux-downcase-region)
:general
("M-D" #'crux-duplicate-current-line-or-region
"C-k" #'crux-smart-kill-line
"C-c R" #'crux-rename-file-and-buffer
"C-c K" #'crux-kill-other-buffers
"C-c c c" #'crux-capitalize-region
"C-c c u" #'crux-upcase-region
"C-c c l" #'crux-downcase-region
)
:config
(crux-reopen-as-root-mode))
(define-key mode-specific-map "c" '("change case"))
(general-define-key (kbd "C-c DEL") 'delete-trailing-whitespace)
(use-package dynamic-spaces
:delight
:defer
:hook (emacs-startup . dynamic-spaces-global-mode))
(use-package string-inflection
:defer
:delight
:general ("M-C" #'string-inflection-all-cycle))
(general-define-key (kbd "M-c") 'capitalize-dwim)
(general-define-key (kbd "M-u") 'upcase-dwim)
(general-define-key (kbd "M-l") 'downcase-dwim)
(use-package avy-zap
:delight
:defer
:after (avy)
:general
("M-z" #'avy-zap-to-char-dwim)
("M-Z" #'avy-zap-up-to-char-dwim)
)
flyspell-correct is a package for distraction-free words correction with flyspell via a selected interface.
(use-package flyspell-correct-ivy
:delight
:defer
:general
("C-M-;" #'flyspell-correct-wrapper)
:init
(setq flyspell-correct-interface #'flyspell-correct-ivy))
Jump to and correct spelling errors using avy and flyspell.
(use-package ace-flyspell
:delight
:defer
:hook (emacs-startup-hook . ace-flyspell-setup))
(setq query-replace-highlight t)
Ripgrep is faster than grep, and deadgrep
provides a great
UI. It also allows inline editing through deadgrep-edit-mode
,
which is great for refactoring in combination with visual-regexp
.
(use-package deadgrep
:defer
:delight
:init
(defun config-editing--on-enter-deadgrep-edit-mode (&rest _)
(message "Entering edit mode. Changes will be made to underlying files as you edit."))
(defun config-editing--on-exit-deadgrep-edit-mode (&rest _)
(when (derived-mode-p 'deadgrep-edit-mode)
(message "Exiting edit mode.")))
:general
("C-c d" 'deadgrep)
:config
(advice-add #'deadgrep-edit-mode :after #'config-editing--on-enter-deadgrep-edit-mode)
(advice-add #'deadgrep-mode :before #'config-editing--on-exit-deadgrep-edit-mode)
(defun deadgrep--format-command-patch (rg-command)
"Add --hidden to rg-command."
(replace-regexp-in-string "^rg " "rg --hidden " rg-command)))
(general-define-key
:keymaps 'deadgrep-mode-map
"e" 'deadgrep-edit-mode
"t" '(lambda () (interactive) (deadgrep--search-term nil))
"r" '(lambda () (interactive) (setq deadgrep--search-type 'regexp) (deadgrep-restart))
"s" '(lambda () (interactive) (setq deadgrep--search-type 'string) (deadgrep-restart))
"d" '(lambda () (interactive) (deadgrep--directory nil))
)
(general-define-key
:keymaps 'deadgrep-edit-mode-map
"<escape>" 'deadgrep-mode)
(use-package synosaurus
:defer
:delight
; doesn't work with emacs-plus
;:ensure-system-package
; (wn . wordnet)
:commands (synosaurus-mode
synosaurus-lookup
synosaurus-choose-and-replace)
:general
("C-c S" #'synosaurus-choose-and-replace)
:init
(setq synosaurus-backend 'synosaurus-backend-wordnet
synosaurus-choose-method 'popup))
(use-package visual-regexp
:delight
:defer
:general
(:keymaps 'global
"C-c r" 'vr/replace
"C-c q" 'vr/query-replace)
:config
(general-define-key
:keymaps 'my/mc-map
"q" #'vr/mc-mark))
(use-package balanced-windows
:delight
:defer
:hook
(emacs-startup . balanced-windows-mode))
(use-package ace-window
:delight
:defer
:general
("M-o" 'ace-window)
:config
(ace-window-display-mode t)
(setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)
aw-dispatch-always t))
(use-package zoom-window
:delight
:defer
:init (custom-set-variables
'(zoom-window-mode-line-color "DarkGreen"))
:general
("C-x C-z" 'zoom-window-zoom))
(use-package persp-projectile
:defer
:delight
:after (projectile)
:init (use-package perspective)
:hook (emacs-startup . persp-mode)
:general
(:keymaps 'perspective-map
"x" 'projectile-persp-switch-project)
;; override projectile-switch-project since
;; we always want to be in a perspective
(:keymaps 'projectile-command-map
"p" 'projectile-persp-switch-project))
(defun my/load-winner-mode ()
(winner-mode 1))
(add-hook 'emacs-startup-hook 'my/load-winner-mode)
(setq display-buffer-alist
'((".*" (display-buffer-reuse-window display-buffer-same-window))))
(setq display-buffer-reuse-frames t)
(setq even-window-sizes nil)
(defun split-window-sensibly-prefer-horizontal (&optional window)
"Based on split-window-sensibly, but designed to prefer a horizontal split,
i.e. windows tiled side-by-side."
(let ((window (or window (selected-window))))
(or (and (window-splittable-p window t)
;; Split window horizontally
(with-selected-window window
(split-window-right)))
(and (window-splittable-p window)
;; Split window vertically
(with-selected-window window
(split-window-below)))
(and
;; If WINDOW is the only usable window on its frame (it is
;; the only one or, not being the only one, all the other
;; ones are dedicated) and is not the minibuffer window, try
;; to split it horizontally disregarding the value of
;; `split-height-threshold'.
(let ((frame (window-frame window)))
(or
(eq window (frame-root-window frame))
(catch 'done
(walk-window-tree (lambda (w)
(unless (or (eq w window)
(window-dedicated-p w))
(throw 'done nil)))
frame)
t)))
(not (window-minibuffer-p window))
(let ((split-width-threshold 0))
(when (window-splittable-p window t)
(with-selected-window window
(split-window-right))))))))
(defun split-window-really-sensibly (&optional window)
(let ((window (or window (selected-window))))
(if (> (window-total-width window) (* 2 (window-total-height window)))
(with-selected-window window (split-window-sensibly-prefer-horizontal window))
(with-selected-window window (split-window-sensibly window)))))
(setq
split-height-threshold 4
split-width-threshold 40
split-window-preferred-function 'split-window-really-sensibly)
(use-package rotate
:delight
:defer
:general
("C-c C-SPC" 'rotate-layout))
(use-package diff-hl
:delight
:defer
:general
(:keymaps 'goto-map
"n" 'diff-hl-next-hunk
"p" 'diff-hl-previous-hunk)
:hook (emacs-startup . global-diff-hl-mode))
browse-at-remote
opens the current buffer at
github/gitlab/bitbucket/stash/git.savannah.gnu.org/sourcehut
from Emacs.
It supports various kind of emacs buffer, like:
- file buffer
- dired buffer
- magit-mode buffers representing code
- vc-annotate mode (use get there by pressing C-x v g by default)
(use-package browse-at-remote
:defer
:delight
:general ("C-c g g" 'browse-at-remote))
(use-package magit-todos
:delight
:after (projectile magit))
(use-package magit-gitflow
:delight
:after (projectile magit))
(use-package magit-diff-flycheck
:delight
:after (projectile magit))
(use-package magit
:delight
:defer
:after projectile
:general ("C-x g" 'magit-status)
:commands (magit-status
magit-log
magit-commit
magit-stage-file)
:hook (magit-mode . turn-on-magit-gitflow)
:config
(setq magit-branch-arguments nil
magit-branch-read-upstream-first 'fallback
magit-diff-paint-whitespace t
magit-diff-highlight-indentation (quote (("" . tabs)))
magit-fetch-arguments (quote ("--prune"))
magit-pull-arguments (quote ("--rebase"))
magit-push-arguments (quote ("--set-upstream"))
magit-log-arguments (quote ("--graph" "--color" "--decorate" "-n256")))
(magit-todos-mode t)
(transient-append-suffix 'magit-pull "C"
'("A" "Autostash" "--autostash"))
)
(defun magit-set-repo-dirs-from-projectile ()
"Set `magit-repository-directories' with known Projectile projects."
(setq magit-repository-directories
(mapcar (lambda (dir)
(cons dir 0))
(seq-filter (lambda (dir)
(file-exists-p (expand-file-name ".git" dir)))
projectile-known-projects))))
(with-eval-after-load 'projectile
(magit-set-repo-dirs-from-projectile))
(add-hook 'projectile-switch-project-hook
#'magit-set-repo-dirs-from-projectile)
(setq smerge-command-prefix "\C-c m")
I originally had the following magit hook, but without hydra it raises an error. Needs to be replaced with a General setup.
(magit-diff-visit-file . (lambda ()
(when smerge-mode
(my/smerge-hydra/body))))
(general-define-key
:keymaps 'smerge-mode-map
:prefix "C-c -"
"" '(nil :which-key "smerge")
"n" '(smerge-next :which-key "next")
"p" '(smerge-prev :which-key "previous")
"o" '(smerge-keep-lower :which-key "other (upper)")
"m" '(smerge-keep-upper :which-key "mine (lower)")
"u" '(smerge-keep-upper :which-key "upper (other)")
"l" '(smerge-keep-lower :which-key "lower (mine)")
)
(advice-add 'magit-whitespace-disallowed :around
(lambda (orig-fun &rest args) (interactive) (insert "-")))
This tweak uses the package ov
, short for Overlay. Overlay is
capable of manipulating text appearance, cursor behavior, etc. It
doesn’t affect font-lock or text-properties.
;;; fix syntax highlighting: <(
(use-package ov
:after (magit)
:delight
:config
(defun unpackaged/magit-log--add-date-headers (&rest _ignore)
"Add date headers to Magit log buffers."
(when (derived-mode-p 'magit-log-mode)
(save-excursion
(ov-clear 'date-header t)
(goto-char (point-min))
(cl-loop with last-age
for this-age = (-some--> (ov-in 'before-string 'any (line-beginning-position) (line-end-position))
car
(overlay-get it 'before-string)
(get-text-property 0 'display it)
cadr
(s-match (rx (group (1+ digit) ; number
" "
(1+ (not blank))) ; unit
(1+ blank) eos)
it)
cadr)
do (when (and this-age
(not (equal this-age last-age)))
(ov (line-beginning-position) (line-beginning-position)
'after-string (propertize (concat " " this-age "\n")
'face 'magit-section-heading)
'date-header t)
(setq last-age this-age))
do (forward-line 1)
until (eobp)))))
(define-minor-mode unpackaged/magit-log-date-headers-mode
"Display date/time headers in `magit-log' buffers."
:global t
(if unpackaged/magit-log-date-headers-mode
(progn
;; Enable mode
(add-hook 'magit-post-refresh-hook #'unpackaged/magit-log--add-date-headers)
(advice-add #'magit-setup-buffer-internal :after #'unpackaged/magit-log--add-date-headers))
;; Disable mode
(remove-hook 'magit-post-refresh-hook #'unpackaged/magit-log--add-date-headers)
(advice-remove #'magit-setup-buffer-internal #'unpackaged/magit-log--add-date-headers)))
)
(setq dired-listing-switches "-alh")
(use-package dired-collapse
:delight
:defer
:commands (dired-collapse-mode)
:hook (dired-mode . dired-collapse-mode))
(use-package dired-efap
:delight
:defer
:general
(:keymaps 'dired-mode-map
"r" 'dired-efap))
(use-package dired-avfs
:delight
:defer)
(use-package dired-k
:delight
:defer
:init
(setq dired-k-human-readable 1
dired-k-padding 1)
:hook
((dired-mode-hook . dired-k)
(dired-after-readin-hook . dired-k-no-revert)))
(use-package dired-rainbow
:delight
:defer
:after (dired)
:config
(progn
(dired-rainbow-define-chmod directory "#6cb2eb" "d.*")
(dired-rainbow-define html "#eb5286" ("css" "less" "sass" "scss" "htm" "html" "jhtm" "mht" "eml" "mustache" "xhtml"))
(dired-rainbow-define xml "#f2d024" ("xml" "xsd" "xsl" "xslt" "wsdl" "bib" "json" "msg" "pgn" "rss" "yaml" "yml" "rdata"))
(dired-rainbow-define document "#9561e2" ("docm" "doc" "docx" "odb" "odt" "pdb" "pdf" "ps" "rtf" "djvu" "epub" "odp" "ppt" "pptx"))
(dired-rainbow-define markdown "#ffed4a" ("org" "etx" "info" "markdown" "md" "mkd" "nfo" "pod" "rst" "tex" "textfile" "txt"))
(dired-rainbow-define database "#6574cd" ("xlsx" "xls" "csv" "accdb" "db" "mdb" "sqlite" "nc"))
(dired-rainbow-define media "#de751f" ("mp3" "mp4" "MP3" "MP4" "avi" "mpeg" "mpg" "flv" "ogg" "mov" "mid" "midi" "wav" "aiff" "flac"))
(dired-rainbow-define image "#f66d9b" ("tiff" "tif" "cdr" "gif" "ico" "jpeg" "jpg" "png" "psd" "eps" "svg"))
(dired-rainbow-define log "#c17d11" ("log"))
(dired-rainbow-define shell "#f6993f" ("awk" "bash" "bat" "sed" "sh" "zsh" "vim"))
(dired-rainbow-define interpreted "#38c172" ("py" "ipynb" "rb" "pl" "t" "msql" "mysql" "pgsql" "sql" "r" "clj" "cljs" "scala" "js"))
(dired-rainbow-define compiled "#4dc0b5" ("asm" "cl" "lisp" "el" "c" "h" "c++" "h++" "hpp" "hxx" "m" "cc" "cs" "cp" "cpp" "go" "f" "for" "ftn" "f90" "f95" "f03" "f08" "s" "rs" "hi" "hs" "pyc" ".java"))
(dired-rainbow-define executable "#8cc4ff" ("exe" "msi"))
(dired-rainbow-define compressed "#51d88a" ("7z" "zip" "bz2" "tgz" "txz" "gz" "xz" "z" "Z" "jar" "war" "ear" "rar" "sar" "xpi" "apk" "xz" "tar"))
(dired-rainbow-define packaged "#faad63" ("deb" "rpm" "apk" "jad" "jar" "cab" "pak" "pk3" "vdf" "vpk" "bsp"))
(dired-rainbow-define encrypted "#ffed4a" ("gpg" "pgp" "asc" "bfe" "enc" "signature" "sig" "p12" "pem"))
(dired-rainbow-define fonts "#6cb2eb" ("afm" "fon" "fnt" "pfb" "pfm" "ttf" "otf"))
(dired-rainbow-define partition "#e3342f" ("dmg" "iso" "bin" "nrg" "qcow" "toast" "vcd" "vmdk" "bak"))
(dired-rainbow-define vc "#0074d9" ("git" "gitignore" "gitattributes" "gitmodules"))
(dired-rainbow-define-chmod executable-unix "#38c172" "-.*x.*")
))
These are tweaks to the built-in org-mode
(setq org-export-coding-system 'utf-8)
Wit this option enabled, markers like =, /, * are hidden which makes for a neater view.
(setq org-hide-emphasis-markers t)
Standard Emacs S-<cursor>
commands conflict with Org’s use of
S-<cursor>
to change timestamps, TODO keywords, priorities, and
item bullet types, etc. Since S-<cursor>
commands outside of
specific contexts do not do anything, Org offers the variable
org-support-shift-select
for customization. Org mode accommodates
shift selection by:
- making it available outside of the special contexts where special commands apply, and
- extending an existing active region even if point moves across a special context.
(setq org-support-shift-select t)
It’s useful to have titles like TODO
and DONE
hilight differently.
(setq org-fontify-done-headline t)
(setq org-src-fontify-natively t)
(setq org-src-tab-acts-natively nil)
Use C-c C-,
to trigger.
(require 'org-tempo)
Org-mode has “Easy Templates”, here are some additions:
(add-to-list 'org-structure-template-alist
'("sl" . "src emacs-lisp :tangle yes"))
(add-to-list 'org-structure-template-alist
'("se" . "src elixir"))
(add-to-list 'org-structure-template-alist
'("sc" . "src crystal"))
(add-to-list 'org-structure-template-alist
'("sn" . "src nim"))
(add-to-list 'org-structure-template-alist
'("sp" . "src python"))
(add-to-list 'org-structure-template-alist
'("sz" . "src zsh"))
(general-define-key
"C-c o" 'org-capture)
(setq org-default-notes-file "~/Dropbox/notes.org")
(defvar my-org-capture-directory
(expand-file-name "~/Dropbox/org")
"Location for all org-mode capture files.")
(defvar my-org-blog-directory
(expand-file-name "~/Dropbox/blog/content-org")
"Location for all org-mode capture files.")
(use-package doct
:defer
:delight
:after org
:init
(setq org-capture-templates '())
(setq til-categories
'(
("Ack" "a")
("ASDF" "A")
("Elixir" "x")
("Emacs" "e")
("Git" "g")
("Magit" "M")
("Makefiles" "m")
("Phoenix" "p")
("Python" "P")
("Shell" "s")
("Tmux" "t")
))
(setq idea-categories
'(
("Open-Source Library" "o")
("Physical Product" "p")
("Web App" "w")
("iOS App" "i")
("macOS App" "m")
))
:hook (emacs-startup . (lambda ()
(setq org-capture-templates
(doct `(:group
:empty-lines 1
:children
(("Tasks"
:keys "t"
:file ,(expand-file-name "tasks.org" my-org-capture-directory)
:clock-in t
:clock-resume t
:children
(("Today" :keys "t" :type entry :headline "Uncategorized"
:datetree t :tree-type week :template "* TODO %?\n %i\n %a\n")
("Reading" :keys "r" :type entry :headline "Reading"
:template "* TODO %^{name}\n %a\n")
("Work" :keys "w" :type entry :headline "Work"
:template "* TODO %^{taskname}\n %a\n")))
("Blog"
:keys "b"
:file ,(expand-file-name "posts.org" my-org-blog-directory)
:type entry
:template "* TODO %^{name} :%^{tags}:\n :PROPERTIES:\n :EXPORT_DATE: %u\n :EXPORT_FILE_NAME: %^{slug}.md\n :END:\n \n %i%?"
)
("TIL"
:keys "l"
:file ,(expand-file-name "til.org" my-org-blog-directory)
:type entry
:template "* TODO %^{name} :%^{tags}:\n :PROPERTIES:\n :EXPORT_DATE: %u\n :EXPORT_FILE_NAME: %^{slug}.md\n :END:\n \n %i%?"
:children ,(cl-loop for (key value) in (sort til-categories (lambda (a b) (string< (car a) (car b))))
collect (list key :keys value :headline key))
)
("Idea"
:keys "i"
:file ,(expand-file-name "project-ideas.org" my-org-capture-directory)
:type entry
:template "* TODO %^{name}\n %i%?\n "
:children ,(cl-loop for (key value) in (sort idea-categories (lambda (a b) (string< (car a) (car b))))
collect (list key :keys value :headline key))
)
("Project"
:keys "p"
:file ,(defun my/project-todo-file ()
(let ((file (expand-file-name "TODO.org"
(when (functionp 'projectile-project-root)
(projectile-project-root)))))
(with-current-buffer (find-file-noselect file)
(org-mode)
;; Set to UTF-8 because we may be visiting raw file
(setq buffer-file-coding-system 'utf-8-unix)
(when-let* ((headline (doct-get :headline)))
(unless (org-find-exact-headline-in-buffer headline)
(goto-char (point-max))
(insert "* " headline)
(org-set-tags (downcase headline))))
file)))
:template (lambda () (concat "* %{todo-state} " (when (y-or-n-p "Link? ") "%A\n") "%?"))
:todo-state "TODO"
:children (("bug" :keys "b" :headline "Bugs")
("documentation" :keys "d" :headline "Documentation")
("enhancement" :keys "e" :headline "Enhancements")
("feature" :keys "f" :headline "Features")
("optimization" :keys "o" :headline "Optimizations")
("miscellaneous" :keys "m" :headline "Miscellaneous")
("security" :keys "s" :headline "Security")))))
)))))
(use-package org-bullets
:after org
:delight
:init
(setq org-bullets-bullet-list '("☰" "☷" "▶" "●" "✱" "✲" "✸" "⦿" "⌾" "◦"))
:hook (org-mode . org-bullets-mode))
(use-package ox-hugo
:after (:all org ox))
This section contains generally useful functions.
(defun parent-directory (dir)
(unless (equal "/" dir)
(file-name-directory (directory-file-name dir))))
(defun find-file-in-hierarchy (current-dir fname)
"Search for a file named FNAME upwards through the directory hierarchy, starting from CURRENT-DIR"
(let ((file (concat current-dir fname))
(parent (parent-directory (expand-file-name current-dir))))
(if (file-exists-p file)
file
(when parent
(find-file-in-hierarchy parent fname)))))
(defun find-dir-in-hierarchy (current-dir dname)
"Search for a dir named DNAME upwards through the directory hierarchy, starting from CURRENT-DIR"
(let ((dir (concat current-dir dname))
(parent (parent-directory (expand-file-name current-dir))))
(if (file-directory-p dir)
dir
(when parent
(find-dir-in-hierarchy parent dname)))))
(defun find-include-dir ()
"Search for the next available include dir from START."
(let ((idir (find-dir-in-hierarchy (buffer-file-name) "include")))
(if idir (concat "-I" idir) "")))
dumb-jump
is an Emacs “jump to definition” package for 40+
languages that I find works really well.
Here I add certain functions to my custom jump keymap.
(use-package dumb-jump
:defer
:delight
; doesn't work with emacs-plus
;:ensure-system-package
; (rg . ripgrep)
:general
(:keymaps 'goto-map
"j j" 'dumb-jump-go
"j b" 'dumb-jump-back
"j o" 'dumb-jump-go-other-window)
:config
(setq dumb-jump-selector 'swiper
dumb-jump-prefer-searcher 'rg
dumb-jump-default-project "~/Projects"))
(use-package comment-dwim-2
:defer
:delight
:general ("M-;" 'comment-dwim-2))
(defun func-region (start end func)
"run a function over the region between START and END in current buffer."
(save-excursion
(let ((text (delete-and-extract-region start end)))
(insert (funcall func text)))))
(defun url-encode (start end)
"urlencode the region between START and END in current buffer."
(interactive "r")
(func-region start end #'url-hexify-string))
(defun url-decode (start end)
"de-urlencode the region between START and END in current buffer."
(interactive "r")
(func-region start end #'url-unhex-string))
(use-package lsp-mode
:commands lsp
:delight
:defer
:bind ("C-c h" . lsp-describe-thing-at-point)
:hook
(elixir-mode . lsp-deferred)
(python-mode . lsp-deferred)
:init
(add-to-list 'exec-path "~/Projects/elixir/elixir-ls/release")
(setq lsp-keymap-prefix "C-c l")
:config
(dolist (dir '("build$" "deps$"))
(push (concat "[/\\\\]" dir) lsp-file-watch-ignored))
(setq
lsp-auto-configure t
lsp-auto-guess-root t
lsp-eldoc-enable-hover nil
lsp-enable-completion-at-point t
lsp-enable-file-watchers t
lsp-file-watch-threshold 10000
lsp-keep-workspace-alive nil
lsp-log-io t
lsp-prefer-flymake nil
)
(add-hook 'before-save-hook
(lambda () (when (eq major-mode 'elixir-mode)
(ignore-errors 'lsp-format-buffer))))
(lsp-register-custom-settings
'(("pyls.plugins.pyls_mypy.enabled" t t)
("pyls.plugins.pyls_mypy.live_mode" nil t)
;("pyls.plugins.pyls_black.enabled" t t)
;("pyls.plugins.pyls_isort.enabled" t t)
))
)
lsp-eldoc-hook ‘(lsp-hover)
(use-package lsp-ui
:delight
:commands lsp-ui-mode
:after lsp-mode
:config
(setq
lsp-ui-doc-enable nil
lsp-ui-sideline-enable nil
lsp-ui-sideline-show-hover nil
lsp-ui-flycheck-enable t
lsp-ui-sideline-ignore-duplicate t
))
lsp-ui-doc-enable t lsp-ui-doc-delay 1.0 lsp-ui-doc-position ‘bottom
(use-package lsp-ivy
:delight
:after (lsp ivy)
:commands lsp-ivy-workspace-symbol)
(use-package eglot
:defer
:delight
:commands (eglot)
:hook ((python-mode . eglot-ensure))
:bind (:map eglot-mode-map
("C-c n" . eglot-rename) ; rename identifier
("C-c f" . eglot-format)))
Disabled since I don’t actively use it at the moment.
(use-package dap-mode)
(use-package company
:defer
:delight
:config
(setq company-idle-delay 0.1) ; Make Company open a little faster
(define-key company-active-map (kbd "C-f") 'company-filter-candidates) ; allow filtering
(define-key company-active-map (kbd "C-/") 'counsel-company) ; move to minibuffer
:hook (emacs-startup . global-company-mode) ; Enable company-mode globally
)
(use-package company-lsp
:delight
:after (company lsp-mode)
:commands company-lsp
:config
(setq company-lsp-cache-candidates 'auto)
(push 'company-lsp company-backends))
(use-package flycheck
:defer
:delight " ✓ "
:hook (emacs-startup . global-flycheck-mode))
An Emacs minor-mode for Flycheck which colors the mode line according to the Flycheck state of the current buffer.
(use-package flycheck-color-mode-line
:defer
:delight
:after (flycheck)
:hook (flycheck-mode . flycheck-color-mode-line-mode))
Disabled for now as I don’t use it
(use-package lsp-origami
:defer
:delight
:general
("C-c TAB" 'origami-recursively-toggle-node)
:hook (lsp-mode . lsp-origami-mode))
I have to use the http://zotonic.com framework at my day job, so let’s add erlang and some zotonic helpers
(defun find-zotonic-include-dir ()
"Search for the next available zotonic include dir from START."
(let ((zdir (find-dir-in-hierarchy
(file-name-directory buffer-file-name)
(concat (file-name-as-directory "zotonic") "include"))))
(if zdir (concat "-I" zdir) "")))
(defun my/define-erlang-flychecker ()
(flycheck-define-checker erlang-otp
"An Erlang syntax checker using the Erlang interpreter."
:command ("~/.asdf/shims/erlc" "-o" temporary-directory "-Wall"
(option-list "-I" flycheck-erlang-include-path)
(eval (find-zotonic-include-dir))
source)
:error-patterns
((warning line-start (file-name) ":" line ": Warning:" (message) line-end)
(error line-start (file-name) ":" line ": " (message) line-end))
:modes erlang-mode))
(defun erlang-mode-flycheck-hook ()
(flycheck-select-checker 'erlang-otp)
(flycheck-mode))
(defun erlang-mode-compile-hook ()
(require 'erlang-eunit)
(when (projectile-project-p)
(add-to-list 'erlang-compile-extra-opts (cons 'i (projectile-project-p)))
(add-to-list 'erlang-eunit-src-candidate-dirs (projectile-project-p))
(add-to-list 'erlang-eunit-test-candidate-dirs (projectile-project-p))))
(defun erlang-mode-prettify-symbols-hook ()
(setq-local
prettify-symbols-alist
(append
'(("->" . ?→)
("=>" . ?⇒)
("<-" . ?←)
("<=" . ?⇐)
(">=" . ?≥)
("=<" . ?≤)
("=/=" . ?≠)
("fun" . ?ƒ))
prettify-symbols-alist)))
(use-package erlang
:defer
:delight
:after (flycheck)
:init (my/define-erlang-flychecker)
:mode (("\\.[eh]rl\\'" . erlang-mode)
("\\.yaws?\\'" . erlang-mode)
("\\.escript?\\'" . erlang-mode))
:hook ((erlang-mode . erlang-mode-flycheck-hook)
(erlang-mode . erlang-mode-prettify-symbols-hook)
(erlang-mode . company-mode)
(erlang-mode . erlang-mode-compile-hook)))
I don’t use Zotonic currently, so disabled for now
(use-package zotonic-tpl-mode
:straight (zotonic-tpl-mode :type git :host github :repo "OldhamMade/zotonic-tpl-mode")
:config
(add-to-list 'auto-mode-alist '("\\.tpl\\'" . zotonic-tpl-mode)))
Elixir is fast becoming my primary programming language, so there’s lots of tweaks and focus here
(use-package ruby-end
:defer
:delight)
(use-package elixir-mode
:after (ruby-end elgot)
:init
(add-to-list
'eglot-server-programs
'(elixir-mode . ("sh" "~/Projects/elixir/elixir-ls/release/language-server.sh")))
(add-to-list 'eglot-server-programs '(python-mode "pyls"))
:delight
(elixir-mode "[ex]")
(ruby-end-mode "")
:mode ("\\.exs?\\'" . elixir-mode)
)
;(:config
; (add-to-list 'eglot-server-programs
; `(elixir-mode "~/Projects/elixir/elixir-ls/release/language-server.sh")))
:hook (company-mode lsp)
(use-package exunit
:straight (exunit :type git :host github :repo "ananthakumaran/exunit.el")
:delight
:defer)
Pop open and interact with iEX
(use-package inf-elixir
:straight (inf-elixir :host github :repo "J3RN/inf-elixir")
:functions
(inf-elixir
run-elixir
inf-elixir-project
inf-elixir-send-line
inf-elixir-send-region
inf-elixir-send-buffer))
(use-package flycheck-credo
:delight
:after (flycheck)
:config
(flycheck-credo-setup)
(setq flycheck-elixir-credo-strict t))
(defun elixir-find-definition (var)
(interactive "vDefinition: ")
(lsp-find-definition var)
)
Elixir keys start with C-c e
.
(general-define-key
:prefix "C-c e"
"" '(nil :which-key "elixir")
"t" '(exunit-verify-all :which-key "test project")
"b" '(exunit-verify :which-key "test buffer")
"u" '(exunit-verify-all-in-umbrella :which-key "test umbrella")
"." '(exunit-verify-single :which-key "test at point")
"r" '(exunit-rerun :which-key "rerun last")
"F" '(xref-find-definitions :which-key "defs (here)")
"f" '(xref-find-definitions-other-window :which-key "defs (other window)")
"d" '(elixir-find-definition :which-key "jump to def")
)
(defun elixir-mode-prettify-symbols-hook ()
(setq-local
prettify-symbols-alist
(append
'(("->" . ?→)
("=>" . ?⇒)
("<-" . ?←)
("<=" . ?⇐)
(">=" . ?≥)
("=<" . ?≤)
("!=" . ?≠)
("fn" . ?ƒ))
prettify-symbols-alist)))
(add-hook 'elixir-mode-hook 'elixir-mode-prettify-symbols-hook)
(add-hook 'elixir-mode-hook 'ruby-block-mode)
(add-hook 'elixir-mode-hook
(lambda ()
(set (make-variable-buffer-local 'ruby-end-expand-keywords-before-re)
"\\(?:^\\|\\s-+\\)\\(?:do\\)")
(set (make-variable-buffer-local 'ruby-end-check-statement-modifiers)
nil)
(ruby-end-mode 1)
))
(eval-after-load 'elixir-mode '(require 'ruby-mode-expansions))
Here we’ll switch on web-mode
so that we can edit HTML properly.
(add-to-list 'auto-mode-alist '("\\.l?eex\\'" . web-mode))
(setq web-mode-engines-alist
'(("elixir" . "\\.l?eex\\'")))
(font-lock-add-keywords 'elixir-mode
'(("\\<\\(defabcast\\|defabcastp\\|defcall\\|defcallp\\|defcast\\|defcastp\\|defhandlecall\\|defhandlecast\\|defhandleinfo\\|definit\\|defmulticall\\|defmulticallp\\|defstart\\|defstartp\\)\\>" 1 font-lock-keyword-face)))
(add-to-list 'hs-special-modes-alist
'(elixir-mode
("\\(cond\\|quote\\|defmacro\\|defmacrop\\|defp\\|def\\|if\\) .*\\(do\\)" 2) "\\(end\\)" "#"
nil nil))
(defun my/elixir-on-save-hook ()
(add-hook 'before-save-hook
(lambda ()
(if (equal major-mode 'elixir-mode)
(ignore-errors (elixir-format nil t))))))
(add-hook 'elixir-mode-hook 'my/elixir-on-save-hook)
(use-package python-mode
:defer
:delight
:config
(setq lsp-pyls-plugins-pylint-enabled nil)
(setq lsp-pyls-plugins-pycodestyle-enabled nil)
:hook
(python-mode . subword-mode)
;(python-mode .
; (lambda ()
; (defun py-describe-symbol nil)
; (defun py-help-at-point nil)
; ))
:mode ("\\.py\\'" . python-mode))
(use-package python-docstring
:after python
:bind
(:map python-mode-map
([remap fill-paragraph] . python-docstring-fill)))
Allows multiple syntax checkers to run in parallel on Python code Ideal use-case: pyflakes for syntax combined with mypy for typing
(use-package flycheck-pycheckers
:after flycheck
:ensure t
:init
(with-eval-after-load 'flycheck
(add-hook 'flycheck-mode-hook #'flycheck-pycheckers-setup)
)
(setq flycheck-pycheckers-checkers
'(
mypy3
pyflakes
)
)
)
(use-package poetry
:delight
:defer
:init (setq poetry-tracking-strategy 'projectile)
:hook (python-mode . poetry-tracking-mode))
(use-package nim-mode
:defer
:delight
:mode ("\\.nim\\'" . nim-mode))
(use-package crystal-mode
:after (ruby-end)
:defer
:delight
:mode ("\\.cr\\'" . crystal-mode)
:hook
((crystal-mode . ruby-block-mode)
(crystal-mode . ruby-end-mode)))
(use-package flycheck-crystal
:after (flycheck crystal-mode)
:defer)
Disabled for now as I don’t use this language often.
(use-package ponylang-mode
:defer
:mode ("\\.pony\\'" . ponylang-mode)
:config (setq tab-width 2))
Disabled for now as I don’t use this language often.
(use-package go-mode
:defer
:mode ("\\.go\\'" . go-mode)
:config
(setq tab-width 4))
(use-package flycheck-pony
:defer)
(use-package web-mode
:defer
:delight
:config
;; use eslint with web-mode for jsx files
(with-eval-after-load 'flycheck
(flycheck-add-mode 'javascript-eslint 'web-mode))
;; adjust indents for web-mode to 2 spaces
(defun my-web-mode-hook ()
"Hooks for Web mode. Adjust indents"
;;; http://web-mode.org/
(setq web-mode-markup-indent-offset 2)
(setq web-mode-css-indent-offset 2)
(setq web-mode-code-indent-offset 2))
(add-hook 'web-mode-hook 'my-web-mode-hook))
(add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
;(add-to-list 'auto-mode-alist '("\\.jsx\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.ecr\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
(use-package js2-mode
:defer
:delight
:config
(progn
(setq-default js-indent-level 4)
(setq-default js2-basic-offset 4)
(setq tab-width 4)
(setq js-switch-indent-offset 4)
))
(add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
(add-hook 'js2-mode-hook #'js2-imenu-extras-mode)
(add-hook 'js2-mode-hook 'company-mode)
(use-package typescript-mode
:defer
:delight
:mode "\\.ts\\'"
:config
(setq typescript-indent-level 2)
(setq tab-width 2)
)
(use-package sass-mode
:delight
:defer
:mode ("\\.s(c|a)ss\\'" . sass-mode))
(use-package yaml-mode
:defer
:delight
:mode ("\\.ya?ml\\'" . yaml-mode))
(use-package markdown-mode
:delight
:defer
:mode ("\\.md\\'" . markdown-mode))
(use-package dockerfile-mode
:defer
:delight
:mode ("\\Dockerfile\\'" . dockerfile-mode))
Disabled for now as I don’t use this language often.
(use-package raml-mode
:straight (raml-mode :type git :host github :repo "victorquinn/raml-mode")
:defer
:delight
:init (setq raml-indent-offset 2)
:mode "\\.raml\\'")
(use-package feature-mode
:defer
:delight
:mode ("\\.feature$" . feature-mode))
Bound to C-c t
for “time”.
Use c
to start a new timer, then s
to stop it,
and finally t
to edit the time entry at that point.
g
: refresh the list
n/p
: next/previous entry
f/b
: Go to next/previous day
q
: bury buffer
d
: change date (Y-M-D
or Y.M.D
, or use +/-D
)
Expects that ~/.authinfo
will contain an entry for Harvest.
(use-package reaper
:defer
:delight
:init (require 'auth-source)
:general ("C-c t" #'reaper)
:config
(setq reaper-account-id (my-username "id.getharvest.com"))
(setq reaper-api-key (my-password "id.getharvest.com"))
)
(defun my-username (host)
(nth 0 (auth-source-user-and-password host)))
(defun my-password (host)
(nth 1 (auth-source-user-and-password host)))
(use-package multi-term
:delight
:defer
:config
(setq multi-term-program "/bin/zsh"))
(setq term-scroll-show-maximum-output 1)
(setq system-uses-terminfo nil)
(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)
(add-to-list 'comint-output-filter-functions 'ansi-color-process-output)
(use-package eterm-256color
:demand
:delight
:hook (term-mode . eterm-256color-mode))
(use-package esup
:delight
:defer)
(add-hook 'emacs-startup-hook
(lambda ()
(message "Emacs ready in %s with %d garbage collections."
(format "%.2f seconds"
(float-time
(time-subtract after-init-time before-init-time)))
gcs-done)))
Tangle the early-init lines to a separate file.
Matcha may fix a lot of my annoyances about discoverability https://github.com/jojojames/matcha
I really want to see a which-key
menu for the
extra commands available for ace-window
as I constantly
forget them.
Unfortunately ace-window
doesn’t have it’s own keymap, and
I don’t want to create a hydra
for this.
Current approach would be to create a custom sparse-keymap,
iterate through aw-dispatch-alist
and populate the new
keymap from it, then add some advice to one of the which-key
functions to trigger which-key-show-minor-mode-keymap
.
After a time-boxed attempt I have the following, but it isn’t working yet.
(use-package ace-window
:delight
:bind ("M-o" . ace-window)
:config
(ace-window-display-mode t)
(setq aw-dispatch-always t)
(progn
(setq ace-window-map (make-sparse-keymap))
(cl-loop for (key . value) in aw-dispatch-alist
do (define-key ace-window-map key
(if (car-safe value)
(quote (car-safe value))
(quote value))))))
Another alternative, but the menu triggers after
ace-window
has completed, not during.
(general-def
:prefix-map 'my-ace-window-map
"x" #'aw-delete-window
"m" #'aw-swap-window
"M" #'aw-move-window
"c" #'aw-copy-window
"j" #'aw-switch-buffer-in-window
"n" #'aw-flip-window
"u" #'aw-switch-buffer-other-window
"e" #'aw-execute-command-other-window
"F" #'aw-split-window-fair
"v" #'aw-split-window-vert
"b" #'aw-split-window-horz
"o" #'delete-other-windows
"T" #'aw-transpose-frame
"?" #'aw-show-dispatch-help
)
(hercules-def
:toggle-funs #'ace-window
:keymap 'my-ace-window-map
:transient t
:flatten t)
https://github.com/whitmo/whit-dot-emacs/blob/28f14007a3df9dda092faa6bfeee18d91754a485/lisp/init-ace.el
https://github.com/abingham/emacs-ycmd#company-ycmd
https://github.com/emacscollective/no-littering
https://github.com/tonini/alchemist.el/blob/6f99367511ae209f8fe2c990779764bbb4ccb6ed/alchemist.el#L141
I don’t use 90% of projectile functionality, and can’t imagine I ever would. Primarily I just want to have a perspective per project, and limit searches to that perspective.
Maybe moving to project.el
would be better.
https://old.reddit.com/r/emacs/comments/b0jzy4/emacscast_8_writing_in_emacs_and_org_mode_part_1/eiiywwm/ https://old.reddit.com/r/emacs/comments/88v344/workspace_with_isolated_buffers_eyebrowseperspmode/
https://github.com/emacs-lsp/dap-mode#elixir
to see whether anything can be removed or should be focused on https://github.com/dacap/keyfreq
https://github.com/Wilfred/emacs-refactor
M-`
brings up tmm
which is completely useless to me, so might
as well be remapped to something handy. Maybe iterate through
frames/windows?
https://github.com/joostkremers/nswbuff
For better https://github.com/emacsmirror/scroll-on-jump
https://github.com/andre-r/centered-cursor-mode.el
Display coverage inline https://github.com/trezona-lecomte/coverage