The Emacs Operating System

Table of Contents

1. Initialization

1.1. Basics

  • Menu and toolbars
(scroll-bar-mode -1)                  ; Disable visible scrollbar
(tool-bar-mode -1)                    ; Disable tool bar
(tooltip-mode -1)                     ; Disable tooltip
(set-fringe-mode 30)                  ; Give some breathing room?
(menu-bar-mode -1)                    ; Go full spartan
  • Scroll Emacs
(setq scroll-step    1
      scroll-conservatively 10000)
  • Set up the visible bell
(setq visible-bell t)
  • Set Fonts
(set-face-attribute 'default nil :font "Cascadia Code PL" :height 180)
  • Display line numbers
(global-display-line-numbers-mode t)

;; We don't want line numbers everywhere, e.g shell
(dolist (mode '(org-hook-mode
                term-mode-hook
                eshell-mode-hook
                shell-mode-hook))
  (add-hook mode(lambda() (display-line-numbers-mode 0))))
  • Truncate long lines in certain modes
(add-hook 'org-mode-hook (lambda() (setq truncate-lines nil)))
  • Set tab-width
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
(setq indent-line-function 'insert-tab)
  • Display a vertical line on 80th character

    (setq-default display-fill-column-indicator-column 79)
    (global-display-fill-column-indicator-mode 1)
    
  • Start emacs in a fullscreen mode
;; start the initial frame maximized
(add-to-list 'initial-frame-alist '(fullscreen . maximized))

;; start every frame maximized
(add-to-list 'default-frame-alist '(fullscreen . maximized))

1.2. Emacs Backups

Backup in one place. flat, no tree structure

(setq backup-directory-alist '(("" . "~/.emacs.d/emacs-backup")))

2. Packages

2.1. Intialize packages

  • Initialize Melpa
(require 'package)
(setq package-archives '(("elpa"   . "https://elpa.gnu.org/packages/")
                         ("melpa"  . "https://melpa.org/packages/")
                         ("melpa-stable" . "https://stable.melpa.org/packages/")
                         ("org" . "https://orgmode.org/elpa/")))

(package-initialize)
(unless package-archive-contents
  (package-refresh-contents))

;; Initialize use-package on non-linux platforms
(unless (package-installed-p 'use-package)
  (package-install 'use-package))
(require 'use-package)
(setq use-package-always-ensure t)

  • Install local packages manually

    TODO

    • Certain packages are needed to be installed maually, e.g tomelr

2.2. Appearence

2.2.1. Themes

Doom Emacs has consolidated a bunch of nice looking themes, we would like to get them all.

(use-package doom-themes
  :ensure t
  :config
  ;; Global settings (defaults)
  (setq doom-themes-enable-bold t    ; if nil, bold is universally disabled
        doom-themes-enable-italic t) ; if nil, italics is universally disabled
  ;; (load-theme 'doom-old-hope t)
  (load-theme 'doom-gruvbox t)

  ;; Enable flashing mode-line on errors
  (doom-themes-visual-bell-config)
  ;; Enable custom neotree theme (all-the-icons must be installed!)
  (doom-themes-neotree-config)
  ;; or for treemacs users
  (setq doom-themes-treemacs-theme "doom-gruvbox") ; use "doom-colors" for less minimal icon theme
  (doom-themes-treemacs-config)
  ;; Corrects (and improves) org-mode's native fontification.

  (doom-themes-org-config))


I love how Org files look with poet theme, so it's a must.

(use-package poet-theme)

2.2.2. Dashboard

We would need emacs-dashboard package for an easier configuration.

(use-package dashboard
  :config
  (setq dashboard-center-content t)
  (setq dashboard-show-shortcuts nil)
  :init
  (dashboard-setup-startup-hook))

2.2.3. Modeline

A custom modeline until we figure out the issue with doom-modeline.

(set-face-attribute 'mode-line nil
                    :background "#353644"
                    :foreground "white"
                    :box '(:line-width 8 :color "#353644")
                    :overline nil
                    :underline nil)

(set-face-attribute 'mode-line-inactive nil
                    :background "#565063"
                    :foreground "white"
                    :box '(:line-width 8 :color "#565063")
                    :overline nil
                    :underline nil)

Show the git branch on the mode-line

;; (defun vc-branch ()
;;   (let ((backend (vc-backend buffer-file-name)))
;;     (substring vc-mode (+ (if (eq backend 'Hg) 2 3) 2))))
;; (vc-branch)
(use-package all-the-icons
  :ensure t)

;; (use-package doom-modeline
;; :init (doom-modeline-mode 1)
;; :custom ((doom-modeline-height 30)))

2.3. Evil Mode

  • Since I have been using VI for quite sometime now, I don't want to train myself to learn Emacs
;; package: evil
;; First thing first. Be EVIL!
(use-package evil
  :init
  (setq evil-want-integration t)
  (setq evil-want-keybinding nil)
  (setq evil-want-C-u-scroll t)
  (setq evil-want-C-i-jump nil)
  :config
  (evil-mode 1)
  (define-key evil-insert-state-map (kbd "C-g") 'evil-normal-state)
  (define-key evil-insert-state-map (kbd "C-h") 'evil-delete-backward-char-and-join)

  ;; Use visual line motions even outside of visual-line-mode buffers
  (evil-global-set-key 'motion "j" 'evil-next-visual-line)
  (evil-global-set-key 'motion "k" 'evil-previous-visual-line)

  (evil-set-initial-state 'messages-buffer-mode 'normal)
  (evil-set-initial-state 'dashboard-mode 'normal))


;; package: evil-collection
;; Now be EVIL on every mode
;; TODO: Doesn't work
(use-package evil-collection
  :after evil
  :ensure t
  :config
  (evil-collection-init))

Unbind certain keys.

(with-eval-after-load 'evil-maps
  (define-key evil-motion-state-map (kbd "SPC") nil)
  (define-key evil-motion-state-map (kbd "RET") nil)
  (define-key evil-motion-state-map (kbd "TAB") nil))

2.4. Org Mode

Org mode is probably the best thing happened to the mankind. j/k By default Org mode doesn't look very nice, at least not as a word processor. Our goal is to make it look like one.

Enable indentation(org-indent-mode). To control individual files, use #+STARTUP: indent or #+STARTUP: noindent.

(setq org-startup-indented t)

Set a conservative indentation, By default the value is set to 2

(setq org-indent-indentation-per-level 2)

Emacs shouldn't add whitespace to indent text.

(setq org-adapt-indentation nil)

RETURN will follow links in org mode.

(setq org-return-follows-link  t)

There are a couple of nice blog posts on beautifying the Org mode, but I plan to go with the "org-modern" package. It looks nice enough for now. However, Certain features don't work wit org-indent-mode, which I have turned on.

(use-package org-modern
  :config
  (global-org-modern-mode))

;; For shorthand completions, lile <s-TAB for source code blocks.
(require'org-tempo)

2.4.1. Org TOC

Create table of contents for Org files. Usage:

  • Add table of content tags such as TOC_2 and TOC_2_gh
  • While at the TOC entry call M-x org-open-at-point (C-c C-o) to

jump to the corresponding heading.

ref: toc-org

(use-package toc-org
  :hook (org-mode . toc-org-enable)
  :config
  (setq toc-org-hrefify-default "gh"))

2.4.2. Org Babel

Active Babel languages

;; TODO

;; (org-babel-do-load-languages
;;  'org-babel-load-languages
;;  '((C . t) (elisp . t) (python . t) ))

2.4.3. Org Hugo(ox-hugo)

ox-hugo helps me manage my website using org files.

I couldn't install tomelr package, using use-package, so had to install it manually.

(use-package ox-hugo
  :ensure t   ;Auto-install the package from Melpa
  :pin melpa  ;`package-archives' should already have ("melpa" . "https://melpa.org/packages/")
  :after ox)    

2.4.4. Org Roam

A sophisticated note taking mechanishm. Essentially a clone of Roam-research running on Emacs.

(use-package org-roam
  :ensure t
  :pin melpa-stable)

Set the notes directory

(setq org-roam-directory "~/dev/notes/org-roam")
  1. Full text search with Deft

    Deft is an Emacs mode for quickly browsing and filtering plain text notes.

    (use-package deft
      :after org
      :bind
      ("C-c n d" . deft)
      :custom
      (deft-recursive t)
      (deft-use-filter-string-for-filename t)
      (deft-default-extension "org")
      (deft-directory org-roam-directory))
    

2.4.5. Org export settings(htmlize)

Org mode usually ships with this package, However in certain cases you might need to install it manually.

(use-package htmlize
  :ensure t
  :init
  (setq org-html-htmlize-output-type 'css)
  (setq org-html-htmlize-font-prefix "org-"))

2.4.6. Human readable IDs

While exporting html, org-html-export-to-html function generates IDs for each header, so that it can get linked to from the Table of contents. However, the default generated IDs aren't human-redable.

Also the default generated IDs can change every time you generate a new version, which can be annoying while hosting a public website.

I have found some hacks on the internet and Amit Patel's implementation seemed like the simplest.

;; The only dependency
(use-package s
  :ensure t)

(defun t/org-generate-custom-ids ()
  "Generate CUSTOM_ID for any headings that are missing one"
  (let ((existing-ids (org-map-entries 
                       (lambda () (org-entry-get nil "CUSTOM_ID")))))
    (org-map-entries
     (lambda ()
       (let* ((custom-id (org-entry-get nil "CUSTOM_ID"))
              (heading (org-heading-components))
              (level (nth 0 heading))
              (todo (nth 2 heading))
              (headline (nth 4 heading))
              (slug (t/title-to-filename headline))
              (duplicate-id (member slug existing-ids)))
         (when (and (not custom-id)
                    (< level 4)
                    (not todo)
                    (not duplicate-id))
           (message "Adding entry %s to %s" slug headline)
           (org-entry-put nil "CUSTOM_ID" slug)))))))

(defun t/title-to-filename (title)
  "Convert TITLE to a reasonable filename."
  ;; Based on the slug logic in org-roam, but org-roam also uses a
  ;; timestamp, and I use only the slug. BTW "slug" comes from
  ;; <https://en.wikipedia.org/wiki/Clean_URL#Slug>
  (setq title (s-downcase title))
  (setq title (s-replace-regexp "[^a-zA-Z0-9]+" "-" title))
  (setq title (s-replace-regexp "-+" "-" title))
  (setq title (s-replace-regexp "^-" "" title))
  (setq title (s-replace-regexp "-$" "" title))
  title)

Run the function on save, while in org-mode.

(add-hook 'after-save-hook 't/org-generate-custom-ids)

2.5. Projectile

Projectile is instrumental in managing different projects and working on them.

(use-package counsel-projectile
  :after projectile
  :config (counsel-projectile-mode))

(counsel-projectile-mode)

(use-package projectile
  :diminish projectile-mode
  :config (projectile-mode)
  :custom ((projectile-completion-system 'ivy))
  :bind (:map projectile-mode-map
              ("C-c p" . projectile-command-map))
  :init
  ;; NOTE: Set this to the folder where you keep your Git repos!
  (when (file-directory-p "~/dev")
    (setq projectile-project-search-path '("~/dev"))
    (setq projectile-project-search-path '("~/rocket")))
  (setq projectile-switch-project-action #'projectile-dired))

2.6. Magit

The magical Git client for emacs.

(use-package magit
  :ensure t
  :pin melpa-stable)

2.7. Completions

;; package: ivy/counsel/swiper
;; Set up Ivy for better completions
;; Installing counsel will install ivy and swiper.
(use-package counsel
  :diminish
  :bind (("C-s" . swiper)
         ;; Counsel stuff
         ("M-x" . counsel-M-x)
         ("M-x" . counsel-M-x)
         ("s-x" . counsel-M-x) ;; Super-X should do M-X
         ("C-x b" . counsel-ibuffer)
         ("C-x C-f" . counsel-find-file)
         :map minibuffer-local-map
         ("C-x C-r" . 'counsel-minibuffer-history)
         :map ivy-minibuffer-map
         ;; ("TAB" . ivy-alt-done)
         ;; ("TAB" . ivy-partial-or-done)
         ("C-f" . ivy-alt-done)
         ("C-l" . ivy-alt-done)
         ("C-j" . ivy-next-line)
         ("C-k" . ivy-previous-line)
         :map ivy-switch-buffer-map
         ("C-k" . ivy-previous-line)
         ("C-l" . ivy-done)
         ("C-d" . ivy-switch-buffer-kill)
         :map ivy-reverse-i-search-map
         ("C-k" . ivy-previous-line)
         ("C-d" . ivy-reverse-i-search-kill))
  :init
  (ivy-mode 1))

(use-package counsel
  :bind (("C-M-j" . 'counsel-switch-buffer)
         :map minibuffer-local-map
         ("C-r" . 'counsel-minibuffer-history))
  :custom
  (counsel-linux-app-format-function #'counsel-linux-app-format-function-name-only)
  :config
  (counsel-mode 1))

(use-package ivy-prescient
  :after counsel
  :custom
  (ivy-prescient-enable-filtering nil)
  :config
  ;; Uncomment the following line to have sorting remembered across sessions!
                                        ;(prescient-persist-mode 1)


  (ivy-prescient-mode 1))

;; Remove the "^" character from counsel-M-X 
(setcdr (assoc 'counsel-M-x ivy-initial-inputs-alist) "") 

2.7.1. Ivy rich

A more friendly interface for Ivy.

;; package: ivy-rich
;; More friendly interface (display transformer) for ivy.
(use-package ivy-rich
  :init
  (ivy-rich-mode 1))

;; All the icons + Ivy
(use-package all-the-icons-ivy-rich
  :ensure t
  :init (all-the-icons-ivy-rich-mode 1))

2.7.2. Hydra

(use-package hydra
  :defer t)

(defhydra hydra-text-scale (:timeout 4)
  "scale text"
  ("j" text-scale-increase "in")
  ("k" text-scale-decrease "out")
  ("f" nil "finished" :exit t))

2.7.3. Which-key

It's a minor mode that shows kebindings for an incomplete command.

(use-package which-key
  :init
  (which-key-mode))

2.8. Treemacs

Unless it's a large project, I don't use Treemacs. However, it gets quite annoying while switching project since treemacs-follow-mode sometimes doesn't work as intended.

(use-package treemacs
  :ensure t
  :defer t
  :init
  (with-eval-after-load 'winum
    (define-key winum-keymap (kbd "M-0") #'treemacs-select-window))
  :config
  (progn
    (setq treemacs-collapse-dirs                 (if (treemacs--find-python3) 3 0)
          treemacs-deferred-git-apply-delay      0.5
          treemacs-width                         35)

    (treemacs-resize-icons 18)
    (treemacs-follow-mode t)
    (treemacs-project-follow-mode t)
    (treemacs-filewatch-mode t))
   :bind
   (:map global-map
         ("M-0"       . treemacs-select-window)
         ("C-x t 1"   . treemacs-delete-other-windows)
         ("C-x t t"   . treemacs-add-and-display-current-project-exclusively)
         ("C-x t d"   . treemacs-select-directory)
         ("C-x t B"   . treemacs-bookmark)
         ("C-x t C-t" . treemacs-find-file)
         ("C-x t M-t" . treemacs-find-tag)))

  ;; (add-hook 'projectile-after-switch-project-hook 'treemacs-add-and-display-current-project-exclusively)

  (use-package treemacs-evil
    :after (treemacs evil)
    :ensure t
    :pin melpa)

  (use-package treemacs-projectile
    :after (treemacs projectile)
    :ensure t
    :pin melpa) 

2.9. RestClient

Postman for Emacs, A mode to run HTTP queries.

(use-package restclient
  :ensure t
  :pin melpa
  :mode (("\\.http\\'" . restclient-mode)))

2.10. Command-log-mode

Show event and command history, really helpful while debugging Emacs configs. Default binding to toggle is C-c o

(use-package command-log-mode
  :ensure t
  :config
  (global-command-log-mode t)
  :pin melpa)

3. System configuration

3.1. Emacs environment variables

Ensure Emacs env variables match system variables.

(use-package exec-path-from-shell
  :ensure t)

(when (memq window-system '(mac ns x))
  (exec-path-from-shell-initialize))

4. KeyBindings

Custom keybindings for emacs. This section is expected to be edited frequently, as my requirements evolve.

4.1. Global constants

(defconst t-version "0.0.1-a0"
  "Current version of the tmacs.")


;; DEPRECATED
(defconst IS-MAC      (eq system-type 'darwin))
(defconst IS-LINUX    (eq system-type 'gnu/linux))
(defconst IS-WINDOWS  (memq system-type '(cygwin windows-nt ms-dos)))
(defconst IS-BSD      (memq system-type '(darwin berkeley-unix gnu/kfreebsd)))


4.2. A leader

Both Doom and Spacemacs use a leader key as a prefix to many commands.

Inspiration: Doom keybindings

(defvar t-leader-key "SPC"
  "The leader prefix key for Evil users.")

Mac command should act like super

;; (cond
;;  (IS-MAC
;;   ;; mac-* variables are used by the special emacs-mac build of Emacs by
;;   ;; Yamamoto Mitsuharu, while other builds use ns-*.
;;   (setq mac-command-modifier      'super
;;  ns-command-modifier       'super
;;  mac-option-modifier       'meta
;;  ns-option-modifier        'meta
;;  ;; Free up the right option for character composition
;;  mac-right-option-modifier 'none
;;  ns-right-option-modifier  'none))
;;  (IS-WINDOWS
;;   (setq w32-lwindow-modifier 'super
;;  w32-rwindow-modifier 'super)))

4.3. Random goodness

  • ESC Quit prompts

    (global-set-key  (kbd "<escape>") 'keyboard-escape-quit)
    

5. Code Completion

5.1. Corfu

Corfu is a simpler alternative to Company-mode; However it doesn't show me the completion pop-ups automatically. #FIXME

(use-package corfu
  ;; Optional customizations
  :custom
  (corfu-cycle t)                ;; Enable cycling for `corfu-next/previous'
  (corfu-preselect 'prompt)      ;; Always preselect the prompt
  (corfu-auto t)                 ;; Enable auto-completion
  (corfu-auto-delay 0)
  (corfu-auto-prefix 0)
  (completion-styles '(basic))
  :init
  (global-corfu-mode))

;; A few more useful configurations...
(use-package emacs
  :init
  ;; TAB cycle if there are only few candidates
  (setq completion-cycle-threshold 3)

  (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))
  • Swap default Dabbrev completions.
(use-package dabbrev
  ;; Swap M-/ and C-M-/
  :bind (("M-/" . dabbrev-completion)
         ("C-M-/" . dabbrev-expand))
  ;; Other useful Dabbrev configurations.
  :custom
  (dabbrev-ignored-buffer-regexps '("\\.\\(?:pdf\\|jpe?g\\|png\\)\\'")))

6. Programming stuff

This section has configurations for various programming language modes.

6.1. Eglot

Language Server Protocol can provide IDE like support for multiple programming languages on Emacs.

(setq eglot-ensure "C-c l")

6.2. Yaml

Emacs should automatically switch on to yaml-mode while editing yml or yaml files.

(use-package yaml-mode
  :ensure t
  :init
  (push '("\\.yaml$" . yaml-mode) auto-mode-alist))

6.3. Go

(use-package go-mode
  :ensure t
  :init
  (push '("\\.go\\'" . go-mode) auto-mode-alist))

Eglot hooks for Go

(defun lsp-go-install-save-hooks ()
  ;; Format before save
  (add-hook 'before-save-hook #'eglot-format-buffer t t)
  ;; Sort imports before save
  (add-hook 'before-save-hook #'eglot-code-action-organize-imports t t))


(add-hook 'go-mode-hook #'lsp-go-install-save-hooks)

;; Start eglot mode
(add-hook 'go-mode-hook 'eglot-ensure)