Skip to content

chriswifn/emacs-config

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 

Repository files navigation

Emacs configuration

Introduction

This is my Emacs configuration. It is not written to be used by anyone other than me. It’s just a bunch of thrown together packages that are propably not configured well. I don’t care it works for me.

  • Author: Christian
  • Created: 15-12-2023
  • License: MIT

Installation

If you, for what ever reason want to use this configuration, go through these steps:

Just like any other “emacs configuration” you are effectively running unknown elisp code on your machine. Be prepared for the worst.

Package Management

An essential part to Emacs is packages. You can extend Emacs by installing packages and changing to variables.

Melpa

Reference: https://www.melpa.org/

By default, Emacs contains a limited number of package choices. Melpa provides a nice collection of semi safe packages. No need to worry about cloning some weird out of date packages of the internet with no safety measurements.

(require 'package)
(setq package-enable-at-startup nil)
(add-to-list 'package-archives
	       '("melpa" . "https://melpa.org/packages/"))
(package-initialize)

Package manager

Reference: https://jwiegley.github.io/use-package use-package is an elisp macro that allows package isolation in terms of configuration. Essentially it tidies up the configuration a bit with focus on performance (always nice to have).

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

Basic settings

Emacs out of the box can be pretty annoying. The appearance sucks and you got weird dialogs popping up all over the place. Some things HAVE to be set or disabled.

Reload the configuration file

A short and easy way to reload the configuration when changing something in this file.

(defun chris/reload-config ()
  "Reload the configuration file"
  (interactive)
  (org-babel-load-file (expand-file-name "~/.emacs.d/README.org")))

(define-key global-map (kbd "\C-c r") 'chris/reload-config)

Basic interface settings

  • Disable dialog boxes: This setting disables the display of dialog boxes, such as confirmation or warning pop-ups. It allows for a smoother und uninterrupted experience
    (setq use-dialog-box nil)
        
  • Disable file dialogs: This setting disables the use of file selection dialogs. Instead, Emacs will rely on command-line or programmatic methods for file operations.
    (setq use-file-dialog nil)
        
  • Disable backup files: Stop emacs from creating annoying backup files all over the place. Backups should be done by other applications than the text editor.
    (setq make-backup-files nil)
        
  • Disable auto-save: No particular reason to disable this. Periodic saves are nice.
    (setq auto-save-default nil)
        
  • Hide menu bar: Hides the menu bar. More vertical space.
    (menu-bar-mode -1)
        
  • Hide tool-bar: Hides the tool bar. It is pointless anyway (Hint M-x).
    (tool-bar-mode -1)
        
  • Hide fringes: Fringes are the narrow areas on the left and right side of the Emacs window.
    (fringe-mode -1)
        
  • Hide scroll bar: Emacs provides a scroll bar for navigating through the buffer. It is just a waste of space.
    (scroll-bar-mode -1)
        
  • Use y-or-n-p: By default, Emacs prompts for user confirmation using ‘yes’ or ‘no’ This setting changes it to use ‘y’ oder ‘n’ for shorter and faster responses.
    (defalias 'yes-or-no-p 'y-or-n-p)
        
  • Custom variables file: Don’t clutter any other files with custom variables.
    (setq custom-file (expand-file-name "custom.el" user-emacs-directory))
        

Locales

Reference: https://www.gnu.org/software/emacs/manual/html_node/elisp/Locales.html

(setq locale-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(prefer-coding-system 'utf-8)

Keybindings to navigate splits and buffers.

Keybinding section that is not related to any package specifically but to enhance the Emacs keybindings Very much inspired by my tmux keybindings.

(global-unset-key (kbd "C-a"))
(define-prefix-command 'chris-window-map)
(let ((bindings '(("-" . (lambda () (interactive) (split-window-vertically) (other-window 1)))
		    ("\\" . (lambda () (interactive) (split-window-horizontally) (other-window 1)))
		    ("x" . kill-buffer-and-window)
		    ("n" . next-buffer)
		    ("p" . previous-buffer)
		    ("h" . windmove-left)
		    ("j" . windmove-down)
		    ("k" . windmove-up)
		    ("l" . windmove-right))))
  (dolist (binding bindings)
    (global-set-key (kbd (concat "\C-a" (car binding))) (cdr binding))))

(global-set-key (kbd "\C-x C-b") 'ibuffer)

Evil mode

Reference: https://github.com/emacs-evil/evil Evil is an extensible vi layer for Emacs. It emulates the main features of Vim, and provides facilities for writing custom extensions.

(use-package evil
  :ensure t
  :init
  (setq evil-search-module 'isearch)
  (setq evil-want-C-u-scroll t)
  (setq evil-want-C-d-scroll t)
  ;; for evil-collection
  (setq evil-want-integration t)
  (setq evil-want-keybinding nil)
  ;; splits
  (setq evil-split-window-below t)
  (setq evil-vsplit-window-right nil)
  (setq evil-want-C-i-jump nil)
  ;; undo
  (setq evil-undo-system 'undo-redo)
  :config
  (evil-mode t))

Evil collection

Reference: https://github.com/emacs-evil/evil-collection Vim Keybindings for non text editing buffers.

(use-package evil-collection
  :ensure t
  :after evil
  :init
  (setq evil-collection-outline-bind-tab-p t)
  :config
  (evil-collection-init))

Evil commentary

Reference: https://github.com/linktohack/evil-commentary Makes it easy to comment out (lines of) code.

;; evil-commentary: quick commenting
(use-package evil-commentary
  :ensure t
  :after evil
  :config
  (evil-commentary-mode))

Better jumps

By default, the C-d und C-u jumps don’t center the cursor position. This fixes that annoying behavior.

;; C-d: center cursor after jump
(defun chris/scroll-down-and-center ()
  "Scroll down and center the text to the screen"
  (interactive)
  (evil-scroll-down 0)
  (evil-scroll-line-to-center (line-number-at-pos)))
(define-key evil-motion-state-map (kbd "\C-d") 'chris/scroll-down-and-center)

;; C-u: center cursor after jump
(defun chris/scroll-up-and-center ()
  "Scroll up and center the text to the screen"
  (interactive)
  (evil-scroll-up 0)
  (evil-scroll-line-to-center (line-number-at-pos)))
(define-key evil-motion-state-map (kbd "\C-u") 'chris/scroll-up-and-center)

Appearance

Emacs out of the box looks like a programm from the 70s. A theme and a better modeline can do wonders.

Theme

Reference: https://github.com/doomemacs/themes A theme megapack for Emacs. These themes have decent enough integration with most other packages.

(use-package doom-themes
  :ensure t
  :if window-system
  :config
  (setq doom-themes-enable-bold t
	  doom-themes-enable-italic nil)
  (load-theme 'doom-gruvbox t))

Font

Set the default font and font-size.

(add-to-list 'default-frame-alist
	       '(font . "monospace-16"))

Modeline

Reference: https://github.com/seagle0128/doom-modeline A fancy and fast mode-line inspired by minimalism design.

(use-package doom-modeline
  :ensure t
  :init
  (doom-modeline-mode 1))

Completion

Reference: https://github.com/abo-abo/swiper Not to be confused with code/text completion. This part of the configuration is intended to make the input completion more streamline and nicer to use.

(use-package ivy
  :ensure t
  :init
  (ivy-mode))

(use-package counsel
  :ensure t)

(use-package swiper
  :ensure t
  :config
  (setq ivy-use-virtual-buffers t)
  (setq enable-recursive-minibuffers t)
  ;; enable this if you want `swiper' to use it
  ;; (setq search-default-mode #'char-fold-to-regexp)
  (global-set-key "\C-s" 'swiper)
  (global-set-key (kbd "C-c C-r") 'ivy-resume)
  (global-set-key (kbd "<f6>") 'ivy-resume)
  (global-set-key (kbd "M-x") 'counsel-M-x)
  (global-set-key (kbd "C-x C-f") 'counsel-find-file)
  (global-set-key (kbd "<f1> f") 'counsel-describe-function)
  (global-set-key (kbd "<f1> v") 'counsel-describe-variable)
  (global-set-key (kbd "<f1> o") 'counsel-describe-symbol)
  (global-set-key (kbd "<f1> l") 'counsel-find-library)
  (global-set-key (kbd "<f2> i") 'counsel-info-lookup-symbol)
  (global-set-key (kbd "<f2> u") 'counsel-unicode-char)
  (global-set-key (kbd "C-c g") 'counsel-git)
  (global-set-key (kbd "C-c j") 'counsel-git-grep)
  (global-set-key (kbd "C-c k") 'counsel-ag)
  (global-set-key (kbd "C-x l") 'counsel-locate)
  (global-set-key (kbd "C-S-o") 'counsel-rhythmbox)
  (define-key minibuffer-local-map (kbd "C-r") 'counsel-minibuffer-history))

Eshell

Eshell is all the shell Emacs will ever need. Full blown terminal emulation is overkill (if ever needed install vterm).

 (defun chris/configure-eshell ()
   (add-hook 'eshell-pre-command-hook 'eshell-save-some-history)
   (add-to-list 'eshell-output-filter-functions 'eshell-truncate-buffer)
   (setq eshell-history-size         10000
	  eshell-buffer-maximum-lines 10000
	  eshell-hist-ignoredups t
	  eshell-scroll-to-bottom-on-input t)
   (setq tramp-default-method "ssh"))

 ;; configure eshell
 (use-package eshell
   :hook
   (eshell-first-time-mode . chris/configure-eshell)
   (eshell-mode . (lambda ()
		     (setq-local global-hl-line-mode nil)))
   :config
   (with-eval-after-load 'esh-opt
     (setq eshell-destroy-buffer-when-process-dies t)
     (setq eshell-visual-commands '("ssh" "tail" "htop" "pulsemixer"))))

 ;; git status
 (defun eshell/gst (&rest args)
   "Git status in eshell"
   (magit-status (pop args) nil)
   (eshell/echo))

 ;; find wrapper for eshell 
 (defun eshell/f (filename &optional dir try-count)
   "Searches for files matching FILENAME in either DIR or the
 current directory."
   (let* ((cmd (concat
		 (executable-find "find")
		 " " (or dir ".")
		 "      -not -path '*/.git*'"
		 " -and -not -path '*node_modules*'"
		 " -and -not -path '*classes*'"
		 " -and "
		 " -type f -and "
		 "-iname '" filename "'"))
	   (results (shell-command-to-string cmd)))

     (if (not (s-blank-str? results))
	  results
	(cond
	 ((or (null try-count) (= 0 try-count))
	  (eshell/f (concat filename "*") dir 1))
	 ((or (null try-count) (= 1 try-count))
	  (eshell/f (concat "*" filename) dir 2))
	 (t "")))))

 ;; find wrapper for eshell
 (defun eshell/ef (filename &optional dir)
   "Searches for the first matching filename and loads it into a
 file to edit."
   (let* ((files (eshell/f filename dir))
	   (file (car (s-split "\n" files))))
     (find-file file)))

 ;; clear
 (defun eshell/clear ()
   "Clear the eshell buffer."
   (let ((inhibit-read-only t))
     (erase-buffer)
     (eshell-send-input)))

 ;; create directory and switch to it immediately
 (defun eshell/mkdir-and-cd (dir)
   "Create a directory then cd into it."
   (make-directory dir t)
   (eshell/cd dir))

 (add-hook 'eshell-mode-hook
		(lambda ()
		  (local-set-key (kbd "C-c h")
				 (lambda ()
				   (interactive)
				   (insert
				    (completing-read "Eshell history: "
							 (delete-dups
							  (ring-elements eshell-history-ring))))))))

VCS and Project Management

In the same section, because they are linked together, at least the way I use them.

Projectile

Reference: https://github.com/bbatsov/projectile Projectile is a project interaction library for Emacs. Its goal is to provide a nice set of features operating on a project level without introducing external dependencies. Minimizes the overhead of managing thousands of buffers by grouping them into projects.

(use-package projectile
  :ensure t
  :config
  (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)
  (add-to-list 'projectile-globally-ignored-modes "org-mode")
  (setq projectile-indexing-method 'hybrid)
  :init
  (projectile-mode +1))

;; ibuffer-projectile: sort ibuffer according to projects
(use-package ibuffer-projectile
  :ensure t
  :config 
  (add-hook 'ibuffer-hook
	      (lambda ()
		(ibuffer-projectile-set-filter-groups)
		(unless (eq ibuffer-sorting-mode 'alphabetic)
		  (ibuffer-do-sort-by-alphabetic)))))

Magit

Reference: https://magit.vc/ The best git client there is and will ever be. It is worth using Emacs just for magit.

(use-package magit
  :ensure t
  :config
  (setq magit-push-always-verify t)
  (setq magit-display-buffer-function #'magit-display-buffer-fullframe-status-v1)
  (setq magit-repository-directories
	  '(("~/repos/github.com/chriswifn" . 2)))
  :bind
  ("C-x g" . magit-status)
  ("C-x C-g" . magit-list-repositories))

IDE Features

Direnv

Reference: https://github.com/wbolster/emacs-direnv Direnv integration for emacs

(use-package direnv
  :ensure t
  :config
  (direnv-mode))

Format all

Reference: https://github.com/lassik/emacs-format-all-the-code Simplifies the process of formatting code in various programming languages.

(use-package format-all
  :ensure t)

Company

Reference: https://github.com/company-mode/company-mode Versatily and intelligent completion framework. Text-completion

(use-package company
  :ensure t
  :defer t
  :config
  (add-hook 'after-init-hook 'global-company-mode))

Advanced IDE Features

While I prefer a minimalist text editor with fewer distractions, I recognize the need for more robust IDE features, especially for larger projects involving frameworks. Auto-completion and type checking are indispensable in such scenarios. Therefore, I appreciate the flexibility of Emacs, as it allows me to tailor the environment to suit my various programming needs.

Treesitter

Reference: https://emacs-tree-sitter.github.io/ Treesitter support for Emacs.

  ;; tree-sitter languages for treesitter support
(use-package tree-sitter-langs
  :ensure t)

;; tree-sitter (syntax parsing sitting in a tree)
(use-package tree-sitter
  :ensure t
  :defer t
  :init
  (add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode)
  ;; enable tree-sitter globally
  (global-tree-sitter-mode)
  :custom
  ;; no italics (because italics are for maniacs
  (custom-set-faces
   '(italic ((t nil)))
   '(tree-sitter-hl-face:property ((t (:inherit font-lock-constant-face)))))
  :config
  (setq tree-sitter-debug-jump-buttons t
	  tree-sitter-debug-highlight-jump-region t))

(use-package evil-textobj-tree-sitter
  :ensure t)
(define-key evil-outer-text-objects-map "f"
    (evil-textobj-tree-sitter-get-textobj "function.outer"))
  (define-key evil-inner-text-objects-map "f"
    (evil-textobj-tree-sitter-get-textobj "function.inner"))
  (define-key evil-outer-text-objects-map "c"
    (evil-textobj-tree-sitter-get-textobj "comment.outer"))
  (define-key evil-outer-text-objects-map "C"
    (evil-textobj-tree-sitter-get-textobj "class.outer"))
  (define-key evil-outer-text-objects-map "a"
    (evil-textobj-tree-sitter-get-textobj ("conditional.outer" "loop.outer")))

LSP

Reference: https://github.com/joaotavora/eglot LSP integration for Emacs. Since eglot is built-in and I feel pain when trying to set up lsp-mode I will use eglot. eglot is much easier to integrate with direnv and nix-shell, which is what I use to install project specific language servers.

(use-package eglot)

Languages

Subject to change. Constantly updated (hopefully). Add support for all the languages I commonly use. Some will use LSP, some not. A lot of languages already have nice support in Emacs, so they don’t need to be configured.

Haskell

(use-package haskell-mode
  :ensure t
  :mode ("\\.hs\\'" . haskell-mode))

(defun haskell-evil-open-above ()
  (interactive)
  (evil-beginning-of-line)
  (haskell-indentation-newline-and-indent)
  (evil-previous-line)
  (haskell-indentation-indent-line)
  (evil-append-line nil))

(defun haskell-evil-open-below ()
  (interactive)
  (evil-append-line nil)
  (haskell-indentation-newline-and-indent))

(evil-define-key 'normal haskell-mode-map
  "o" 'haskell-evil-open-below
  "O" 'haskell-evil-open-above)

Lua

(use-package lua-mode
  :ensure t
  :mode ("\\.lua\\'". lua-mode)
  :interpreter ("lua" . lua-mode)
  :config
  (defun chris/open-lua-repl ()
    "open lua repl in horizontal split"
    (interactive)
    (lua-show-process-buffer))
  :init
  (setq lua-indent-level 4
	  lua-indent-string-contents t))

Python

(use-package python-mode
  :interpreter ("python3" . python-mode))

Racket

 (use-package racket-mode
   :ensure t
   :interpreter ("racket" . racket-mode)
   :hook (racket-mode . lsp))

 (defun chris/racket-run-and-switch-to-repl ()
   "Call `racket-run-and-switch-to-repl' and enable insert state"
   (interactive)
   (racket-run-and-switch-to-repl)
   (when (buffer-live-p (get-buffer racket-repl-buffer-name))
     (with-current-buffer racket-repl-buffer-name
	(evil-insert-state))))

Matlab

The biggest piece of trash ever made.

(autoload 'matlab-mode "matlab" "Matlab Editing Mode" t)
(add-to-list
 'auto-mode-alist
 '("\\.m$" . matlab-mode))
(setq matlab-indent-function t)
(setq matlab-shell-command-switches '("-nosplash" "-nodesktop"))
(setq matlab-shell-command "matlab")

(defun chris/matlab-shell-run-buffer ()
  "Run matlab code"
  (interactive)
  (matlab-shell-run-command (concat "cd " default-directory))
  (matlab-shell-run-region (point-min) (point-max)))

Scala

(use-package scala-mode
  :ensure t
  :interpreter ("scala" . scala-mode))

(use-package sbt-mode
  :ensure t
  :commands sbt-start sbt-command
  :config
  ;; WORKAROUND: https://github.com/ensime/emacs-sbt-mode/issues/31
  ;; allows using SPACE when in the minibuffer
  (substitute-key-definition
   'minibuffer-complete-word
   'self-insert-command
   minibuffer-local-completion-map)
  ;; sbt-supershell kills sbt-mode:  https://github.com/hvesalai/emacs-sbt-mode/issues/152
  (setq sbt:program-options '("-Dsbt.supershell=false")))

Custom modifications

This section is only here to provide a place for my custom functionality.

Custom scratch buffers

Create custom scratch buffers based on the major mode of the current buffer or based on text selection if the current major mode is not a derivative of prog-mode.

 ;; custom scratch buffers
 ;; get all proc-mode derivatives to be able to create custom scratch buffers
 (defun chris/simple--scratch-list-modes ()
   "List known major modes."
   (cl-loop for sym the symbols of obarray
	     when (and (functionp sym)
		       (provided-mode-derived-p sym 'prog-mode))
	     collect sym))

 ;; create custom scratch buffers
 (defun chris/simple--scratch-buffer-setup (region &optional mode)
   "Add contents to `scratch' buffer and name it accordingly.
 REGION is added to the contents to the new buffer.
 Use the current buffer's major mode by default.  With optional
 MODE use that major mode instead."
   (let* ((major (or mode major-mode))
	   (string (format "Scratch buffer for: %s\n\n" major))
	   (text (concat string region))
	   (buf (format "*Scratch for %s*" major)))
     (with-current-buffer (get-buffer-create buf)
	(funcall major)
	(save-excursion
	  (insert text)
	  (goto-char (point-min))
	  (comment-region (point-at-bol) (point-at-eol))))
     (switch-to-buffer buf)))

 ;; create custom scratch buffers with current major mode as major mode
 ;; if the current major mode is a prog-mode derivative or a prompt
 ;; for a list to choose from
 (defun chris/simple-scratch-buffer (&optional arg)
   "Produce a bespoke scratch buffer matching current major mode.
 If the major-mode is not derived from 'prog-mode, it prompts for
 a list of all derived prog-modes AND org-mode
 If region is active, copy its contents to the new scratch
 buffer."
   (interactive "P")
   (let* ((modes (chris/simple--scratch-list-modes))
	   (region (with-current-buffer (current-buffer)
		     (if (region-active-p)
			 (buffer-substring-no-properties
			  (region-beginning)
			  (region-end))
		       "")))
	   (m))
     (if (derived-mode-p 'prog-mode)
	  (chris/simple--scratch-buffer-setup region)
	(progn
	  (setq m (intern (completing-read "Select major mode: " modes nil t)))
	  (chris/simple--scratch-buffer-setup region m)))))

Kill all dired buffers

Sometimes the dired buffers accumulate and reach terminal velocity. At this point there is only one option: Kill them all.

(defun chris/kill-dired-buffers ()
  "Kill all open dired buffers."
  (interactive)
  (mapc (lambda (buffer)
	    (when (eq 'dired-mode (buffer-local-value 'major-mode buffer))
	      (kill-buffer buffer)))
	  (buffer-list)))

Nmcli

A semi package for controlling network manager through nmcli. Sort of an elisp API to control Network.

(define-derived-mode chris/nmcli-wifi-preexist-mode tabulated-list-mode
  "nmcli-wifi-preexist"
  "nmcli preexisting WiFi Mode"
  (let ((columns [("NAME" 20 t)
		    ("UUID" 40 t)
		    ("TYPE" 10 t)
		    ("DEVICE" 10 t)])
	  (rows (chris/nmcli-wifi-preexist--shell-command)))
    (setq tabulated-list-format columns)
    (setq tabulated-list-entries rows)
    (tabulated-list-init-header)
    (tabulated-list-print)))

(defun chris/nmcli-wifi-preexist-refresh ()
  "Refresh wifi table."
  (interactive)
  (let ((rows (chris/nmcli-wifi-preexist--shell-command)))
    (setq tabulated-list-entries rows)
    (tabulated-list-print t t)))

(defun chris/nmcli-wifi-preexist-sentinel (process event)
  (cond ((string-match-p "finished" event)
	   (chris/nmcli-wifi-preexist-refresh)
	   (kill-buffer "*async nmcli*"))))

(defun chris/nmcli-wifi-preexist--shell-command ()
  "Shell command to check for preconfigured wifi connections"
  (interactive)
  (mapcar (lambda (x)
	      `(,(car (cdr x))
		,(vconcat [] x)))
	    (mapcar (lambda (x)
		      x)
		    (cdr (mapcar (lambda (x)
				   (split-string x "  " t " "))
				 (split-string (shell-command-to-string "nmcli connection") "\n" t))))))

(defun chris/nmcli-wifi-preexist ()
  "Menu for (dis)connecting from preexisting wifi connections."
  (interactive)
  (switch-to-buffer "*nmcli-wifi-preexist*")
  (chris/nmcli-wifi-preexist-mode))

(defun chris/nmcli-wifi-preexist-connect ()
  "Connect to wifi."
  (interactive)
  (let* ((ssid (aref (tabulated-list-get-entry) 1))
	   (process (start-process-shell-command "nmcli" "*async nmcli*" (format "nmcli connection up \"%s\"" ssid))))
    (set-process-sentinel process 'chris/nmcli-wifi-preexist-sentinel)))

(defun chris/nmcli-wifi-preexist-disconnect ()
  "Disconnect from wifi."
  (interactive)
  (let* ((ssid (aref (tabulated-list-get-entry) 1))
	   (process (start-process-shell-command "nmcli" "*async nmcli*" (format "nmcli connection down \"%s\"" ssid))))
    (set-process-sentinel process 'chris/nmcli-wifi-preexist-sentinel)))

(add-to-list 'display-buffer-alist
	       (cons "\\*Async Shell Command\\*.*" (cons #'display-buffer-no-window nil)))

(let ((map chris/nmcli-wifi-preexist-mode-map))
  (define-key map (kbd "C-c c") 'chris/nmcli-wifi-preexist-connect)
  (define-key map (kbd "C-c d") 'chris/nmcli-wifi-preexist-disconnect)
  (define-key map (kbd "C-c r") 'chris/nmcli-wifi-preexist-refresh))

Releases

No releases published

Packages

No packages published