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
andTOC_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")
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)