The Emacs Operating System

Table of Contents

1. Overview

It's been a while since I started using Emacs, and after using DOOM Emacs for some time, I decided to scratch my own itch and create my very own Emacs configuration. It's not as refined as DOOM or Spacemacs, and it doesn't have all the bells and whistles, but it's mine.

Why do I like Emacs, you ask? For me, it's like riding a sports bike; it's not for everyone, but it's exhilarating! It has its quirks and might not be the most efficient tool for the job, but it’s a joy to use. As a "professional" programmer, I should probably use the most efficient tool for the job, but "professional" is boring, and I don't like boring.

real_programmers.png

2. Initialization

2.1. Faster Startup

Increasing the garbage collection threshold during startup can reduce time spent in GC, speeding things up:

;; Increase to 50MB during startup
(setq gc-cons-threshold (* 50 1000 1000))
(add-hook 'emacs-startup-hook
          ;; Restore to 2MB after startup
          (lambda () (setq gc-cons-threshold (* 2 1000 1000))))

2.2. Custom Variables

2.2.1. Directories

Consolidate all the directories here.

;; Define custom variables for directories
(defvar eos/org-images-dir "~/Documents/org/images"
  "Directory to store images downloaded with org-download.")

(defvar eos/org-roam-dir "~/Documents/org/hivemind/notes"
  "Directory to store org-roam notes.")

(defvar eos/emacs-backup-dir "~/.emacs.d/emacs-backup"
  "Directory where Emacs backup files are stored.")

;; Don't create these directories if not present
(defvar eos/dev-dir "~/dev"
  "Directory for personal development projects.")

(defvar eos/work-dir "~/work"
  "Directory for work projects.")

(defvar eos/school-dir "~/school"
  "Directory for school projects.")

(defvar eos/project-dirs
  (list eos/dev-dir eos/work-dir eos/school-dir)
  "List of all directories for personal, work, and school projects.")

(defvar eos/music-dir "~/Documents/Music"
  "Directory for local music files.")

;; Function to ensure a directory exists
(defun eos/ensure-directory-exists (dir)
  "Ensure that the directory DIR exists. Create it if necessary."
  (unless (file-exists-p dir)
    (make-directory dir t)))

;; Ensure required directories exist
(mapc #'eos/ensure-directory-exists
      (list eos/org-images-dir
            eos/org-roam-dir
            eos/emacs-backup-dir))

2.3. Basics

Basic setup for Emacs appearance and behavior.

  • Disable toolbars, scrollbars, and tooltips.

    (scroll-bar-mode -1)
    (tool-bar-mode -1)
    (tooltip-mode -1)
    (set-fringe-mode 0)
    (menu-bar-mode -1)
    
  • Set scroll length

    (setq scroll-step 1 scroll-conservatively 10000)
    
  • Set visible bell

    (setq visible-bell t)
    
  • Set fonts. TODO: Check if the font exists

    ;; (set-face-attribute 'default nil :font "Cascadia Code NF" :height 160)
    (set-face-attribute 'default nil :font "Iosevka" :height 160)
    
  • Configure line numbers

    ;;(global-display-line-numbers-mode t)
    
    ;; We don't want line numbers in certain modes, e.g., shell and pdf-view
    ;; (dolist (mode '(org-mode-hook
    ;;                 term-mode-hook
    ;;                 eshell-mode-hook
    ;;                 shell-mode-hook
    ;;                 pdf-view-mode-hook))
    ;;   (add-hook mode (lambda () (display-line-numbers-mode 0))))
    
    (add-hook 'prog-mode-hook 'display-line-numbers-mode)
    
  • Configure tabs behavior

    ;; (tab-bar-mode 1)
    (setq-default indent-tabs-mode nil)
    (setq-default tab-width 4)
    (setq indent-line-function 'insert-tab)
    
  • Display vertical line at 79 characters

    (setq-default display-fill-column-indicator-column 79)
    (add-hook 'prog-mode-hook 'display-fill-column-indicator-mode)
    
  • Customize window behavior.

    ;; Start emacs in fullscreen mode (Mac tiling manager conflict)
    ;; (add-to-list 'initial-frame-alist '(fullscreen . maximized))
    ;; (add-to-list 'default-frame-alist '(fullscreen . maximized))
    
    ;; Remove title bar with maximize and minimize options
    ;; (add-to-list 'default-frame-alist '(undecorated . t))
    ;; (add-to-list 'default-frame-alist '(drag-internal-border . 1))
    
    ;; Add an empty header if title bars are removed
    ;; (setq-default header-line-format " ")
    
    
    ;; This displays "Emacs - buffer_name"
    ;; (setq frame-title-format
    ;;       '("Emacs - %b"))  
    ;; (add-to-list 'default-frame-alist
    ;;              '(title . "Emacs"))
    
    (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
    (add-to-list 'default-frame-alist '(ns-appearance . dark))
    (setq ns-use-proxy-icon nil)
    (setq frame-title-format nil)
    
    
  • Backup in one place, flat, no tree structure

    (setq backup-directory-alist `((".*" . ,eos/emacs-backup-dir)))
    
  • Miscellaneous

    ;; Truncate long lines in certain modes
    (add-hook 'org-mode-hook (lambda() (setq truncate-lines nil)))
    
    

3. Package Management

Setting up package managers and installing essential packages.

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

3.1.1. Install straight.el

use-package does not support the additional properties like :type, :host, :repo, etc.). Instead, We want tose the straight.el package manager to handle packages from non-standard sources like Git repositories.

  (defvar bootstrap-version)
(let ((bootstrap-file
   (expand-file-name
    "straight/repos/straight.el/bootstrap.el"
    (or (bound-and-true-p straight-base-dir)
        user-emacs-directory)))
  (bootstrap-version 7))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
    (url-retrieve-synchronously
     "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
     'silent 'inhibit-cookies)
  (goto-char (point-max))
  (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))
(setq straight-use-package-by-default t)

Avoid Org version mismatch with Straight.el

(straight-use-package '(org :type built-in))

4. Appearence

4.1. All the Icons

Why we need all the icons? Because they are pretty.

(use-package all-the-icons
  :ensure t)

4.2. Themes

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

(use-package doom-themes
  :straight (:host github :repo "doomemacs/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

  ;; 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")
  (doom-themes-treemacs-config)
  ;; Corrects (and improves) org-mode's native fontification.
  (doom-themes-org-config))


  (load-theme 'leuven t)

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

(use-package poet-theme
  :ensure t)

Always highlight the commented code. Right now, configured for Leuven theme.

(defun my-leuven-comment-face-customization ()
  "Set custom comment face only when the Leuven theme is active."
  (if (string-equal (car custom-enabled-themes) "leuven")
      (custom-set-faces
       '(font-lock-comment-face ((t (:foreground "#4A90E2" :background "#E6F7FF" :slant italic)))))
    ;; Reset to the default comment face when leaving Leuven theme
    (custom-set-faces
     '(font-lock-comment-face ((t (:foreground "dim gray" :slant italic)))))))

;; Add the function to the after-load-theme-hook to dynamically handle theme changes
(add-hook 'after-load-theme-hook 'my-leuven-comment-face-customization)

4.3. Better Focus

An asthetic plugin designed to visually distinguish "real" buffers from "unreal" buffers (like popups, sidebars, log-buffers, etc) by giving the later a slightly different background.

(use-package solaire-mode
  :straight t
  :config
  (solaire-global-mode +1))

Dimmer mode indicates which buffer is currently active by dimming the faces in the other buffers.

(use-package dimmer
    :straight t
    :config
    (dimmer-configure-which-key)
    (dimmer-mode t))

(setq dimmer-fraction .3)

4.4. Transparency

Toggle transparency for fun and no profit. Emacs on MacOS doesn't support transparency, However it's still fun to have it when using a tiling window manager on a large monitor.

(defvar transparency-level-active 85
  "Opacity level when Emacs is active.")

(defvar transparency-level-inactive 85
  "Opacity level when Emacs is inactive.")

(defvar transparency-enabled t
  "Toggle for the transparency feature.")

(defun toggle-transparency ()
  "Toggle between transparent and opaque Emacs frames."
  (interactive)
  (if transparency-enabled
      (progn
        (set-frame-parameter (selected-frame) 'alpha '(100 . 100))
        (setq transparency-enabled nil)
        (message "Transparency disabled"))
    (progn
      (set-frame-parameter (selected-frame) 'alpha
                           `(,transparency-level-active . ,transparency-level-inactive))
      (setq transparency-enabled t)
      (message "Transparency enabled"))))

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

4.6. Modeline

  1. Nano Modeline

    A minimal modeline for Emacs.

    ;; Hide the default mode line globally
    ;; (setq-default mode-line-format nil)
    
    ;; Set the nano-modeline position to bottom before loading
    ;; nano-modeline.
    ;; (setq nano-modeline-position 'nano-modeline-footer)
    
    ;; Install nano-modeline
    ;; (use-package nano-modeline
    ;;   :ensure t
    ;;   :hook
    ;;   (prog-mode-hook . nano-modeline-prog-mode)
    ;;   (text-mode-hook . nano-modeline-text-mode)
    ;;   (org-mode-hook  . nano-modeline-org-mode)
    ;;   (term-mode-hook . nano-modeline-term-mode)
    ;;   :init (nano-modeline-prog-mode t))
    
  2. Doom Modeline

    I keep getting bored with "nicer" looking modelines and keep coming back to the default Emacs one. Here are a few tweaks to make it look good.

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

4.7. Fancy Mini-Buffer

I like a floating minibuffer, but ivy-posframe] looks better. Mini-frame mode is enabled by default.

(use-package mini-frame
  :straight t
  :config
    (mini-frame-mode 1))

;; make sure they are in the middle of the screen
(custom-set-variables
  '(mini-frame-show-parameters
    '((top . 200)
      (width . 0.7)
      (left . 0.5))))

4.8. Indentation

(use-package indent-bars
  :config
  :custom
  (indent-bars-treesit-support t)
  (indent-bars-treesit-ignore-blank-lines-types '("module"))
  ;; Add other languages as needed
  (indent-bars-treesit-scope '((python function_definition class_definition for_statement
      if_statement with_statement while_statement)))
  ;; Note: wrap may not be needed if no-descend-list is enough
  ;;(indent-bars-treesit-wrap '((python argument_list parameters ; for python, as an example
  ;;                      list list_comprehension
  ;;                      dictionary dictionary_comprehension
  ;;                      parenthesized_expression subscript)))
  :hook ((python-base-mode yaml-mode) . indent-bars-mode))

5. Functionality

5.1. Evil Mode

  • Since I have been using VI for quite sometime now, I don't want to train myself to learn Emacs

    (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))
    
    ;; 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))
    
    ;; 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)
    
  • Evil on every mode

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

5.2. 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)
    
  • Show inline images in org mode.

    (setq org-display-remote-inline-images 'cache) ;; enable caching
    
  • For shorthand completions, lile <s-TAB for source code blocks.

    (require 'org-tempo)
    

5.2.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"))

5.2.2. Org Babel

Active Babel languages

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

5.2.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. Edit: 08/09/2024 - Installed it using straight.el

(use-package tomelr
  :straight (:host github :repo "kaushalmodi/tomelr" :files ("*.el"))
  :ensure t)
(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)    

5.2.4. Org Download

This nice package helps add images to an Org file in a better way.

Note: In order to copy from clipboard using org-download-clipboard, we need to install pngpaste using Homebrew.

(use-package org-download
  :straight t
  :bind
  ("C-c d" . org-download-clipboard)
  :config
  (org-download-enable))

(add-hook 'dired-mode-hook 'org-download-enable)

;; Set the image download directory
(setq org-download-image-dir eos/org-images-dir)

;; Set the image download to not depend on any headline
(setq org-download-heading-lvl nil)

5.2.5. Org Roam

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

(use-package org-roam
  :ensure t
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n g" . org-roam-graph)
         ("C-c n c" . org-roam-capture)
         ("C-c n i" . org-roam-node-insert)
         ("C-c n t" . org-roam-tag-add)
         ("C-c n b" . org-roam-buffer-toggle)
         ("C-c n j" . org-roam-dailies-capture-today))
  :pin melpa-stable
  :config
  (org-roam-setup))
(setq org-roam-directory eos/org-roam-dir)
  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. Backlink buffer

    Org-roam backlink buffer, source

    ;; for org-roam-buffer-toggle
    ;; Recommendation in the official manual
    (add-to-list 'display-buffer-alist
                   '("\\*org-roam\\*"
                      (display-buffer-in-direction)
                      (direction . right)
                      (window-width . 0.33)
                      (window-height . fit-window-to-buffer)))
    

5.2.6. Org Roam UI

A visual interface for Org Roam.

(use-package org-roam-ui
  :straight
    (:host github :repo "org-roam/org-roam-ui" :branch "main" :files ("*.el" "out"))
    :after org-roam
;;         normally we'd recommend hooking orui after org-roam, but since org-roam does not have
;;         a hookable mode anymore, you're advised to pick something yourself
;;         if you don't care about startup time, use
;;  :hook (after-init . org-roam-ui-mode)
    :config
    (setq org-roam-ui-sync-theme t
          org-roam-ui-follow t
          org-roam-ui-update-on-save t
          org-roam-ui-open-on-start t))

5.2.7. 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-"))

5.2.8. 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 eos/org-generate-custom-ids ()
  "Generate CUSTOM_ID for any headings that are missing one, but only in Org mode."
  (when (derived-mode-p 'org-mode)
    (let ((existing-ids (org-map-entries 
                         (lambda () (org-entry-get nil "CUSTOM_ID")))))

      (org-map-entries
       (lambda ()
         (when (org-at-heading-p)  ;; Ensure we're at a heading
           (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 (eos/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)))))))))

;; Function to the after-save-hook only in Org mode
(add-hook 'org-mode-hook
          (lambda () 
            (add-hook 'after-save-hook 'eos/org-generate-custom-ids nil 'local)))

(defun eos/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 'eos/org-generate-custom-ids)

5.3. 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!
  (setq projectile-project-search-path eos/project-dirs)
  (setq projectile-switch-project-action #'projectile-dired))

5.4. Version Control

5.4.1. Magit

The magical Git client for emacs.

Since I am using emacs-plus, In order for spotlight to find the emacs executable, I cpoied the executable to /Applications. However, After I did that, Magit showed an error message saying that it could not find the emacsclient executable. I had to set the variable with-editor-emacsclient-executable to "emacsclient" in order to fix the issue.

(setq-default with-editor-emacsclient-executable "emacsclient")
(use-package magit
  :ensure t
  :pin melpa-stable)

5.4.2. Diff-hl

Emacs port of the Sublime Git Gutter

(use-package diff-hl
  :straight (diff-hl :type git :host github :repo "dgutov/diff-hl")
  :hook ((prog-mode . diff-hl-mode)
         (org-mode . diff-hl-mode)
         (text-mode . diff-hl-mode))
  :config
  ;; Limit diff-hl to specific modes
  (setq diff-hl-global-modes '(not image-mode pdf-view-mode))

  ;; Use histogram diff algorithm
  (setq vc-git-diff-switches '("--histogram"))

  ;; Slightly more conservative delay before updating the diff
  (setq diff-hl-flydiff-delay 0.5)  ; default: 0.3

  ;; Perform async updates to avoid blocking Emacs
  (setq diff-hl-update-async t)

  ;; Do not show staged changes in real-time
  (setq diff-hl-show-staged-changes nil)

  ;; Enable on-the-fly diff highlighting and margin mode
  (diff-hl-flydiff-mode)
  (diff-hl-margin-mode))

5.5. Completions

Set up Ivy, Counsel, and Swiper for better completions.

(use-package counsel
  :straight t
  :diminish
  :bind (("C-s" . swiper)                         ;; Search using Swiper
         ("M-x" . counsel-M-x)                    ;; Enhanced M-x
         ("s-x" . counsel-M-x)                    ;; Super-X for M-x
         ("C-x C-f" . counsel-find-file)          ;; Enhanced find file
         ("C-x b" . ivy-switch-buffer)            ;; Show filtered buffers (code buffers)
         ("C-x B" . counsel-ibuffer)              ;; Show all buffers
         :map minibuffer-local-map
         ("C-x C-r" . counsel-minibuffer-history) ;; Access minibuffer history
         :map ivy-minibuffer-map
         ("C-j" . ivy-next-line)                  ;; Move down the list
         ("C-k" . ivy-previous-line)              ;; Move up the list
         ("C-f" . ivy-alt-done)                   ;; Complete selection
         :map ivy-switch-buffer-map
         ("C-k" . ivy-previous-line)              ;; Move up in buffer list
         ("C-d" . ivy-switch-buffer-kill)         ;; Kill selected buffer
         ("C-f" . ivy-done)                       ;; Complete buffer selection
         :map ivy-reverse-i-search-map
         ("C-k" . ivy-previous-line)              ;; Move up in reverse search
         ("C-d" . ivy-reverse-i-search-kill))     ;; Kill in reverse search
  :custom
  (counsel-linux-app-format-function #'counsel-linux-app-format-function-name-only)
  :init
  (ivy-mode 1)                                    ;; Enable Ivy
  :config
  (counsel-mode 1))                               ;; Enable Counsel

C-x b doesn't show emacs garbage buffers, C-x B shows all the buffers.

;; Configure ivy-switch-buffer (C-x b) to ignore certain buffers
(setq ivy-ignore-buffers
      '("\\` "
        "\\`\\*"
        "\\`magit"
        "\\`.+_archive\\'"
        "\\`TAGS\\'"
        "\\`COMMIT_EDITMSG\\'"
        "\\`MERGE_MSG\\'"
        "\\`undo-tree\\*\\'"))

Prescient settings for sorting and filtering.

;; Package: ivy-prescient
(use-package ivy-prescient
  :straight t
  :after counsel
  :custom
  (ivy-prescient-enable-filtering nil)           ;; Disable filtering
  :config
  ;; Uncomment the following line to persist sorting 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) "")

5.5.1. Ivy rich

Ivy-rich for better Ivy interface

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

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

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

5.5.3. Which-key

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

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

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

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

5.7. RestClient

Postman for Emacs, A mode to run HTTP queries.

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

5.8. Command-log-mode

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

(use-package command-log-mode
  :ensure t
  :bind
  ("C-c M-c" . clm/toggle-command-log-buffer)
  :config
  (global-command-log-mode t)
  :pin melpa)

5.9. Vterm

Probably the only usable terminal emulator for Emacs

(use-package vterm
  :ensure t
  :straight t)

6. System configuration

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

7. Code Completion

7.1. Tree-sitter

Tree-sitter is a parser generator tool and an incremental parsing library. It can build a concrete syntax tree for a source file and efficiently update the syntax tree as the source file is edited.

(setq treesit-language-source-alist
    '(  ; use `sort-lines' to sort
      (bash . ("https://github.com/tree-sitter/tree-sitter-bash"))
      (c . ("https://github.com/tree-sitter/tree-sitter-c"))
      (cpp . ("https://github.com/tree-sitter/tree-sitter-cpp"))
      (css "https://github.com/tree-sitter/tree-sitter-css")
      (go "https://github.com/tree-sitter/tree-sitter-go")
      (gomod "https://github.com/camdencheek/tree-sitter-go-mod")
      (html "https://github.com/tree-sitter/tree-sitter-html")
      (java "https://github.com/tree-sitter/tree-sitter-java")
      (javascript "https://github.com/tree-sitter/tree-sitter-javascript")
      (json "https://github.com/tree-sitter/tree-sitter-json")
      (kotlin "https://github.com/fwcd/tree-sitter-kotlin")
      (python . ("https://github.com/tree-sitter/tree-sitter-python"))
      (rust "https://github.com/tree-sitter/tree-sitter-rust")
      (tsx . ("https://github.com/tree-sitter/tree-sitter-typescript" nil "tsx/src"))
      (typescript . ("https://github.com/tree-sitter/tree-sitter-typescript" nil "typescript/src"))
      (typst "https://github.com/uben0/tree-sitter-typst")
      (vue "https://github.com/ikatyang/tree-sitter-vue")
      (yaml "https://github.com/ikatyang/tree-sitter-yaml")
      (toml "https://github.com/ikatyang/tree-sitter-toml")))


(defun eos/treesit-install-all-languages ()
"Install all languages specified by `treesit-language-source-alist'."
(interactive)
(let ((languages (mapcar 'car treesit-language-source-alist)))
  (dolist (lang languages)
      (treesit-install-language-grammar lang)
      (message "`%s' parser was installed." lang)
      (sit-for 0.75))))

Custom functions to check and install tree-sitter.

;; Checks if a specific tree-sitter grammar file exists.
(defun treesit-grammar-installed-p (grammar-file)
  "Check if a specific tree-sitter GRAMMAR-FILE is installed."
  (file-exists-p grammar-file))

;; Ensures that a tree-sitter grammar for a given language is
;; installed, checking if the grammar file is present and if tree-sitter
;; is available.
(defun ensure-treesit-grammar-installed (language grammar-file)
  "Ensure a tree-sitter grammar for LANGUAGE is installed.
   GRAMMAR-FILE is the path to the grammar file."
  (unless (treesit-grammar-installed-p grammar-file)
    (when (and (fboundp 'treesit-available-p)
               (treesit-available-p))
      (treesit-install-language-grammar language))))

8. Programming stuff

This section has configurations for various programming language modes.

8.1. Eglot

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

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

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

8.3. Rust

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

8.4. Haskell

Am I a 21st century Hippie now?

(use-package haskell-mode
  :straight (:host github :repo "haskell/haskell-mode")
  :mode "\\.hs\\'")

8.5. Typst

Seems like a decent alternative to LaTex.

  ;; Ensure Typst tree-sit grammar is installed
  (ensure-treesit-grammar-installed 'typst (expand-file-name "tree-sitter/libtree-sitter-typst.dylib" user-emacs-directory))

(use-package typst-ts-mode
  :straight (:type git :host sourcehut :repo "meow_king/typst-ts-mode" :files (:defaults "*.el"))
  :custom
  ;; (optional) If you want to ensure your typst tree sitter grammar version is greater than the minimum requirement
  ;; Note this only checks and compares file modification time
  (typst-ts-mode-grammar-location (expand-file-name "tree-sitter/libtree-sitter-typst.dylib" user-emacs-directory)))

Modify typst-ts-compile to use absolute path.

(defun typst-ts-compile ()
  "Compile current Typst file."
  (interactive)
  (run-hooks typst-ts-compile-before-compilation-hook)
  (let ((full-file-path (expand-file-name buffer-file-name)))
    (add-hook 'compilation-finish-functions
              (typst-ts-compile--compilation-finish-function (current-buffer)))
    (compile
     (format "%s compile %s %s"
             typst-ts-compile-executable-location
             full-file-path
             typst-ts-compile-options)
     'typst-ts-compilation-mode)))

Custom functions to ask for root directory.

(defcustom typst-ts-root-folder nil
  "Root folder for Typst projects."
  :type 'directory
  :group 'typst-ts-compile)

(defun typst-ts-set-root-folder (folder)
  "Set the root folder for Typst projects."
  (interactive "DSelect Typst root folder: ")
  (setq typst-ts-root-folder folder)
  (setenv "TYPST_ROOT" folder)
  (message "Typst root folder set to: %s" folder))

(defun typst-ts-compile-with-root ()
  "Compile current Typst file with the root folder set."
  (interactive)
  (unless typst-ts-root-folder
    (call-interactively 'typst-ts-set-root-folder))
  (let ((default-directory typst-ts-root-folder))
    (typst-ts-compile)))

(with-eval-after-load 'typst-ts-mode
  (define-key typst-ts-mode-map (kbd "C-c C-c s") #'typst-ts-set-root-folder)
  (define-key typst-ts-mode-map (kbd "C-c C-c r") #'typst-ts-compile-with-root))

8.6. LaTex

Not sure if LaTex belongs here.

;; Install Auctex using straight.el
(use-package auctex
  :straight t
  :straight (:type git :host github :repo "emacs-straight/auctex")
  :config
  (setq TeX-auto-save t)
  (setq TeX-parse-self t)
  (setq-default TeX-master nil)
  (setq TeX-PDF-mode t)
  (setq TeX-source-correlate-mode t)
  (setq TeX-source-correlate-start-server t)
  (setq TeX-view-program-selection '((output-pdf "PDF Tools"))
        TeX-view-program-list '(("PDF Tools" TeX-pdf-tools-sync-view))
        TeX-source-correlate-start-server t)

  (add-hook 'LaTeX-mode-hook 'TeX-source-correlate-mode)
  (add-hook 'LaTeX-mode-hook 'TeX-PDF-mode)
  (add-hook 'LaTeX-mode-hook 'TeX-fold-mode)
  (add-hook 'LaTeX-mode-hook 'turn-on-reftex)
  (add-hook 'LaTeX-mode-hook 'turn-on-auto-fill)
  (add-hook 'LaTeX-mode-hook 'flyspell-mode)
  (add-hook 'LaTeX-mode-hook 'LaTeX-math-mode))

Install pdf tools using straight.el with minimal configuration

(use-package pdf-tools
  :straight (:host github :repo "vedang/pdf-tools")
  :config
  ;; Initialize the PDF Tools package
  (pdf-tools-install)

  ;; Set PDF view mode to continuous mode
  (setq-default pdf-view-display-size 'fit-width))

  ;; Enable midnight mode for PDFs
  (add-hook 'pdf-view-mode-hook 'pdf-view-midnight-minor-mode)

;; Set keybinding to install PDF Tools
(global-set-key (kbd "C-c p") 'pdf-tools-install)
  • Latex Preview Pane

    (use-package latex-preview-pane :straight t)
    

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

8.8. Markdown

Well, most readme files are markdown anyways.

(use-package markdown-mode
  :straight (:host github :repo "jrblevin/markdown-mode")
  :mode "\\.md\\'")

8.9. Lua

Because every damn thing needs lua for configuration.

(use-package lua-mode
  :straight (:host github :repo "immerrr/lua-mode")
  :mode "\\.lua\\'")

9. Non-Human Intelligence

9.1. Copilot

(use-package copilot
  :straight (:host github :repo "copilot-emacs/copilot.el" :files ("*.el"))
  :ensure t
  :hook (prog-mode . copilot-mode)
  :bind (:map copilot-completion-map
              ("<tab>" . 'copilot-accept-completion-by-word)
              ("TAB" . 'copilot-accept-completion-by-word)
              ("C-TAB" . 'copilot-accept-completion)
              ("C-<tab>" . 'copilot-accept-completion)))

;; Suppress warnings from copilot
(setq warning-suppress-types '((copilot)))

10. Music Player

Emacs as a music player? Why not?

(use-package ready-player
  :straight (ready-player :type git :host github :repo "xenodium/ready-player")
  :ensure t
  :config
  (ready-player-mode +1)
  :bind (("C-c m h" . 'ready-player-toggle-shuffle)
         ("C-c m s" . 'ready-player-search)
         ("C-c m q" . 'ready-player-quit)
         ("C-c m f" . 'ready-player-seek-forward)
         ("C-c m b" . 'ready-player-seek-backward)))


(setq ready-player-my-media-collection-location eos/music-dir)
(setq ready-player-set-global-bindings nil)

;; Reload the music collection
(defun ready-player-reload-music-collection ()
  "Reload the music collection."
  (interactive)
  (setq ready-player-reload-media-collection eos/music-dir))

11. Miscellaneous

11.1. Custom Functions

  • eos/toggle-debug-mode - Toggle debug mode on and off.

    (defun eos/toggle-debug-mode ()
      "Toggle debug-on-error mode."
      (interactive)
      (setq debug-on-error (not debug-on-error))
      (if debug-on-error
          (message "Debug mode enabled")
        (message "Debug mode disabled")))
    
    ;; Default enable debug-on-error mode
    (setq debug-on-error t)
    
  • eos/reload-config to reload init.el.

    (defun eos/reload-emacs-config ()
      "Reload the Emacs configuration file (init.el)."
      (interactive)
      (load-file (expand-file-name "~/.emacs.d/init.el")))
    
  • eos/open-config-file to open config.org.

    (defun eos/open-emacs-config ()
      "Open the Emacs configuration file (config.org)."
      (interactive)
      (find-file "~/.emacs.d/config.org"))
    
  • eos/kill-other-buffers to kill all buffers except the current one.

    ;; Kill all other un-important buffers except the current one
    ;; Ask for confirmation
    
    (defun kill-other-buffers ()
      "Kill all other buffers, with confirmation."
      (interactive)
      (when (yes-or-no-p "Are you sure you want to kill all other buffers?")
        (mapc (lambda (buffer)
                (let ((buffer-name (buffer-name buffer)))
                  (unless (or (eq buffer (current-buffer))
                              (string-match "^\\*scratch\\*$" buffer-name)
                              (string-match "^\\*straight" buffer-name)
                              (string-match "^\\*copilot" buffer-name))
    
                    (kill-buffer buffer))))
              (buffer-list))
        (delete-other-windows)))
    

11.2. KeyBindings

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

;; Keybindings
(global-set-key (kbd "<escape>") 'keyboard-escape-quit)          ;; ESC to quit prompts

;; Custom commands
(global-set-key (kbd "C-c e r") 'eos/reload-emacs-config)        ;; Reload init.el
(global-set-key (kbd "C-c e t") 'counsel-load-theme)             ;; Switch themes
(global-set-key (kbd "C-c e d") 'eos/toggle-debug-mode)          ;; Toggle debug mode
(global-set-key (kbd "C-c e s") 'toggle-transparency)            ;; Toggle transparency
(global-set-key (kbd "C-c e f") 'treemacs)                       ;; Open Treemacs
(global-set-key (kbd "C-c e c") 'eos/open-emacs-config)          ;; Open config.org
(global-set-key (kbd "C-c e k") 'kill-other-buffers)             ;; Kill other buffers