This is my personal Emacs configuration. It is implemented using literate programming in org-mode. The way this works is that upon Emacs startup:
- The two-liner
init.el
config file is loaded, which in turns calls theorg-babel-load-file
function with this file. - The org file is processed and the code blocks are tangled into a
config.el
file. - The generated configuration file is then loaded. As the
config.el
file is basically a function of this one, I’ve omitted it from version control.
Enable lexical binding.
;; -*- lexical-binding: t -*-
Add personal details.
(setq user-full-name (base64-decode-string "VHVvbW8gVmlyb2xhaW5lbg==")
user-mail-address (base64-decode-string "dHVvbW8udmlyb2xhaW5lbkBzaWlsaS5jb20="))
Is this the work laptop or something else?
(defconst work-laptop-p
(equal (system-name) "siilim211207.local"))
Set configuration file name for later reference.
(defconst config-file "config.org")
Configure path.
(use-package exec-path-from-shell
:ensure t
:init
(when (memq window-system '(mac ns x))
(require 'exec-path-from-shell)
(dolist (var '("SSH_AUTH_SOCK" "SSH_AGENT_PID" "GPG_AGENT_INFO" "LANG" "LC_CTYPE" "JAVA_HOME" "MAVEN_OPTS" "VARMA_GPG_SYMMETRIC_PASSPHRASE"
"VARMA_GPG_SYMMETRIC_PASSPHRASE_PROD" "VARMA_ARTIFACTORY_USERNAME" "VARMA_ARTIFACTORY_PASSWORD" "VARMA_ELAMA_BOT_USERNAME" "VARMA_ELAMA_BOT_PASSWORD"))
(add-to-list 'exec-path-from-shell-variables var))
(exec-path-from-shell-initialize)))
Configure repos.
(setq package-enable-at-startup nil
package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("melpa" . "https://melpa.org/packages/")
("org" . "https://orgmode.org/elpa/")))
(use-package quelpa :ensure t)
(use-package quelpa-use-package :ensure t)
Enable straight.el
.
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name
"straight/repos/straight.el/bootstrap.el"
(or (bound-and-true-p straight-base-dir)
user-emacs-directory)))
(bootstrap-version 7))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
;; (setq straight-base-dir (expand-file-name "etc" user-emacs-directory))
Configure use-package
.
(setq use-package-always-ensure t)
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(eval-when-compile (require 'use-package))
The cursor may blink.
(blink-cursor-mode t)
Disable unnecessary stuff.
(tool-bar-mode -1)
(scroll-bar-mode -1)
(setq inhibit-startup-screen t)
(setq ring-bell-function 'ignore)
(setq use-short-answers t)
(setq initial-scratch-message "")
Set 10MB as the limit for garbage collection.
(setq gc-cons-threshold 10000000)
Warn when opening files bigger than 100MB.
(setq large-file-warning-threshold 100000000)
Prefer latest bytecode.
(setq load-prefer-newer t)
Increase kill-ring capacity.
(setq kill-ring-max 1000)
Replace a selection with yanked content.
(delete-selection-mode 1)
Shut Emacs down when I’m asking, even if processes are running.
(setq confirm-kill-processes nil)
Revert buffers when the underlying file has changed.
(global-auto-revert-mode 1)
(setq global-auto-revert-non-file-buffers t)
Mac-specific settings.
(setq mac-option-modifier 'nil
mac-command-modifier 'meta
mac-function-modifier 'super
select-enable-clipboard t)
Prevent mouse / trackpad input from accidentally resizing fonts.
(global-set-key (kbd "<pinch>") 'ignore)
(global-set-key (kbd "<C-wheel-up>") 'ignore)
(global-set-key (kbd "<C-wheel-down>") 'ignore)
UTF-8 should be preferred everywhere.
(set-charset-priority 'unicode)
(prefer-coding-system 'utf-8)
(set-language-environment "UTF-8")
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
Highlight the current line.
(use-package hl-line
:config
(global-hl-line-mode +1))
Create a directory for savefiles if it doesn’t exist.
(defconst tv-savefile-dir (expand-file-name "etc/savefile" user-emacs-directory))
(unless (file-exists-p tv-savefile-dir)
(make-directory tv-savefile-dir))
Put backup files under etc/.tmp/
.
(setq backup-directory-alist `(("." . ,(expand-file-name "etc/.tmp/backups/"
user-emacs-directory))))
(setq backup-by-copying t)
(setq delete-by-moving-to-trash t)
(setq auto-save-file-name-transforms
`((".*" ,temporary-file-directory t)))
Dump custom settings in a separate file.
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(load custom-file t)
Store miscellaneous files under /etc
.
(setq bookmark-default-file (expand-file-name "etc/bookmarks" user-emacs-directory))
Allow recursive minibuffers, i.e. using minibuffer inside minibuffer. This is useful for example when yanking stuff from the kill-ring using M-y.
(setq enable-recursive-minibuffers t)
Set keybindings for profiler.
(use-package profiler
:bind
("C-x P r" . profiler-report)
("C-x P 1" . profiler-start)
("C-x P 0" . profiler-stop))
Use tree-sitter
.
(setq treesit-extra-load-path `(,(concat user-emacs-directory "var/tree-sitter-dist/")
,(concat user-emacs-directory "var/tree-sitter")))
(use-package tree-sitter
:hook ((tree-sitter-after-on . tree-sitter-hl-mode)
(prog-mode . turn-on-tree-sitter-mode))
:config (require 'tree-sitter-langs)
;; This makes every node a link to a section of code
(setq tree-sitter-debug-jump-buttons t
;; and this highlights the entire sub tree in your code
tree-sitter-debug-highlight-jump-region t))
(use-package tree-sitter-langs
:ensure t
:after tree-sitter)
(use-package treesit-auto
:ensure t
:custom
(treesit-auto-install 'prompt)
:config
(treesit-auto-add-to-auto-mode-alist 'all)
(global-treesit-auto-mode))
Use doom-dracula
theme.
(use-package doom-themes
:ensure t
:config
(load-theme 'doom-dracula t))
Disable custom themes before loading a new one.
(defun disable-custom-themes (&optional theme no-confirm no-enable)
(mapc #'disable-theme custom-enabled-themes))
(advice-add 'load-theme :before #'disable-custom-themes)
Add helpers to switch between themes.
(defun tv/load-light-theme ()
"Load light theme."
(interactive)
(disable-custom-themes)
(load-theme 'doom-solarized-light t)
(set-frame-parameter nil 'ns-appearance 'light))
(defun tv/load-dark-theme ()
"Load dark theme."
(interactive)
(disable-custom-themes)
(load-theme 'doom-dracula t)
(set-frame-parameter nil 'ns-appearance 'dark))
Setup font.
(set-face-attribute 'default nil
:family "SF Mono"
:height 140
:weight 'normal
:width 'normal)
(set-face-attribute 'variable-pitch nil
:family "SF Mono"
:height 140
:weight 'normal
:width 'normal)
(set-face-attribute 'fixed-pitch nil
:family "SF Mono"
:height 140
:weight 'normal
:width 'normal)
(add-to-list 'default-frame-alist '(font . "SF Mono 14"))
(set-face-attribute 'font-lock-comment-face nil :slant 'italic)
(set-face-attribute 'font-lock-keyword-face nil :slant 'italic)
Tabs should never ever be used for indentation. If they are, they should look silly.
(setq-default tab-width 8)
(setq-default indent-tabs-mode nil)
Require a newline at the end.
(setq require-final-newline t)
Show trailing whitespace
(setq show-trailing-whitespace t)
Maximize the frame on startup.
(add-to-list 'initial-frame-alist '(fullscreen . maximized))
No lockfiles.
(setq create-lockfiles nil)
Wrap lines by default. I hate horizontal scrolling.
(setq global-visual-line-mode t)
Make the titlebar transparent.
(set-frame-parameter nil 'ns-transparent-titlebar t)
(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
We are using a dark color theme by default, which the title bar should reflect too.
(set-frame-parameter nil 'ns-appearance 'dark)
(add-to-list 'default-frame-alist '(ns-appearance . 'dark))
Remove the icon from the titlebar.
(setq ns-use-proxy-icon nil)
Suppress compilation warnings.
(setq native-comp-async-report-warnings-errors nil)
Scroll smoothly when using a mouse or trackpad. Which is basically never.
(pixel-scroll-precision-mode)
Setup doom-modeline
.
(use-package doom-modeline
:ensure t
:hook (after-init . doom-modeline-mode)
:config
(display-time-mode 1)
(setq doom-modeline-icon nil)
(setq doom-modeline-time-icon nil)
;; Prevent crazy path expansions.
(setq doom-modeline-project-detection 'project)
(setq doom-modeline-project-detection 'ffip))
(use-package hide-mode-line
:ensure t)
Use symbol-overlay mode.
(use-package symbol-overlay
:ensure t
:hook (prog-mode . symbol-overlay-mode))
Use spacious-padding
.
(use-package spacious-padding
:ensure t
:defer
:hook (after-init . spacious-padding-mode))
Setup line numbers.
(setq display-line-numbers-type 'relative)
(add-hook 'prog-mode-hook #'display-line-numbers-mode)
(add-hook 'conf-mode-hook #'display-line-numbers-mode)
Create missing parent folders automatically. Source: bbatsov.
;; auto-create missing folders
(defun er-auto-create-missing-dirs ()
"Make missing parent directories automatically."
(let ((target-dir (file-name-directory buffer-file-name)))
(unless (file-exists-p target-dir)
(make-directory target-dir t))))
(add-to-list 'find-file-not-found-functions #'er-auto-create-missing-dirs)
We don’t want line numbers to be shown in org-mode buffers, apart from this one. Here, also electric-pair-mode
should be enabled.
(add-hook 'org-mode-hook #'(lambda ()
(interactive)
(when (cl-search config-file (buffer-name))
(electric-pair-mode)
(display-line-numbers-mode))))
Presentation mode scales font sizes up, which is very useful when showing things in meetings etc.
(use-package presentation
:ensure t)
Rainbow mode shows color codes as well as some other values (like the value `red’ in CSS) in color.
(use-package rainbow-mode
:ensure t
:hook (prog-mode . rainbow-mode))
Rainbow-delimiters are useful in lisps.
(use-package rainbow-delimiters
:ensure t
:hook (prog-mode . rainbow-delimiters-mode))
A mode for reading epub files.
(use-package nov
:ensure t
:defer
:config
(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode)))
Olivetti mode squeezes the buffer into a column of limited width. This helps readability.
(use-package olivetti
:ensure t
:custom
(olivetti-body-width 94))
(use-package pulse
:ensure nil
:init
(defun pulse-line (&rest _)
"Pulse the current line."
(pulse-momentary-highlight-one-line (point)))
(dolist (command '(scroll-up-command
scroll-down-command
evil-scroll-up
evil-scroll-down
windmove-left
windmove-right
windmove-up
windmove-down
move-to-window-line-top-bottom
recenter-top-bottom
other-window))
(advice-add command :after #'pulse-line)))
(add-hook 'before-save-hook #'delete-trailing-whitespace)
(setq-default sentence-end-double-space nil)
(use-package sudo-edit
:ensure t
:defer
:config
(global-set-key (kbd "C-c C-r") 'sudo-edit))
Vim keybindings here.
(use-package evil
:ensure t
:demand t
:bind (("<escape>" . keyboard-escape-quit))
:init
(setq evil-want-keybinding nil)
;; no vim insert bindings
:config
(evil-mode 1)
(evil-set-undo-system 'undo-tree)
;; (evil-set-undo-system 'undo-redo)
(setq evil-split-window-below t
evil-vsplit-window-right t)
(setq evil-ex-substitute-global t)
(setq evil-kill-on-visual-paste nil)
(setq evil-shift-width 2))
(use-package evil-collection
:ensure t
:after evil
:config
(setq evil-want-integration t)
(evil-collection-init))
(defconst tv/undo-dir-name "etc/undo")
(defconst tv/undo-dir (expand-file-name tv/undo-dir-name user-emacs-directory))
(unless (file-exists-p tv/undo-dir)
(make-directory tv/undo-dir))
(use-package undo-tree
:ensure t
:hook (after-init . global-undo-tree-mode)
:config
;; Prevent undo tree files from polluting your git repo
(setq undo-tree-history-directory-alist `(("." . ,tv/undo-dir)))
(setq undo-tree-enable-undo-in-region nil))
Evil-surround.
(use-package evil-surround
:ensure t
:after evil
:config
(global-evil-surround-mode 1))
Comment out / in stuff easily.
(use-package evil-commentary
:ensure t
:after evil
:config
(evil-commentary-mode))
evil-owl
provides a view to register contents.
(use-package evil-owl
:ensure t
:after evil
:config
(setq evil-owl-max-string-length 500)
(setq evil-owl-idle-delay 0.5)
(add-to-list 'display-buffer-alist
'("*evil-owl*"
(display-buffer-in-side-window)
(side . bottom)
(window-height . 0.3)))
(evil-owl-mode))
(with-eval-after-load 'evil
(defalias #'forward-evil-word #'forward-evil-symbol)
;; make evil-search-word look for symbol rather than word boundaries
(setq-default evil-symbol-word-search t)
(define-key isearch-mode-map (kbd "<up>") 'isearch-ring-retreat)
(define-key isearch-mode-map (kbd "<down>") 'isearch-ring-advance))
This maybe fixes some indentation issues in org mode.
(setq evil-want-c-i-jump nil)
Balance windows automatically.
(seq-doseq (fn (list #'split-window #'delete-window))
(advice-add fn
:after
#'(lambda (&rest _args) (balance-windows))))
Magit is the Git package.
(defun tv/kill-magit-diff-buffer-in-current-repo (&rest _)
"Delete the magit-diff buffer related to the current repo."
(let ((magit-diff-buffer-in-current-repo
(magit-mode-get-buffer 'magit-diff-mode)))
(kill-buffer magit-diff-buffer-in-current-repo)))
(defun tv/mu-magit-kill-buffers ()
"Restore window configuration and kill all Magit buffers."
(interactive)
(let ((buffers (magit-mode-get-buffers)))
(magit-restore-window-configuration)
(mapc #'kill-buffer buffers)))
(use-package magit
:defer
:ensure t
:config
(add-hook 'git-commit-setup-hook
(lambda ()
(add-hook 'with-editor-post-finish-hook
#'tv/kill-magit-diff-buffer-in-current-repo
nil t)))
(evil-define-key 'normal magit-status-mode-map
"q" #'tv/mu-magit-kill-buffers)
(add-hook 'magit-post-refresh-hook
#'git-gutter:update-all-windows))
(use-package git-gutter
:ensure t
:defer
:hook (after-init . global-git-gutter-mode))
(use-package git-timemachine
:ensure t
:defer
:bind (("s-g" . git-timemachine)))
Use which-key
, in minibuffer.
(use-package which-key
:ensure t
:hook (after-init . which-key-mode)
:custom
(which-key-idle-delay 0.5)
:config
(which-key-setup-minibuffer))
Make ESC
quit wherever possible.
(defun minibuffer-keyboard-quit ()
"Abort recursive edit.
In Delete Selection mode, if the mark is active, just deactivate it;
then it takes a second \\[keyboard-quit] to abort the minibuffer."
(interactive)
(if (and delete-selection-mode transient-mark-mode mark-active)
(setq deactivate-mark t)
(when (get-buffer "*Completions*") (delete-windows-on "*Completions*"))
(abort-recursive-edit)))
(define-key evil-normal-state-map [escape] 'keyboard-quit)
(define-key evil-visual-state-map [escape] 'keyboard-quit)
(define-key minibuffer-local-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-ns-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-completion-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-must-match-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-isearch-map [escape] 'minibuffer-keyboard-quit)
A handful of bindings inspired by Doom Emacs / Spacemacs.
(evil-set-leader 'normal (kbd "SPC"))
(defvar my-leader-map (make-sparse-keymap)
"Keymap for \"leader key\" shortcuts.")
(define-key evil-normal-state-map (kbd "SPC") my-leader-map)
(define-key my-leader-map "b" 'list-buffers)
(define-key evil-normal-state-map (kbd "SPC h") help-map)
(define-key my-leader-map (kbd "RET") 'consult-bookmark)
(define-key my-leader-map "<" 'consult-buffer)
(define-key my-leader-map "z" 'consult-recent-file)
(define-key my-leader-map "," 'avy-goto-char-timer)
(define-key my-leader-map "." 'consult-line)
(define-key my-leader-map "x" 'consult-imenu)
A handful of must-have keybindings for me.
(evil-define-key 'normal 'global (kbd "ö") 'save-buffer)
(evil-define-key 'normal 'global (kbd "ä") 'delete-other-windows)
;; Grep across open buffers by setting "." as the file regex.
(evil-define-key 'normal 'global (kbd "M-ä") 'multi-occur-in-matching-buffers)
(evil-define-key 'normal 'global (kbd "C-ä") 'split-window-right)
(evil-define-key 'normal 'global (kbd "C-ö") 'split-window-below)
(evil-define-key 'normal 'global (kbd "Ö") 'xref-find-definitions)
(evil-define-key 'normal 'global (kbd "å") 'consult-yank-pop)
(evil-define-key 'normal 'global (kbd "M-ö") 'evil-end-of-line) ;; $ is too unwieldy on a Scandinavian keyboard
(evil-define-key 'normal 'global (kbd "¨") 'evil-search-forward)
(evil-define-key 'normal 'global (kbd "C-j") 'evil-window-next)
(evil-define-key 'normal 'global (kbd "C-k") 'evil-window-prev)
(evil-define-key 'normal 'global (kbd "C-h") 'evil-window-left) ;; Yes! I did it, rebound C-h. SPC h in normal mode is the alternative.
(evil-define-key 'normal 'global (kbd "C-l") 'evil-window-right)
(evil-define-key 'insert 'global (kbd "C-j") 'evil-window-next)
(evil-define-key 'insert 'global (kbd "C-k") 'evil-window-prev)
(evil-define-key 'insert 'global (kbd "C-h") 'evil-window-left)
(evil-define-key 'insert 'global (kbd "C-l") 'evil-window-right)
(evil-define-key 'normal 'global (kbd "C-u") 'evil-scroll-up)
(evil-define-key 'normal 'global (kbd "DEL") 'paredit-splice-sexp)
(evil-define-key 'normal 'global (kbd "´") 'kill-buffer)
(evil-define-key 'normal 'global (kbd "C-M--") 'ibuffer)
Frame management.
(evil-define-key 'normal 'global (kbd "M-§") 'other-frame)
(evil-define-key 'normal 'global (kbd "M-n") 'make-frame)
(evil-define-key 'normal 'global (kbd "M-°") 'delete-frame)
Window management. Source: Mastering Emacs.
(setq switch-to-buffer-obey-display-actions t)
(setq switch-to-buffer-in-dedicated-window 'pop)
(defun tv/toggle-window-dedication ()
"Toggles window dedication in the selected window."
(interactive)
(set-window-dedicated-p (selected-window)
(not (window-dedicated-p (selected-window)))))
Easy buffer switching.
(evil-define-key 'normal 'global (kbd "C-M-l") 'next-buffer)
(evil-define-key 'normal 'global (kbd "C-M-h") 'previous-buffer)
Jump to previous positions from the mark ring by repeating the command.
(setq set-mark-command-repeat-pop t)
(use-package flycheck
:ensure
:defer
:bind (:map flycheck-mode-map
("C-c C-n" . flycheck-next-error)
("C-c C-p" . flycheck-previous-error)))
Use LSP when editing shell scripts.
(add-hook 'bash-ts-mode-hook #'lsp)
(setq sh-basic-offset 2)
Bats is a testing framework for Bash. .bats
-files should be considered as Bash files.
(add-to-list 'auto-mode-alist '("\\.bats\\'" . bash-ts-mode))
Configure the necessary packages.
(use-package paredit
:ensure t
:config
(add-hook 'emacs-lisp-mode-hook #'paredit-mode)
;; enable in the *scratch* buffer
(add-hook 'lisp-interaction-mode-hook #'paredit-mode)
(add-hook 'lisp-mode-hook #'paredit-mode))
(defun initialize-kondo ()
(dolist (checker '(clj-kondo-clj clj-kondo-cljs clj-kondo-cljc clj-kondo-edn))
(setq flycheck-checkers (cons checker (delq checker flycheck-checkers)))))
(defun my-clojure-mode-hook ()
(let ((modes (list #'paredit-mode #'subword-mode #'electric-pairs-mode
#'rainbow-delimiters-mode #'flycheck-mode
#'subword-mode)))
(dolist (mode modes)
(mode 1))))
(use-package clojure-mode
:ensure t
:config
(define-clojure-indent
(returning 1)
(testing-dynamic 1)
(testing-print 1)
(POST 2)
(GET 2)
(PATCH 2)
(PUT 2)))
(use-package inf-clojure
:ensure t
:config
(add-hook 'inf-clojure-mode-hook #'paredit-mode)
(add-hook 'inf-clojure-mode-hook #'rainbow-delimiters-mode))
(use-package cider
:ensure t
:config
(setq nrepl-log-messages t)
(add-hook 'cider-repl-mode-hook #'paredit-mode)
(add-hook 'cider-repl-mode-hook #'rainbow-delimiters-mode))
(defun my-cider-repl-mode-hook ()
(paredit-mode 1)
(evil-local-set-key 'insert (kbd "C-<return>") 'paredit-RET)
(evil-local-set-key 'insert (kbd "RET") 'cider-repl-closing-return)
(setq cider-repl-buffer-size-limit 20000))
(setq gc-cons-threshold (* 100 1024 1024)
read-process-output-max (* 1024 1024)
cider-font-lock-dynamically nil
cider-repl-buffer-size-limit 1000
;; lsp-lens-enable nil ; Show the "1 references" etc text above definitions.
;; lsp-enable-indentation nil ; uncomment to use cider indentation instead of lsp
;; lsp-completion-enable nil ; uncomment to use cider completion instead of lsp
)
(add-hook 'cider-repl-mode-hook #'my-cider-repl-mode-hook)
;; (add-hook 'clojure-ts-mode-hook #'my-clojure-mode-hook)
(add-hook 'clojurescript-mode-hook #'paredit-mode)
(add-hook 'clojurescript-mode-hook #'subword-mode)
(add-hook 'clojurescript-mode-hook #'flycheck-mode)
(add-hook 'clojurescript-mode-hook #'rainbow-delimiters-mode)
(add-hook 'clojurescript-mode-hook #'electric-pair-mode)
(add-hook 'clojure-mode-hook #'lsp)
(add-hook 'clojurescript-mode-hook #'lsp)
(add-hook 'clojure-mode-hook #'hs-minor-mode)
(add-hook 'clojurescript-mode-hook #'hs-minor-mode)
Configure jet.el.
(use-package jet
:ensure t
:defer)
Set keybindings.
(evil-define-key 'normal clojure-mode-map
"°" #'cider-eval-buffer
"§" #'cider-eval-defun-at-point
"Ö" #'cider-find-var
"q" #'cider-popup-buffer-quit
"K" #'cider-doc)
(setq sql-postgres-login-params nil)
(setq lsp-sqls-workspace-config-path nil)
(defun tv/maybe-highlight-ms-sql-kws ()
"Highlight MS SQL keywords when it's certain that's the dialect we're
working with."
(when (cl-search "umaija" (buffer-file-name))
(sql-highlight-ms-keywords)))
(use-package sql
:ensure t
:hook ((sql-mode . sqlup-mode)
(sql-interactive-mode . sqlup-mode)
(sql-mode. lsp))
:defer
:config
(setq lsp-sqls-workspace-config-path nil)
(tv/maybe-highlight-ms-sql-kws))
Use Emacs SQL indent minor mode.
(use-package sql-indent
:ensure t
:after sql
:defer)
(use-package sqlup-mode
:ensure t
:after sql
:defer)
Custom functions for formatting SQL code.
(defun tv/indent-sql-buffer ()
"Since there's some bug that breaks the indentation (`sqlind-indent-line`
specifically) when running it with `newline-and-indent`, I've resorted
to this hack to run the indentation for the whole buffer."
(interactive)
(sqlind-minor-mode)
(indent-region (point-min) (point-max))
(setq sqlind-minor-mode nil)
(progn
(kill-local-variable 'indent-line-function)
(kill-local-variable 'align-mode-rules-list)))
(defun tv/format-sql-buffer ()
(interactive)
;; (tv/indent-sql-buffer)
(sqlup-capitalize-keywords-in-region (point-min) (point-max)))
(evil-define-key 'normal sql-mode-map
"ö" #'(lambda ()
(interactive)
(when (< (buffer-size) 100000)
(tv/format-sql-buffer))
(save-buffer)))
Elisp keybindings.
(evil-define-key 'normal emacs-lisp-mode-map
"°" 'eval-buffer
"§" 'eval-defun)
(evil-define-key 'normal lisp-interaction-mode-map
"°" 'eval-buffer
"§" 'eval-defun)
(use-package ielm
:config
(add-hook 'ielm-mode-hook #'rainbow-delimiters-mode)
(add-hook 'ielm-mode-hook #'(lambda ()
(setq-local corfu-auto nil)
(corfu-mode))))
(use-package typescript-mode
:ensure t
:defer
:custom
(typescript-indent-level 2))
(use-package lsp-java :config (add-hook 'java-mode-hook 'lsp))
(use-package dap-mode :after lsp-mode :config (dap-auto-configure-mode))
(use-package dap-java :ensure nil)
(use-package python-black
:ensure t
:after python
:hook ((python-mode . python-black-on-save-mode)
(python-mode . lsp)
(python-ts-mode . python-black-on-save-mode)
(python-ts-mode . lsp))
:config (setq lsp-pylsp-plugins-flake8-ignore '("D103" "D100" "D105")))
;; (add-hook 'python-mode-hook #'lsp)
;; (add-hook 'python-ts-mode-hook #'lsp)
(use-package lsp-mode
:hook ((lsp-mode . lsp-enable-which-key-integration))
:config (setq lsp-completion-enable-additional-text-edit nil
lsp-lens-enable t
lsp-auto-guess-root t
lsp-headerline-breadcrumb-enable nil
lsp-modeline-code-actions-enable t))
(setq eldoc-echo-area-use-multiline-p nil)
(use-package verb
:ensure t
:defer)
(use-package no-littering
:ensure t)
This makes etags work, i.e. allows us to jump to definitions.
(use-package counsel-etags
:ensure t
:bind (("C-]" . counsel-etags-find-tag-at-point))
:init
(add-hook 'prog-mode-hook
(lambda ()
(add-hook 'after-save-hook
'counsel-etags-virtual-update-tags 'append 'local)))
:config
(setq counsel-etags-update-interval 60)
(push "build" counsel-etags-ignore-directories))
Pick up formatting settings from .editorconfig
files.
(use-package editorconfig
:ensure t
:config
(editorconfig-mode 1))
Ripgrep package is needed for projectile-ripgrep
to be usable.
(use-package ripgrep
:ensure t
:config
(evil-define-key 'normal 'global "Ä" #'consult-ripgrep))
Writable grep. This makes possible to use workflows for search and replace like:
- Do a grep (e.g.
projectile-ripgrep
). wgrep-change-to-wgrep-mode
(ori
).query-replace-regexp
(use-package wgrep
:ensure t
:after evil-collection
:config
(evil-collection-define-key 'normal 'wgrep-mode-map
"d" 'wgrep-mark-deletion
"U" 'wgrep-remove-all-change))
(use-package vertico
:ensure t
:hook (rfn-eshadow-update-overlay . vertico-directory-tidy)
:init
(vertico-mode)
(setq vertico-cycle t))
(use-package vertico-multiform
:ensure nil
:hook (after-init . vertico-multiform-mode))
(use-package dabbrev
:custom
(dabbrev-upcase-means-case-search t)
(dabbrev-check-all-buffers nil)
(dabbrev-check-other-buffers t)
(dabbrev-friend-buffer-function 'dabbrev--same-major-mode-p)
(dabbrev-ignored-buffer-regexps '("\\.\\(?:pdf\\|jpe?g\\|png\\)\\'")))
(use-package corfu
:ensure t
;; Optional customizations
:custom
(corfu-cycle t) ;; Enable cycling for `corfu-next/previous'
(corfu-auto t) ;; Enable auto completion
(corfu-auto-prefix 2)
(corfu-auto-delay 0.2)
(corfu-on-exact-match 'insert) ;; Insert when there's only one match
(corfu-quit-no-match t) ;; Quit when there is no bind
:init
(setq corfu-quit-at-boundary 'separator)
(global-corfu-mode)
(corfu-history-mode))
(use-package cape
:ensure t
:init
(setq cape-dabbrev-min-length 2)
(setq cape-dabbrev-check-other-buffers 'cape--buffers-major-mode)
(add-to-list 'completion-at-point-functions #'cape-dabbrev)
(add-to-list 'completion-at-point-functions #'cape-keyword)
(add-to-list 'completion-at-point-functions #'cape-history)
(add-to-list 'completion-at-point-functions #'cape-file)
(add-to-list 'completion-at-point-functions #'cape-elisp-block)
(defun corfu-enable-always-in-minibuffer ()
"Enable Corfu in the minibuffer if Vertico/Mct are not active."
(unless (or (bound-and-true-p mct--active)
(bound-and-true-p vertico--input)
(eq (current-local-map) read-passwd-map))
(setq-local corfu-auto nil) ;; Enable/disable auto completion
(setq-local corfu-echo-delay nil ;; Disable automatic echo and popup
corfu-popupinfo-delay nil)
(corfu-mode 1)))
(add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)
:bind ("C-c SPC" . cape-dabbrev))
(use-package emacs
:init
;; TAB cycle if there are only few candidates
(setq completion-cycle-threshold 3)
;; Emacs 28: Hide commands in M-x which do not apply to the current mode.
;; Corfu commands are hidden, since they are not supposed to be used via M-x.
;; (setq read-extended-command-predicate
;; #'command-completion-default-include-p)
;; Enable indentation+completion using the TAB key.
;; `completion-at-point' is often bound to M-TAB.
(setq tab-always-indent 'complete))
(use-package orderless
:ensure t
:init
(setq completion-styles '(orderless basic)
completion-category-defaults nil
completion-category-overrides '((file (styles partial-completion)))))
(use-package consult
:ensure t
:bind (("C-å" . consult-line)
("C-c M-x" . consult-mode-command)
("C-x b" . consult-buffer)
("C-x r b" . consult-bookmark)
("M-y" . consult-yank-pop)
;; M-g bindings (goto-map)
("M-g M-g" . consult-goto-line)
("M-g o" . consult-outline) ;; Alternative: consult-org-heading
("M-g m" . consult-mark)
("M-g k" . consult-global-mark)
("C-z" . consult-theme)
:map minibuffer-local-map
("M-s" . consult-history) ;; orig. next-matching-history-element
("M-r" . consult-history)
:map prog-mode-map
("M-g o" . consult-imenu))
:init
(defun remove-items (x y)
(setq y (cl-remove-if (lambda (item) (memq item x)) y))
y)
;; Any themes that are incomplete/lacking don't work with centaur tabs/solair mode
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
(setq consult-narrow-key "<")
(setq consult-line-start-from-top nil))
(use-package avy
:bind (("C-s" . avy-goto-char-timer)))
(use-package marginalia
:ensure
:init
(marginalia-mode))
(use-package embark
:ensure t
:bind
(("C-." . embark-act) ;; pick some comfortable binding
("C-;" . embark-dwim) ;; good alternative: M-.
("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'
:init
;; Optionally replace the key help with a completing-read interface
(setq prefix-help-command #'embark-prefix-help-command)
;; Show the Embark target at point via Eldoc. You may adjust the
;; Eldoc strategy, if you want to see the documentation from
;; multiple providers. Beware that using this can be a little
;; jarring since the message shown in the minibuffer can be more
;; than one line, causing the modeline to move up and down:
;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target)
;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)
:config
;; Hide the mode line of the Embark live/completions buffers
(add-to-list 'display-buffer-alist
'("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
nil
(window-parameters (mode-line-format . none)))))
;; Consult users will also want the embark-consult package.
(use-package embark-consult
:ensure t ; only need to install it, embark loads it after consult if found
:hook
(embark-collect-mode . consult-preview-at-point-mode))
Yasnippets are very handy, and Doom Emacs contains a nice bundle of them.
(use-package yasnippet
:diminish yas-minor-mode
:init (yas-global-mode)
:config
(push '(yasnippet backquote-change) warning-suppress-types)
(yas-global-mode)
(add-hook 'hippie-expand-try-functions-list 'yas-hippie-try-expand)
(setq yas-key-syntaxes '("w_" "w_." "^ "))
(setq yas-installed-snippets-dir ".config/emacs/etc/snippets")
(setq yas-expand-only-for-last-commands nil)
(yas-global-mode 1)
(bind-key "\t" 'hippie-expand yas-minor-mode-map))
(use-package doom-snippets
:after yasnippet
:straight (doom-snippets :type git :host github :repo "doomemacs/snippets" :files ("*.el" "*")))
Remember and restore the last cursor location of opened files.
(use-package saveplace
:config
(setq save-place-file (expand-file-name "saveplace" tv-savefile-dir))
;; activate it for all buffers
(setq-default save-place t))
Remember where we were in the minibuffer.
(use-package savehist
:config
(setq savehist-additional-variables
;; search entries
'() ;;'(search-ring regexp-search-ring kill-ring)
;; save every minute
savehist-autosave-interval 60
;; keep the home clean
history-length 25
savehist-save-minibuffer-history 1
savehist-file (expand-file-name "savehist" tv-savefile-dir))
(savehist-mode +1))
Enable recentf-mode
.
(use-package recentf
:config
(setq recentf-save-file (expand-file-name "recentf" tv-savefile-dir)
recentf-max-saved-items 500
recentf-max-menu-items 15
;; disable recentf-cleanup on Emacs start, because it can cause
;; problems with remote files
recentf-auto-cleanup 'never)
(recentf-mode +1))
(use-package dockerfile-mode
:ensure t
:defer)
(use-package docker
:ensure t
:defer
:bind ("C-c d" . docker))
(use-package markdown-mode
:ensure t
:hook (markdown-mode . display-line-numbers-mode)
:mode ("README\\.md\\'" . gfm-mode)
:init (setq markdown-command "multimarkdown"))
(use-package yaml-ts-mode
:ensure nil
:hook (yaml-ts-mode . display-line-numbers-mode)
:mode
("\\.yml\\'" . yaml-ts-mode)
("\\.yaml\\'" . yaml-ts-mode))
Use 4 spaces as the default indentation level.
(add-hook 'nxml-mode-hook #'display-line-numbers-mode)
(setq nxml-child-indent 4 nxml-attribute-indent 4)
(use-package org
:defer
:custom
(fill-column 100)
;; Disable the underscore-to-subscript thing.
(org-pretty-entities nil)
(org-log-done 'time)
(org-log-into-drawer t)
(org-startup-folded 'nofold)
(org-todo-keywords
'((sequence "TODO(t)" "PROJ(p)" "LOOP(r)" "STRT(s)" "WAIT(w)"
"HOLD(h)" "IDEA(i)" "DOING(g)" "|" "DONE(d)" "KILL(k)")
(sequence "[ ](T)" "[-](S)" "[?](W)" "|" "[X](D)")
(sequence "|" "OKAY(o)" "YES(y)" "NO(n)")))
(org-done ((t (:foreground "PaleGreen"
:strike-through t))))
(org-tags-column 0)
(custom-set-faces
'(org-level-1 ((t (:inherit outline-1 :height 1.30))))
'(org-level-2 ((t (:inherit outline-2 :height 1.25))))
'(org-level-3 ((t (:inherit outline-3 :height 1.20))))
'(org-level-4 ((t (:inherit outline-4 :height 1.15))))
'(org-level-5 ((t (:inherit outline-5 :height 1.10))))
'(org-level-6 ((t (:inherit outline-6 :height 1.05))))
'(org-level-7 ((t (:inherit outline-7 :height 1.00)))))
(org-todo-keyword-faces
'(("AREA" . "DarkOrchid1")
("[AREA]" . "DarkOrchid1")
("PROJECT" . "DarkOrchid1")
("[PROJECT]" . "DarkOrchid1")
("INBOX" . "cyan")
("[INBOX]" . "cyan")
("PROPOSAL" . "orange")
("[PROPOSAL]" . "orange")
("DRAFT" . "yellow3")
("[DRAFT]" . "yellow3")
("INPROGRESS" . "yellow4")
("[INPROGRESS]" . "yellow4")
("MEETING" . "purple")
("[MEETING]" . "purple")
("CANCELED" . "blue")
("[CANCELED]" . "blue")))
:config
(define-key org-mode-map (kbd "C-c C-r") verb-command-map)
(evil-define-key 'normal org-mode-map
(kbd "M-l") #'org-metaright
(kbd "M-h") #'org-metaleft
(kbd "M-k") #'org-metaup
(kbd "M-j") #'org-metadown
(kbd "M-L") #'org-shiftmetaright
(kbd "M-H") #'org-shiftmetaleft
(kbd "M-K") #'org-shiftmetaup
(kbd "M-J") #'org-shiftmetadown
(kbd "§") #'verb-send-request-on-point-other-window-stay)
(setq org-directory "~/Dropbox/org/")
(setq org-default-notes-file (concat org-directory "inbox.org"))
(setq org-archive-location "archive/Archive_%s::")
(setq org-ellipsis " ▾")
(setq org-src-fontify-natively t)
(setq org-superstar-headline-bullets-list '("› "))
(setq org-agenda-start-with-log-mode t)
(setq org-cycle-emulate-tab nil)
(org-babel-do-load-languages
'org-babel-load-languages
'((sql . t)
(sqlite . t)
(python . t)
(java . t)
(C . t)
(emacs-lisp . t)
(clojure . t)
(shell . t)))
(setq org-src-preserve-indentation nil
org-edit-src-content-indentation 0
org-indent-mode t)
(setq org-capture-templates
'(("f" "Fleeting note" item
(file+headline org-default-notes-file "Notes")
"- %?"
:jump-to-captured t)
("t" "New task" entry
(file+headline org-default-notes-file "Tasks")
"* TODO %i%?")))
(global-set-key (kbd "C-c c") 'org-capture)
;; https://github.com/zzamboni/dot-emacs/blob/master/init.org
:hook ((org-mode . visual-line-mode)
(org-mode . org-indent-mode)))
;; From elken
(defun tv/org-archive-done-tasks ()
"Attempt to archive all done tasks in file"
(interactive)
(org-map-entries
(lambda ()
(org-archive-subtree)
(setq org-map-continue-from (org-element-property :begin (org-element-at-point))))
"/DONE" 'file))
(defun tv/org-remove-kill-tasks ()
(interactive)
(org-map-entries
(lambda ()
(org-cut-subtree)
(pop kill-ring)
(setq org-map-continue-from (org-element-property :begin (org-element-at-point))))
"/KILL" 'file))
(evil-define-key 'normal org-mode-map
(kbd "C-c DEL a") #'tv/org-archive-done-tasks
(kbd "C-c DEL k") #'tv/org-remove-kill-tasks)
(use-package hl-todo
:ensure t
:defer
:hook ((org-mode . hl-todo-mode)
(prog-mode . hl-todo-mode)))
(use-package org-appear
:ensure t
:defer
:after org
:custom
(org-appear-autoemphasis t)
(org-appear-autosubmarkers t)
:hook (org-mode . org-appear-mode)
:config
(setq org-hide-emphasis-markers t) ;; Must be activated for org-appear to work
(setq org-appear-autoemphasis t ;; Show bold, italics, verbatim, etc.
org-appear-autolinks t ;; Show links
org-appear-autosubmarkers t))
(use-package evil-org
:ensure t
:after org
:hook (org-mode . evil-org-mode)
:config
(require 'evil-org-agenda)
(evil-org-agenda-set-keys)
(defun tv/org-todo-toggle-or-open-link ()
"Open link or toggle a TODO, depending on which one is under point."
(interactive)
(let ((type (car (org-element-context))))
(if (eq 'link type)
(org-open-at-point)
(progn
(let ((state (org-get-todo-state)))
(cond ((string= state "[ ]") (org-todo "[X]"))
((string= state "[X]") (org-todo "[ ]"))
((string= state "TODO") (org-todo "DOING"))
((string= state "DOING") (org-todo "DONE"))
((string= state "DONE") (org-todo "TODO"))
(t (message state)))
(org-flag-subtree t))))))
(evil-define-key 'normal org-mode-map
(kbd "RET") #'tv/org-todo-toggle-or-open-link))
(use-package org-roam
:ensure t
:defer
:custom
(org-roam-v2-ack t)
(org-roam-tag-sources '(prop))
(org-roam-db-update-method 'immediate)
:hook (after-init . org-roam-db-autosync-mode)
:bind (:map global-map
(("C-c n i" . org-roam-node-insert)
("C-c n f" . org-roam-node-find)
("C-c n n" . org-roam-capture)
("C-c n d" . org-roam-dailies-capture-today)
("C-c n s" . consult-org-roam-search)))
:config
(setq org-roam-node-display-template (concat "${title:50} " (propertize "${tags:50}" 'face 'org-tag)))
(setq org-roam-db-location (expand-file-name "etc/org-roam.db" user-emacs-directory))
(setq org-roam-directory "~/Dropbox/org/roam")
(setq org-roam-capture-templates
`(("n" "default note" plain "%?"
:if-new
(file+head "%<%Y%m%d%H%M%S>-${slug}.org"
"#+title: ${title}\n#+date: %t\n#+filetags: \n\n ")
:unnarrowed t)
("b" "book" plain "%?"
:if-new
(file+head "%<%Y%m%d%H%M%S>-${slug}.org"
"#+author: ${author}\n#+title: ${title}\n#+subtitle: \n#+date: %t\n#+origin: ${origin}\n#+category: \n#+filetags: :kirjat:\n\n")
:unnarrowed t)
("p" "project" plain "* Goals\n\n%?\n\n* Tasks\n\n** TODO Add initial tasks\n\n* Dates\n\n"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: project")
:unnarrowed t)
("w" "work/bug" plain "%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+date: %t\n#+filetags: :bugit:työ:verb:")
:unnarrowed t)
("m" "meeting" plain "%?"
:if-new
(file+head "%<%Y%m%d%H%M%S>-${slug}.org"
"#+title: %^{title}\n#+present: %^{present} \n#+date: %t\n#+category: \n#+filetags: :työ:\n\n ")
:unnarrowed t))))
(use-package org-agenda
:after org
:ensure nil
:bind (("C-c a" . org-agenda))
;; :hook (org-agenda-finalize . org-agenda-entry-text-mode)
:custom
(org-agenda-current-time-string (if (and (display-graphic-p)
(char-displayable-p ?←)
(char-displayable-p ?─))
"← now"
"now - - - - - - - - - - - - - - - - - - - - - - - - -"))
(org-agenda-timegrid-use-ampm t)
(org-agenda-tags-column 0)
(org-agenda-window-setup 'only-window)
(org-agenda-restore-windows-after-quit t)
(org-agenda-log-mode-items '(closed clock state))
(org-agenda-time-grid '((daily today require-timed)
(600 800 1000 1200 1400 1600 1800 2000)
" ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄"))
;; (org-agenda-start-with-log-mode '(closed clock state))
(org-agenda-files (list org-default-notes-file))
;; (org-agenda-todo-ignore-scheduled 'future)
;; TODO entries that can't be marked as done b/c of children are shown as dimmed in agenda view
(org-agenda-dim-blocked-tasks 'invisible)
;; Start the week view on whatever day im on
(org-agenda-start-on-weekday nil)
;; How to identify stuck/non-stuck projects
;; Projects are identified by the 'project' tag and its always the first level
;; Next any of these todo keywords means it's not a stuck project
;; 3rd, theres no tags that I use to identify a stuck Project
;; Finally, theres no special text that signify a non-stuck project
(org-stuck-projects
'("+project+LEVEL=1"
("IN-PROGRESS" "WAITING" "DONE" "CANCELED" "DELEGATED")
nil
""))
(org-agenda-prefix-format
'((agenda . " %-4e %i %-12:c%?-12t% s ")
(todo . " %i %-10:c %-5e %(get-schedule-or-deadline-if-available)")
(tags . " %i %-12:c")
(search . " %i %-12:c")))
;; Lets define some custom cmds in agenda menu
(org-agenda-custom-commands
'(("h" "Agenda and Home tasks"
((agenda "" ((org-agenda-span 2)))
(todo "WAITING|IN-PROGRESS")
(tags-todo "inbox|break")
(todo "NEXT"))
((org-agenda-sorting-strategy '(time-up habit-up priority-down category-up))))
("w" "Agenda and break|inbox tasks"
((agenda "" ((org-agenda-span 1)))
(tags-todo "inbox|break"))
((org-agenda-sorting-strategy '(time-up habit-up priority-down category-up))))
("i" "In-Progress Tasks"
((todo "IN-PROGRESS|WAITING")
(agenda ""))
((org-agenda-sorting-strategy '(time-up habit-up priority-down category-up))))
("g" "Goals: 12 Week Year"
((agenda "")
(todo "IN-PROGRESS|WAITING"))
((org-agenda-sorting-strategy '(time-up habit-up priority-down category-up))
(org-agenda-tag-filter-preset '("+12WY"))
(org-agenda-start-with-log-mode '(closed clock state))
(org-agenda-archives-mode t)
))
("r" "Weekly Review"
((agenda "")
(todo))
((org-agenda-sorting-strategy '(time-up habit-up category-up priority-down ))
(org-agenda-files "~/Dropbox/org/weekly-reivew-agenda-files.org")
(org-agenda-include-diary nil)))))
:init
;; Originally from here: https://stackoverflow.com/a/59001859/2178312
(defun get-schedule-or-deadline-if-available ()
(let ((scheduled (org-get-scheduled-time (point)))
(deadline (org-get-deadline-time (point))))
" ")))
(use-package org-present
:ensure t
:defer)
RSS feeds are a convenient way to consume information on a pull-basis from different sources. I can’t be bothered to tweak the stock elfeed experience too much.
(use-package elfeed
:defer
:ensure t
:init
(elfeed-org)
:config
(setq elfeed-search-filter "@2-week-ago +unread")
(evil-define-key 'normal elfeed-search-mode-map
(kbd "M-RET") #'elfeed-search-browse-url
(kbd "DEL") #'tv/elfeed-mark-read
(kbd "M-DEL") #'tv/elfeed-mark-all-as-read
"§" #'elfeed-update))
(use-package elfeed-org
:defer
:ensure t
:config
(setq rmh-elfeed-org-files (list "~/Dropbox/org/elfeed.org")))
(defun tv/elfeed-mark-all-as-read ()
"Mark all elfeed items as read."
(interactive)
(when (equal 'elfeed-search-mode major-mode)
(elfeed-untag elfeed-search-entries 'unread)
(elfeed-search-update :force)))
(defun tv/elfeed-mark-read (entry)
"Display the currently selected item in a buffer."
(interactive (list (elfeed-search-selected :ignore-region)))
(when (elfeed-entry-p entry)
(elfeed-untag entry 'unread)
(elfeed-search-update-entry entry)
(unless elfeed-search-remain-on-entry (forward-line))))
(defun tv/elfeed-kill-buffers ()
"Kill elfeed buffer and the elfeed.org feed definition buffer."
(interactive)
(let ((buffer (get-buffer "elfeed.org")))
(kill-buffer buffer)
(elfeed-kill-buffer)))
Use ansi-colors in shell.
(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)
(use-package eshell
:hook ((eshell-mode . hide-mode-line-mode)
(eshell-mode . (lambda ()
(setenv "TERM" "xterm-256color")
(setq-local completion-styles '(basic))
(setq-local corfu-count 10)
(setq-local corfu-auto nil)
(setq-local corfu-preview-current nil)
(setq-local completion-at-point-functions '(pcomplete-completions-at-point cape-file)))))
:init
(setq eshell-scroll-to-bottom-on-input 'all
eshell-error-if-no-glob t
eshell-hist-ignoredups t
evil-set-initial-state 'insert
eshell-save-history-on-exit t
eshell-prefer-lisp-functions nil
eshell-directory-name (expand-file-name "etc/eshell" user-emacs-directory)
eshell-destroy-buffer-when-process-dies t))
Eshell aliases.
(setq tv/eshell-aliases
'((g . magit)
(gl . magit-log)
(d . dired)
(c . clear)
(cl . clear)
(o . find-file)
(ff . find-file)
(oo . find-file-other-window)
(l . (lambda () (eshell/ls '-la)))))
(mapc (lambda (alias)
(defalias (car alias) (cdr alias)))
tv/eshell-aliases)
Use syntax highlighting in eshell
.
(use-package eshell-syntax-highlighting
:ensure t
:config
(eshell-syntax-highlighting-global-mode +1)
:init
(defface eshell-syntax-highlighting-invalid-face
'((t :inherit diff-error))
"Face used for invalid Eshell commands."
:group 'eshell-syntax-highlighting))
Eshell-autosuggest.
(use-package esh-autosuggest
:hook (eshell-mode . esh-autosuggest-mode)
:ensure t)
Copied from abrochard.
(defun tv/eshell-here ()
"Opens up a new shell in the directory associated with the
current buffer's file. The eshell is renamed to match that
directory to make multiple eshell windows easier."
(interactive)
(let* ((height (/ (window-total-height) 3)))
(split-window-vertically (- height))
(other-window 1)
(eshell "new")
(insert (concat "ls"))
(eshell-send-input)))
(bind-key "C-!" 'tv/eshell-here)
(use-package vterm
:hook (vterm-mode . hide-mode-line-mode)
:ensure t
:defer
:custom
(vterm-max-scrollback 100000)
:config
(setq vterm-shell "/bin/zsh")
(setq vterm-kill-buffer-on-exit t)
(setq vterm-max-scrollback 100000)
(setq vterm-keymap-exceptions nil))
;; (use-package multi-vterm
;; :after vterm
;; :config (add-hook 'vterm-mode-hook
;; (lambda ()
;; (evil-insert-state))))
This is needed for pass
and epa
.
(setq epg-pinentry-mode 'loopback)
Use the pass
package to interact with the similarly named Linux password manager.
(use-package pass
:ensure t
:defer
:config
(require 'auth-source-pass)
(auth-source-pass-enable))
Show information about the file under editing.
(use-package file-info
:ensure t
:bind (("C-c f" . 'file-info-show)))
Disable images.
(setq shr-inhibit-images t)
(use-package dired
:ensure nil
:defer
:hook ((dired-mode . dired-hide-details-mode)
(dired-mode . hl-line-mode))
:bind (:map dired-mode-map
("C-c C-e" . wdired-change-to-wdired-mode))
:custom
(dired-kill-when-opening-new-dired-buffer t) ;; Without this, each directory level opens in its own buffer.
(dired-do-revert-buffer t)
(dired-auto-revert-buffer t)
(delete-by-moving-to-trash t)
(dired-mouse-drag-files t)
(dired-dwim-target t)
:config
(setq dired-listing-switches "-alFh")
(setq dired-use-ls-dired nil)
(setq dired-recursive-deletes 'always)
(setq dired-recursive-copies 'always)
(setq dired-dwim-target t)
(evil-define-key 'normal 'global (kbd "C-M-ä") 'dired-jump))
(use-package diredfl
:ensure t
:hook (after-init . diredfl-global-mode))
(use-package all-the-icons-dired
:ensure t
:defer
:hook (dired-mode . all-the-icons-dired-mode)
:custom
(all-the-icons-dired-monochrome nil))
Modified from Gopar.
(use-package transient
:ensure t
:bind ("C-M-o" . windows-transient-window)
:config
(transient-define-prefix windows-transient-window ()
"Display a transient buffer showing useful window manipulation bindings."
[["Resize"
("}" "h+" enlarge-window-horizontally :transient t)
("{" "h-" shrink-window-horizontally :transient t)
("^" "v+" enlarge-window :transient t)
("V" "v-" shrink-window :transient t)]
["Split"
("v" "vertical" (lambda ()
(interactive)
(split-window-right)
(windmove-right)) :transient t)
("x" "horizontal" (lambda ()
(interactive)
(split-window-below)
(windmove-down)) :transient t)
("wv" "win-vertical" (lambda ()
(interactive)
(select-window (split-window-right))
(windows-transient-window)) :transient nil)
("wx" "win-horizontal" (lambda ()
(interactive)
(select-window (split-window-below))
(windows-transient-window)) :transient nil)]
["Misc"
("B" "switch buffer" (lambda ()
(interactive)
(consult-buffer)
(windows-transient-window)))
("z" "undo" (lambda ()
(interactive)
(winner-undo)
(setq this-command 'winner-undo)) :transient t)
("Z" "redo" winner-redo :transient t)]]
[["Move"
("h" "←" windmove-left :transient nil)
("j" "↓" windmove-down :transient nil)
("l" "→" windmove-right :transient nil)
("k" "↑" windmove-up :transient nil)]
["Swap"
("sh" "←" windmove-swap-states-left :transient t)
("sj" "↓" windmove-swap-states-down :transient t)
("sl" "→" windmove-swap-states-right :transient t)
("sk" "↑" windmove-swap-states-up :transient t)]
["Delete"
("dh" "←" windmove-delete-left :transient t)
("dj" "↓" windmove-delete-down :transient t)
("dl" "→" windmove-delete-right :transient t)
("dk" "↑" windmove-delete-up :transient t)
("D" "This" delete-window :transient t)]
["Transpose"
("tt" "↜" (lambda ()
(interactive)
(transpose-frame)
(windows-transient-window)) :transient nil)
("ti" "↕" (lambda ()
(interactive)
(flip-frame)
(windows-transient-window)) :transient nil)
("to" "⟷" (lambda ()
(interactive)
(flop-frame)
(windows-transient-window)) :transient nil)
("tc" "⟳" (lambda ()
(interactive)
(rotate-frame-clockwise)
(windows-transient-window)) :transient nil)
("ta" "⟲" (lambda ()
(interactive)
(rotate-frame-anticlockwise)
(windows-transient-window)) :transient nil)]
["Exit"
("<escape>" "exit menu" (lambda ()
(interactive)
(transient-quit-one)) :transient nil)
("q" "exit menu" (lambda ()
(interactive)
(transient-quit-one)) :transient nil)]]))
(use-package transpose-frame
:ensure t
:after transient)
(use-package winner
:ensure nil
:hook after-init
:commands (winner-undo winnner-redo)
:custom
(winner-boring-buffers '("*Completions*" "*Help*" "*Apropos*"
"*Buffer List*" "*info*" "*Compile-Log*")))
(use-package helpful)
Read a GPG passphrase from environment variable to a register for easier access.
(when work-laptop-p
(set-register ?o (getenv (base64-decode-string "VkFSTUFfR1BHX1NZTU1FVFJJQ19QQVNTUEhSQVNF")))
(set-register ?p (getenv (base64-decode-string "VkFSTUFfR1BHX1NZTU1FVFJJQ19QQVNTUEhSQVNFX1BST0Q="))))
Clean up some buffers. Modified from: https://themagitian.github.io/posts/emacsconfig/.
(defun tv/kill-other-buffers ()
"Keep only the current buffer and scratch buffer, kill all others."
(interactive)
(let ((buffers-to-keep (cons (buffer-name)
'("*scratch*" "*Minibuf-0*" "*Minibuf-1*" "*Echo Area 0*" "*mood-line*"))))
(mapc (lambda (buffer)
(let ((bname (string-trim (buffer-name buffer))))
(unless (member bname buffers-to-keep)
(kill-buffer buffer))))
(buffer-list)))
(message "Killed other buffers"))
Source: https://github.com/daedreth/UncleDavesEmacs.
(defun tv/config-visit ()
"Open the configuration file."
(interactive)
(find-file (expand-file-name config-file user-emacs-directory)))
(defun tv/config-reload ()
"Reload config.org."
(interactive)
(org-babel-load-file (expand-file-name config-file user-emacs-directory)))
(global-set-key (kbd "C-c e") 'tv/config-visit)
(global-set-key (kbd "C-c r") 'tv/config-reload)
From abrochard.
(defun tv/sudo ()
"Use TRAMP to `sudo' the current buffer"
(interactive)
(when buffer-file-name
(find-alternate-file
(concat "/sudo:root@localhost:"
buffer-file-name))))
Sort all lines in a buffer.
(defun tv/sort-buffer()
"Select the lines in a buffer."
(interactive)
(sort-lines nil (point-min) (point-max)))
From abrochard.
(defun tv/generate-scratch-buffer ()
"Create and switch to a temporary scratch buffer with a random
name."
(interactive)
(switch-to-buffer (make-temp-name "*scratch-")))
From bbatsov.
(defun tv/copy-filename ()
"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))))
When doing lots of base64 encoding and decoding, it’s more ergonomical to process a word under point with a single command rather than always define a region first.
(defun tv/apply-fn-to-word-under-point (fn)
(let* ((point-loc (point))
(bounds (bounds-of-thing-at-point 'symbol))
(text (buffer-substring-no-properties (car bounds) (cdr bounds))))
(when bounds
(delete-region (car bounds) (cdr bounds))
(insert (funcall fn text))
(goto-char point-loc))))
(defun tv/base64-encode-word ()
"Base64 encode the word under point."
(interactive)
(tv/apply-fn-to-word-under-point 'base64-encode-string))
(defun tv/base64-decode-word ()
"Base64 decode the word under point."
(interactive)
(tv/apply-fn-to-word-under-point 'base64-decode-string))
(evil-define-key 'normal 'global (kbd "M-s-e") 'tv/base64-encode-word)
(evil-define-key 'normal 'global (kbd "M-s-d") 'tv/base64-decode-word)
Insert a random 4-line quote from a corpus file on top of the scratch buffer. The corpus on my work laptop is a file containing all the lyrics of Manowar, on the private machine I have the screenplay for The Room.
(defvar tv/scratch-message "")
(defvar scratch-message-beg-marker (make-marker))
(defvar scratch-message-end-marker (make-marker))
(defvar lyric-file (if work-laptop-p
"etc/manowar.txt"
"etc/room.txt"))
(defun slurp (f)
(with-temp-buffer
(insert-file-contents f)
(buffer-substring-no-properties
(point-min)
(point-max))))
(defun get-quote (rows)
(let* ((count (length rows))
(quote-length 4)
(start-index (random (- count quote-length)))
(res (seq-subseq rows start-index (+ start-index quote-length))))
(if (seq-filter (lambda (x)
(string-match-p "^\\(?:0\\|[1-9][0-9]*\\)" x))
res)
(get-quote rows)
(mapconcat 'identity res "\n"))))
(defun tv/generate-quote ()
(if (file-exists-p (expand-file-name lyric-file
user-emacs-directory))
(get-quote
(split-string
(slurp (expand-file-name lyric-file
user-emacs-directory)) "\n" t))
(message "Lyrics not found!")))
;; From https://github.com/thisirs/scratch-message/blob/master/scratch-message.el
(defun tv/scratch-message-insert (message)
"Replace or insert the message MESSAGE in the scratch buffer.
If there is no previous message, insert MESSAGE at the end of the
buffer, make sure we are on a beginning of a line and add three
newlines at the end of the message."
(if (get-buffer "*scratch*")
(with-current-buffer "*scratch*"
(let ((bm (buffer-modified-p)))
(if (and (marker-position scratch-message-beg-marker)
(marker-position scratch-message-end-marker))
(delete-region scratch-message-beg-marker scratch-message-end-marker))
(save-excursion
(if (marker-position scratch-message-beg-marker)
(goto-char (marker-position scratch-message-beg-marker))
(goto-char (point-min))
(search-forward (or initial-scratch-message "") nil t)
(or (bolp) (insert "\n"))
(save-excursion (insert "\n\n\n")))
(set-marker scratch-message-beg-marker (point))
(insert message)
(set-marker scratch-message-end-marker (point))
(let ((comment-start (or comment-start ";;")))
(comment-region scratch-message-beg-marker
scratch-message-end-marker)))
(set-buffer-modified-p bm)))
(error "No scratch buffer")))
(defun tv/reset-scratch-message ()
(interactive)
(let ((msg (tv/generate-quote)))
(setq tv/scratch-message msg)
(tv/scratch-message-insert msg)))
(tv/reset-scratch-message)
(when work-laptop-p
(defvar visited-jira-issues '())
(defun tv/jira ()
"Open a Jira ticket in browser. Accepts either a prefixed or non-prefixed input."
(interactive)
(let* ((ticket-number (if visited-jira-issues
(completing-read "Tikettinumero: " visited-jira-issues)
(read-string "Tikettinumero: ")))
(parsed-ticket (if (cl-search "-" ticket-number)
ticket-number
(concat "UMP-" ticket-number)))
(url (concat "https://varmajira.eficode.com/browse/" parsed-ticket)))
(progn
(push parsed-ticket visited-jira-issues)
(browse-url url)))))
No Evil mode when playing Tetris.
(use-package tetris
:hook (tetris-mode . turn-off-evil-mode))