My Emacs Config

Artifact [21d1b774f1]
Login

Artifact 21d1b774f1be63a7741f99fe1606f2ef9a26ffc919abf89224fbe1833eeaae8d:


#+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

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

** 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 (if (eq system-type 'darwin)
                  "Menlo 12"
                "Hack 10")))
    (set-face-attribute 'default t :font font)
    (set-frame-font font nil t))
#+END_SRC

** Environment variables

#+BEGIN_SRC elisp
  (setenv "GOPATH" "/home/avalos/go")
  (setenv "PATH" (concat
                  "/usr/local/bin" ":"
                  (getenv "PATH")))
  (setq exec-path (append exec-path
                          '("/usr/local/bin"
                            "/usr/bin"
                            "/home/avalos/.cargo/bin"
                            "/home/avalos/Flutter/bin"
                            "/home/avalos/go/bin"
                            "/home/avalos/.platformio/penv/bin")))
#+END_SRC

* Package management

I prefer to automate package management as much as possible, but I
don't want it to happen in startup, because since Emacs does
everything synchronously, I'd have to wait until this whole
refresh/install/update process finishes so I can use Emacs. It's
annoying. That's why I put this process inside an interactive
function, so I can call it whenever I want.

I'm using the stable MELPA repos because they are, well, stable. I
used the unstable repo before, and things always broke: it was
annoying. That's why I switched to stable. I don't plan to switch
back!

#+BEGIN_SRC elisp
  (defun do-package-stuff ()
    "Do package refresh/install/update and stuff."
    (interactive)  
    (load "package")
    (package-initialize)
    (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)))

    (defvar avalos/packages '(add-node-modules-path
                              auto-package-update
                              challenger-deep-theme
                              company
                              company-irony
                              dart-mode
                              docker-compose-mode
                              dockerfile-mode
                              dracula-theme
                              emmet-mode
                              f
                              flycheck
                              flycheck-irony
                              flycheck-languagetool
                              geiser
                              geiser-chicken
                              geiser-guile
                              gemini-mode
                              go-mode
                              irony
                              irony-eldoc
                              js2-mode
                              kaolin-themes
                              load-dir
                              load-env-vars
                              lsp-dart
                              lsp-mode
                              lsp-pyright
                              lsp-ui
                              magit
                              markdown-mode
                              masm-mode
                              meson-mode
                              org
                              ox-epub
                              password-store
                              platformio-mode
                              prettier-js
                              projectile
                              py-isort
                              rainbow-delimiters
                              rust-mode
                              smartparens
                              tide
                              typescript-mode
                              use-package
                              vala-mode
                              vc-fossil
                              web-mode
                              which-key
                              yaml-mode
                              yasnippet-snippets)
      "Default packages.")

    (defun avalos/packages-installed-p ()
      "Install listed packages if not installed."
      (cl-loop for pkg in avalos/packages
            when (not (package-installed-p pkg)) do (cl-return nil)
            finally (cl-return t)))

    (unless (avalos/packages-installed-p)
      (message "%s" "Refreshing package database...")
      (package-refresh-contents)
      (dolist (pkg avalos/packages)
        (when (not (package-installed-p pkg))
          (package-install pkg))))

    (use-package auto-package-update
      :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
    :init
    (setq load-dirs (expand-file-name "vendor" user-emacs-directory))
    (load-dirs))
#+END_SRC

I'll figure out how not to load this at startup.
  
#+BEGIN_SRC elisp  
  ;;(require 'lsp-mode)
#+END_SRC

* Theming

My favorite editor theme in general, is probably Fluffy, which was
only available for Gedit, until I ported it to Emacs. It has some
missing colors, so I'm loading Kaolin first, so that Fluffy uses it as
its base. The combination actually looks good!

[[https://fossil.avalos.me/fluffy.el][Fluffy.el]]

#+BEGIN_SRC elisp
  (add-to-list 'custom-theme-load-path "~/.emacs.d/themes")
  ;; (load-theme 'kaolin-light t)
  ;; (load-theme 'fluffy t)
  (load-theme 'kaolin-dark t)
  (load-theme 'fluffy-dark t)
#+END_SRC

* Package config

** which-key

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

** Smartparens

#+BEGIN_SRC elisp
  (use-package smartparens-config
    :init (smartparens-global-mode))
#+END_SRC

** Flycheck

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

** LSP

These settings are mainly for performance.

#+BEGIN_SRC elisp
  (setq gc-cons-threshold 100000000)
  (setq read-process-output-max (* 1024 1024))
#+END_SRC

#+BEGIN_SRC elisp
  (use-package lsp-mode
    :ensure t
    :commands (lsp lsp-deferred)
    :config
    (setq lsp-response-timeout 25)
    (setq lsp-completion-provider :capf)
    (setq lsp-idle-delay 0.500))

  (use-package lsp-ui
    :ensure t
    :commands lsp-ui-mode
    :config
    (setq lsp-ui-doc-position 'at-point)
    (setq lsp-ui-doc-alignment 'window)
    (setq lsp-ui-sideline-show-code-actions t))
#+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 lsp-install-save-hooks () "Install save hooks for lsp."
         (add-hook 'before-save-hook #'lsp-format-buffer t t)
         (add-hook 'before-save-hook #'lsp-organize-imports t t))
#+END_SRC

** 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)
    (setq lsp-completion-provider :capf)
    :init (global-company-mode))
#+END_SRC

** Yasnippet

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

** Emmet mode

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

** Org Mode

*** Initialization

My Org Mode setup requires significantly more
configuration. Obviously, we need to import Org first.

#+BEGIN_SRC elisp
  (require 'org)
#+END_SRC

Then, I will proceed to do some general initialization:

#+BEGIN_SRC elisp
  (setq org-adapt-indentation nil)
  (add-hook 'org-mode-hook 'auto-fill-mode)
  (add-hook 'org-mode-hook 'org-indent-mode)
#+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
  (add-to-list 'org-latex-packages-alist
               '("" "tikz" t))
  (eval-after-load "preview"
    '(add-to-list preview-default-preamble "\\PreviewEnvironment{tikzpicture}" t))
  (setq org-preview-latex-default-process 'imagemagick)
#+END_SRC

*** Agenda

#+BEGIN_SRC elisp
  (setq-default org-display-custom-times t)
  (setq org-time-stamp-custom-formats '("<%a %b %e %Y>" . "<%a %b %e %Y %H:%M>"))
  (setq 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.

#+BEGIN_SRC elisp
  (require 'ox-latex)
  (add-to-list 'org-latex-packages-alist '("" "minted"))
  (setq org-latex-listings 'minted)

  (setq 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"))

  (setq org-src-fontify-natively 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

** Spell checking

#+BEGIN_SRC elisp
  (use-package flycheck-languagetool
    :ensure t
    :hook (text-mode . flycheck-languagetool-setup)
    :init
    (setq flycheck-languagetool-server-jar "/usr/share/java/languagetool/languagetool-commandline.jar"))
#+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 '("\\.yml$" . yaml-mode))
  (add-to-list 'auto-mode-alist '("\\.yaml$" . yaml-mode))
  (add-to-list 'auto-mode-alist '("\\.wiki$" . html-mode))
  (add-to-list 'auto-mode-alist '("\\.vala$" . vala-mode))
  (add-to-list 'auto-mode-alist '("\\.vapi$" . vala-mode))
  (add-to-list 'auto-mode-alist '("\\.gmi$" . gemini-mode))
  (add-to-list 'auto-mode-alist '("\\.sxml$" . scheme-mode))
  (add-to-list 'auto-mode-alist '("\\.php$" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.js$" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.tsx$'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.html$" . web-mode))
  (add-to-list 'file-coding-system-alist '("\\.vala$" . utf-8))
  (add-to-list 'file-coding-system-alist '("\\.vapi$" . utf-8))
  (add-to-list 'auto-mode-alist '("PKGBUILD" . sh-mode))
  (add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
#+END_SRC

* IDE

** Golang

#+BEGIN_SRC elisp
  (use-package lsp-mode
    :ensure t
    :commands (lsp lsp-deferred)
    :hook
    (go-mode . lsp-deferred)
    (go-mode . lsp-install-save-hooks)
    (go-mode . yas-minor-mode)
    :config
    (lsp-register-custom-settings
     '(("gopls.completeUnimported" t t))))
#+END_SRC

** Rust

#+BEGIN_SRC elisp
  (use-package lsp-mode
    :hook (rust-mode . lsp-deferred))
#+END_SRC

** Python

#+BEGIN_SRC elisp
  (use-package lsp-pyright
    :config
    (setq lsp-pyright-auto-import-completions t)
    (setq lsp-pyright-diagnostic-mode t)
    :init (when (executable-find "/bin/python3")
            (setq lsp-pyright-python-executable-cmd "/bin/python3"))
    :hook
    (python-mode . (lambda ()
                     (require 'lsp-pyright)
                     (add-hook 'before-save-hook 'py-isort-before-save)
                     (lsp-deferred))))
#+END_SRC

** Flutter/Dart

#+BEGIN_SRC elisp
  (use-package dart-mode
    :hook
    (dart-mode . lsp-deferred)
    ;; (dart-mode . flutter-test-mode)
    (dart-mode . lsp-install-save-hooks)
    (dart-mode . yas-minor-mode)
    :custom (dart-format-on-save t))

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

#+END_SRC

** Web (PHP)

#+BEGIN_SRC elisp
  (use-package web-mode
    :custom
    (web-mode-code-indent-offset 4))

  (use-package lsp-mode
    :hook
    (web-mode . lsp-deferred)
    (web-mode . emmet-mode)
    :config (setq lsp-clients-php-server-command "phpactor"))
#+END_SRC

** Web (JS/TS)

[[https://mihamina.rktmb.org/2020/08/emacs-typescript-development.html][Source]]

#+BEGIN_SRC elisp
  (use-package typescript-mode
    :hook (typescript-mode . lsp-deferred))

  (use-package web-mode
    :hook (web-mode . lsp-deferred))
#+END_SRC

** PlatformIO

#+BEGIN_SRC elisp
  (use-package platformio-mode
    :hook (c++-mode . (lambda ()
                        (lsp-deferred)
                        (yas-minor-mode)
                        (platformio-conditionally-enable))))
#+END_SRC