#+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!
* Imports
I will only import Common Lisp macros. Emacs says importing them is
deprecated, because they're required at runtime; but everytime I
delete this line, Emacs complains that they are missing.
#+BEGIN_SRC elisp
(require 'cl-lib)
#+END_SRC
* Initialization
** User identity
Honestly, I don't remember what this is for. Git, maybe?
#+BEGIN_SRC elisp
(setq user-full-name "Iván Ávalos")
(setq user-mail-address "avalos@disroot.org")
#+END_SRC
** General parameters
#+BEGIN_SRC elisp
(tool-bar-mode -1)
(scroll-bar-mode -1)
(setq default-directory "~/")
(setq inhibit-splash-screen t
initial-scratch-message nil
initial-major-mode 'org-mode)
(setq mac-right-option-modifier 'meta
mac-right-option-modifier nil)
(setq select-enable-clipboard t)
(setq-default tab-width 4
indent-tabs-mode nil
c-basic-offset 4)
(setq make-backup-files nil)
(defalias 'yes-or-no-p 'y-or-n-p)
(global-set-key (kbd "RET") 'newline-and-indent)
(setq echo-keystrokes 0.1
use-dialog-box nil
visible-bell t)
#+END_SRC
** Font
#+BEGIN_SRC elisp
(set-face-attribute 'default t :font "Hack 10")
(set-frame-font "Hack 10" nil t)
#+END_SRC
** Environment variables
#+BEGIN_SRC elisp
(setenv "GOPATH" "/Users/avalos/go")
(setenv "PATH" (concat
"/usr/local/bin" ":"
"/usr/local/go/bin" ":"
(getenv "PATH")))
(setq exec-path (append exec-path
'("/usr/local/bin"
"/usr/bin"
"/home/avalos/flutter/bin"
"/home/avalos/.cargo/bin"
"/home/avalos/Flutter/bin"
"/home/avalos/go/bin"
"/home/avalos/go")))
#+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)
(add-to-list 'package-archives
'("melpa-stable" . "http://stable.melpa.org/packages/") t)
(defvar avalos/packages '(add-node-modules-path
auto-package-update
company
company-irony
dart-mode
dracula-theme
emmet-mode
flycheck
flycheck-irony
geiser
geiser-chicken
go-mode
irony
js2-mode
kaolin-themes
lsp-dart
lsp-mode
lsp-ui
magit
markdown-mode
org
ox-epub
password-store
platformio-mode
projectile
rainbow-delimiters
smartparens
tide
typescript-mode
use-package
web-mode
which-key
yaml-mode
yasnippet-snippets)
"Default packages.")
(defun avalos/packages-installed-p ()
"Install listed packages if not installed."
(loop for pkg in avalos/packages
when (not (package-installed-p pkg)) do (return nil)
finally (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
(defvar avalos/vendor-dir (expand-file-name "vendor" user-emacs-directory))
(defun load-directory (dir) "Load eLisp files on DIR."
(let ((load-it (lambda (f)
(load-file (concat (file-name-as-directory dir) f)))))
(mapc load-it (directory-files dir nil "\\.el$"))))
(load-directory avalos/vendor-dir)
#+END_SRC
* Theming
#+BEGIN_SRC elisp
(add-to-list 'custom-theme-load-path "~/.emacs.d/themes")
(load-theme 'dracula 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)
(require 'yasnippet)
(yas-reload-all)
#+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 org-agenda-files (list "~/Sync/Tareas.org"
"~/Sync/Agenda.org"))
(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"
"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
* 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 '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
(require 'lsp-go)
(use-package lsp-mode
:ensure t
:commands (lsp lsp-deferred)
:hook (go-mode . lsp-deferred))
(lsp-register-custom-settings
'(("gopls.completeUnimported" t t)))
(add-hook 'go-mode-hook #'lsp-install-save-hooks)
(add-hook 'go-mode-hook 'yas-minor-mode)
#+END_SRC
** Flutter/Dart
#+BEGIN_SRC elisp
(use-package dart-mode
:hook (dart-mode . flutter-test-mode)
:custom (dart-format-on-save t))
(add-hook 'dart-mode-hook 'lsp)
(add-hook 'dart-mode-hook #'lsp-install-save-hooks)
(add-hook 'dart-mode-hook 'yas-minor-mode)
#+END_SRC
** Web (PHP)
#+BEGIN_SRC elisp
(use-package lsp-mode
:hook (web-mode . lsp-deferred)
:config (setq lsp-clients-php-server-command "/usr/bin/phpactor"))
(use-package lsp-mode
:hook (php-mode . lsp-deferred)
:config (setq lsp-clients-php-server-command "/usr/bin/phpactor"))
#+END_SRC
** Web (JS/TS)
[[https://mihamina.rktmb.org/2020/08/emacs-typescript-development.html][Source]]
#+BEGIN_SRC elisp
(defun setup-tide-mode ()
(interactive)
;; (setq tide-tsserver-process-environment '("TSS_LOG=-level verbose -file /tmp/tss.log"))
(tide-setup)
(if (file-exists-p (concat tide-project-root "node_modules/typescript/bin/tsserver"))
(setq tide-tsserver-executable "node_modules/typescript/bin/tsserver"))
(flycheck-mode +1)
(setq flycheck-check-syntax-automatically '(save mode-enabled))
(eldoc-mode +1)
(tide-hl-identifier-mode +1)
(setq tide-format-options '(:indentSize 2 :tabSize 2 :insertSpaceAfterFunctionKeywordForAnonymousFunctions t :placeOpenBraceOnNewLineForFunctions nil))
(local-set-key (kbd "C-c d") 'tide-documentation-at-point)
(company-mode +1)
(setq company-minimum-prefix-length 1))
(use-package tide
:ensure t
:config
(progn
(company-mode +1)
(setq company-tooltip-align-annotations t)
(add-hook 'typescript-mode-hook #'setup-tide-mode)
(add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-mode))))
(use-package js2-mode
:ensure t
:config
(progn
(add-hook 'js2-mode-hook #'setup-tide-mode)
;; configure javascript-tide checker to run after your default javascript checker
(setq js2-basic-offset 2)
(flycheck-add-next-checker 'javascript-eslint 'javascript-tide 'append)
(add-to-list 'interpreter-mode-alist '("node" . js2-mode))
(add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))))
(use-package json-mode
:ensure t
:config
(progn
(flycheck-add-mode 'json-jsonlint 'json-mode)
(add-hook 'json-mode-hook 'flycheck-mode)
(setq js-indent-level 2)
(add-to-list 'auto-mode-alist '("\\.json" . json-mode))))
(use-package web-mode
:ensure t
:config
(progn
(add-to-list 'auto-mode-alist '("\\.tsx\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.js" . web-mode))
(add-to-list 'auto-mode-alist '("\\.html" . web-mode))
;; this magic incantation fixes highlighting of jsx syntax in .js files
(setq web-mode-content-types-alist
'(("jsx" . "\\.js[x]?\\'")))
(add-hook 'web-mode-hook
(lambda ()
(setq web-mode-code-indent-offset 2)
(when (string-equal "tsx" (file-name-extension buffer-file-name))
(setup-tide-mode))
(when (string-equal "jsx" (file-name-extension buffer-file-name))
(setup-tide-mode))
(when (string-equal "js" (file-name-extension buffer-file-name))
(progn
(setup-tide-mode)
(with-eval-after-load 'flycheck
(flycheck-add-mode 'typescript-tslint 'web-mode)
(flycheck-add-mode 'javascript-tide 'web-mode))))))))
#+END_SRC
** PlatformIO
#+BEGIN_SRC elisp
(require 'platformio-mode)
(add-to-list 'company-backends 'company-irony)
(add-hook 'c++-mode-hook (lambda ()
(irony-mode)
(irony-eldoc)
(platformio-conditionally-enable)))
(add-hook 'irony-mode-hook
(lambda ()
(define-key irony-mode-map [remap completion-at-point]
'irony-completion-at-point-async)
(define-key irony-mode-map [remap complete-symbol]
'irony-completion-at-point-async)
(irony-cdb-autosetup-compile-options)))
(add-hook 'flycheck-mode-hook 'flycheck-irony-setup)
#+END_SRC