[go: up one dir, main page]

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

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

Disable unnecessary stuff.

(setq inhibit-startup-screen t)
(blink-cursor-mode -1)
(setq ring-bell-function 'ignore)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(setq use-short-answers t)
(setq initial-scratch-message "")

Set 50MB as the limit for garbage collection.

(setq gc-cons-threshold 50000000)

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)

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.

(prefer-coding-system '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))

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

Add helpers to switch between themes.

(defun disable-all-themes ()
  "Disable all enabled custom themes."
  (mapc #'disable-theme custom-enabled-themes))

(defun load-light-theme ()
  "Load light theme."
  (interactive)
  (disable-all-themes)
  (load-theme 'doom-flatwhite t))

(defun load-dark-theme ()
  "Load dark theme."
  (interactive)
  (disable-all-themes)
  (load-theme 'doom-dracula t))

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)

Maximize the frame on startup.

(add-to-list 'initial-frame-alist '(fullscreen . maximized))

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)

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)

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.org" (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
  ;; allows for using cgn
  ;; (setq evil-search-module 'evil-search)
  (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))))

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
  :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 "<down>") 'isearch-ring-advance)
  (define-key isearch-mode-map (kbd "<up>") 'isearch-ring-retreat))

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

Evil-multiedit

(use-package evil-multiedit
  :ensure t
  :defer
  :config (evil-multiedit-default-keybinds))

Version control

Magit is the Git package.

(defun 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 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
  ;;                       #'kill-magit-diff-buffer-in-current-repo
  ;;                       nil t)))
  (evil-define-key 'normal magit-status-mode-map
    "q" #'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)

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)

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 "å") 'yank-from-kill-ring)
(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)
(evil-define-key 'normal '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)

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)

Flycheck

(use-package flycheck
  :ensure
  :defer
  :hook ((python-mode . flycheck-mode))
  :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 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-mode. lsp)
         (sql-interactive-mode . sqlup-mode))
  :defer
  :config
  (setq lsp-sqls-workspace-config-path nil)
  (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
  :demand t
  :after python
  :hook ((python-mode . python-black-on-save-mode-enable-dwim)
         (python-ts-mode . python-black-on-save-mode-enable-dwim)))

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

Projectile

(use-package projectile
  :ensure t
  :init (projectile-global-mode)
  :config
  (setq projectile-project-search-path '("~/dev"))
  (setq projectile-cache-file (expand-file-name "etc/projectile.cache" user-emacs-directory))
  (setq projectile-dirconfig-file (expand-file-name "etc/.projectile" user-emacs-directory))
  (setq projectile-known-projects-file (expand-file-name "etc/projectile.bookmarks.eld" user-emacs-directory))
  (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map))

ibuffer-projectile groups the open buffers in ibuffer by project.

(use-package ibuffer-projectile
  :hook ((ibuffer . (lambda ()
                      (ibuffer-projectile-set-filter-groups)
                      (unless (eq ibuffer-sorting-mode 'alphabetic)
                        (ibuffer-do-sort-by-alphabetic)))))
  :ensure t)

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 "Ä" 'projectile-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 match
  :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-file)

  (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
  :after projectile
  :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 projectile-command-map
         ("b" . consult-project-buffer)
         :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 consult-project-function (lambda (_) (projectile-project-root)))
  (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 consult-ag
  :ensure
  :defer
  :bind (:map projectile-command-map
              ("s s" . consult-ag)
              ("s g" . consult-grep)))

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

(add-hook 'nxml-mode-hook #'display-line-numbers-mode)

Org-mode

(use-package org
  :defer
  :custom
  (fill-column 100)
  (org-pretty-entities t)
  (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 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 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") #'org-archive-done-tasks
  (kbd "C-c DEL k") #'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))

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 %(gopar/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 gopar/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
        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 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-!" 'eshell-here)

Vterm

(use-package vterm
  :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
  :defer
  :bind ("C-M-o" . windows-transient-window)
  :init
  (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.

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

Quicky visit and evaluate configuration

Source: https://github.com/daedreth/UncleDavesEmacs.

(defun config-visit ()
  "Open the configuration file."
  (interactive)
  (find-file (expand-file-name "config.org" user-emacs-directory)))

(defun config-reload ()
  "Reload config.org."
  (interactive)
  (org-babel-load-file (expand-file-name "config.org" user-emacs-directory)))

(global-set-key (kbd "C-c e") 'config-visit)
(global-set-key (kbd "C-c r") 'config-reload)

Sudo current buffer

From abrochard.

(defun sudo ()
  "Use TRAMP to `sudo' the current buffer"
  (interactive)
  (when buffer-file-name
    (find-alternate-file
     (concat "/sudo:root@localhost:"
             buffer-file-name))))

Generate scratch buffer

From abrochard.

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

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))
(defconst lyric-file "etc/lyrics.txt")
;; (defconst lyric-file "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)

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