My Emacs Config

emacs.org at tip
Login

File emacs.org from the latest check-in


#+TITLE: My Emacs Config
#+PROPERTY: header-args :tangle ~/.emacs
#+STARTUP: showeverything
#+AUTHOR: Iván Ávalos

#+LATEX_CLASS_OPTIONS: [letterpaper, portrait]
#+LATEX_HEADER: \usepackage[plain]{fullpage}

* License

#+BEGIN_EXAMPLE
   Copyright 2021 Iván Ávalos

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
#+END_EXAMPLE

* Introduction

This file contains all my configuration, and it is written similarly
to what is known as “literate programming“, in which instead of adding
comments to your code, you add code to your comments. It's
interesting, because it allows you to document and organize your code
alongside the code itself.

However, you won't be able to just download this file and expect it to
work. First, you have to extract the Emacs Lisp code from this Org
Mode file; but that's very easy! Just do ~M-x org-babel-tangle-file
<enter>~, write the name of this file, and the resulting file with
only the code will be written to ~\~/.emacs~, which will replace your
current Emacs config, *SO BE CAREFUL!* Backup your Emacs config before
doing this!

You can also export this file as a PDF using LaTeX! Which is fun,
because TeX is written similarly to this config file. In fact, Donald
Knuth, the author of TeX, invented literate programming! I found it
fascinating, so I decided to try this. I got this idea of writing
config files in literate programming using Org mode from Derek, of
Distro Tube. Thanks, Derek!

* Initialization

** General parameters

Fix that annoying scroll!

#+BEGIN_SRC elisp
  (setq mouse-wheel-scroll-amount '(0.07))
  (setq mouse-wheel-progressive-speed nil)
#+END_SRC

Remove most window elements, so that only the menu bar remains.

#+BEGIN_SRC elisp
  (tool-bar-mode -1)
  (scroll-bar-mode -1)
#+END_SRC

Stop Emacs from polluting backup files everywhere.

#+BEGIN_SRC elisp
  (setq make-backup-files nil)
#+END_SRC

I work at $HOME most of the time.

#+BEGIN_SRC elisp
    (setq default-directory "~/")
#+END_SRC

No giant GNU at startup, no ~*scratch*~ buffer warning, and we should
start in Org mode.

#+BEGIN_SRC elisp
  (setq inhibit-splash-screen t
        initial-scratch-message nil
        initial-major-mode 'org-mode)
#+END_SRC

Under macOS, use the command key (⌘) as the option key (M), otherwise
using Emacs gets annoying.

#+BEGIN_SRC elisp
  (setq mac-right-option-modifier 'meta)
#+END_SRC

I usually prefer 4 spaces for indentation. No tabs!

#+BEGIN_SRC elisp
  (setq-default tab-width 4
                indent-tabs-mode nil
                c-basic-offset 4)
#+END_SRC

Too lazy to type "yes" or "no", let's make it "y" or "n" instead.

#+BEGIN_SRC elisp
  (defalias 'yes-or-no-p 'y-or-n-p)
#+END_SRC

No visual or typing annoyances, please.

#+BEGIN_SRC elisp
  (setq echo-keystrokes 0.1
        use-dialog-box nil
        visible-bell t)
#+END_SRC

In Emacs 29 under Wayland, there's an input lag for mouse events,
which makes the whole experience feel sluggish. Let's disable it.

#+BEGIN_SRC elisp
  (setq-default pgtk-wait-for-event-timeout 0)
#+END_SRC

Oh, finally, the holy line numbers!

#+BEGIN_SRC elisp
  (setq-default display-line-numbers t)
#+END_SRC

Here's another life-changing feature enabled! I don't know how I
managed to live without wider spacing between lines.

#+BEGIN_SRC elisp
  (setq-default line-spacing 2)
#+END_SRC

** Font

Now that I'm constantly switching between two platforms (GNU/Linux and
macOS), fonts look different in both of them. 10pt is ultra small in
macOS, but perfectly normal in GNU/Linux.

#+BEGIN_SRC elisp
  (let ((font (cond ((eq system-type 'darwin) "Menlo 12")
                    ((eq system-type 'gnu/linux) "Hack 10")
                    ((eq system-type 'windows-nt) "Consolas 10")
                    (t "Monospace 10"))))
    (set-face-attribute 'default t :font font)
    (set-frame-font font nil t))
#+END_SRC

** Environment variables

#+BEGIN_SRC elisp
  (setenv "PATH" (concat
                  "/usr/local/bin" ":"
                  (getenv "PATH")))
  
  (setq exec-path (append exec-path
                          '("/usr/local/bin"
                            "/usr/bin"
                            ,(expand-file-name "~/.cargo/bin")
                            ,(expand-file-name "~/go/bin")
                            ,(expand-file-name "~/.platformio/penv/bin"))))
#+END_SRC

* Package management

Package installation used to be handled using a custom function that
loaded packages from a list where I had to manually put all the
packages to install. It was a pain to maintain this list, that is,
until I discovered that the mighty ~use-package~ could handle that for
me. Now the package management code is much cleaner!

I prefer MELPA stable over MELPA because packages there are, well,
stable. I used the unstable repo before, and things always broke: it
was annoying. That's why I switched to stable.

#+BEGIN_SRC elisp
  (require 'package)
  
  (setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
                           ("nongnu" . "https://elpa.nongnu.org/nongnu/")
                           ("melpa" . "https://melpa.org/packages/")
                           ("melpa-stable" . "https://stable.melpa.org/packages/"))
        package-archive-priorities '(("gnu" . 3)
                                     ("nongnu" . 2)
                                     ("melpa" . 0)
                                     ("melpa-stable" . 1)))
#+END_SRC  

Set packages to update automatically.

#+BEGIN_SRC elisp
  (use-package auto-package-update
      :ensure t
      :config
      (setq auto-package-update-delete-old-versions t)
      (setq auto-package-update-hide-results t)
      (auto-package-update-maybe))
#+END_SRC

However, vendored packages require manual management anyway, so
there's no need to connect to a repository and fetch package lists and
that stuff.

#+BEGIN_SRC elisp
  (use-package load-dir
    :ensure t
    :init
    (setq load-dirs (expand-file-name "vendor" user-emacs-directory))
    (load-dirs))
#+END_SRC

* Theming

#+BEGIN_SRC elisp
  (use-package atom-one-dark-theme
    :ensure t
    :config (load-theme 'atom-one-dark t))
#+END_SRC

* Package config

** Company

#+BEGIN_SRC elisp
  (use-package company
    :ensure t
    :config
    ;; Optionally enable completion-as-you-type behavior.
    (setq company-idle-delay 0.01)
    (setq company-minimum-prefix-length 1)
    :init (global-company-mode))
#+END_SRC

** Emmet mode

#+BEGIN_SRC elisp
  (use-package emmet-mode
    :ensure t
    :hook
    (sgml-mode . emmet-mode) ;; Auto-start on any markup modes
    (css-mode . emmet-mode)) ;; enable Emmet's CSS abbreviation
#+END_SRC

** Eglot (LSP)

#+BEGIN_SRC elisp
  (use-package eglot
    :ensure t
    :config
    (add-to-list 'eglot-server-programs
                 '(go-mode . ("gopls")))
    (add-to-list 'eglot-server-programs
                 '(web-mode . ("phpactor" "language-server"))))
#+END_SRC

This function adds the hooks that will enable LSP-powered formatting
and import organizing when saving a file with code.

#+BEGIN_SRC elisp
  (defun eglot-install-save-hooks () "Install save hooks for lsp."
         (add-hook 'before-save-hook #'eglot-format-buffer t t)
         (add-hook 'before-save-hook #'eglot-code-action-organize-imports t t))
#+END_SRC

** Flycheck

#+BEGIN_SRC elisp
  (use-package flycheck
    :ensure t
    :init (global-flycheck-mode))
#+END_SRC

** Irony

#+BEGIN_SRC elisp
  (use-package irony
    :ensure t
    :hook (c++-ts-mode . irony-mode)
    :hook (c-ts-mode . irony-mode)
    :hook (objc-mode . irony-mode)
    :hook (irony-mode . irony-cdb-autosetup-compile-options))

  (use-package flycheck-irony
    :ensure t
    :after irony
    :hook (flycheck-mode . flycheck-irony-setup))

  (use-package irony-eldoc
    :ensure t
    :after irony
    :hook (irony-mode . irony-eldoc))
#+END_SRC

** Org Mode

*** Initialization

My Org Mode setup requires more configuration than the default one. In
this block I do a bunch of initialization, such as enabling auto-fill
mode, visually indenting sections, and highlighting code blocks.

#+BEGIN_SRC elisp
  (use-package org
    :ensure t
    :custom
    (org-adapt-indentation nil)
    (org-src-fontify-natively t)
    :hook
    (org-mode . auto-fill-mode)
    (org-mode . org-indent-mode))
#+END_SRC

*** ePUB exports

#+BEGIN_SRC elisp
  (use-package ox-epub
    :ensure t)
#+END_SRC

*** Agenda

#+BEGIN_SRC elisp
  (use-package org
    :ensure t
    :custom
    (org-display-custom-times t)
    (org-time-stamp-custom-formats '("<%a %b %e %Y>" . "<%a %b %e %Y %H:%M>"))
    (org-todo-keywords '((sequence "TODO" "DOING" "DONE"))))
#+END_SRC

*** \latex

This particular configuration enables syntax highlighting when
exporting to PDF using \latex. It uses a package called “minted” and
adds some processing flags to the rendering pipeline.

The reason why there are multiple ~pdflatex~ passes in the rendering
pipeline is because BibTeX requires them to properly process the
bibliography database and the references.

#+BEGIN_SRC elisp
  (use-package ox-latex
    :ensure nil
    :demand t
    :custom
    (org-latex-pdf-process
     '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
       "bibtex %b"
       "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"
       "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))
    (org-latex-listings 'minted)
    :config
    (add-to-list 'org-latex-packages-alist '("" "minted" t)))
#+END_SRC

**** Tikz

PGF/Tikz is an awesome tool for creating a lot of different kinds of
2D graphics. It's a \tex package, and if I want to use it within Org
Mode, I need to do some extra configuration, mainly for previewing
inline graphics inside Emacs.

#+BEGIN_SRC elisp
  (use-package ox-latex
    :ensure nil
    :demand t
    :custom
    (org-preview-latex-default-process 'imagemagick)
    :config
    (add-to-list 'org-latex-packages-alist '("" "tikz" t))
    (eval-after-load "preview"
      '(add-to-list preview-default-preamble "\\PreviewEnvironment{tikzpicture}" t)))
#+END_SRC

*** Markdown

Since we need to be able to export Org files to Markdown, we have to
enable it by adding it to the ~org-export-backends~ variable.

#+BEGIN_SRC elisp
(add-to-list 'org-export-backends 'md)
#+END_SRC

** Projectile

#+BEGIN_SRC elisp
  (use-package projectile
    :ensure t)
#+END_SRC

** Rainbow delimiters

#+BEGIN_SRC elisp
  (use-package rainbow-delimiters
    :ensure t
    :hook (prog-mode . rainbow-delimiters-mode))
#+END_SRC

** Smartparens

#+BEGIN_SRC elisp
  (use-package smartparens-mode
    :ensure smartparens
    :hook (prog-mode text-mode markdown-mode)
    :config (require 'smartparens-config))
#+END_SRC

** Treemacs

#+BEGIN_SRC elisp
  (use-package treemacs
    :ensure t)
#+END_SRC

** tree-sitter

#+BEGIN_SRC elisp
  (use-package tree-sitter
    :ensure t
    :config
    (global-tree-sitter-mode)
    :hook
    (tree-sitter-after-on . tree-sitter-hl-mode))

  (use-package tree-sitter-langs
    :ensure t
    :after tree-sitter)

  (use-package treesit-auto
    :ensure t
    :custom
    (treesit-auto-install 'prompt)
    :config
    (treesit-auto-add-to-auto-mode-alist 'all)
    (global-treesit-auto-mode))


#+END_SRC

** which-key

#+BEGIN_SRC elisp
  (use-package which-key
    :ensure t
    :init (which-key-mode))
#+END_SRC

** Yasnippet

#+BEGIN_SRC elisp
  (use-package yasnippet-snippets
    :ensure t)
#+END_SRC

#+BEGIN_SRC elisp
  (use-package yasnippet
    :ensure t
    :after yasnippet-snippets
    :commands yas-minor-mode
    :init (yas-global-mode 1))
#+END_SRC

* Language hooks

#+BEGIN_SRC elisp
  (add-to-list 'auto-mode-alist '("\\.zsh$" . shell-script-mode))
  (add-to-list 'auto-mode-alist '("\\.gitconfig$" . conf-mode))
  (add-to-list 'auto-mode-alist '("\\.sxml$" . scheme-mode))
  (add-to-list 'auto-mode-alist '("PKGBUILD" . sh-mode))
#+END_SRC

* Version control

** Fossil

#+BEGIN_SRC elisp
  (use-package vc-fossil
    :ensure t)
#+END_SRC

** Magit

#+BEGIN_SRC elisp
  (use-package magit
    :ensure t)
#+END_SRC

* Editing

** Docker

#+BEGIN_SRC elisp
  (use-package docker-compose-mode
    :ensure t)
#+END_SRC

** Meson

#+BEGIN_SRC elisp
  (use-package meson-mode
    :ensure t)
#+END_SRC

* IDE

I try to use Emacs as my IDE as much as possible, replacing other
heavier code editors such as Visual Studio Code, or more advanced
IDEs. While I haven't managed to replace some of them, I try to update
this config whenever I want to try replacing a new one.

** Flutter/Dart

#+BEGIN_SRC elisp
  (use-package dart-mode
    :ensure t
    :after (eglot yasnippet)
    :hook
    (dart-mode . eglot-ensure)
    (dart-mode . eglot-install-save-hooks)
    (dart-mode . yas-minor-mode)
    :custom (dart-format-on-save t))

  (use-package flutter
    :ensure t
    :after dart-mode
    :bind (:map dart-mode-map
                ("C-M-x" . #'flutter-run-or-hot-reload)
                ("C-M-z" . #'flutter-hot-restart)))
#+END_SRC

** Golang

#+BEGIN_SRC elisp
    (use-package go-ts-mode
      :after (eglot yasnippet)
      :hook
      (go-ts-mode . eglot-ensure)
      (go-ts-mode . eglot-install-save-hooks)
      (go-ts-mode . yas-minor-mode))
#+END_SRC

** PlatformIO

#+BEGIN_SRC elisp
  (use-package platformio-mode
    :ensure t
    :after (eglot yasnippet)
    :hook (c++-mode . (lambda ()
                        (eglot-ensure)
                        (yas-minor-mode)
                        (platformio-conditionally-enable))))
#+END_SRC

** Python

#+BEGIN_SRC elisp
  (use-package python
    :after (eglot yasnippet)
    :hook
    (python-mode . eglot-ensure)
    (python-mode . eglot-install-save-hooks)
    (python-mode . yas-minor-mode))
#+END_SRC

** Rust

#+BEGIN_SRC elisp
  (use-package rust-ts-mode
    :after eglot
    :hook
    (rust-mode . eglot-ensure)
    (rust-mode . eglot-install-save-hooks))
#+END_SRC

** Scheme

#+BEGIN_SRC elisp
  (use-package geiser
    :ensure t
    :mode "\\.sxml$")

  (use-package geiser-chicken
    :ensure t
    :after geiser)

  (use-package geiser-guile
    :ensure t
    :after geiser)
#+END_SRC

** Web (general)

#+BEGIN_SRC elisp
  (use-package web-mode
    :ensure t
    :after (eglot emmet-mode)
    :hook
    (web-mode . eglot-ensure)
    (web-mode . eglot-install-save-hooks)
    (web-mode . emmet-mode)
    :mode ("\\.php$" "\\.html?$"))
#+END_SRC

** Web (JS/TS)

#+BEGIN_SRC elisp
  (use-package js-mode
    :hook
    (js-mode . eglot-ensure)
    (js-mode . eglot-install-save-hooks))
#+END_SRC

#+BEGIN_SRC elisp
  (use-package typescript-ts-mode
    :after eglot
    :hook
    (typescript-ts-mode . eglot-ensure)
    (typescript-ts-mode . eglot-install-save-hooks))
#+END_SRC