This configuration is mostly based on:
The early-init.el
file is supported from version 27.1. It allows to set variables (that have effect on startup speed) early in the initialisation to make Emacs start faster.
The content of early-init.el
file:
(defvar gc-cons-percentage-default gc-cons-percentage
"The default value for `gc-cons-percentage'.")
(defvar file-name-handler-alist-default file-name-handler-alist
"The default value for `file-name-handler-alist'.")
;; Defer garbage collection further back in the startup process.
(setq gc-cons-threshold most-positive-fixnum)
(setq gc-cons-percentage 0.5)
;; For every .el and .elc file loaded during start up, Emacs runs those
;; regexps against it.
(setq file-name-handler-alist nil)
;; Do not load site-wide runtime initializations.
(setq site-run-file nil)
;; Do not initialise the package manager. This is done later.
(setq package-enable-at-startup nil)
;; Do not resize the frame at this early stage.
(setq frame-inhibit-implied-resize t)
;; Faster to disable GUI elements here (before they've been
;; initialized).
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(tooltip-mode -1)
(setq use-file-dialog nil)
(setq use-dialog-box nil)
(setq inhibit-startup-screen t)
(provide 'early-init)
;;; early-init.el ends here
The main configuration file init.el
starts here.
The minimal supported version of Emacs is 28.0.
(when (version< emacs-version "28.0")
(error "Emacs 28.0 or newer versions are required!"))
In order to call garbage collection less frequently the gc-cons-threshold
is increased from its default value of 800 KB. Check this link to find out more details. Similarly, the value of gc-cons-threshold-upper-limit
is increased.
(defvar gc-cons-threshold-default
(if (display-graphic-p) 16000000 1600000)
"The default value for `gc-cons-threshold'.")
(defvar gc-cons-threshold-upper-limit
(if (display-graphic-p) 128000000 32000000)
"The upper limit value for `gc-cons-threshold' to defer it.")
(defun j-setup-default-startup-values ()
"Setup default startup values."
(setq gc-cons-threshold gc-cons-threshold-default)
(setq gc-cons-percentage gc-cons-percentage-default)
(setq file-name-handler-alist file-name-handler-alist-default)
;; Unset the symbols created in early-init.el.
(makunbound 'gc-cons-percentage-default)
(makunbound 'file-name-handler-alist-default))
(defun j-minibuffer-setup-hook ()
"Setup `gc-cons-threshold' to a large number.
When minibuffer is open, garbage collection never occurs so there
is no freezing."
(setq gc-cons-threshold gc-cons-threshold-upper-limit))
(defun j-minibuffer-exit-hook ()
"Setup `gc-cons-threshold' to a small number.
When a selection is made or minibuffer is cancelled, garbage
collection kicks off."
(setq gc-cons-threshold gc-cons-threshold-default))
(defun j-garbage-collect-when-minibuffer-exit ()
"Setup setup and exit hooks for minibuffer."
(add-hook 'minibuffer-setup-hook #'j-minibuffer-setup-hook)
(add-hook 'minibuffer-exit-hook #'j-minibuffer-exit-hook))
(defun j-garbage-collect-when-unfocused ()
"Setup garbage collection when unfocused."
(if (boundp 'after-focus-change-function)
(add-function :after after-focus-change-function
(lambda ()
(unless (frame-focus-state)
(garbage-collect))))
(add-hook 'after-focus-change-function #'garbage-collect)))
(add-hook 'emacs-startup-hook #'j-setup-default-startup-values)
(add-hook 'emacs-startup-hook #'j-garbage-collect-when-minibuffer-exit)
(add-hook 'emacs-startup-hook #'j-garbage-collect-when-unfocused)
The default package manager for Emacs is package.el
. It downloads the packages as tarballs. The straight.el
replaces the default package manager. The main difference is that the straight.el
downloads the packages as git repositories, not as tarballs. It is well integrated with use-package
.
(setq straight-use-package-by-default nil)
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
use-package
is not a package manager (it doesn’t list, install or remove packages). Instead, it uses declarative syntax to configure packages.
(straight-use-package 'use-package)
(eval-and-compile
;; Needed for straight.el.
(setq use-package-always-ensure nil)
(setq use-package-always-defer nil)
(setq use-package-always-demand nil)
(setq use-package-expand-minimally nil)
(setq use-package-enable-imenu-support t)
;;(setq use-package-hook-name-suffix nil)
(setq use-package-compute-statistics nil))
To keep the home directory clean, the configuration files are placed in ~/.config/emacs
directory. The configuration files include:
early-init.el
;init.el
;j-lisp
directory with custom libraries (must be added to theload-path
);straight
directory with package repositories.
The other Emacs related files such as history, backup files, etc. are in ~/.cache/emacs
directory. The package no-littering
takes care of organising files in this directory.
(defconst j-emacs-config-directory user-emacs-directory
"Directory with Emacs configuration files.")
(defconst j-emacs-config-early-init-el
(concat j-emacs-config-directory "early-init-test.el")
"The file with early initialisation configuration.")
(defconst j-emacs-config-init-el
(concat j-emacs-config-directory "init-test.el")
"The main Emacs configuration file.")
(defconst j-emacs-config-source-org
(concat j-emacs-config-directory "README.org")
"The org file with all the Emacs configuration.")
(defconst j-emacs-cache-directory (expand-file-name "~/.cache/emacs/")
"Directory with Emacs transient files.")
(add-to-list 'load-path (concat j-emacs-config-directory "j-lisp"))
(setq user-emacs-directory j-emacs-cache-directory)
(use-package no-littering
:straight t)
The customisation settings file is placed in ~/.cache/emacs/etc
directory.
(setq custom-file (no-littering-expand-etc-file-name "custom.el"))
(load custom-file 'noerror)
The first running process of Emacs is started as server so Emacs clients can connect to it. Calling emacsclient
(with or without --create-frame
), will share the same buffer list and data as the original running process (server). The server persists for as long as there is an Emacs frame attached to it.
(use-package server
:hook
(after-init . server-mode))
UTF-8 is the default encoding. Check this article to find out how to setup the default and other encodings.
(set-charset-priority 'unicode)
(prefer-coding-system 'utf-8)
(set-language-environment 'utf-8)
(set-default-coding-systems 'utf-8)
(set-buffer-file-coding-system 'utf-8)
(set-clipboard-coding-system 'utf-8)
(set-file-name-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(modify-coding-system-alist 'process "*" 'utf-8)
In order to improve the performance of Emacs, we can allow support only for languages that are read/written from left to right. This reduces number of line scans (for example, a check for Arabic languages is not done). Learn more in Comprehensive guide on handling long lines in Emacs.
(setq-default bidi-paragraph-direction 'left-to-right)
(setq bidi-inhibit-bpa t)
;; Disable slow minor modes when reading very long lines to speed up
;; Emacs.
(use-package so-long
:config
(global-so-long-mode +1))
The j-lisp/j-common.el
defines commonly used functions within this configuration.
;;; j-common.el --- Commonly used functions -*- lexical-binding: t -*-
;;; Commentary:
;;
;; Commonly used function.
;;; Code:
(defgroup j-common ()
"Commonly used functions."
:group 'editing)
;;;###autoload
(defconst j-common-linuxp
(eq system-type 'gnu/linux)
"Are we running on a GNU/Linux system?")
;;;###autoload
(defconst j-common-macp
(eq system-type 'darwin)
"Are we running on a Mac system?")
;;;###autoload
(defconst j-common-guip
(display-graphic-p)
"Are we using GUI?")
;;;###autoload
(defconst j-common-rootp
(string-equal "root" (getenv "USER"))
"Are you a ROOT user?")
;;;###autoload
(defun j-common-number-negative (n)
"Make N negative."
(if (and (numberp n) (> n 0))
(* -1 n)
(error "%s is not a valid positive number" n)))
(provide 'j-common)
;;; j-common.el ends here
Load j-common
package.
(use-package j-common
:straight (:type built-in)
:demand t)
;;; j-simple.el --- Generic commonly used commands -*- lexical-binding: t -*-
;;; Commentary:
;;
;; Generic commonly used commands.
;;; Code:
(defgroup j-simple ()
"Generic commonly used commands."
:group 'editing)
;; Commands for lines.
;;;###autoload
(defun j-simple-new-line-below (&optional arg)
"Create an empty line below the current one.
Move the point to the absolute beginning. Adapt indentation by
passing optional prefix ARG (\\[universal-argument]). Also see
`j-simple-new-line-above'."
(interactive "P")
(end-of-line)
(if arg
(newline-and-indent)
(newline)))
;;;###autoload
(defun j-simple-new-line-above (&optional arg)
"Create an empty line above the current one.
Move the point to the absolute beginning. Adapt indentation by
passing optional prefix ARG (\\[universal-argument])."
(interactive "P")
(let ((indent (or arg nil)))
(if (or (bobp)
(eq (line-number-at-pos) 1))
(progn
(beginning-of-line)
(newline)
(forward-line -1))
(forward-line -1)
(j-simple-new-line-below indent))))
;;;###autoload
(defun j-simple-kill-line (&optional arg)
"Kill to the end of the line, the whole line on the next call.
If ARG is specified, do not modify the behaviour of `kill-line'.
line, kill whole line."
(interactive "P")
(if arg
(kill-line arg)
(if (eq (point-at-eol) (point))
(kill-line 0)
(kill-line))))
;;;###autoload
(defun j-simple-kill-line-backward ()
"Kill from point to the beginning of the line."
(interactive)
(kill-line 0))
;;;###autoload
(defun j-simple-yank-replace-line-or-region ()
"Replace line or region with latest kill.
This command can then be followed by the standard
`yank-pop' (default is bound to \\[yank-pop])."
(interactive)
(if (use-region-p)
(delete-region (region-beginning) (region-end))
(delete-region (point-at-bol) (point-at-eol)))
(yank))
;; Commands for text insertion or manipulation.
;; TODO
;; Commands for object transposition.
(defmacro j-simple-transpose (name scope &optional doc)
"Macro to produce transposition functions.
NAME is the function's symbol. SCOPE is the text object to
operate on. Optional DOC is the function's docstring.
Transposition over an active region will swap the object at
mark (region beginning) with the one at point (region end)"
`(defun ,name (arg)
,doc
(interactive "p")
(let ((x (format "%s-%s" "transpose" ,scope)))
(if (use-region-p)
(funcall (intern x) 0)
(funcall (intern x) arg)))))
(j-simple-transpose
j-simple-transpose-lines
"lines"
"Transpose lines or swap over active region.")
(j-simple-transpose
j-simple-transpose-paragraphs
"paragraphs"
"Transpose paragraphs or swap over active region.")
(j-simple-transpose
j-simple-transpose-sentences
"sentences"
"Transpose sentences or swap over active region.")
(j-simple-transpose
j-simple-transpose-sexps
"sexps"
"Transpose balanced expressions or swap over active region.")
;;;###autoload
(defun j-simple-transpose-chars ()
"Always transposes the two characters before point.
There is no 'dragging' the character forward. This is the
behaviour of `transpose-chars' when point is at the end of the
line."
(interactive)
(transpose-chars -1)
(forward-char))
;;;###autoload
(defun j-simple-transpose-words (arg)
"Transpose ARG words.
If region is active, swap the word at mark (region beginning)
with the one at point (region end).
Otherwise, and while inside a sentence, this behaves as the
built-in `transpose-words', dragging forward the word behind the
point. The difference lies in its behaviour at the end or
beginning of a line, where it will always transpose the word at
point with the one behind or ahead of it (effectively the
last/first two words)."
(interactive "p")
(cond
((use-region-p)
(transpose-words 0))
((eq (point) (point-at-eol))
(transpose-words -1))
((eq (point) (point-at-bol))
(forward-word 1)
(transpose-words 1))
(t
(transpose-words arg))))
;; Commands for marking syntactic constructs
;; Commands for building and loading Emacs config.
;;;###autoload
(defun j-simple-build-emacs-config ()
"Generate Emacs configuration files from Org file."
(interactive)
(org-babel-tangle-file j-emacs-config-source-org))
(provide 'j-simple)
;;; j-simple.el ends here
Load j-simple
package.
(use-package j-simple
:straight (:type built-in)
:demand t)
Set super and meta keys for different operating systems.
(cond (j-common-macp
(setq mac-option-modifier 'super)
(setq mac-command-modifier 'meta))
(t nil))
The most essential key bindings:
- moving the point
- by single char, word, sentence, paragraph
Key binding Description C-f
forward-char
C-b
backward-char
M-f
forward-word
M-b
backward-word
C-M-f
forward-sexp
C-M-b
backward-sexp
M-a
forward-sentence
M-e
backward-sentence
M-}
forward-paragraph
M-{
backward-paragraph
Moving by s-expression (sexp for short) is useful in code. For example, a sexp in Lisp is a block of code enclosed in parentheses.
In order to distinguish between an abbreviation and a sentence end, the sentence is considered to end with two spaces or a new line after
.
,?
or!
(the abbreviation ends with just single space). This is also American typist’s convention and it’s recommended practise in Emacs.(setq sentence-end-double-space t) (setq sentence-end-without-period nil) (setq colon-double-space nil) (setq use-hard-newlines nil)
Paragraph is basically any section of text separated by blank lines. This can be used in code (see
paragraph-start
variable).- to next/previous line
Key binding Description C-n
next-line
C-p
previous-line
- to beginning/end of line
Key binding Description C-a
move-beginning-of-line
C-e
move-end-of-line
Note that there is
mwin
package that improves this behaviour (TODO: link).- to beginning/end of buffer
Key binding Description M-<
beginning-of-buffer
M->
end-of-buffer
Note that there is
beginend
package that improves this behaviour (TODO: link).- to a specific line/column
Key binding Description M-g M-g
goto-line
M-g <tab>
move-to-column
Note that
goto-line
is replaced by and improved version -consult-goto-line
with an additional key bindingM-g g
(TODO: link). - scrolling and centering the window
Key binding Description C-v
scroll-up-command
M-v
scroll-down-command
C-l
recenter-top-bottom
C-M-l
reposition-window
recenter-top-bottom
makes the line with the point to be in the middle/top/bottom of the window depending on how many times it’s used in a row.reposition-window
scrolls the screen so it fits the current “thing” at point as much as possible in the window (paragraph, function definition, etc.).
Disable unused global key bindings.
(let ((map global-map))
;; Disable `suspend-emacs'.
(define-key map (kbd "C-x C-z") nil)
;; Disable `view-hello-file'.
(define-key map (kbd "C-h h") nil)
;; Disable `tmm-menubar'.
(define-key map (kbd "M-`") nil))
Redefine or enhance global key bindings.
(let ((map global-map))
;; Commands for help.
(define-key map (kbd "C-h K") #'describe-keymap)
;; Commands for lines.
(define-key map (kbd "<C-return>") #'j-simple-new-line-below)
(define-key map (kbd "<C-S-return>") #'j-simple-new-line-above)
(define-key map (kbd "C-k") #'j-simple-kill-line)
(define-key map (kbd "M-k") #'j-simple-kill-line-backward)
(define-key map (kbd "C-S-y") #'j-simple-yank-replace-line-or-region)
;; TODO: line joins
;; Commands for object transposition.
(define-key map (kbd "C-t") #'j-simple-transpose-chars)
(define-key map (kbd "C-x C-t") #'j-simple-transpose-lines)
(define-key map (kbd "C-S-t") #'j-simple-transpose-paragraphs)
(define-key map (kbd "C-x M-t") #'j-simple-transpose-sentences)
(define-key map (kbd "C-M-t") #'j-simple-transpose-sexps)
(define-key map (kbd "M-t") #'j-simple-transpose-words))
The whole-line-or-region
package changes behaviour of killing, yanking and commenting of lines and regions:
- if no region is activated, the current line is copied/yanked/commented;
- if a region is activated, the whole region is copied/yanked/commented;
- with numeric prefix, it possible to operate on multiple lines starting with the current line.
(use-package whole-line-or-region
:straight t
:config
(whole-line-or-region-global-mode +1))
The expand-region
package replaces:
mark-word
which doesn’t mark the whole word if the cursor is in the middle, only marks the part of the word from the cursor to the end of the word.er/mark-word
works better as it marks the whole word regardless of where the cursor is placed;mark-sexp
is replaced wither/expand-region
which with every call marks more context (sexp).
(use-package expand-region
:straight t
:bind
(;; C-@
([remap mark-word] . er/mark-word)
;; C-M-@ or C-M-SPC
([remap mark-sexp] . er/expand-region)))
The behaviour of C-a
and C-e
is changed to move the cursor to the first/last actionable character of the line.
(use-package mwim
:straight t
:bind
(;; C-a
([remap move-beginning-of-line] . mwim-beginning-of-code-or-line)
;; C-e
([remap move-end-of-line] . mwim-end-of-code-or-line)))
The behaviour of M-<
and M->
is changed to move to the first/last actionable point in a buffer (DWIM style).
(use-package beginend
:straight t
:config
(beginend-global-mode +1))
The subword
package changes the way how word boundaries are treated in programming modes. For example, “CamelCase” are two words as well as “foo_bar”.
(use-package subword
:hook (prog-mode . subword-mode))
The hungry-delete
packages deletes multiple white chars at once, until there is a non-white char.
(use-package hungry-delete
:straight t
:config
(setq-default hungry-delete-chars-to-skip " \t\f\v")
(global-hungry-delete-mode +1))
If there is some text selected and we start typing, the selected text is deleted and replaced with the newly typed text.
(use-package delsel
:config
(delete-selection-mode +1))
The helpful
package is an alternative to the built-in Emacs help. It provides more contextual information. Note that helpful-callable
includes both functions and macros.
(use-package helpful
:straight t
:bind
(("s-h" . helpful-at-point)
("C-h f" . helpful-callable)
("C-h v" . helpful-variable)
("C-h k" . helpful-key)))
The goto-last-change
package makes it possible to move the cursor back to the last change.
(use-package goto-last-change
:straight t
:bind
("C-z" . goto-last-change))
The which-key
is a minor mode that displays the key bindings following currently entered incomplete command (a prefix).
(use-package which-key
:straight t
:config
(setq which-key-dont-use-unicode t)
(setq which-key-add-column-padding 2)
(setq which-key-show-early-on-C-h nil)
(setq which-key-idle-delay 0.8)
(setq which-key-idle-secondary-delay 0.05)
(setq which-key-popup-type 'side-window)
(setq which-key-show-prefix 'echo)
(setq which-key-max-display-columns 3)
(setq which-key-separator " ")
(setq which-key-special-keys nil)
(setq which-key-paging-key "<down>")
(which-key-mode +1))
The themes have documentation.
(use-package modus-themes
:straight t
:init
(setq modus-themes-bold-constructs t)
(setq modus-themes-slanted-constructs t)
(setq modus-themes-syntax 'green-strings)
(setq modus-themes-prompts 'subtle-accented)
(setq modus-themes-mode-line nil)
(setq modus-themes-fringes 'subtle)
(setq modus-themes-lang-checkers 'subtle-foreground-straight-underline)
(setq modus-themes-intense-hl-line t)
(setq modus-themes-paren-match 'intense-bold)
(setq modus-themes-org-blocks 'greyscale)
(setq modus-themes-org-habit 'traffic-light)
(setq modus-themes-scale-headings t)
(modus-themes-load-themes)
:config
(modus-themes-load-vivendi)
:bind
("C-c t" . modus-themes-toggle))
(defconst j-font-sizes-families-alist
'(("phone" . (110 "Hack" "Source Serif Variable"))
("laptop" . (120 "Hack" "Source Serif Variable"))
("desktop" . (130 "Hack" "Source Serif Variable"))
("presentation" . (180 "Iosevka Nerd Font Mono" "Source Serif Pro")))
"Alist of desired typefaces and their point sizes.
Each association consists of a display type mapped to a point
size, followed by monospaced and proportionately spaced font
names. The monospaced typeface is meant to be applied to the
`default' and `fixed-pitch' faces. The proportionately spaced
font is intended for the `variable-pitch' face.")
(defun j-set-font-face-attributes (height fixed-font variable-font)
"Set font face attributes.
HEIGHT is the font's point size, represented as either '10' or
'10.5'. FIXED-FONT is a fixed pitch typeface (also the default
one). VARIABLE-FONT is proportionally spaced type face."
(set-face-attribute 'default nil :family fixed-font :height height)
(set-face-attribute 'fixed-pitch nil :family fixed-font)
(set-face-attribute 'variable-pitch nil :family variable-font))
(defun j-set-font-for-display (display)
"Set defaults based on DISPLAY."
(let* ((font-data (assoc display j-font-sizes-families-alist))
(height (nth 1 font-data))
(fixed-font (nth 2 font-data))
(variable-font (nth 3 font-data)))
(j-set-font-face-attributes height fixed-font variable-font)))
;; TODO: determine pixel width for phone.
(defun j-get-display ()
"Get display size."
(if (<= (display-pixel-width) 1280)
"laptop"
"desktop"))
(defun j-set-font-init ()
"Set font for the current display."
(if j-common-guip
(j-set-font-for-display (j-get-display))
(user-error "Not running a graphical Emacs, cannot set font")))
(add-hook 'after-init-hook #'j-set-font-init)
(defun j-font-mono-p (font)
"Check if FONT is monospaced."
(when-let ((info (font-info font)))
;; If the string is found the match function returns an integer.
(integerp (string-match-p "spacing=100" (aref info 1)))))
;; Set fixed-pitch and variable-pitch fonts and font height
;; interactively. Mainly for testing purposes to check different font families.
(defun j-set-font ()
"Set font."
(interactive)
(when sys/guip
(let* ((font-groups (seq-group-by #'j-font-mono-p (font-family-list)))
fixed-fonts
variable-fonts
all-fonts
fixed-font
variable-font
(heights (mapcar #'number-to-string (list 110 115 120 125 130 135 140)))
height)
(if (caar font-groups)
(setq fixed-fonts (cdar font-groups)
variable-fonts (cdadr font-groups))
(setq fixed-fonts (cdadr font-groups)
variable-fonts (cdar font-groups)))
(setq all-fonts (append variable-fonts fixed-fonts))
(setq fixed-font (completing-read "Select fixed pitch font: " fixed-fonts nil t))
(setq variable-font (completing-read "Select variable pitch font: " all-fonts nil t))
(setq height (completing-read "Select or insert font height: " heights nil))
(j-set-font-face-attributes (string-to-number height) fixed-font variable-font))))
This section contains configuration of packages that are used for making file backups, keeping history of cursor position, file changes, etc.
These packages produce files where the history and backups are kept. The location of these files is not configured in this section, the package no-littering
has sane defaults which are not overwritten here.
The built-in desktop
package saves the state of the desktop when Emacs is closed or crashes. The desktop state is read on the next Emacs startup and restores:
- buffers (
desktop-restore-eager
restores just a couple of buffers, the rest is restored lazily); - frame configuration including windows (with their position) and workspaces. The alternative to storing the frame configuration is using registers with
C-x r f
and reading the it back usingC-x r j
.
(use-package desktop
:config
(setq desktop-base-file-name "desktop")
(setq desktop-base-lock-name "desktop.lock")
(setq desktop-auto-save-timeout 60)
(setq desktop-restore-eager 5)
(setq desktop-restore-frames t)
(setq desktop-files-not-to-save nil)
(setq desktop-globals-to-clear nil)
(setq desktop-load-locked-desktop t)
(setq desktop-missing-file-warning t)
(setq desktop-save 'ask-if-new)
(desktop-save-mode +1))
Remember actions related to the minibuffer, such as input and choices. The history is read by the completion frameworks.
(use-package savehist
:config
(setq history-length 10000)
(setq history-delete-duplicates t)
(setq savehist-autosave-interval 60)
(setq savehist-additional-variables '(search-ring regexp-search-ring))
(setq savehist-save-minibuffer-history t)
(savehist-mode +1))
Remember where the cursor position is in any file.
(use-package saveplace
:config
(setq save-place-forget-unreadable-files t)
(save-place-mode +1))
Keep backups of visited files. The explanation of some of the settings:
make-backup-files
- make a backup of a file when it’s saved the first time;vc-make-backup-files
- backup also versioned files (git, svn, etc.);backup-by-copying
- don’t clobber symlinks;version-control
- version numbers for backup files;delete-old-versions
- delete excess backup files without asking.
(setq make-backup-files t)
(setq vc-make-backup-files t)
(setq backup-by-copying t)
(setq version-control t)
(setq delete-old-versions t)
(setq kept-old-versions 6)
(setq kept-new-versions 9)
auto-save
files use hashmarks (#
) and shall be written locally within the project directory (along with the actual files). The reason is that auto-save files are just temporary files that Emacs creates until a file is saved again. auto-save
files are created whenever Emacs crashes.
We disable the auto-save
mode and use super-save
package instead - it auto-saves buffers, when certain events happen - e.g. switch between buffers, an Emacs frame loses focus, etc. It’s both something that augments and replaces the standard auto-save-mode
.
(setq auto-save-default nil)
(use-package super-save
:straight t
:config
(setq super-save-auto-save-when-idle t)
(setq super-save-idle-duration 15)
(setq super-save-remote-files nil)
(super-save-mode +1))
The changes of buffer are also saved to a file, so this works between Emacs restarts as well.
(use-package undo-tree
:straight t
:init
(setq undo-tree-visualizer-timestamps t)
(setq undo-tree-visualizer-diff t)
(setq undo-tree-auto-save-history t)
:config
(global-undo-tree-mode +1))
TODO
Completion framework refers to searching, narrowing and selecting a candidate from multiple alternatives.
A completion style is a back-end for completion. Probably the most powerful completion style, that combines multiple different styles, is orderless. The orderless project page has extensive documentation.
In orderless completion style, a search pattern is split into components (check orderless-component-separator
). Each of these components can be matched using a different matching style. It’s possible to force a particular matching style for a given component (check orderless-style-dispatchers
) to have more fine-grained control.
;;; j-orderless.el --- Extensions for orderless -*- lexical-binding: t -*-
;;; Commentary:
;;
;; Extensions for orderless.
;;; Code:
(defgroup j-orderless ()
"Extensions for orderless."
:group 'minibuffer)
(defcustom j-orderless-default-styles
'(orderless-flex
orderless-strict-leading-initialism
orderless-regexp
orderless-prefixes
orderless-literal)
"List that should be assigned to `orderless-matching-styles'."
:type 'list
:group 'j-orderless)
(defun j-orderless-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))))
(defun j-orderless-initialism-dispatcher (pattern _index _total)
"Leading initialism dispatcher using the comma as a suffix."
(when (string-suffix-p "," pattern)
`(orderless-strict-leading-initialism . ,(substring pattern 0 -1))))
(provide 'j-orderless)
;;; j-orderless.el ends here
Load j-orderless
package.
(use-package j-orderless
:straight (:type built-in)
:demand t)
(use-package orderless
:straight t
:after j-orderless
:config
(setq orderless-component-separator " +")
(setq orderless-matching-styles j-orderless-default-styles)
(setq orderless-style-dispatchers
'(j-orderless-literal-dispatcher
j-orderless-initialism-dispatcher))
:bind
(:map minibuffer-local-completion-map
("SPC" . nil)
("?" . nil)))
The minibuffer is where commands read input such as file names, search strings, buffer names, etc. When the input string is being typed in the minibuffer Emacs can fill in the rest, or some of it. When the completion is available, some keys can be bound to commands that try to complete the mininbuffer input.
There are multiple completion styles used in the minibuffer:
orderless
which is described in the previous section;partial-completion
which is built-in and provides completions for file system paths e.g. by typing~/.l/s/fo
we get~/.local/share/fonts
;substring
mapsfoobar
with point betweenfoo
andbar
as.*foo.*bar.*
;flex
mapsabc
asa.*b.*c
.
The minibuffer history is saved using the built-in mechanism (see section TODO).
While typing input into the minibuffer, the *Completions*
buffer is shown (with all possible candidates) by pressing tab key. This behaviour is supressed by setting minibuffer-auto-help
to nil
as we use Embark instead (see section TODO).
;;; j-minibuffer.el --- Extensions for minibuffer -*- lexical-binding: t -*-
;;; Commentary:
;;
;; Extensions for minibuffer.
;;; Code:
(defgroup j-minibuffer ()
"Extensions for minibuffer."
:group 'minibuffer)
(defcustom j-minibuffer-completions-regexp
"\\*\\(Completions\\|Embark Collect \\(Live\\|Completions\\)\\)"
"Regexp to match window names with completion candidates.
Used by `j-minibuffer--get-completions'."
:group 'j-minibuffer
:type 'string)
;;;###autoload
(defun j-minibuffer-focus-mini ()
"Focus the active minibuffer."
(interactive)
(let ((mini (active-minibuffer-window)))
(when mini
(select-window mini))))
(defun j-minibuffer--get-completions ()
"Find completions buffer."
(get-window-with-predicate
(lambda (window)
(string-match-p
j-minibuffer-completions-regexp
(format "%s" window)))))
;;;###autoload
(defun j-minibuffer-focus-mini-or-completions ()
"Focus the active minibuffer or completions buffer."
(interactive)
(let* ((minibuffer (active-minibuffer-window))
(completions (j-minibuffer--get-completions)))
(cond ((and minibuffer (not (minibufferp)))
(select-window minibuffer nil))
((and completions (not (eq (selected-window) completions)))
(select-window completions nil)))))
(provide 'j-minibuffer)
;;; j-minibuffer.el ends here
Load j-minibuffer
package.
(use-package j-minibuffer
:straight (:type built-in)
:demand t)
(use-package minibuffer
:after j-minibuffer
:config
(setq completion-styles '(partial-completion substring flex orderless))
(setq completion-category-defaults nil)
(setq completion-cycle-threshold nil)
(setq completion-flex-nospace nil)
(setq completion-pcm-complete-word-inserts-delimiters t)
(setq completion-pcm-word-delimiters "-_./:| ")
(setq completion-show-help nil)
(setq completion-auto-help nil)
(setq completion-ignore-case t)
(setq-default case-fold-search t)
(setq completions-format 'one-column)
(setq completions-detailed t)
(setq read-buffer-completion-ignore-case t)
(setq read-file-name-completion-ignore-case t)
(setq enable-recursive-minibuffers t)
(setq read-answer-short t)
(setq resize-mini-windows t)
(setq minibuffer-eldef-shorten-default t)
(file-name-shadow-mode +1)
(minibuffer-depth-indicate-mode +1)
(minibuffer-electric-default-mode +1)
:bind
(("s-f" . find-file)
("s-F" . find-file-other-window)
("s-b" . switch-to-buffer)
("s-B" . switch-to-buffer-other-window)
("s-d" . dired)
("s-D" . dired-other-window)
("s-v" . j-minibuffer-focus-mini-or-completions)))
The marginalia
package provides annotations for completion candidates in vertical view.
(use-package marginalia
:straight t
:config
(setq marginalia-annotators
'(marginalia-annotators-heavy
marginalia-annotators-light))
(marginalia-mode +1))
The consult
package enhances various commands that are meant to replace the existing ones. The consult commands offer an improved interactive experience, can add live previews, filtering and narrowing. It is achieved by creating a wrapper for completing-read
function.
(use-package consult
:straight t
:config
(setq consult-line-numbers-widen t)
(setq completion-in-region-function #'consult-completion-in-region)
(setq consult-async-min-input 3)
(setq consult-narrow-key ">")
:bind
(("s-y" . consult-yank)
("C-x M-:" . consult-complex-command)
("C-x M-m" . consult-minor-mode-menu)
("C-x M-k" . consult-kmacro)
("M-g g" . consult-goto-line)
("M-g M-g" . consult-goto-line)
("M-X" . consult-mode-command)
("M-K" . consult-keep-lines) ; M-S-k is similar to M-S-5 (M-%)
("M-F" . consult-focus-lines) ; same principle
("M-s g" . consult-grep)
("M-s m" . consult-mark)
("C-x r r" . consult-register) ; Use the register's prefix
("C-x r S" . consult-register-store)
("C-x r L" . consult-register-load)
(:map consult-narrow-map
("?" . consult-narrow-help))))
embark
provides the ability to execute an action on a target using embark-act
command. The target can be a completion candidate in the minibuffer, a region, symbol or URL at point, etc. The action is dependent on the type of the target. More precisely, there are multiple actions associated with a target (defined in an action keymap for the given target type) to choose from.
Embark acts both on individual targets and a set of candidates - for example the set of buffer candidates in minibuffer when switching to a different buffer. Embark can produce a buffer with the list of the current candidate set using embark-collect-snapshot
. Similarly it can produce live/updating view of the current candidate set using embark-collect-live
. The embark-export
tries to open an appropriate buffer for the list of candidates (dired
for list of files, ibuffer
for list of buffers, etc.).
The “live” candidate view is used as the front-end for minibuffer (check minibuffer-setup-hook
). This buffer pops up only after there is some input typed.
The j-embark
enhances the embark
package:
TODO
(use-package embark
:straight t
:config
(setq embark-collect-initial-view-alist
'((kill-ring . zebra)
(t . list)))
(setq embark-action-indicator
(lambda (map _target)
(which-key--show-keymap "Act" map nil nil 'no-paging)
#'which-key--hide-popup-ignore-command))
(setq embark-become-indicator
(lambda (map)
(which-key--show-keymap "Become" map nil nil 'no-paging)
#'which-key--hide-popup-ignore-command))
:hook
((minibuffer-setup . embark-collect-completions-after-input)
(embark-post-action . embark-collect--update-linked))
:bind
(("C-," . embark-act)
(:map minibuffer-local-completion-map
("C-," . embark-act)
("C->" . embark-become)
("M-q" . embark-collect-toggle-view))
(:map embark-collect-mode-map
("C-," . embark-act)
("M-q" . embark-collect-toggle-view))))
;;; j-embark.el --- Extensions for embark -*- lexical-binding: t -*-
;;; Commentary:
;;
;; Extensions for embark.
;;; Code:
(when (featurep 'embark)
(require 'embark))
(require 'j-common)
(require 'j-minibuffer)
(defgroup j-embark ()
"Extensions for embark."
:group 'editing)
(defun j-embark--live-buffer-p ()
"Determine presence of a linked live occur buffer."
(let ((buf embark-collect-linked-buffer))
(when buf
(window-live-p (get-buffer-window buf)))))
(defun j-embark--live-completions-p ()
"Determine whether current collection is for live completions."
(and (derived-mode-p 'embark-collect-mode)
(eq embark-collect--kind :completions)))
;;;###autoload
(defun j-embark-collect-fit-window (&rest _)
"Fit Embark's collect completions window to its buffer.
To be added to `embark-collect-post-revert-hook'."
(when (derived-mode-p 'embark-collect-mode)
(fit-window-to-buffer (get-buffer-window)
(round (* 0.30 (frame-height))) 1)))
;;;###autoload
(defun j-embark-completions-toggle ()
"Toggle `embark-collect-completions'."
(interactive)
(cond ((j-embark--live-buffer-p)
(kill-buffer embark-collect-linked-buffer))
((j-embark--live-completions-p)
(kill-buffer)
(select-window (active-minibuffer-window)))
(t (embark-collect-completions))))
;;;###autoload
(defun j-embark-keyboard-quit ()
"Control the exit behaviour for Embark collect buffers.
If in a live Embark collect/completions buffer and unless the
region is active, run `abort-recursive-edit'. Otherwise run
`keyboard-quit'.
If the region is active, deactivate it. A second invocation of
this command is then required to abort the session.
This is meant to be bound in `embark-collect-mode-map'."
(interactive)
(if (j-embark--live-completions-p)
(if (use-region-p)
(keyboard-quit)
(kill-buffer)
(abort-recursive-edit))
(keyboard-quit)))
;;;###autoload
(defun j-embark-next-line-or-mini (&optional arg)
"Move to the next line or switch to the minibuffer.
This performs a regular motion for optional ARG lines, but when
point can no longer move in that direction, then it switches to
the minibuffer."
(interactive "p")
(if (or (eobp) (eq (point-max) (save-excursion (forward-line 1) (point))))
(j-minibuffer-focus-mini) ; from `j-minibuffer.el'
(forward-line (or arg 1)))
(setq this-command 'next-line))
;;;###autoload
(defun j-embark-previous-line-or-mini (&optional arg)
"Move to the next line or switch to the minibuffer.
This performs a regular motion for optional ARG lines, but when
point can no longer move in that direction, then it switches to
the minibuffer."
(interactive "p")
(let ((num (j-common-number-negative arg))) ; from `j-common.el'
(if (bobp)
(j-minibuffer-focus-mini) ; from `j-minibuffer.el'
(forward-line (or num 1)))))
(defun j-embark--switch-to-completions ()
"Subroutine for switching to the Embark completions buffer."
(unless (j-embark--live-buffer-p)
(j-embark-completions-toggle))
(let ((win (get-buffer-window embark-collect-linked-buffer)))
(select-window win)))
;;;###autoload
(defun j-embark-switch-to-completions-top ()
"Switch to the top of Embark's completions buffer.
Meant to be bound in `minibuffer-local-completion-map'."
(interactive)
(j-embark--switch-to-completions)
(goto-char (point-min)))
;;;###autoload
(defun j-embark-switch-to-completions-bottom ()
"Switch to the bottom of Embark's completions buffer.
Meant to be bound in `minibuffer-local-completion-map'."
(interactive)
(j-embark--switch-to-completions)
(goto-char (point-max))
(forward-line -1)
(goto-char (point-at-bol))
(recenter
(- -1
(min (max 0 scroll-margin)
(truncate (/ (window-body-height) 4.0))))
t))
(provide 'j-embark)
;;; j-embark.el ends here
Load j-embark
package.
(use-package j-embark
:straight (:type built-in)
:after embark
:hook
(embark-collect-post-revert . j-embark-collect-fit-window)
:bind
(:map embark-collect-mode-map
("h" . helpful-at-point)
("C-g" . j-embark-keyboard-quit)
("C-n" . j-embark-next-line-or-mini)
("C-p" . j-embark-previous-line-or-mini)
("C-l" . j-embark-completions-toggle))
(:map minibuffer-local-completion-map
("C-n" . j-embark-switch-to-completions-top)
("C-p" . j-embark-switch-to-completions-bottom)
("C-l" . j-embark-completions-toggle)))
TODO
(use-package embark-consult
:straight t
:after (embark consult)
:bind
(:map embark-collect-mode-map
("C-j" . embark-consult-preview-at-point)))
A “project” is a version controlled directory governed by a program (such as git
). The project.el
has commands (C-x p
prefix) that are useful for working with projects:
- switching to a project;
- executing a command in a project;
- starting a shell in project root;
- find file, search matches (regexp) in the current project;
- etc.
Using any of the commands listed in C-x p C-h
will append the current project to a list of known projects, stored in the dynamically updated project--list
variable, whose contents are stored in a file defined by project-list-file
.
isearch
and replace
are built-in into Emacs. Their main functionality includes:
- incremental search forward/backward;
- search and replace a matched string with another string;
- listing all matches (string or regular expression) into a separate buffer (
occur-mode
).
The most common key bindings:
Key binding | Description |
---|---|
C-s | search forward |
C-r | search backward |
C-M-s | search regexp forward |
C-M-r | search regexp backward |
M-% | replace string matches |
C-M-% | replaces regexp matches |
C-s M-r | toggle regexp search |
M-s o | list all regexp matches in a separate buffer |
C-s C-w | search char or word at point |
M-s . | search symbol at point |
M-s h r | highlight regexp |
M-s h u | highlight undo |
C-h k C-s | show help with additional keybindings |
The occur and replace operations are aware of active region, so the search and replace operation is executed only in the highlighted area (also possible to be done with narrowing C-x n ...
).
Due to the combined effect of the values assigned to the variables search-whitespace-regexp
, isearch-lax-whitespace
, isearch-regexp-lax-whitespace
, the space is interpreted as wildcard (a b c
as search input is interpreted as a.*b.*c.*
). This affects string search, but not the regexp search. To interprate space as literal, toggle whitespace matching with M-s SPC
.
(use-package isearch
:config
(setq search-highlight t)
(setq search-whitespace-regexp ".*?")
(setq isearch-lax-whitespace t)
(setq isearch-regexp-lax-whitespace nil)
(setq isearch-lazy-highlight t)
(setq isearch-lazy-count t)
(setq lazy-count-prefix-format nil)
(setq lazy-count-suffix-format " (%s/%s)")
(setq isearch-yank-on-move 'shift)
(setq isearch-allow-scroll 'unlimited)
:bind
((:map minibuffer-local-isearch-map
("M-/" . isearch-complete-edit))
(:map isearch-mode-map
("C-g" . isearch-cancel)
("M-/" . isearch-complete))))
The occur-mode
buffer can be changed to editable (occur-edit-mode
) by pressing e
. To switch back from editable buffer, use C-c C-c
.
(use-package replace
:config
(setq list-matching-lines-jump-to-current-line t)
:hook
((occur-mode . hl-line-mode)
(occur-mode . (lambda () (toggle-truncate-lines t))))
:bind
(("M-s M-o" . multi-occur)
(:map occur-mode-map
("t" . toggle-truncate-lines))))
(use-package j-search
:straight (:type built-in)
:bind
(("M-s %" . j-search-isearch-replace-symbol)
("M-s M-<" . j-search-isearch-beginning-of-buffer)
("M-s M->" . j-search-isearch-end-of-buffer)
("M-s g" . j-search-grep)
("M-s u" . j-search-occur-urls)
("M-s M-u" . j-search-occur-browse-url)
(:map isearch-mode-map
("<backspace>" . j-search-isearch-abort-dwim)
("<C-return>" . j-search-isearch-other-end))))
;;; j-search.el --- Extensions for search, replace and grep -*- lexical-binding: t -*-
;;; Commentary:
;;
;; Extensions for search, replace and grep.
;;; Code:
(require 'isearch)
(require 'replace)
(require 'grep)
;;;; Isearch
;;;###autoload
(defun j-search-isearch-other-end ()
"End current search in the opposite side of the match.
Particularly useful when the match does not fall within the
confines of word boundaries (e.g. multiple words)."
(interactive)
(isearch-done)
(when isearch-other-end
(goto-char isearch-other-end)))
;;;###autoload
(defun j-search-isearch-abort-dwim ()
"Delete failed `isearch' input, single char, or cancel search.
This is a modified variant of `isearch-abort' that allows us to
perform the following, based on the specifics of the case: (i)
delete the entirety of a non-matching part, when present; (ii)
delete a single character, when possible; (iii) exit current
search if no character is present and go back to point where the
search started."
(interactive)
(if (eq (length isearch-string) 0)
(isearch-cancel)
(isearch-del-char)
(while (or (not isearch-success) isearch-error)
(isearch-pop-state)))
(isearch-update))
(defmacro j-search-isearch-occurrence (name edge &optional doc)
"Construct function for moving to `isearch' occurrence.
NAME is the name of the function. EDGE is either the beginning
or the end of the buffer. Optional DOC is the resulting
function's docstring."
`(defun ,name (&optional arg)
,doc
(interactive "p")
(let ((x (or arg 1))
(command (intern (format "isearch-%s-of-buffer" ,edge))))
(isearch-forward-symbol-at-point)
(funcall command x))))
(j-search-isearch-occurrence
j-search-isearch-beginning-of-buffer
"beginning"
"Run `isearch-beginning-of-buffer' for the symbol at point.
With numeric ARG, move to ARGth occurrence counting from the
beginning of the buffer.")
(j-search-isearch-occurrence
j-search-isearch-end-of-buffer
"end"
"Run `isearch-end-of-buffer' for the symbol at point.
With numeric ARG, move to ARGth occurrence counting from the
end of the buffer.")
;;;; Replace/Occur
;; TODO: make this work backwardly when given a negative argument
(defun j-search-isearch-replace-symbol ()
"Run `query-replace-regexp' for the symbol at point."
(interactive)
(isearch-forward-symbol-at-point)
(isearch-query-replace-regexp))
(defvar j-search-url-regexp
(concat
"\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|"
"nntp\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)"
"\\(//[-a-z0-9_.]+:[0-9]*\\)?"
(let ((chars "-a-z0-9_=#$@~%&*+\\/[:word:]")
(punct "!?:;.,"))
(concat
"\\(?:"
;; Match paired parentheses, e.g. in Wikipedia URLs:
;; http://thread.gmane.org/47B4E3B2.3050402@gmail.com
"[" chars punct "]+" "(" "[" chars punct "]+" ")"
"\\(?:" "[" chars punct "]+" "[" chars "]" "\\)?"
"\\|"
"[" chars punct "]+" "[" chars "]"
"\\)"))
"\\)")
"Regular expression that matches URLs.
Copy of variable `browse-url-button-regexp'.")
(autoload 'goto-address-mode "goto-addr")
;;;###autoload
(defun j-search-occur-urls ()
"Produce buttonised list of all URLs in the current buffer."
(interactive)
(add-hook 'occur-hook #'goto-address-mode)
(occur j-search-url-regexp "\\&")
(remove-hook 'occur-hook #'goto-address-mode))
;;;###autoload
(defun j-search-occur-browse-url ()
"Point browser at a URL in the buffer using completion.
Which web browser to use depends on the value of the variable
`browse-url-browser-function'.
Also see `j-search-occur-url'."
(interactive)
(let ((matches nil))
(save-excursion
(goto-char (point-min))
(while (search-forward-regexp j-search-url-regexp nil t)
(push (match-string-no-properties 0) matches)))
(funcall browse-url-browser-function
(completing-read "Browse URL: " matches nil t))))
;;;; Grep
(defvar j-search--grep-hist '()
"Input history of grep searches.")
;;;###autoload
(defun j-search-grep (regexp &optional recursive)
"Run grep for REGEXP.
Search in the current directory using `lgrep'. With optional
prefix argument (\\[universal-argument]) for RECURSIVE, run a
search starting from the current directory with `rgrep'."
(interactive
(list
(read-from-minibuffer "Local grep for PATTERN: "
nil nil nil 'j-search--grep-hist)
current-prefix-arg))
(unless grep-command
(grep-compute-defaults))
(if recursive
(rgrep regexp "*" default-directory)
(lgrep regexp "*" default-directory)
(add-to-history 'j-search--grep-hist regexp)))
(provide 'j-search)
;;; j-search.el ends here
The search result of grep
is placed into a separate buffer (similar to occur
). In order to edit this buffer, wgrep
package is necessary.
(use-package wgrep
:straight t
:config
(setq wgrep-auto-save-buffer t)
(setq wgrep-change-readonly-file t)
:bind
(:map grep-mode-map
("e" . wgrep-change-to-wgrep-mode)
("C-x C-q" . wgrep-change-to-wgrep-mode)
("C-c C-c" . wgrep-finish-edit)))
An identifier is a syntactical unit of a program: a function, a class, a data type, etc. Software development requires quickly looking up identifiers, their definitions, renaming them across the project, etc. xref
provides an interface for these capabilities. The backend for xref
is major-mode specific, it can be built-in (for elisp) or it can be an external program such as etags
that comes with Emacs.
(use-package xref
:config
;; M-.
(setq xref-show-definitions-function #'xref-show-definitions-completing-read)
;; grep and the like
(setq xref-show-xrefs-function #'xref-show-definitions-buffer)
(setq xref-file-name-display 'project-relative)
(setq xref-search-program 'ripgrep))
If there are mutliple files open with the same name, Emacs will append a number such as <1>
, <2>
, etc. to distinguish them. uniquify
makes the buffer names unique by adding just enough path of the file.
(use-package uniquify
:config
(setq uniquify-buffer-name-style 'forward)
(setq uniquify-strip-common-suffix t)
(setq uniquify-after-kill-buffer-p t))
Buffers are displayed by calling display-buffer
function. It performs several steps in order to find a window for displaying a buffer. These steps are referred to as display actions. This list of actions is traversed one by one until a display action returns a window to display buffer in (if it can’t return the window, nil
is returned).
The display action is composed of display action functions and display action alist. The display-action
function accepts two arguments: the buffer to be displayed and the display action alist. For example:
(display-buffer
(get-buffer-create "foo") ; buffer
'((display-buffer-below-selected display-buffer-at-bottom) ; display action functions
(inhibit-same-window . t) ; display action alist
(window-height . fit-window-to-buffer))) ; display action alist
The display actions are taken from these sources (from highest to lowest priority):
display-buffer-overriding-action
(variable);display-buffer-alist
(user option);action
(argument);display-buffer-base-action
(user option);display-buffer-fallback-action
(constant).
Note that switching to a buffer (C-x b
) calls switch-to-buffer
, which normally just uses low-level routine set-buffer
where the entries of display-buffer-alist
are irrelevant. There are also occassions when switch-to-buffer
calls pop-to-buffer
. In that case the display-buffer-alist
entries are relevant. To make switch-to-buffer
behave according to display actions, there is the option switch-to-buffer-obey-display-actions
.
Some commands that pop up a new window don’t focus the new window. There is a macro in j-window
. It is a thin wrapper around display-buffer-*
functions that makes the newly created window focused.
Load j-window
package.
(use-package j-window
:straight (:type built-in)
:demand t)
The behaviour of how windows are displayed is controlled by user option display-buffer-alist
.
(use-package window
:after j-window
:config
(setq display-buffer-alist
'(;; top side window
("^\\*Apropos\\*"
(j-display-buffer-in-side-window-and-select)
(side . top)n
(slot . -1)
(window-height . 0.40))
("^\\*[Hh]elp.*"
(j-display-buffer-in-side-window-and-select)
(side . top)
(slot . 0)
(window-height . 0.40))
("^\\*Messages\\*"
(display-buffer-in-side-window)
(side . top)
(slot . 0)
(window-height . 0.40))
("^\\*\\(Backtrace\\|Warnings\\|Compile-Log\\)\\*"
(display-buffer-in-side-window)
(side . top)
(slot . 1)
(window-height . 0.40)
(window-parameters . ((no-other-window . t))))
;; bottom side window
("^\\*\\(Embark\\)?.*Completions.*"
(display-buffer-in-side-window)
(window-height . 0.25)
(side . bottom)
(slot . 0)
(window-parameters . ((no-other-window . t)
(mode-line-format . none))))
;; bottom window
("^\\*.*\\(e?shell\\|v?term\\).*"
(j-display-buffer-reuse-mode-window-and-select
j-display-buffer-at-bottom-and-select)
(window-height . 0.25))
;; below current window
("^\\*Calendar.*"
(j-display-buffer-reuse-mode-window-and-select
j-display-buffer-below-selected-and-select)
(window-height . shrink-window-if-larger-than-buffer))))
(setq window-resize-pixelwise t)
(setq window-combination-resize t)
(setq window-sides-vertical nil)
(setq switch-to-buffer-in-dedicated-window 'pop)
(setq switch-to-buffer-obey-display-actions t)
(setq help-window-select t)
:bind
(("s-n" . next-buffer)
("s-p" . previous-buffer)
("s-o" . other-window)
("s-2" . split-window-below)
("s-3" . split-window-right)
("s-0" . delete-window)
("s-1" . delete-other-windows)
("s-5" . delete-frame)
("C-x _" . balance-windows)
("C-x +" . balance-windows-area)
("s-q" . window-toggle-side-windows)))
;;; j-window.el --- Extensions for window -*- lexical-binding: t -*-
;;; Commentary:
;;
;; Extensions for window.
;;; Code:
(defgroup j-window ()
"Extensions for window."
:group 'windows)
(defmacro j-display-buffer-and-select (fun)
"Macro to display buffer and select its window.
FUN is one of display-buffer-* functions."
`(defun ,(intern (format "j-%s-and-select" fun)) (buffer alist)
,(format "Display BUFFER as `%s' and select its window." fun)
(let ((window (,fun buffer alist)))
(if window
(select-window window)
nil))))
(j-display-buffer-and-select display-buffer-reuse-window)
(j-display-buffer-and-select display-buffer-reuse-mode-window)
(j-display-buffer-and-select display-buffer-in-side-window)
(j-display-buffer-and-select display-buffer-at-bottom)
(j-display-buffer-and-select display-buffer-below-selected)
(provide 'j-window)
;;; j-window.el ends here
The popper
package improves handling of windows. Based on the configuration, some windows are popups. They shouldn’t mess up the window layout, can be hidden and displayed easily.
Popups can be displayed based on the context (which can be the current directory, project, etc.). Depending on the context, some popups are visible in a given context and some aren’t. Note that the commands with universal argument prefix (C-u
) are useful as well.
There is a great video on how to use this package.
(use-package popper
:straight t
:config
(setq popper-display-control 'user)
(setq popper-group-function #'popper-group-by-project)
(setq popper-reference-buffers
'("^\\*Apropos\\*"
"^\\*[Hh]elp.*"
"^\\*Messages\\*"
"^\\*\\(Backtrace\\|Warnings\\|Compile-Log\\)\\*"))
(popper-mode +1)
:bind
(("C-." . popper-toggle-latest)
("M-." . popper-cycle)
("C-M-." . popper-toggle-type)))
(defconst j-org-directory "~/org"
"Directory with org-mode files.")
(defconst j-org-inbox-file
(expand-file-name "inbox.org" j-org-directory)
"File with captured and unprocessed TODO items.")
(defconst j-org-projects-file
(expand-file-name "projects.org" j-org-directory)
"File with project TODO items.")
(defconst j-org-actions-file
(expand-file-name "actions.org" j-org-directory)
"File with processed TODO items from inbox file.")
(defconst j-org-repeaters-file
(expand-file-name "repeaters.org" j-org-directory)
"File with TODO items that repeat (habits).")
(use-package org
:straight (:type built-in)
:config
(setq org-directory j-org-directory)
(setq org-imenu-depth 6)
;; General settings.
(setq org-adapt-indentation nil)
(setq org-special-ctrl-a/e nil)
(setq org-special-ctrl-k nil)
(setq org-M-RET-may-split-line '((default . nil)))
(setq org-hide-emphasis-markers nil)
(setq org-hide-macro-markers nil)
(setq org-hide-leading-stars nil)
(setq org-catch-invisible-edits 'show)
(setq org-return-follows-link nil)
(setq org-loop-over-headlines-in-active-region 'start-level)
(setq org-cycle-separator-lines -1)
(setq org-modules '(ol-info org-tempo org-habit))
;; Code blocks.
(setq org-structure-template-alist
'(("s" . "src")
("el" . "src emacs-lisp")
("sh" . "src shell")
("yaml" . "src yaml")
("toml" . "src toml")
("c" . "center")
("C" . "comment")
("e" . "example")
("q" . "quote")
("v" . "verse")))
(setq org-confirm-babel-evaluate nil)
(setq org-src-window-setup 'current-window)
(setq org-edit-src-persistent-message nil)
(setq org-src-fontify-natively t)
(setq org-src-preserve-indentation t)
(setq org-src-tab-acts-natively t)
(setq org-edit-src-content-indentation 0)
;; Links.
(setq org-link-keep-stored-after-insertion t)
;; Todo, tags.
(setq org-todo-keywords
'(;; TODO an item that needs addressing;
;; STARTED being addressed;
;; WAITING dependent on something else happening;
;; DELEGATED someone else is doing it and I need to follow up with them;
;; ASSIGNED someone else has full, autonomous responsibility for it;
;; CANCELLED no longer necessary to finish;
;; DONE complete.
(sequence "TODO(t)" "STARTED(s!)" "WAITING(w@/!)" "DELEGATED(e@/!)" "|"
"ASSIGNED(a@/!)" "CANCELLED(c@/!)" "DONE(d!)")))
(setq org-tag-alist
'(;; ERRAND requires a short trip (deliver package, buy something);
;; CALL requires calling via phone, internet, etc.;
;; REPLY requires replying to an email;
;; VISIT requires a longer trip. ;; TODO
(:startgroup . nil)
("ERRAND" . ?e) ("CALL" . ?c) ("REPLY" . ?r) ("VISIT" . ?v)
(:endgroup . nil)))
(setq org-highest-priority ?A)
(setq org-lowest-priority ?C)
(setq org-default-priority ?B)
(setq org-fontify-done-headline nil)
(setq org-fontify-quote-and-verse-blocks t)
(setq org-fontify-whole-heading-line nil)
(setq org-fontify-whole-block-delimiter-line nil)
(setq org-enforce-todo-dependencies t)
(setq org-enforce-todo-checkbox-dependencies t)
(setq org-track-ordered-property-with-tag t)
;; Logs.
(setq org-log-into-drawer t)
(setq org-log-done 'time)
(setq org-log-redeadline t)
(setq org-log-reschedule t)
(setq org-treat-insert-todo-heading-as-state-change nil)
(setq org-read-date-prefer-future 'time)
;; Capture
(setq org-default-notes-file j-org-inbox-file)
(setq org-capture-templates
`(("t" "TODO task" entry
(file j-org-inbox-file)
,(concat "* TODO %?\n"
":PROPERTIES:\n"
":CREATED: %U\n"
":END:\n"))))
;; Agenda.
(setq org-agenda-files
(list j-org-inbox-file
j-org-projects-file
j-org-actions-file
j-org-repeaters-file))
(setq org-agenda-span 14)
(setq org-agenda-start-on-weekday 1)
(setq org-agenda-confirm-kill t)
(setq org-agenda-show-all-dates t)
(setq org-agenda-show-outline-path nil)
(setq org-agenda-window-setup 'current-window)
(setq org-agenda-restore-windows-after-quit t)
(setq org-agenda-skip-comment-trees t)
(setq org-agenda-menu-show-matcher t)
(setq org-agenda-menu-two-columns nil)
(setq org-agenda-sticky nil)
(setq org-agenda-custom-commands-contexts nil)
(setq org-agenda-max-entries nil)
(setq org-agenda-max-todos nil)
(setq org-agenda-max-tags nil)
(setq org-agenda-max-effort nil)
(setq org-agenda-block-separator ?—)
(setq org-agenda-follow-indirect t)
(setq org-agenda-include-deadlines t)
(setq org-deadline-warning-days 14)
(setq org-agenda-skip-scheduled-if-done nil)
(setq org-agenda-skip-scheduled-if-deadline-is-shown t)
(setq org-agenda-skip-timestamp-if-deadline-is-shown t)
(setq org-agenda-skip-deadline-if-done nil)
(setq org-agenda-skip-deadline-prewarning-if-scheduled 1)
(setq org-agenda-skip-scheduled-delay-if-deadline nil)
(setq org-agenda-skip-additional-timestamps-same-entry nil)
(setq org-agenda-skip-timestamp-if-done nil)
(setq org-agenda-search-headline-for-time t)
(setq org-scheduled-past-days 365)
(setq org-deadline-past-days 365)
(setq org-agenda-time-leading-zero t)
(setq org-agenda-current-time-string
"Now -·-·-·-·-·-·-")
(setq org-agenda-time-grid
'((daily today require-timed)
(0600 0700 0800 0900 1000 1100
1200 1300 1400 1500 1600
1700 1800 1900 2000 2100)
" ....." "-----------------"))
(setq org-agenda-custom-commands
`(("d" "Daily schedule"
((agenda ""
((org-agenda-span 'day)
(org-deadline-warning-days 14)))
(todo "TODO"
((org-agenda-overriding-header
"--- To refile -------------------------------")
(org-agenda-files (list j-org-inbox-file))))
(todo "TODO"
((org-agenda-overriding-header
"--- Projects --------------------------------")
(org-agenda-files (list j-org-projects-file))))
(todo "TODO"
((org-agenda-overriding-header
"--- Actions ---------------------------------")
(org-agenda-files (list j-org-actions-file))
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'deadline 'scheduled)))))
((org-agenda-compact-blocks t)
(org-use-property-inheritance t)))))
(setq org-refile-targets
'((j-org-projects-file :maxlevel . 1)
(j-org-actions-file :level . 0)
(j-org-repeaters-file :level . 0)))
;; Refile.
(setq org-refile-use-outline-path 'file)
(setq org-outline-path-complete-in-steps nil)
(setq org-refile-allow-creating-parent-nodes 'confirm)
(setq org-refile-use-cache t)
;; Habit.
(setq org-habit-preceding-days 10)
(setq org-habit-following-days 5)
(setq org-habit-graph-column 65)
(setq org-habit-show-habits-only-for-today nil)
:bind
(("C-c a" . org-agenda)
("C-c c" . org-capture)
("C-c l" . org-store-link)
(:map org-mode-map
("M-n" . outline-next-visible-heading)
("M-p" . outline-previous-visible-heading)
("C-'" . nil)
("C-," . nil)
("<C-return>" . nil)
("<C-S-return>" . nil)
("<C-M-return>" . org-insert-subheading))))
; (:map org-agenda-mode-map
; ("n" . org-agenda-next-item)
; ("p" . org-agenda-previous-item))))