Skip to content

tvirolai/.emacs.d

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Config

Introduction

This is my personal Emacs configuration. It is implemented using literate programming in org-mode. The way this works is that upon Emacs startup:

  1. The two-liner init.el config file is loaded, which in turns calls the org-babel-load-file function with this file.
  2. The org file is processed and the code blocks are tangled into a config.el file.
  3. 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.

General setup

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))

Tree-sitter

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))

Appearance

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

Presentation mode scales font sizes up, which is very useful when showing things in meetings etc.

(use-package presentation
  :ensure t)

Rainbow-mode

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-mode

Rainbow-delimiters are useful in lisps.

(use-package rainbow-delimiters
  :ensure t
  :hook (prog-mode . rainbow-delimiters-mode))

Nov.el 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

Olivetti mode squeezes the buffer into a column of limited width. This helps readability.

(use-package olivetti
  :ensure t
  :custom
  (olivetti-body-width 94))

Pulse

(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)))

Whitespace

(add-hook 'before-save-hook #'delete-trailing-whitespace)

(setq-default sentence-end-double-space nil)

Sudo edit

(use-package sudo-edit
  :ensure t
  :defer
  :config
  (global-set-key (kbd "C-c C-r") 'sudo-edit))

Evil mode

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))))

Version control

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)))

Keybindings

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)

Flycheck

(use-package flycheck
  :ensure
  :defer
  :bind (:map flycheck-mode-map
              ("C-c C-n" . flycheck-next-error)
              ("C-c C-p" . flycheck-previous-error)))

Programming languages

Bash

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))

Clojure

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)

SQL

(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)))

Emacs Lisp

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))))

Typescript

(use-package typescript-mode
  :ensure t
  :defer
  :custom
  (typescript-indent-level 2))

Java

(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)

Python

(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)

LSP-mode

(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))

Eldoc

(setq eldoc-echo-area-use-multiline-p nil)

Verb

(use-package verb
  :ensure t
  :defer)

No littering

(use-package no-littering
  :ensure t)

Counsel-etags

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))

Editorconfig

Pick up formatting settings from .editorconfig files.

(use-package editorconfig
  :ensure t
  :config
  (editorconfig-mode 1))

Ripgrep

Ripgrep package is needed for projectile-ripgrep to be usable.

(use-package ripgrep
  :ensure t
  :config
  (evil-define-key 'normal 'global "Ä" #'consult-ripgrep))

Wgrep

Writable grep. This makes possible to use workflows for search and replace like:

  1. Do a grep (e.g. projectile-ripgrep).
  2. wgrep-change-to-wgrep-mode (or i).
  3. 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))

Completion

Vertico

(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))

Dabbrev

(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\\)\\'")))

Corfu

(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))

Orderless

(use-package orderless
  :ensure t
  :init
  (setq completion-styles '(orderless basic)
        completion-category-defaults nil
        completion-category-overrides '((file (styles partial-completion)))))

Consult

(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))

Avy

(use-package avy
  :bind (("C-s" . avy-goto-char-timer)))

Marginalia

(use-package marginalia
  :ensure
  :init
  (marginalia-mode))

Embark

(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))

Yasnippet

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" "*")))

History

Save-place-mode

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))

Savehist-mode

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))

Recentf-mode

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))

File formats

Docker

(use-package dockerfile-mode
  :ensure t
  :defer)

(use-package docker
  :ensure t
  :defer
  :bind ("C-c d" . docker))

Markdown

(use-package markdown-mode
  :ensure t
  :hook (markdown-mode . display-line-numbers-mode)
  :mode ("README\\.md\\'" . gfm-mode)
  :init (setq markdown-command "multimarkdown"))

YAML

(use-package yaml-ts-mode
  :ensure nil
  :hook (yaml-ts-mode . display-line-numbers-mode)
  :mode
  ("\\.yml\\'" . yaml-ts-mode)
  ("\\.yaml\\'" . yaml-ts-mode))

XML

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)

Org-mode

(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))

Evil-org

(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))

Org Roam

(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))))

Org Agenda

(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)

Elfeed

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)))

Shell stuff

General

Use ansi-colors in shell.

(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on)

Eshell

(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)

Vterm

(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))))

Configure epg-pinentry-mode

This is needed for pass and epa.

(setq epg-pinentry-mode 'loopback)

Pass

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))

File info

Show information about the file under editing.

(use-package file-info
  :ensure t
  :bind (("C-c f" . 'file-info-show)))

EWW

Disable images.

(setq shr-inhibit-images t)

Dired

(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))

Transient

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)

Winner

(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*")))

Helpful

(use-package helpful)

Various minor tweaks

Insert GPG passphrase to register ?o

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="))))

Kill buffers at scale

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"))

Quickly visit and evaluate configuration

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)

Sudo current buffer

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 buffer

Sort all lines in a buffer.

(defun tv/sort-buffer()
  "Select the lines in a buffer."
  (interactive)
  (sort-lines nil (point-min) (point-max)))

Generate scratch buffer

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-")))

Copy filename and path to clipboard

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))))

Base64 wrappers

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)

An inspirational quote

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)

Open a Jira ticket in the browser

(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)))))

Tetris

No Evil mode when playing Tetris.

(use-package tetris
  :hook (tetris-mode . turn-off-evil-mode))

About

A personal Emacs configuration

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published