An Emacs configuration for note taking, documenting and development. Clone using:
git clone https://github.com/madhavan-raja/emacs-config.git ~/.emacs.d
Open README.org
in Emacs and run:
(org-babel-tangle)
Lastly, restart Emacs.
- Introduction
- Packages
- Window
- Behaviour
- Theme
- Basic Visual Adjustments
- Fonts
- Indentations
- All The Icons
- Evil Mode
- Additional Packages
- Org Mode
- Functions
We set up melpa, use-package, and straight.el for installing package later on. Melpa has a lot more packages than the default elpa. Use-package is pretty useful (pun intended) since packages can be automatically installed and lazy-loaded. Straight.el is for downloading packages from git repos. We also install the package auto-package-update
to update installed packages.
(require 'package)
(setq package-archives '(("gnu" . "http://elpa.gnu.org/packages/")
("melpa" . "http://melpa.org/packages/")))
(package-initialize)
(unless package-archive-contents
(package-refresh-contents))
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(require 'use-package)
(setq use-package-always-ensure t)
(setq package-enable-at-startup nil)
(use-package auto-package-update
:ensure t
:config
(setq auto-package-update-delete-old-versions t
auto-package-update-interval 4)
(auto-package-update-maybe))
Set window title, start the application maximized and disable the startup screen.
(setq frame-title-format "%b - Emacs")
(add-to-list 'default-frame-alist '(fullscreen . maximized))
(setq ring-bell-function 'ignore)
(setq-default
delete-by-moving-to-trash t ;; Move to trash instead of deleting
require-final-newline t ;; Auto create a newline at end of file
custom-safe-themes t ;; Don't ask if theme is safe
warning-minimum-level :emergency ;; Emacs, honestly, I want you to shut up
disabled-command-function nil ;; Yes I want to use that command
vc-follow-symlinks) ;; Follow those damn symlinks!
(defalias 'yes-or-no-p 'y-or-n-p) ;; Y or N instead of yes or no
(setq scroll-margin 1
scroll-step 1
scroll-conservatively 10000
smooth-scroll-margin 1)
Disable the generation of backup files.
(setq make-backup-files nil)
Disable the notifications.
(setq ring-bell-function 'ignore)
Map the escape key to cancel the current command. Useful with evil-mode
.
(global-set-key (kbd "<escape>") 'keyboard-escape-quit)
Set the location of the custom files to .custom
directory.
(setq custom-file (expand-file-name ".custom" user-emacs-directory))
(use-package doom-themes
:config
(load-theme 'doom-gruvbox t)
(doom-themes-neotree-config))
(scroll-bar-mode -1)
(menu-bar-mode -1)
(tool-bar-mode -1)
(tooltip-mode -1)
(setq inhibit-splash-screen nil
inhibit-startup-echo-area-message t
inhibit-startup-message t)
This replaces some text with icons. Also ligatures.
(defun org/prettify-set ()
(interactive)
(setq prettify-symbols-alist
'(("#+begin_example" . "")
("#+BEGIN_EXAMPLE" . "")
("#+end_example" . "")
("#+END_EXAMPLE" . "")
("#+results:" . "")
("#+RESULTS:" . "")
("#+begin_quote" . "❝")
("#+BEGIN_QUOTE" . "❝")
("#+end_quote" . "❞")
("#+END_QUOTE" . "❞")
("[ ]" . "☐")
("[-]" . "◯")
("[X]" . "☑"))))
(add-hook 'org-mode-hook 'org/prettify-set)
(defun prog/prettify-set ()
(interactive)
(setq prettify-symbols-alist
'(("lambda" . "λ")
("->" . "→")
("<-" . "←")
("<=" . "≤")
(">=" . "≥")
("!=" . "≠")
("~=" . "≃")
("=~" . "≃"))))
(add-hook 'prog-mode-hook 'prog/prettify-set)
(global-prettify-symbols-mode)
Display line numbers when programming.
(global-display-line-numbers-mode)
(setq display-line-numbers-type 'relative)
And sometimes line numbers can be distracting, so we disable them for certain modes.
(dolist (mode '(org-mode-hook
term-mode-hook
eshell-mode-hook
neotree-mode-hook
elfeed-show-mode-hook
circe-channel-mode-hook
circe-chat-mode-hook
doc-view-mode-hook
xwidget-webkit-mode-hook
woman-mode-hook))
(add-hook mode (lambda () (display-line-numbers-mode 0))))
These are some useful minor modes that I tend to use.
(save-place-mode) ;; Save location
(global-visual-line-mode) ;; Wrap lines
(global-auto-revert-mode) ;; Revert buffers
Set font and fringe background color:
(setq text-scale-mode-step 1.1)
(set-face-attribute 'default nil :family "Iosevka" :weight 'regular :height 140)
(set-face-attribute 'fixed-pitch nil :font "Iosevka" :weight 'regular :height 1.0)
(set-face-attribute 'variable-pitch nil :font "Times New Roman" :height 140)
Configuring indentation.
(setq-default indent-tabs-mode nil
tab-width 2)
(setq indent-line-function 'insert-tab)
Fonts used by doom-modeline
.
Install the fonts first by running:
(all-the-icons-install-fonts)
Install the package using:
(use-package all-the-icons)
Here we install and configure evil, since I cannot use the default Emacs keys. Evil is the only way I’ve managed to move to Emacs. The Vim key bindings are a lot better than the Emacs keybindings. Evil-collection is for miscellaneous minor modes, evil-org for org mode, and evil-leader adds a leader key.
This is the main evil package, that allows you to use vim keybindings.
(use-package evil
:init
(setq evil-want-integration t)
(setq evil-want-keybinding nil)
:config
(evil-mode 1))
This package adds Vim keybindings for miscellaneous minor modes, such as dired and mu4e.
(use-package evil-collection
:after evil
:config
(evil-collection-init))
For some reason evil-collection doesn’t include org bindings, so we install another package.
(use-package evil-org
:diminish evil-org-mode
:after org
:config
(add-hook 'org-mode-hook 'evil-org-mode)
(add-hook 'evil-org-mode-hook
(lambda () (evil-org-set-key-theme))))
(require 'evil-org-agenda)
(evil-org-agenda-set-keys)
This adds a leader key to Emacs, which is incredibly useful.
(use-package evil-leader
:config
(global-evil-leader-mode)
(evil-leader/set-leader "<SPC>")
(evil-leader/set-key
;; General
".f" 'consult-isearch
".q" 'delete-frame
".e" 'eval-region
".s" 'straight-use-package
;; Configs
"ce" (lambda () (interactive) (find-file "~/.emacs.d/README.org"))
;; Undo
"uv" 'undo-tree-visualize
"uu" 'undo-tree-undo
"ur" 'undo-tree-redo
"uc" 'consult-yank-pop
;; Words
"wt" 'mw-thesaurus-lookup-dwim
"wd" 'dictionary-lookup-definition
"we" 'emoji-insert
;; Files
"fr" 'consult-recent-file
"fb" 'consult-bookmark
"ff" 'find-file
;; Bufffers
"bv" 'split-window-right
"bh" 'split-window-below
"bd" 'kill-current-buffer
"bb" 'consult-buffer
"bx" 'switch-to-scratch
;; Projectile
"pa" 'projectile-add-known-project
"pf" 'consult-projectile
"pp" 'projectile-switch-project
"pg" 'projectile-grep
"pm" 'projectile-commander
"pc" 'projectile-compile-project
;; Org Mode
"oc" 'org-edit-special
"ol" 'org-latex-preview
"ot" 'org-ctrl-c-ctrl-c
"oi" 'org-toggle-inline-images
"oa" 'org-agenda
"os" 'org-schedule
; Export
"oep" 'org-latex-export-to-pdf
"oeh" 'org-html-export-to-html
"oem" 'org-man-export-to-man
"oeu" 'org-publish-project
; Babel
"obs" 'org-babel-execute-src-block
"obb" 'org-babel-execute-buffer
"obl" 'org-babel-load-file
;; Help
"hh" 'help
"hk" 'describe-key
"hv" 'describe-variable
"hf" 'describe-function
"hs" 'describe-symbol
"hm" 'describe-mode
;; Magit
"gi" 'magit-init
"gc" 'magit-commit
"gp" 'magit-push
"gC" 'magit-clone
"gs" 'magit-status))
Here I bind some extra keybindings for evil mode.
(define-key evil-normal-state-map (kbd "M-s") 'save-buffer)
(define-key evil-normal-state-map (kbd "M-q") 'delete-window)
(define-key evil-normal-state-map (kbd "M-w") 'kill-current-buffer)
(define-key evil-normal-state-map (kbd "<C-tab>") 'consult-buffer)
(define-key evil-normal-state-map (kbd "C-h") 'evil-window-left)
(define-key evil-normal-state-map (kbd "C-j") 'evil-window-down)
(define-key evil-normal-state-map (kbd "C-k") 'evil-window-up)
(define-key evil-normal-state-map (kbd "C-l") 'evil-window-right)
(define-key evil-normal-state-map (kbd "M-j") 'evil-scroll-down)
(define-key evil-normal-state-map (kbd "M-k") 'evil-scroll-up)
(define-key evil-normal-state-map "u" 'undo-tree-undo)
(define-key evil-normal-state-map (kbd "C-r") 'undo-tree-redo)
(define-key evil-normal-state-map (kbd "M-t") 'neotree-toggle)
(define-key evil-normal-state-map (kbd "M-m") 'minimap-mode)
(define-key evil-normal-state-map (kbd "<C-return>") 'shr-browse-url)
(define-key key-translation-map (kbd "ESC") (kbd "C-g"))
(define-key evil-normal-state-map (kbd "C-=") 'text-scale-increase)
(define-key evil-normal-state-map (kbd "C--") 'text-scale-decrease)
(define-key evil-normal-state-map (kbd "C-0") 'text-scale-adjust)
(define-key evil-normal-state-map (kbd "<remap> <evil-next-line>") 'evil-next-visual-line)
(define-key evil-normal-state-map (kbd "<remap> <evil-previous-line>") 'evil-previous-visual-line)
(define-key evil-motion-state-map (kbd "<remap> <evil-next-line>") 'evil-next-visual-line)
(define-key evil-motion-state-map (kbd "<remap> <evil-previous-line>") 'evil-previous-visual-line)
(defun my/c-c ()
(interactive)
(setq unread-command-events (listify-key-sequence (kbd "C-c"))))
(defun my/c-k ()
(interactive)
(setq unread-command-events (listify-key-sequence (kbd "C-k"))))
(evil-define-key 'normal global-map (kbd ",c") 'my/c-c)
(evil-define-key 'normal global-map (kbd ",x") 'my/c-k)
Set the cursor shape for different evil states.
(set-default 'evil-normal-state-cursor 'box)
(set-default 'evil-insert-state-cursor 'bar)
(set-default 'evil-visual-state-cursor 'hbar)
(set-default 'evil-motion-state-cursor 'box)
(set-default 'evil-replace-state-cursor 'box)
(set-default 'evil-operator-state-cursor 'hbar)
(set-cursor-color "#B37AAE")
(setq-default cursor-type 'bar)
We want some Emacs in evil, so we change a few settings here.
(setq evil-cross-lines t
evil-move-beyond-eol t
evil-symbol-word-search t
evil-want-Y-yank-to-eol t
evil-cross-lines t)
We install which-key in case we ever forget any keybinds.
(use-package which-key
:config (which-key-mode)
(which-key-setup-side-window-bottom)
(setq which-key-idle-delay 0.1))
Vertico helps with better completion and to replace the default M-x. Consult adds a few things. Orderless adds fuzzy findings, marginalia adds stuff to your minibuffer.
(use-package consult)
(use-package vertico
:ensure t
:bind (:map vertico-map
("C-j" . vertico-next)
("C-k" . vertico-previous))
:init
(vertico-mode)
:config
(setq vertico-cycle t))
(use-package orderless
:init
(setq completion-styles '(orderless)
completion-category-defaults nil
completion-category-overrides '((file (styles partial-completion)))))
(use-package marginalia
:after vertico
:ensure t
:custom
(maarginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil))
:init
(marginalia-mode))
Neotree is a cool file tree, so we install it. Although I usually use dired, neotree can be useful if you need a tree layout.
(use-package neotree)
(setq neo-theme (if (display-graphic-p) 'icons 'arrow))
(add-hook 'neotree-mode-hook
(lambda ()
(define-key evil-normal-state-local-map (kbd "SPC") 'neotree-quick-look)
(define-key evil-normal-state-local-map (kbd "RET") 'neotree-enter)
(define-key evil-normal-state-local-map (kbd "g") 'neotree-refresh)
(define-key evil-normal-state-local-map (kbd "n") 'neotree-next-line)
(define-key evil-normal-state-local-map (kbd "p") 'neotree-previous-line)
(define-key evil-normal-state-local-map (kbd "A") 'neotree-stretch-toggle)
(define-key evil-normal-state-local-map (kbd "H") 'neotree-hidden-file-toggle)))
(setq neo-window-fixed-size nil)
'(neo-dir-link-face ((t (:foreground "deep sky blue" :slant normal :weight bold :height 100 :family "Fira Code"))))
'(neo-file-link-face ((t (:foreground "White" :weight normal :height 120 :family "Fira Code"))))
Magit is the best git client, and it is a must. Less typing, less time spent using git, and more coding.
(use-package magit
:defer t)
Most code editors automatically match parentheses, but Emacs doesn’t do this, so we install a package.
(use-package smartparens
:config (smartparens-global-mode)
(show-smartparens-mode))
Most editors also automatically color matching parentheses, but we need to install a package for this to happen.
(use-package rainbow-delimiters
:config
(add-hook 'prog-mode-hook #'rainbow-delimiters-mode))
The default mode line is ugly, so this package replaces it with one that looks like the Doom mode line.
(setq display-time-default-load-average nil)
(line-number-mode)
(column-number-mode)
(display-time-mode -1)
(size-indication-mode -1)
(use-package doom-modeline
:init (doom-modeline-mode)
:config
(setq doom-modeline-buffer-file-name-style 'file-name
doom-modeline-height 30
doom-modeline-enable-word-count t
doom-modeline-buffer-encoding nil
doom-modeline-icon t
doom-modeline-modal-icon nil
doom-modeline-major-mode-icon t
doom-modeline-major-mode-color-icon t
doom-modeline-bar-width 3))
(custom-set-faces
'(mode-line ((t (:family "Iosevka" :height 110)))))
(use-package hide-mode-line
:hook
(special-mode . hide-mode-line-mode)
(term-mode . hide-mode-line-mode)
(neotree-mode . hide-mode-line-mode))
We want to visualize the undo history better, so we install the undo-tree package. Oh and we don’t want to save the history.
(use-package undo-tree
:config
(global-undo-tree-mode)
(setq undo-tree-auto-save-history nil))
Let’s install a formatter to format our horrible code.
(use-package format-all
:init (format-all-mode))
I use Emacs for coding as well, so we’re going to configure lsp-mode.
(use-package lsp-mode
:init
:hook (prog-mode . lsp-mode)
; (lua-mode . lsp)
; (python-mode . lsp)
; (sh-mode . lsp)
; (lisp-mode . lsp)
; (css-mode . lsp)
; (html-mode . lsp)
; (json-mode . lsp)
; (markdown-mode . lsp)
; (latex-mode . lsp)
; (go-mode . lsp)
; (text-mode . lsp)
; (org-mode . lsp-mode))
:commands lsp
:config
(setq lsp-enable-symbol-highlighting nil
lsp-enable-which-key-integration t
lsp-ui-doc-enable t
lsp-lens-enable nil
lsp-headerline-breadcrumb-enable nil
lsp-ui-sideline-enable nil
lsp-ui-sideline-enable t
lsp-modeline-code-actions-enable t
lsp-ui-sideline-enable t
lsp-ui-doc-border nil
lsp-eldoc-enable-hover t
lsp-log-io nil
lsp-enable-file-watchers nil))
; (use-package lsp-grammarly)
(use-package lsp-ui :commands lsp-ui-mode)
(use-package lsp-treemacs
:after lsp)
(setq lsp-enable-symbol-highlighting nil)
Company is used for completions.
(use-package company
:after lsp-mode
:hook (lsp-mode . company-mode)
:bind (:map company-active-map
("<tab>" . company-complete-selection))
(:map lsp-mode-map
("<tab>" . company-indent-or-complete-common))
:custom
(company-minimum-prefix-length 1)
(company-idle-delay 0.0))
(use-package company-box
:hook (company-mode . company-box-mode))
Syntax checking for code.
; (use-package flycheck
; :ensure t
; :init (global-flycheck-mode))
Here, we install and configure projectile, which is a project interaction library.
(use-package projectile
:config (projectile-mode 1))
Drag-and-drop images directly into Emacs!
(use-package org-download)
Indent the contents of an Org document.
(add-hook 'org-mode-hook 'org-indent-mode)
Renders a handful of LaTeX expressions.
; (add-hook 'org-mode-hook 'org-toggle-pretty-entities)
Customize the face of the bullets.
(use-package org-bullets
:ensure t
:init
(setq org-bullets-bullet-list
'("•"))
:config
(add-hook 'org-mode-hook (lambda () (org-bullets-mode 1))))
Change the character for the ellipses.
(setq org-ellipsis " ⤵ ")
We like table of contents, right?
(use-package toc-org)
(use-package svg-tag-mode
:init
(defconst date-re "[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}")
(defconst time-re "[0-9]\\{2\\}:[0-9]\\{2\\}")
(defconst day-re "[A-Za-z]\\{3\\}")
(defun svg-progress-percent (value)
(svg-image (svg-lib-concat
(svg-lib-progress-bar (/ (string-to-number value) 100.0)
nil :margin 0 :stroke 2 :radius 3 :padding 2 :width 11)
(svg-lib-tag (concat value "%")
nil :stroke 0 :margin 0)) :ascent 'center))
(defun svg-progress-count (value)
(let* ((seq (mapcar #'string-to-number (split-string value "/")))
(count (float (car seq)))
(total (float (cadr seq))))
(svg-image (svg-lib-concat
(svg-lib-progress-bar (/ count total) nil
:margin 0 :stroke 2 :radius 3 :padding 2 :width 11)
(svg-lib-tag value nil
:stroke 0 :margin 0)) :ascent 'center)))
(setq svg-tag-tags
`(
;; Org tags
; (":\\([A-Za-z0-9]+\\)" . ((lambda (tag) (svg-tag-make tag))))
; (":\\([A-Za-z0-9]+[ \-]\\)" . ((lambda (tag) tag)))
;; Task priority
("\\[#[A-Z]\\]" . ( (lambda (tag)
(svg-tag-make tag :face 'org-priority
:beg 2 :end -1 :margin 0))))
;; Progress
("\\(\\[[0-9]\\{1,3\\}%\\]\\)" . ((lambda (tag)
(svg-progress-percent (substring tag 1 -2)))))
("\\(\\[[0-9]+/[0-9]+\\]\\)" . ((lambda (tag)
(svg-progress-count (substring tag 1 -1)))))
;; TODO / DONE
("TODO" . ((lambda (tag) (svg-tag-make "TODO" :face 'org-todo :inverse t :margin 0))))
("DONE" . ((lambda (tag) (svg-tag-make "DONE" :face 'org-done :margin 0))))
("IN PROGRESS" . ((lambda (tag) (svg-tag-make "IN PROGRESS" :face 'org-done :margin 0))))
("CANCELLED" . ((lambda (tag) (svg-tag-make "CANCELLED" :face 'org-done :margin 0))))
;; Citation of the form [cite:@Knuth:1984]
("\\(\\[cite:@[A-Za-z]+:\\)" . ((lambda (tag)
(svg-tag-make tag
:inverse t
:beg 7 :end -1
:crop-right t))))
("\\[cite:@[A-Za-z]+:\\([0-9]+\\]\\)" . ((lambda (tag)
(svg-tag-make tag
:end -1
:crop-left t))))
;; Active date (without day name, with or without time)
(,(format "\\(<%s>\\)" date-re) .
((lambda (tag)
(svg-tag-make tag :beg 1 :end -1 :margin 0))))
(,(format "\\(<%s *\\)%s>" date-re time-re) .
((lambda (tag)
(svg-tag-make tag :beg 1 :inverse nil :crop-right t :margin 0))))
(,(format "<%s *\\(%s>\\)" date-re time-re) .
((lambda (tag)
(svg-tag-make tag :end -1 :inverse t :crop-left t :margin 0))))
;; Inactive date (without day name, with or without time)
(,(format "\\(\\[%s\\]\\)" date-re) .
((lambda (tag)
(svg-tag-make tag :beg 1 :end -1 :margin 0 :face 'org-date))))
(,(format "\\(\\[%s *\\)%s\\]" date-re time-re) .
((lambda (tag)
(svg-tag-make tag :beg 1 :inverse nil :crop-right t :margin 0 :face 'org-date))))
(,(format "\\[%s *\\(%s\\]\\)" date-re time-re) .
((lambda (tag)
(svg-tag-make tag :end -1 :inverse t :crop-left t :margin 0 :face 'org-date))))))
:hook org-mode)
Let’s add our own custom keywords and highlight them.
(setq org-todo-keywords
'((sequence "TODO" "IN PROGRESS" "CANCELLED" "DONE")))
A keybind for compilation.
(global-set-key (kbd "S-<f9>") 'compile)
(global-set-key (kbd "<f9>") 'recompile)