Skip to content

Instantly share code, notes, and snippets.

@ianthetechie
Last active April 18, 2026 05:20
Show Gist options
  • Select an option

  • Save ianthetechie/535e68e39f00f8f77f1bbb27959c35ef to your computer and use it in GitHub Desktop.

Select an option

Save ianthetechie/535e68e39f00f8f77f1bbb27959c35ef to your computer and use it in GitHub Desktop.
My emacs config
;;; early-init.el --- Pre-GUI initialization -*- lexical-binding: t -*-
;; Performance: maximize GC threshold during init (reset in init.el after startup)
(setq gc-cons-threshold most-positive-fixnum)
(setq gc-cons-percentage 0.6)
;; Performance: read up to 1MB from subprocesses at a time (default 4KB)
;; Prevents pipe buffer backpressure that blocks child processes like cargo nextest
(setq read-process-output-max (* 1024 1024))
;; Suppress UI chrome before the frame is drawn (prevents flash)
(setq default-frame-alist
'((menu-bar-lines . 0)
(tool-bar-lines . 0)
(vertical-scroll-bars . nil)))
;;; init.el --- ianthetechie's configuration -*- lexical-binding: t -*-
;;
;; System dependencies:
;; Required:
;; - git
;; - ripgrep
;; - nushell
;; - nuspell/hunspell/aspell
;; - A C compiler (e.g. clang)
;; - CMake and gmake (for vterm)
;; - libenchant (brew install enchant on macOS; enchant2 package on FreeBSD/OpenBSD)
;;
;; Optional:
;; - Berkeley Mono font
;; - sourcekit-lsp
;;
;; macOS only:
;; - Homebrew
;; ============================================================
;; Bootstrap & Package System
;; ============================================================
;; Reset GC to a reasonable runtime value after init
;; (early-init.el sets it to most-positive-fixnum for fastest startup)
(add-hook 'emacs-startup-hook
(lambda ()
(setq gc-cons-threshold (* 20 1024 1024)
gc-cons-percentage 0.1)))
;; Package archives
(require 'package)
(add-to-list 'package-archives '("nongnu" . "https://elpa.nongnu.org/nongnu/"))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(package-initialize)
;; Allow MELPA to upgrade built-in packages (needed for eglot semantic tokens)
(setq package-install-upgrade-built-in t)
;; All use-package blocks auto-install their packages
(require 'use-package-ensure)
(setq use-package-always-ensure t)
;; ============================================================
;; Platform & Environment
;; ============================================================
;; Enable basic mouse support over SSH connections
(unless (display-graphic-p)
(xterm-mouse-mode 1))
;; macOS-specific configuration
(when (eq system-type 'darwin)
;; Homebrew bin path (needed for some tools like spelling backends for Jin)
(add-to-list 'exec-path "/opt/homebrew/bin")
;; Use clang (otherwise strange defaults and some packages won't compile)
(setenv "CC" "/usr/bin/clang")
(setenv "CXX" "/usr/bin/clang++"))
;; Ensure package archive index is available on fresh installs
(unless package-archive-contents
(package-refresh-contents))
;; Preferred shell (nushell s not POSIX complient so I normally don't set it as my login shell)
(setq explicit-shell-file-name "nu")
(setq vterm-shell "nu")
;; SSH config already sets ControlMaster options globally;
;; tell TRAMP not to add its own ControlMaster options (Emacs 30+)
(setopt tramp-use-connection-share nil)
;; ============================================================
;; UI & Appearance
;; ============================================================
;; Font (with availability check)
(when (display-graphic-p)
(load-theme 'leuven-dark t)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(let ((preferred-font "Berkeley Mono"))
(if (member preferred-font (font-family-list))
(set-face-attribute 'default nil :family preferred-font :height 150)
(message "WARNING: Font '%s' not found; using Emacs default" preferred-font))))
;; Configure bars to my liking
(menu-bar-mode -1)
(setq column-number-mode t)
(setq window-resize-pixelwise t)
(setq frame-resize-pixelwise t)
(tab-bar-mode t)
;; ============================================================
;; Core Editor Behavior
;; ============================================================
; guess major mode from the file name
(setq-default major-mode
(lambda ()
(unless buffer-file-name
(let ((buffer-file-name (buffer-name)))
(set-auto-mode)))))
;; Confirm quit
(setq confirm-kill-emacs #'yes-or-no-p)
(setq auto-save-default nil)
(setq make-backup-files nil)
(save-place-mode t)
(savehist-mode t)
(recentf-mode t)
;; Automatically revert buffers when files change on disk
(global-auto-revert-mode 1)
(setq auto-revert-check-vc-info nil)
(defalias 'yes-or-no #'y-or-n-p)
;; Store automatic customisation options elsewhere
(setq custom-file (locate-user-emacs-file "custom.el"))
(when (file-exists-p custom-file)
(load custom-file))
;; Use editorconfig
(use-package editorconfig
:config
(editorconfig-mode t))
;; Use ripgrep instead of grep
(setq xref-search-program 'ripgrep)
;; Easy layout swapping
(use-package transpose-frame
:bind ("C-x |" . transpose-frame))
;; ============================================================
;; Completion Framework (Vertico + Orderless + Consult)
;; ============================================================
(use-package vertico
:init
(vertico-mode t)
:bind (:map vertico-map
;; improve directory navigation
("RET" . vertico-directory-enter)
("DEL" . vertico-directory-delete-word)
("M-d" . vertico-directory-delete-char)))
;; Orderless completion style (space-separated fuzzy matching)
(use-package orderless
:demand t
:custom
(completion-styles '(orderless basic)))
;; Extended completion framework
(use-package consult
:bind (([rebind switch-to-buffer] . consult-buffer)
("C-c s r" . consult-ripgrep) ; project-wide text search with live preview
("C-c s l" . consult-line) ; in-buffer line search
("C-c s i" . consult-imenu)) ; in-buffer symbol navigation
:custom
(read-buffer-completion-ignore-case t)
(read-file-name-completion-ignore-case t)
(completion-ignore-case t))
;; ============================================================
;; Version Control
;; ============================================================
(use-package magit
:custom
;; Massive speedup on macOS, and shouldn't affect sane platforms like FreeBSD
;; https://indieweb.social/@xenodium/114793849088519458
(magit-git-executable (executable-find "git")))
(use-package diff-hl
:config
(global-diff-hl-mode))
;; Ediff: side-by-side view
(setq ediff-split-window-function 'split-window-horizontally)
(setq magit-ediff-dwim-show-on-hunks t)
;; ============================================================
;; LSP (Eglot)
;; ============================================================
;; Face for mutable variables via semantic tokens (like RustRover/JetBrains)
(defface eglot-semantic-mutable
'((t :underline t))
"Face for mutable variables via semantic tokens.")
;; Built-in eglot (1.17) lacks semantic token support; install MELPA version
(unless (package-installed-p 'eglot '(1 21))
(package-install 'eglot))
(use-package eglot
:hook (prog-mode . eglot-ensure)
:config
;; Swift LSP
(add-to-list 'eglot-server-programs '(swift-mode . ("sourcekit-lsp")))
;; Highlight mutable variables
(add-to-list 'eglot-semantic-token-modifiers "mutable")
;; Re-enable workspace refresh handlers (disabled/missing in eglot 1.21+ due to
;; clangd/lean issues, but needed for rust-analyzer which sends incomplete
;; results before project analysis finishes).
;; Also refresh inlay hints — eglot doesn't advertise inlayHint refreshSupport
;; so there's no separate refresh request; piggyback on semantic tokens instead.
(cl-defmethod eglot-handle-request
(_server (_method (eql workspace/semanticTokens/refresh)))
(dolist (buffer (eglot--managed-buffers _server))
(eglot--when-live-buffer buffer
(setq eglot--semtok-state nil)
(font-lock-flush)
(when (bound-and-true-p eglot-inlay-hints-mode)
(eglot-inlay-hints-mode -1)
(eglot-inlay-hints-mode 1))))))
;; Consult + eglot interop
(use-package consult-eglot
:bind ("C-c s s" . consult-eglot-symbols))
(use-package eldoc-box
:custom
(eldoc-box-clear-with-C-g t)
:bind
(:map help-map ("D" . eldoc-box-help-at-point)))
;; ============================================================
;; Programming language Modes & Behavior
;; ============================================================
;; Parens pairing
(electric-pair-mode t)
;; Indentation
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
;; Trailing newlines
(setq-default require-final-newline t)
(use-package auctex
:defer t
:custom
(TeX-auto-save t)
(TeX-parse-self t)
:config
(setq-default TeX-master nil)
:hook (LaTeX-mode . LaTeX-math-mode))
(use-package caddyfile-mode :defer t)
(use-package dockerfile-mode :defer t)
(use-package haskell-mode :defer t)
(use-package just-mode :defer t)
(use-package json-mode
:hook (json-mode . (lambda () (setq-local js-indent-level 2))))
(use-package kdl-mode :defer t)
(use-package kotlin-mode :defer t)
(use-package lua-mode :defer t)
(use-package markdown-mode :defer t)
(use-package nushell-mode :defer t)
(use-package php-mode :defer t)
(use-package rust-mode :defer t)
(use-package swift-mode :defer t)
(use-package terraform-mode :defer t)
(use-package typescript-mode :defer t)
(use-package uiua-ts-mode :defer t)
(use-package vcl-mode :defer t)
(use-package yaml-mode :defer t)
;; Line numbers in programming buffers
(add-hook 'prog-mode-hook #'display-line-numbers-mode)
;; Styling of TODOs and FIXMEs
(use-package hl-todo
:config
(global-hl-todo-mode))
;; ============================================================
;; Tools
;; ============================================================
;; Spell checking (requires a backend like nuspell or hunspell)
(use-package jinx
:hook ((text-mode prog-mode conf-mode markdown-mode) . jinx-mode))
;; Terminal emulators
(use-package eat :defer t)
(use-package vterm
:defer t
:commands (vterm vterm-mode))
(defun project-term (&optional arg)
"Open a project-aware terminal in a bottom side window.
Reuses an existing buffer if one exists for the project.
With a prefix argument, always create a new terminal buffer."
(interactive "P")
(let* ((project (project-current t))
(default-directory (project-root project))
(name (file-name-nondirectory (directory-file-name default-directory)))
(base-name (format "*term[%s]*" name))
(buf-name (if arg (generate-new-buffer-name base-name) base-name))
(buf (get-buffer buf-name)))
(unless (and buf (buffer-live-p buf))
(setq buf (get-buffer-create buf-name))
(with-current-buffer buf
(vterm-mode)))
(select-window
(display-buffer-in-side-window
buf '((side . bottom)
(window-height . 0.3)
(window-parameters . ((no-delete-other-windows . t))))))))
(define-key project-prefix-map (kbd "t") #'project-term)
;; ============================================================
;; AI / Assistants
;; ============================================================
(use-package claude-code-ide
:vc (:url "https://github.com/manzaltu/claude-code-ide.el" :rev :newest)
:bind ("C-c C-'" . claude-code-ide-menu)
:config
(claude-code-ide-emacs-tools-setup)
(setq claude-code-ide-terminal-backend 'eat))
;; ============================================================
;; Org Mode
;; ============================================================
(setq org-directory "~/org")
(setq org-todo-keywords
'((sequence "TODO" "IN-PROGRESS" "IN-REVIEW" "DONE")))
;; ============================================================
;; Global Keybindings
;; ============================================================
(global-set-key (kbd "s-/") #'comment-line)
(global-set-key (kbd "M-RET") #'eglot-code-actions)
(global-set-key (kbd "S-<f6>") #'eglot-rename)
;; Mouse button navigation for buffer history (GUI only)
(when (display-graphic-p)
(global-set-key (kbd "<mouse-3>") #'previous-buffer)
(global-set-key (kbd "<mouse-4>") #'next-buffer))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment