/.emacs.d

My emacs setup

Primary LanguageEmacs Lisp

Djole’s Emacs Config

About

Last Emacs bankruptcy forced me to try a different route - put everything in org file and have it documented, explained and never stale.

Explanations combined with code in one file is effectuating old paradigm of Literate Programming, which might have its flaws but lisp configuration file is exactly where it shines. I will try not to underestimate “Literate” part of this concept and give thorough explanations for every line of code along with guiding principles for bigger sections.

This is obviously opinionated setup, being my actual working init, but I made some effort making it modular so it’s possible to pick any part of it separately. Reminding future me on what the hell I was thinking when I included something is part of the reason I’m writing this, but I also hope it can be of help to someone trying to learn how Emacs initialization works. I had tremendous help from other people’s init directories and I hope detailed instructions presented here could help someone in similar fashion.

For somebody who is starting his journey with Emacs, this repository can serve as a starting point for personal setup. Just copying it will leave you with nice looking and reasonable settings. I have a tendency for minimalism so this should be easy to build upon. Similarly, separation of code blocks makes it trivial to remove parts that are of no interest in someone else’s workflow.

If you find something achievable in simpler or more efficient way, please feel free to tell me. This is in no way finished and I don’t expect it ever to be.

Files in .emacs.d

README.org

This is where the magic happens… Babel extracts code from this file copying it to self-created config/settings.el which Emacs uses for initialization. Using that extracted code Custom creates config/custom.el and lists packages mentioned, preparing them for download and installation.

  • Source of all packages is MELPA, after I found out that MELPA Stable is not superior in any way. Emacs package sources are still evolving ecosystem and everything relies on your specific needs, but MELPA seem to be best choice for now.
  • Strategy used for installation is use-package, which to me looks superior to all other techniques (and I tried them all). Possibility to require and define package in the same place where you are setting its behavior does wonders for readability. Some packages might make you jump through hoops if you try to install them this way, but it’s worth it.

init.el

In init we have to set some things beforehand so settings can run consistently between reboots and there is no clutter created in Emacs root directory.

  • (package-initialize) is there just because Custom would put it there anyway being the starting file of initialization.
  • config-dir and data-dir are names of directories that no-littering package uses for storing transient files which need to be created in the pre-processing stage.

Prerequisites

Before we go on with installing packages it’s essential to configure some things. Everything in this section concerns initializing set-package and making sure we don’t create clutter in the init directory.

TLS Setup

Before installing anything, it’s essential to setup TLS certificate because Emacs is not handling that in ideal way. For openssl to work on OSX we need to install libressl, which is easiest to do via Homebrew: brew install libressl. Popular Linux distros have this predefined so there is no need for any setup and for other systems you should easily find equivalent ssl libraries.

(require 'gnutls)
(add-to-list 'gnutls-trustfiles "/usr/local/etc/openssl@1.1/cert.pem")

Sources

Having only one source repository keeps things simple and I don’t need fancy, rare or bleeding edge packages. I used to go with melpa-stable, but since that time I learned that there is no advantage presumed with “stable” postfix in the name. I can always download packages and set local source for use-package, making it even more secure if that becomes important. I added jcs-elpa for some packages that are newer.

(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(add-to-list 'package-archives '( "jcs-elpa" . "https://jcs-emacs.github.io/jcs-elpa/packages/") t)

use-package

I already explained why I like use-package as my preferred installer. This setup could obviously work with some other macro, but discrepancies are probable.

(unless (package-installed-p 'use-package)
    (package-refresh-contents)
    (package-install 'use-package))
(eval-when-compile
  (require 'use-package))

Now we can use use-package for use-package!

(use-package use-package
  :init
  (setq use-package-always-ensure t)	; Try installing automatically
  (setq use-package-verbose nil)		; Set to true when interested in load times
  (use-package use-package-ensure-system-package :ensure t)) ; Need this because we are in use-package config

no-littering

no-littering package is the first we are going to install. It’s job is to make sub-directories in .init.d and save all temporary files there. This reduces clutter and helps with having one place to look in case that something is missing.

  • /config is for auto generated files that would end up cluttering init.el. Process of installation creates settings.el and custom.el files, but any package that needs configuration files should use this directory to save them.
  • /data serves as temporary directory for all packages. This is place for auto-save and backup, along with any other package that needs to save some transient data.
(use-package no-littering
  :init (progn
          (setq no-littering-etc-directory config-dir)
          (setq no-littering-var-directory data-dir)
          :config (progn
                    (require 'no-littering)
                    (require 'recentf)
                    (add-to-list 'recentf-exclude no-littering-var-directory)
                    (add-to-list 'recentf-exclude no-littering-etc-directory)
                    (setq backup-directory-alist
                          `((".*" . ,(no-littering-expand-var-file-name "backup/"))))
                    (setq auto-save-file-name-transforms
                          `((".*" ,(no-littering-expand-var-file-name "auto-save/") t)))
                    (setq custom-file (expand-file-name "custom.el" config-dir))
                    (when (file-exists-p custom-file)
                      (load custom-file)))))

Suppress warnings

Some packages are sending unnecessary warnings while installed through use-package and it’s bothering me, so this is just for quieter experience with installation. Default value for this variable is :warning and I boosted it up to :error.

(setq byte-compile-warnings '(cl-functions))
(setq warning-minimum-level :error)

General Settings

In this section we are dealing with overall look and behavior of Emacs. Values and packages set here are the ones that will influence every mode in Emacs and it would be good for you to understand what they are doing. I tried to add links to repos or other pages of importance that can shine some light on what given package is trying to achieve.

Set defaults

Maximize Emacs

GUI app should take as much screen real estate as possible.

(custom-set-variables
 '(initial-frame-alist (quote ((fullscreen . maximized)))))

Cursor appearance

I want text cursor looking like bar (other options include: box, hollow, hbar, nil). This is purely personal preference, play with it and find what works for you.

(setq-default cursor-type 'bar)

Default mode

Opening files with unknown extension is best to start in text-mode and specify later.

(setq initial-major-mode 'text-mode)

Local values

This kind of safety is not needed and I want Emacs to load variables when it’s in some directory.

(setq enable-local-variables :all)

Global line numbering

Since Emacs 25, there is a built in replacement for linum, we turn it on for programming modes.

(defun display-numbers-hook()
  (display-line-numbers-mode 1))

(add-hook 'prog-mode-hook 'display-numbers-hook)

Dim unfocused window

Slightly dim window that is not currently in focus.

(use-package dimmer
  :custom (dimmer-fraction 0.2)
  :config (dimmer-mode))

Newline

Newline at the end is needed in most cases.

(setq require-final-newline t)
(setq mode-require-final-newline t)

Remove mouse

I often click on the touchpad by accident and I don’t really need a mouse anyway so I decided to turn it off completely. Steve Purcell packed that functionality into a package so I didn’t need to implement it here.

(use-package disable-mouse
  :config (global-disable-mouse-mode))

Split

(setq split-height-threshold nil)
(setq split-width-threshold 200)

Remove unwanted

Decorations

If you use Emacs without mouse there is not much need for toolbar, scrollbar or menu.

(tool-bar-mode 0)
(menu-bar-mode -1)

Messages

While these screens might be helpful for beginners when they start their journey with Emacs, after a while they become annoyances.

(setq inhibit-startup-message t)
(setq inhibit-splash-screen t)
(setq initial-scratch-message nil)

Confirmation

Expect y/n instead of yes/no when needing confirmation - this really ought to be default.

(fset 'yes-or-no-p 'y-or-n-p)

Tooltips

I never need GUI tooltips in Emacs and can’t imagine type of usage that welcomes it. Same goes for the font selection panel.

(setq tooltip-use-echo-area t)
(unbind-key "s-t")

Sound Beep

Beep is frequent, irritating and not at all helpful. Send it to message screen instead of speakers so you still have some kind of visible cue that it happened.

(setq ring-bell-function (lambda () (message "*beep*")))

Buffer specific

Switching

After trying out different solutions, I’m most comfortable switching windows with Ctrl Tab, probably because it’s the default way of switching tabs in browsers so I can use the same mental mapping.

(global-set-key [C-S-tab] 'windmove-left) ; move to left window
(global-set-key [C-tab] 'windmove-right) ; move to right window

Ace-window brings some additional options for case when there are more windows.

(use-package ace-window
  :config
  (global-set-key (kbd "M-o") 'ace-window)
  (global-set-key (kbd "M-i") 'ace-swap-window)
  (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)))

Clear

It might be personal quirk but most frequent use of C-l command for me is to move cursor position to top of the screen, so I usually type C-l C-l. Whenever something is repeating, aim for simplification.

(setq recenter-positions '(top middle bottom))

Cursor position

Show current row and column at the bottom of the buffer. This is helpful in most modes and unobtrusive in rest of them.

(setq column-number-mode t)

Wrap lines

Only case known to me where you would want unwrapped text is parsing binary files. It’s better to override behavior for those purposes, then to scroll left-right through buffer in all other scenarios.

(global-visual-line-mode 1)

Double space sentences

American typist’s convention for end of the sentence can cause trouble in some modes. If you need it just turn on M-x repunctuate-sentences.

(setq sentence-end-double-space nil)

Reverting buffers

When file edited in buffer changes from some outside source (say, git reset), I expect buffer to render that change immediately.

(global-auto-revert-mode t)

Visible go-to line

Jumping to line with goto-line can be more ergonomic if you have a preview of where that jump will land you.

(use-package goto-line-preview
  :config (global-set-key [remap goto-line] 'goto-line-preview)) ; replace goto-line globally

Editing

Fast buffer kill

Confirming or picking exact buffer when trying to kill it wastes time, just leave finger on Control and do it faster with C-x C-k.

(global-set-key (kbd "C-x C-k") 'kill-this-buffer)

Pasting text

When typing over selected text, I want it replaced and not appended. One of the rare cases when Emacs is in the wrong compared to majority of editors.

(delete-selection-mode 1)

Undo Tree

Interesting and efficient way of dealing with undo in Emacs. Takes some time to get used to, but ability to move through undo/redo tree can be great.

(use-package undo-tree
  :config (global-undo-tree-mode))

Whitespaces

Whitespace shrink

Really simple package, but I find it incredibly useful. Replaces rows of whitespaces with just one or deletes single whitespace. Shortcut is M-Space.

(use-package shrink-whitespace
  :config (global-set-key (kbd "M-SPC") 'shrink-whitespace))

Whitespace cleanup

Removing whitespaces in buffer from the end of the lines introduced by you. This is convenient in messy codebases because it doesn’t change other parts of the code.

(use-package ws-butler
  :config (ws-butler-global-mode 1))

Beacon

Whenever the window scrolls a light will shine on top of your cursor so you know where it is.

(use-package beacon
  :config
  (setq beacon-blink-duration 0.3)
  (setq beacon-blink-delay 0.5)
  (beacon-mode 1))

Expand Region

Expand or contract selected region by semantic units. Surprisingly usable for both code and text, with language-specific definitions of s-expressions.

(use-package expand-region
  :bind
  ("C->" . 'er/expand-region)
  ("C-<" . 'er/contract-region))

OS-specific

For now, I only customized things related to OSX because that’s the system I’m spending most of my time in. I plan to do fine tuning for Ubuntu also.

OSX

  • Bound Control to Caps-Lock key system-wide, not inside Emacs. This is something I encourage everybody to try.
  • Option is Meta by default, no need to do anything there.
  • Left Cmd is Super by default, no need to do anything there.
  • Right Cmd is Control, it’s the only key that makes sense for right hand.
  • Suppress killing and minimizing Emacs with OS shortcuts.
(when (eq system-type 'darwin)
  (global-set-key (kbd "s-q") nil)
  (global-set-key (kbd "s-w") nil)
  (global-set-key (kbd "C-~") nil)
  (setq mac-left-command-modifier 'super)
  (setq mac-right-command-modifier 'hyper))

Minibuffer

There are lot of packages that are trying to influence all aspects of working with Emacs and consequentially change behavior of minibuffer. I tried working with Helm, but in the end decided I don’t need such an invasive package because I started spending time chasing its quirks around some other big packages.

Another possible route is having just ido-mode and big number of specialized settings for different scenarios which also tends to become clutter after a while.

For now, I settled with Ivy which is a little bit more “overall solution” than I’m comfortable with, but it keeps things confined.

Ivy, Counsel, Swiper

Ivy is split into three packages - Ivy, Swiper and Counsel. Basic functionality of Ivy is to present list of options as completion mechanism. It’s not strictly bound to minibuffer and it can manage various inputs. Swiper is enhancement for I-search, and Counsel is collection of enhanced Emacs commands. By installing Counsel other two are brought as dependencies, but they all can be used separately. I made lot of global keybindings for these packages because they are created to replace standard functions of Emacs and enhance them in some way. Good doc for learning about this package can be found here and comprehensive manual is here.

(use-package counsel
  :config
  (ivy-mode 1) ; Use ivy-mode globally
  (setq ivy-use-virtual-buffers t)
  (setq ivy-count-format "%d/%d ")
  (setq ivy-height 20)
  :bind (
	 ;; Ivy bindings
	 ("C-x b" . ivy-switch-buffer)
	 ("C-c z" . ivy-resume)
	 ;; Swiper bindings
	 ("C-s" . swiper) ; replace I-search with swiper version
	 ("C-r" . swiper) ; replace backward I-search with swiper version
	 ("C-c u" . swiper-all) ; search in all opened buffers
	 ;; Counsel bindings
	 ("M-x" . counsel-M-x)
	 ("C-c g" . counsel-ag)
	 ("C-x l" . counsel-locate)
	 ("C-c m" . counsel-imenu)
   ("C-c o" . counsel-outline)
   ("C-c t" . counsel-load-theme)
	 ("C-x C-f" . counsel-find-file)
   ("C-x y" . counsel-find-library)
   ("C-x p" . counsel-list-processes)
   ("C-h f" . counsel-describe-function)
   ("C-h v" . counsel-describe-variable)
   ("C-h a" . counsel-apropos)
   ("C-h i" . counsel-info-lookup-symbol)
   ("C-h u" . counsel-unicode-char)
   ("C-h b" . counsel-descbinds) ; it hides `describe-bindings` from help.el
   ("C-h W" . woman) ; not part of counsel, but it belongs with these keybindings
	 ("C-M-y" . counsel-yank-pop)
   ))

Ivy Rich

Before ivy-rich, we can install all-the-icons-ivy-rich to present icons in lists. Run M-x all-the-icons-install-fonts beforehand to download and install them.

(use-package all-the-icons-ivy-rich
  :init (all-the-icons-ivy-rich-mode 1)
  :config (setq all-the-icons-ivy-rich-icon-size 1.3))

Not really essential, but ivy-rich adds some details to all Ivy results, such as keybindings, descriptions of commands on counsel-M-x etc.

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

Which key

which-key opens popup after entering incomplete command. Delay of one second gives enough time to finish command without seeing it, and if I’m stuck it shows available endings to entered prefix.

(use-package which-key 
  :config
  (which-key-setup-minibuffer)
  (setq which-key-side-window-location 'bottom)
    ;;(which-key-setup-side-window-right-bottom)
  (which-key-mode))

Help

For augmentation of describe functions. It adds lots of valuable information to standard Help.

(use-package helpful
  :custom
  (counsel-describe-function-function #'helpful-callable)
  (counsel-describe-variable-function #'helpful-variable)
  :bind
  ([remap describe-key] . helpful-key)
  ([remap describe-command] . helpful-command))

Meta

Emacs configuration is job that is never really finished so I added convenient shortcut to open README.org file from anywhere: C-c i. When I’m inside README, it tangles and reloads it again.

(defun djole/load-init ()
  "Open main README.org file or reload if it's opened."
  (interactive)
  (if (equal original-source buffer-file-name) ;; if: I'm already inside README.org
      (progn
	(org-babel-tangle-file original-source compiled-source) ;; do: recompile
	(load-file compiled-source)) ;; and: load again
    (find-file original-source))) ;; else: open README
(global-set-key (kbd "C-c i") 'djole/load-init) ;; Add global keybinding for this function

Authentication

Storing credentials can become complicated, look here for more info.

(setq auth-sources '((:source "~/.authinfo.gpg")))

;; (let ((password (auth-source-pick-first-password :host '("openai.com"))))
;;      (message "Password %s" password))

Theme

Picking theme is personal for everybody so if you don’t like my choice explore some resources out there and pick one that suits you. There are lot of repositories out there so you shouldn’t limit yourself to base16, but they do have some variety.

(use-package base16-theme
  :if window-system
  :config
  (load-theme 'base16-solarized-dark t)
  ; Remove background of code blocks in org files (TODO: Might be better way of doing this)
  (custom-set-faces
   '(org-block ((t (:background nil))))
   '(org-block-begin-line ((t (:background nil))))
   '(org-block-end-line ((t (:background nil))))))
;; light candidates: 'base16-mexico-light 'base16-atelier-cave-light
;; dark candidates: 'base16-oceanicnext 'base16-materia 'base16-apathy 'base16-atelier-savanna 'base16-chalk 'base16-google-dark 'base16-gruvbox-dark-pale

Org Mode

Customizing one of the biggest and most popular packages for Emacs could be an infinite job in itself, but I try to go with defaults as much as I can.

General Layout

Indentation

Indent everything to the level of its title, but skip further indentation of code.

(setq org-startup-indented t)
(setq org-edit-src-content-indentation 0)

Code highlights

Add some colors to the code using native mode for given language.

(setq org-src-fontify-natively t)

Code confirmation

I never accidentally type C-c C-c so there is no need for confirmation.

(setq org-confirm-babel-evaluate nil)

Tabs in code

Tabs should behave in expected way when in code block, default is quite confusing.

(setq org-src-tab-acts-natively t)

Emphasized text

Display emphasis immediately: Bold, italic

(setq org-hide-emphasis-markers t)

Special symbols

Present symbols as intended (pi -> π).

(setq org-pretty-entities t)

Bullets

org-bullets are presenting nice looking bullets instead of asterisks.

(use-package org-bullets
  :config (add-hook 'org-mode-hook 'org-bullets-mode))

Bindings

While trying to be as close to defaults as possible, I still have some preferences when it comes to customizing org-mode.

Template for elisp code

Org mode 9.2 changed structure template expansion, preferred way now is to open popup with ~C-c C-,~ where you can pick template with one letter. I mostly use source with emacs-lisp, so I added it to he list under letter p.

(add-to-list 'org-structure-template-alist '("p" . "src emacs-lisp"))

Company Org Block

This package ensures that writing < as first symbol in line opens up context menu with possible org blocks.

(use-package company-org-block
  :ensure t
  :custom
  (company-org-block-edit-style 'auto) ;; 'auto, 'prompt, or 'inline
  (company-begin-commands '(self-insert-command org-self-insert-command))
  :hook ((org-mode . (lambda ()
                       (setq-local company-backends '(company-org-block))
                       (company-mode +1)))))

Heading visibility

I found myself needing to close everything to some level in some org files and it turned out 5 levels is in the sweet spot. This one is a little helper for that.

(defun org-show-five-levels()
  (interactive)
  (org-content 5))

(add-hook 'org-mode-hook
  (lambda ()
    (define-key org-mode-map (kbd "C-c 5") 'org-show-five-levels)))

Checkmark shortcut

I often want to add checkmark to some places so I created a keybinding

(defun insert-large-checkmark ()
  (interactive)
  (insert (propertize "" 'face '(:height 3.5))))

(with-eval-after-load 'org
  (define-key org-mode-map (kbd "C-c x") 'insert-large-checkmark))

Appearance

Just one way for org-mode to look nice. I copied most of it from somewhere and added couple of things, but it’s a matter of personal preference so feel free to play with it. One more important note is that layout settings are tightly related to theme you are using, so this section is something you will probably often fine tune.

(let*
      ((variable-tuple (cond
                        ((x-list-fonts "Source Sans Pro") '(:font "Source Sans Pro"))
                        ((x-list-fonts "Lucida Grande")   '(:font "Lucida Grande"))
                        ((x-list-fonts "Verdana")         '(:font "Verdana"))
                        ((x-family-fonts "Sans Serif")    '(:family "Sans Serif"))
                        (nil (warn "Cannot find a Sans Serif Font.  Install Source Sans Pro."))))
       (base-font-color     (face-foreground 'default nil 'default))
       (headline           `(:inherit default :weight normal :foreground ,base-font-color)))

    (custom-theme-set-faces 'user
                            `(org-level-8 ((t (,@headline ,@variable-tuple))))
                            `(org-level-7 ((t (,@headline ,@variable-tuple))))
                            `(org-level-6 ((t (,@headline ,@variable-tuple))))
                            `(org-level-5 ((t (,@headline ,@variable-tuple))))
                            `(org-level-4 ((t (,@headline ,@variable-tuple))))
                            `(org-level-3 ((t (,@headline ,@variable-tuple :height 1.33))))
                            `(org-level-2 ((t (,@headline ,@variable-tuple :height 1.33))))
                            `(org-level-1 ((t (,@headline ,@variable-tuple :height 1.33))))
                            `(org-document-title ((t (,@headline ,@variable-tuple :height 1.33 :underline nil))))))

Exporters

I’m usually exporting to pdf, so ox-pandoc looks like package that covers all my needs. Can’t say that default exporters are pretty, but most of it looks customizable so I will stay with it for now. It needs to have package Pandoc installed on the system.

(use-package ox-pandoc
  :after (org))

Agenda

Global Shortcuts

(global-set-key (kbd "C-c l") 'org-store-link)
(global-set-key (kbd "C-c a") 'org-agenda)
(global-set-key (kbd "C-c c") 'org-capture)
(setq org-log-done t)

Files

Define default place for my agenda, all files with org extension inside this directory are taken into account.

(setq org-agenda-files '("~/org/agenda"))
(setq org-default-notes-file "~/org/agenda/notes.org")
(setq org-training-file "~/org/training.org")

Capture

Whenever capture is taken, it can be straight refiled to the agenda files. When C-c c brings out capture template C-c w can prompt with all the headings from agenda files making it easy to add a new task to the list.

(setq org-refile-use-outline-path 'file)                   ;; print path starting with file
(setq org-outline-path-complete-in-steps nil)              ;; print full paths to items, not just file
(setq org-refile-targets '((org-agenda-files :level . 1))) ;; only two levels should be listed as targets

Templates

Journal

I use org-journal to keep my daily diary.

(use-package org-journal
  :ensure t
  :init
  ;; Change default prefix key; needs to be set before loading org-journal
  (setq org-journal-prefix-key "C-c j ")
  :config
  (setq org-journal-dir "~/org/journal/"
	org-journal-file-format "%Y-%m-%d.org"
	org-journal-time-format ""
	org-journal-time-prefix ""
	org-journal-date-format "%A, %d %B %Y")
    :bind (
	   ("s-<left>" . org-journal-previous-entry)
	   ("s-<right>" . org-journal-next-entry)
	 ))

Capture Templates

Save todos in the default notes file showing list of headings as possible targets. Training file is organized in weekly format.

(defun refile-heading ()
  (interactive)
  (org-refile '(4)))

(setq org-capture-templates
      '(("d" "Todo" entry (file+function org-default-notes-file refile-heading)
	 "* TODO %?\nSCHEDULED: %(org-insert-time-stamp nil)")
	("t" "Training")
	("tw" "Weight" plain (file+datetree+prompt org-training-file "Timeline") "%^{WEIGHT}p" :tree-type week)
	("tr" "Running" entry (file+datetree+prompt org-training-file "Timeline")
	 "* Running\n- distance: %^{Distance} km\n- time: %^{Time} min\n- notes: %^{Notes}" :tree-type week)
	("to" "Outdoor Archery" entry (file+datetree+prompt org-training-file "Timeline")
	 "* Outdoor practice %(concat \":archery:\" (read-string \"Poundage: \") \"lb:\")\n- arrows: %^{Arrows}\n- distance: %^{Distance} m\n- notes: %^{Notes}" :tree-type week)
	("tg" "Gym" entry (file+datetree+prompt org-training-file "Timeline")
	 "* Gym\n%?" :tree-type week)))

Git

Version control is important part of Emacs ever since Magit entered the scene showing factual difference between “porcelain” and “plumbing”. After spending some time getting used to it, Magit’s efficiency will look like magic to seasoned git user.

Magit

Learn it, use it and never look back on days of typing something like: git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit

(use-package magit
  :bind ("C-x g" . magit-status)
  :config
  (add-to-list 'magit-no-confirm 'stage-all-changes) ; don't confirm staging all (S)
  (setq magit-save-repository-buffers 'dontask)) ; save related buffers when opening magit

Git Time Machine

git-timemachine lets me browse through previous commits in given file. It’s not used often, but reverting files can be touchy operation and this package presents differences in obvious way. Using it is easy: M-x git-timemachine and move through historic revisions of file with p and n.

(use-package git-timemachine)

Git Forge

Forge is package used in conjunction with Magit and primarily used for connection with Github, Gitlab or similar remotes (forges).

TODO: Make sure it is used and remove if not!

(use-package forge
  :after magit)

Programming

General settings

Camel Case

Treat CamelCase as separate words while editing.

(add-hook 'prog-mode-hook 'subword-mode)

Company

“Complete Anything” or company is used to complete text at point of typing. Make it global and let other packages add appropriate backends.

(use-package company
  :config (global-company-mode t))

Folding

I fought against using folding for my code because I feel that need for it is major red flag, but programmer’s life is a hard one and I found myself working with company that believes in ruby methods with hundreds of lines of code. This package folds methods so you can see bigger picture.

(use-package yafolding
  :config
  (add-hook 'prog-mode-hook 'yafolding-mode)
  (global-set-key (kbd "C-c y") 'yafolding-discover))

Projectile

(use-package projectile
  :config (projectile-mode)
  :custom (projectile-completion-system 'ivy)
  :bind-keymap ("C-c p" . projectile-command-map)
  :init
  (setq projectile-switch-project-action #'projectile-dired))

Clojure

Main Clojure package.

(use-package cider)

Paredit is used for structural editing, especially important when working with lisps.

(use-package paredit
  :config
  (add-hook 'emacs-lisp-mode-hook 'paredit-mode)
  (add-hook 'lisp-mode-hook 'paredit-mode)
  (add-hook 'clojure-mode-hook 'paredit-mode)
  (add-hook 'cider-repl-mode-hook 'paredit-mode)
  (add-hook 'eval-expression-minibuffer-setup-hook 'paredit-mode))

Good fit with paredit, forces indentation as you type.

(use-package aggressive-indent
  :after paredit
  :config
  (add-hook 'paredit-mode-hook 'aggressive-indent-mode)
  (add-to-list 'aggressive-indent-excluded-modes 'cider-repl-mode))

Colorful presentation of the parenthesis can be of use when there is so many of them.

(use-package rainbow-delimiters
  :after paredit
  :config (add-hook 'paredit-mode-hook 'rainbow-delimiters-mode))

Showing short flash when sexp is evaluated, adding some clarity to in-place eval.

(use-package cider-eval-sexp-fu
  :after paredit)

Ruby

REPL

Common library for opening REPL inside Emacs is inf-ruby, make it available for all ruby files.

(use-package inf-ruby
  :init
  (add-hook 'ruby-mode-hook 'inf-ruby-minor-mode)
  (setq inf-ruby-default-implementation "pry")
  :bind
  ("C-c q" . 'ruby-send-buffer)
  ("C-c C-q" . 'ruby-send-buffer-and-go)
  ("C-c r" . 'ruby-send-line)
  ("C-c C-r" . 'ruby-send-line-and-go))

Use Robe with ruby-mode, attach it to inf-ruby subprocess to show info about loaded methods. After configuring robe and company, add company-robe to the list of its backends.

(use-package robe
  :init (add-hook 'ruby-mode-hook 'robe-mode)
  :bind ("C-M-." . robe-jump)
  :config (eval-after-load 'company '(push 'company-robe company-backends)))

Refactoring

Ruby tools brings few refactoring options. I’m still not sure is it worth to include separate package but I’m trying it out.

TODO: Make sure that I’m using Ruby tools or remove it

(use-package ruby-tools
  :init (add-hook 'ruby-mode-hook 'ruby-tools-mode))

RSpec

Minor mode for specs rspec-mode is a great productivity booster when setup correctly. I don’t find default C-c ,~ binding convenient in given workflow, so I applied some faster bindings just for this mode. Various variables are moved in ~:config part of the setup for clarity.

(use-package rspec-mode
  :config
  ;; lot of repeating for keybindings, but kept like this for clarity
  (define-key rspec-mode-map (kbd "C-q a") 'rspec-verify-all)
  (define-key rspec-mode-map (kbd "C-q b") 'rspec-verify-matching)
  (define-key rspec-mode-map (kbd "C-q q") 'rspec-verify-single)
  (define-key rspec-mode-map (kbd "C-c C-c") 'rspec-verify-single)
  (define-key rspec-mode-map (kbd "C-q f") 'rspec-run-last-failed)
  (define-key rspec-mode-map (kbd "C-q r") 'rspec-rerun)
  (add-hook 'compilation-filter-hook 'inf-ruby-auto-enter); make RSpec get into editing mode on pry.
  (setq compilation-scroll-output 'first-error) ; scroll to the first test failure
  (setq compilation-ask-about-save nil) ; don't ask for confirmation of save when compiling
  (setq compilation-always-kill t)) ; don't ask for confirmation when killing compilation

Rubocop

Rubocop is a static code analyzer, enforcing good practices in coding. After you install rubocop gem (gem install rubocop) you can add rubocop-emacs to integrate it with Emacs.

(use-package rubocop
  :init (add-hook 'ruby-mode-hook 'rubocop-mode))

Rails

Part of projectile, projectile-rails helps navigating Rails projects. I added couple of handy keybindings that utilize Super key along with Control.

(use-package projectile-rails
  :config (projectile-rails-global-mode)
  (define-key projectile-rails-mode-map (kbd "C-c r") 'projectile-rails-command-map)
  (define-key projectile-rails-mode-map (kbd "C-s-m") 'projectile-rails-find-model)
  (define-key projectile-rails-mode-map (kbd "C-s-v") 'projectile-rails-find-view)
  (define-key projectile-rails-mode-map (kbd "C-s-c") 'projectile-rails-find-controller)
  (define-key projectile-rails-mode-map (kbd "C-s-h") 'projectile-rails-find-helper)
  (define-key projectile-rails-mode-map (kbd "C-s-f") 'projectile-rails-find-fixture)
  (define-key projectile-rails-mode-map (kbd "C-s-a") 'projectile-rails-find-locale)
  (define-key projectile-rails-mode-map (kbd "C-s-r") 'projectile-rails-find-component)
  (define-key projectile-rails-mode-map (kbd "C-s-s") 'projectile-rails-find-spec)
  (define-key projectile-rails-mode-map (kbd "C-s-l") 'projectile-rails-find-lib)
  (define-key projectile-rails-mode-map (kbd "C-s-p") 'projectile-rails-find-current-spec)
  (define-key projectile-rails-mode-map (kbd "C-s-i") 'projectile-rails-find-initializer)
  (define-key projectile-rails-mode-map (kbd "C-s-j") 'projectile-rails-find-job)
  (define-key projectile-rails-mode-map (kbd "C-s-n") 'projectile-rails-find-migration)
  (define-key projectile-rails-mode-map (kbd "C-s-g")  projectile-rails-mode-goto-map)
  (define-key projectile-rails-mode-map (kbd "C-s-<return>") 'projectile-rails-goto-file-at-point))

Web

HTML and CSS

Use web-mode for html, erb and various stylesheet files, indent by 2 spaces.

(use-package web-mode
  :mode ("\\.erb\\'" ".html?\\'" ".s?css\\'" ".sass\\'")
  :config
  (setq web-mode-markup-indent-offset 2)
  (setq web-mode-css-indent-offset 2)
  (setq web-mode-code-indent-offset 2))

Use separate mode for slim files, because web-mode doesn’t indent as it should. I’m still not happy with the mode but can’t find anything better for now.

(use-package slim-mode
  :mode ("\\.slim\\'"))

JavaScript

I tried js2 mode, but upgraded it with rjsx-mode which is derived from it. It’s far from perfect, but such is the state of fast moving front-end standards and old editors.

(use-package rjsx-mode
  :mode ("\\.jsx\\'" ".js\\'")
  :config
  (setq js-indent-level 2)
  (setq js2-strict-missing-semi-warning nil)
  (define-key rjsx-mode-map "<" nil)
  (define-key rjsx-mode-map (kbd "C-d") nil)
  (define-key rjsx-mode-map ">" nil))

Vue files are opened with vue-mode.

(use-package vue-mode
  :mode ("\\.vue\\'")
  :config
  (setq mmm-submode-decoration-level 1)
  (setq js-indent-level 2)) ;; 0, 1, or 2 == none, low, and high coloring

Jest for testing front end

(use-package jest-test-mode
:commands jest-test-mode
:init
(add-hook 'typescript-mode-hook 'jest-test-mode)
(add-hook 'js-mode-hook 'jest-test-mode)
(add-hook 'typescript-tsx-mode-hook 'jest-test-mode)
:config
(define-key jest-test-mode-map (kbd "C-c C-c") 'jest-test-run-at-point)
(define-key jest-test-mode-map (kbd "C-q q") 'jest-test-run-at-point)
;(define-key jest-test-mode-map (kbd "C-q a") 'jest-test-run-)
(define-key jest-test-mode-map (kbd "C-q a") 'jest-test-run) ; all in a buffer
(define-key jest-test-mode-map (kbd "C-q p") 'jest-test-run-all-tests)) ; all in a project

REST

Restclient is used in place of Postman or Insomnia. Versatile package, but it takes some practice to get used to it.

(use-package restclient
    :mode (("\\.http\\'" . restclient-mode))
    :bind (:map restclient-mode-map
                ("C-c C-f" . json-mode-beautify)))

Markup

Installing modes for various markup languages.

YAML

(use-package yaml-mode
  :mode "\\(\\.\\(yaml\\|yml\\)\\)\\'")

Markdown

(use-package markdown-mode 
  :init (setq-default markdown-hide-markup nil))

JSON

(use-package json-mode)

ASCII Doc

(use-package adoc-mode)

Drawing UML

UML diagrams can be drawn with Plant UML, using plantuml-mode. Library has to be installed on the system, and its path is loaded with (shell-command-to-string "which plantuml"). File plantuml.jar needs to be copied in the user’s home dir, which is easily done with M-x plantuml-download-jar<RET>. If this path is to be changed for some reason, both org-plantuml-jar-path and plantuml-jar-path variables have to be set here.

(use-package plantuml-mode
  :init
  (setq plantuml-default-exec-mode 'executable)
  (setq plantuml-executable-path (replace-regexp-in-string "\n$" "" (shell-command-to-string "which plantuml")))
  (setq org-plantuml-jar-path "~/plantuml.jar")
  (add-to-list 'org-src-lang-modes '("plantuml" . plantuml))
  (org-babel-do-load-languages 'org-babel-load-languages '((plantuml . t))))

Redis

Use eredis for connecting to Redis server.

(use-package eredis)

Docker

Try dockerfile-mode.

(use-package dockerfile-mode
  :config (add-to-list 'auto-mode-alist '("Dockerfile\\'" . dockerfile-mode)))

Try this for docker-compose. TODO: Check if really needed.

(use-package docker-compose-mode)

File System

Wgrep

Every search reasult or file list can be conveyed into grep buffer which can be edited with wgrep, effectively giving you power of sed in Emacs.

(use-package wgrep)

Ansible

Small package for helping with ansible files, especially with encryption of buffers.

(use-package ansible
  :config (add-hook 'yaml-mode-hook 'ansible))

Zettelkasten

TODO: Explain zettelkasten basics.

Zetteldeft relies on deft, package that improves working with large number of small files.

(use-package deft
  :custom
    (deft-extensions '("org" "md" "txt"))
    (deft-directory "~/org/zettelkasten/")
    (deft-use-filename-as-title t))

Zetteldeft looks like the most advanced package for Zettelkasten on Emacs.

(use-package zetteldeft
  :after deft
  :config
  (zetteldeft-set-classic-keybindings)
  (setq zetteldeft-title-suffix "\n#+TAGS #"))

Writing

English is not my native language so I need more help than some. I still try to keep spellcheck unobtrusive and grammar or style suggestions on minimum so this setting could just be starting point for someone who needs more substantial suggestions or is writing more in natural than programming languages.

fly-spell

flyspell-correct is wrapper for flyspell with interface that can easily work with ivy, helm or simple popup presentation. Fly-spell uses separate program to compare words, on Mac it’s easiest to install aspell which comes with it’s own dictionaries. Other option is hunspell, but it’s harder for setup because you have to manually put dictionaries in path. Tutorial for usage is available on it’s homepage and you should make sure that you have dictionaries for preferred languages by running hunspell -D in console, and make sure that one of them is labeled “default”. More explanation about setting Hunspell to work with Emacs can be found here. I hooked text and org mode with flyspell-mode, binding correction to C-;.

(use-package flyspell-correct-popup ; Seems more convenient than `flyspell-correct-ivy` that I used for a long time
  :after flyspell
  :config
  (setq ispell-program-name (executable-find "hunspell"))
  (add-hook 'text-mode-hook 'flyspell-mode)
  (add-hook 'org-mode-hook 'flyspell-mode)
  (define-key flyspell-mode-map (kbd "C-;") #'flyspell-correct-wrapper))

guess-language

For those who frequently use more than one language, it’s convenient to have that language automatically recognized without need for some headers in files. guess-language does exactly that and can work even in files where languages are mixed. You just put cursor on wanted paragraph and run guess-language. I hooked it with flyspell-mode because I use it for switching dictionaries which seems like common usage. My dictionaries obviously will not work for everyone, but it’s fairly easy to change them. Just make sure that names are the same as dictionaries available to `ispell-program-mode` you picked in `fly-spell` section. For example, Hunspell uses en_US as a name for dictionary, so you have to connect language en to it.

(use-package guess-language
  :after flyspell-correct
  :load-path "elpa/guess_tmp/" ;; Temporary line because updated package is still not on MELPA
  :config
  (add-hook 'flyspell-mode-hook 'guess-language-mode)
  (setq guess-language-languages '(en sr sr_LAT))
  (setq guess-language-langcodes
	'((en . ("en_US" "English")) (sr . ("sr" "Српски")) (sr_LAT . ("sr_LAT" "Srpski")))))

writegood-mode

writegood-mode is checking for weasel words, passive voice or duplicates in prose.

(use-package writegood-mode
  :init (global-set-key (kbd "C-c w") 'writegood-mode))

define-word

Nice little tool that pull definition of the word from wordnik and present it as message. I bound it to the symbol #, and it can be called from anywhere with M-# to define word with cursor on it or prompt for word with C-M-#.

(use-package define-word
  :bind
  ("M-#" . define-word-at-point)
  ("C-M-#" . define-word))

Reading

RSS

elfeed

Elfeed is the de facto standard for reading RSS feeds. It’s globally bound to C-c f, because I primarily read my feeds like this. Most of the specific settings for elfeed are moved to the next heading where I use elfeed-org. Elfeed saves database in the `~/.emacs.d/data/elfeed` directory which you can delete if you want to start over.

(use-package elfeed
  :bind ("C-c f" . elfeed))

elfeed-org

I use elfeed-org mostly to load my feeds from the org file. If you choose to do the same thing, make sure to change path to your file and to tag root node in it with :elfeed:.

(use-package elfeed-org
  :after elfeed
  :config
  (elfeed-org)
  (setq rmh-elfeed-org-files (list "~/org/elfeed.org"))
  (setq elfeed-org-tree-id "elfeed"))

Focus

Focus can be useful for increasing visibility of smaller part of the buffer. Active thing can be word, paragraph, s-expression… and rest of the text in the buffer gets dimmed to highlight region of interest. It’s not turned on by default because it can get in the way when reading is the main activity in the buffer.

(use-package focus
:config (define-key focus-mode-map (kbd "C-c f") 'focus-change-thing)) ; override global elfeed keybinding

Small side packages

Touch typing

Spare minutes are best spent on practicing some touch typing and I added some packages that can be helpful.

speed-type

speed-type takes practicing examples on random which sometimes can be demanding with exotic examples that it puts in front of you.

(use-package speed-type)

typit

typit is convenient for building speed on common words.

(use-package typit)

AI

In this stage, there are many libraries for Emacs that are using ChatGPT. Here is more info: Hacker News, and this list also. I intend to change this section and try every package I run into.

One of the packages, has a bit different layout because chat is formatted as markup.

(use-package gptel
  :config (setq gptel-api-key (auth-source-pick-first-password :host '("openai.com"))))

Another lib for chat, maybe a bit better organized.

(use-package chatgpt
  :config (setq openai-key (auth-source-pick-first-password :host '("openai.com"))))

Library that produces images from explanation.

(use-package dall-e
    :config (setq openai-key (auth-source-pick-first-password :host '("openai.com"))))

Library specialized for code analysis.

(use-package codegpt
    :config
    (setq codegpt-model "gpt-3.5-turbo")
    (setq openai-key (auth-source-pick-first-password :host '("openai.com"))))

Codium

;; we recommend using use-package to organize your init.el
(use-package codeium
  :init
  (add-to-list 'completion-at-point-functions #'codeium-completion-at-point)
  :config
  (setq use-dialog-box nil) ;; do not use popup boxes
  ;; get codeium status in the modeline
  (setq codeium-mode-line-enable
        (lambda (api) (not (memq api '(CancelRequest Heartbeat AcceptCompletion)))))
  (add-to-list 'mode-line-format '(:eval (car-safe codeium-mode-line)) t)
  ;; alternatively for a more extensive mode-line
  ;; (add-to-list 'mode-line-format '(-50 "" codeium-mode-line) t)

  ;; use M-x codeium-diagnose to see apis/fields that would be sent to the local language server
  (setq codeium-api-enabled
        (lambda (api)
          (memq api '(GetCompletions Heartbeat CancelRequest GetAuthToken RegisterUser auth-redirect AcceptCompletion))))
  ;; You can overwrite all the codeium configs!
  ;; for example, we recommend limiting the string sent to codeium for better performance
  (defun my-codeium/document/text ()
    (buffer-substring-no-properties (max (- (point) 3000) (point-min)) (min (+ (point) 1000) (point-max))))
  ;; if you change the text, you should also change the cursor_offset
  ;; warning: this is measured by UTF-8 encoded bytes
  (defun my-codeium/document/cursor_offset ()
    (codeium-utf8-byte-length
     (buffer-substring-no-properties (max (- (point) 3000) (point-min)) (point))))
  (setq codeium/document/text 'my-codeium/document/text)
  (setq codeium/document/cursor_offset 'my-codeium/document/cursor_offset))

Copilot

Installing Github Copilot just to see how it works.

;; Various packages needed for copilot
(let ((pkg-list '(s dash editorconfig)))
  (package-initialize)
  (when-let ((to-install (map-filter (lambda (pkg _) (not (package-installed-p pkg))) pkg-list)))
    (package-refresh-contents)
    (mapc (lambda (pkg) (package-install pkg)) pkg-list)))

;; Copilot pulled from the local folder where it was added by git submodule add https://github.com/zerolfx/copilot.el
(use-package copilot
  :load-path (lambda () (expand-file-name "copilot.el" user-emacs-directory))
  ;; don't show in mode line
  :diminish)

Some settings for copilot, let’s see how it goes.

(defun rk/no-copilot-mode ()
  "Helper for `rk/no-copilot-modes'."
  (copilot-mode -1))

(defvar rk/no-copilot-modes '(shell-mode
                              inferior-python-mode
                              eshell-mode
                              term-mode
                              vterm-mode
                              comint-mode
                              compilation-mode
                              debugger-mode
                              dired-mode-hook
                              compilation-mode-hook
                              flutter-mode-hook
                              minibuffer-mode-hook
			          *rspec-compilation*)
  "Modes in which copilot is inconvenient.")

(defun rk/copilot-disable-predicate ()
  "When copilot should not automatically show completions."
  (or rk/copilot-manual-mode
      (member major-mode rk/no-copilot-modes)
      (company--active-p)))

(add-to-list 'copilot-disable-predicates #'rk/copilot-disable-predicate)

(defvar rk/copilot-manual-mode nil
  "When `t' will only show completions when manually triggered, e.g. via M-C-<return>.")

(defun rk/copilot-change-activation ()
  "Switch between three activation modes:
- automatic: copilot will automatically overlay completions
- manual: you need to press a key (M-C-<return>) to trigger completions
- off: copilot is completely disabled."
  (interactive)
  (if (and copilot-mode rk/copilot-manual-mode)
      (progn
        (message "deactivating copilot")
        (global-copilot-mode -1)
        (setq rk/copilot-manual-mode nil))
    (if copilot-mode
        (progn
          (message "activating copilot manual mode")
          (setq rk/copilot-manual-mode t))
      (message "activating copilot mode")
      (global-copilot-mode))))

(define-key global-map (kbd "M-C-<escape>") #'rk/copilot-change-activation)

(defun rk/copilot-complete-or-accept ()
  "Command that either triggers a completion or accepts one if one
is available. Useful if you tend to hammer your keys like I do."
  (interactive)
  (if (copilot--overlay-visible)
      (progn
        (copilot-accept-completion)
        (open-line 1)
        (next-line))
    (copilot-complete)))

(define-key copilot-mode-map (kbd "s-<up>") #'copilot-next-completion)
(define-key copilot-mode-map (kbd "s-<down>") #'copilot-previous-completion)
(define-key copilot-mode-map (kbd "s-[") #'copilot-accept-completion-by-word)
(define-key copilot-mode-map (kbd "s-]") #'copilot-accept-completion-by-line)
(define-key global-map (kbd "s-<return>") #'rk/copilot-complete-or-accept)


(defun rk/copilot-quit ()
  "Run `copilot-clear-overlay' or `keyboard-quit'. If copilot is
cleared, make sure the overlay doesn't come back too soon."
  (interactive)
  (condition-case err
      (when copilot--overlay
        (lexical-let ((pre-copilot-disable-predicates copilot-disable-predicates))
          (setq copilot-disable-predicates (list (lambda () t)))
          (copilot-clear-overlay)
          (run-with-idle-timer
           1.0
           nil
           (lambda ()
             (setq copilot-disable-predicates pre-copilot-disable-predicates)))))
    (error handler)))

(advice-add 'keyboard-quit :before #'rk/copilot-quit)