Mi configuración de Emacs

Publicado el 20 Jun, 2022

¿Por qué uso Emacs?

Emacs es uno de los mejores editores de texto que conozco, aunque lo de editor de texto queda muy corto. Con Emacs puedes hacer prácticamente de todo lo que imagines. Emacs puede ser tu navegador web, tu IDE para programar, tu herramienta de control de tiempo, una aplicacion de To-Do, un reproductor de música, tu sistema operativo(sí, tu sistema operativo). Emacs es parte del proyecto GNU desarrollado en 1975 por Richard Stallman, en un principio fue un interprete de LISP, el cual, se ha ido nutriendo de un sin fin de nuevas caracteristicas a través de los años. Actualmente es considerado junto con VIM uno de los editores de texto más potentes que existen (aunque esto no significa sencillo de aprender).

Hace unos 5 años atrás comencé a usar Emacs como mi editor preferido para programar PHP en esos tiempos, pero no fue hasta que conocí Org-Mode donde me olvidé por completo de Sublime Text. Ahora uso Emacs para programar en Python y como mi herramienta GTD para organizar mi vida, proyectos y que haceres.

Es por eso que a continuación te muestro mi configuración por si te ánimas a probar el mejor editor que existe1.

Instalación

Emacs está desarrollado para sacar toda su potencia en sistemas operativos GNU/Linux, aunque existen versiones para sistemas Windows, MacOS y BSDs veamos como instalar en cada uno:

GNU/Linux

En el sistema del pingüino emacs se encuentra en los repositorios2 oficiales de cada distribución y lo único que debes tener en cuenta es la versión disponible en estos repositorios, por ejemplo para instalar en:

Archlinux

sudo pacman -S emacs

Debian y derivadas

sudo apt install emacs-gtk

Fedora

sudo dnf install emacs

Void Linux

sudo xbps-install emacs

BSDs

FreeBSD

pkg install emacs-devel

MacOS

Homebrew

brew cask install emacs

MacPorts

sudo port install emacs-app

Windows

En windows basta descargar el ejecutable .exe desde un GNU Mirror y seguir el proceso de instalación.

Configuración

Los archivos de configuración de emacs deben tener la extensión .el(también pueden ser .org) que es la extensión de archivos escritos en lenguaje de programación LISP y se ubican dentro de la carpeta .emacs.d, la cual, está oculta en tu directorio personal(fijate en el punto inicial). En windows esta carpeta se encuentra en la ruta C:\Users\username\AppData\Roaming(aunque se puede configurar tu HOME con variables de entorno). El archivo de configuración principal en emacs es init.el y mantengo separados otros archivos en una carpeta lisp. Ejemplo de mi estructura es como la siguiente:

.emacs.d (folder)
-- lisp (folder)
---- my-gui.el (setup gui emacs)
---- my-org.el (setup org-mode)
---- my-dev.el (setup desarrollo)
---- my-defun.el (funciones propias)
-- init.el (archivo principal setea folder config)
-- custom.el (archivo autogenerado)

init.el

Mi archivo init.el contiene el setup de repositorios para instalar aplicaciones en Emacs, repositorios como GNU ELPA, MELPA y Org-Mode:

;; init.el  -- Emacs configuration file  -*- lexical-binding: t -*-
;;; Code:
;; Adjust garbage collection thresholds during startup, and thereafter
(let ((normal-gc-cons-threshold (* 20 1024 1024))
      (init-gc-cons-threshold (* 128 1024 1024)))
  (setq gc-cons-threshold init-gc-cons-threshold)
  (add-hook 'emacs-startup-hook
     (lambda () (setq gc-cons-threshold normal-gc-cons-threshold))))

;; Custom file
(setq custom-file (concat user-emacs-directory "custom.el"))
(load custom-file :noerror)

;; Packages and Repositories
(require 'package)
(setq package-enable-at-startup nil)
(unless (assoc-default "melpa" package-archives)
  (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t))
(unless (assoc-default "nongnu" package-archives)
  (add-to-list 'package-archives '("nongnu" . "https://elpa.nongnu.org/nongnu/") t))
(unless (assoc-default "gnu" package-archives)
  (add-to-list 'package-archives '("gnu" . "https://elpa.gnu.org/packages/") t))

(add-to-list 'load-path "~/.emacs.d/lisp")
(unless (package-installed-p 'use-package)
  (package-install 'use-package))
(setq use-package-verbose t)
(setq use-package-always-ensure t)
(require 'use-package)
(use-package diminish)
(use-package bind-key)

;; No Backup
(setq make-backup-files nil)
(setq backup-by-copying nil)
(setq create-lockfiles nil)
(setq auto-save-default nil)

;; My lisp file
(require 'my-gui)
(require 'my-org)
(require 'my-defun)
(require 'my-dev)

(provide 'init)
;;; init.el ends here

custom.el

El archivo custom.el contiene codigo autogenerado por emacs cada vez que seteas un valor a alguna de las innumerables variables que puedes modificar dentro de emacs, yo mantengo esto separado para llevar un orden y que mi init.el se mantenga limpio.

lisp/my-gui.el

El archivo my-gui.el es donde seteo la configuración de la interfaz visual de emacs. Esto se refiere a configuración de letras, temas, modos, buffer, etc…

;; my-gui.el  -- Emacs GUI configuration  -*- lexical-binding: t -*-
;;; Code:
;; Emacs
(use-package emacs
  :ensure nil
  :config
  (set-language-environment "utf-8")
  (set-default-coding-systems 'utf-8)
  (prefer-coding-system 'utf-8)
  (global-visual-line-mode 1)
  (show-paren-mode 1)
  (global-hl-line-mode t)
  (global-linum-mode)
  (global-auto-revert-mode t)
  (size-indication-mode t)
  (setq user-full-name "Francisco Mora")
  (setq user-email-address "huasodev@tutanota.com")
  (setq calendar-week-start-day 1)
  (setq european-calendar-style t)
  (setq calendar-latitude -35.6970637)
  (setq calendar-longitude -71.4039066)
  (setq calendar-location-name "Colbún, Colbún")
  (setq initial-scratch-message "Happy Hacking!!!")
  (setq inhibit-startup-screen t)
  (setq ring-bell-function 'ignore)
  (setq confirm-kill-processes nil)
  (setq default-fill-column 80)
  (setq column-number-mode t)
  (setq use-short-answers t)
  (setq ring-bell-function 'ignore)
  (setq vc-follow-symlinks t)
  (setq large-file-warning-threshold nil)
  (setq display-time-24hr-format t)
  (setq display-time-format "%H:%M")
  (setq select-enable-clipboard t)
  (setq load-prefer-newer t)
  (setq require-final-newline t)
  (if (fboundp 'menu-bar-mode) (menu-bar-mode -1))
  (if (fboundp 'tool-bar-mode) (tool-bar-mode -1))
  (if (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))
  (when window-system (set-frame-size (selected-frame) 130 50))
  (set-face-attribute 'default nil :font "Jetbrains Mono-10"))

;; Ivy
(use-package ivy
  :config (ivy-mode t))
(use-package ivy-hydra)
(use-package ivy-rich
  :after ivy
  :config
  (ivy-rich-mode 1))

(use-package helpful
  :init
  (setq counsel-describe-function-function #'helpful-callable)
  (setq counsel-describe-variable-function #'helpful-variable))

;; Counsel
(use-package counsel
  :demand
  :bind (("C-c A" . swiper-all)
         ("C-c e" . counsel-flycheck)
         ("C-c f" . counsel-fzf)
         ("C-c g" . counsel-git)
         ("C-c i" . counsel-imenu)
         ("C-c j" . counsel-git-grep)
         ("C-c L" . counsel-locate)
         ("C-c o" . counsel-outline)
         ("C-c r" . counsel-rg)
         ("C-c R" . counsel-register)
         ("C-c T" . counsel-load-theme)
         ("C-S-s" . swiper)
         ([remap dired] . counsel-dired))
  :init
  (setq ivy-use-virtual-buffers t)
  :config
  (ivy-mode 1)
  (counsel-mode 1))

;; Prescient
(use-package prescient
  :config (prescient-persist-mode))
(use-package ivy-prescient
  :config (ivy-prescient-mode t))

;; Swiper
(use-package swiper
  :bind (("M-s" . counsel-grep-or-swiper)))

;; Auto Package update
(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))

;; Which-key
(use-package which-key
  :diminish
  :config (which-key-mode)
          (which-key-setup-side-window-bottom)
          (setq which-key-idle-delay 0.05))

;; Company
(use-package company
  :bind (:map prog-mode-map
         ("C-i" . company-indent-or-complete-common)
         ("C-M-i" . counsel-company))
  :hook (emacs-lisp-mode . company-mode))

(use-package company-prescient
  :after company
  :config
  (company-prescient-mode))

;; all the icons
(use-package all-the-icons
  :defer)
(use-package all-the-icons-dired
  :hook (dired-mode . all-the-icons-dired-mode))
(use-package all-the-icons-ivy-rich
  :after ivy-rich
  :config (all-the-icons-ivy-rich-mode 1))

;; Emojify
(use-package emojify
  :hook (erc-mode . emojify-mode)
  :commands emojify-mode)

;; Smart mode line
(use-package smart-mode-line
  :config
  (setq sml/no-confirm-load-theme t
        sml/theme 'respectful)
  (sml/setup))

;; mixed-pitch
(use-package mixed-pitch
  :hook (text-mode . mixed-pitch-mode)
  :config
  (set-face-attribute 'variable-pitch nil :family "Fira Code" :height 1.2)
  (set-face-attribute 'fixed-pitch nil :family "Jetbrains Mono" :height 1.0))

;; Ibuffer
(use-package ibuffer
  :config
  (global-set-key (kbd "C-x C-b") #'ibuffer))

;; whitespaces
(use-package whitespace
  :defer 1
  :hook (before-save . delete-trailing-whitespace))

(use-package hungry-delete
  :defer 0.7
  :delight
  :config (global-hungry-delete-mode))

;; Parenthesis
(use-package rainbow-delimiters
  :defer 1
  :hook (prog-mode . rainbow-delimiters-mode))

(use-package smartparens
  :defer 1
  :delight
  :custom (sp-escape-quotes-after-insert nil)
  :config (smartparens-global-mode 1))

;; Theme modus-themes
(use-package modus-themes
  :init (modus-themes-load-themes)
  :bind ("<f5>" . modus-themes-toggle)
  :config (modus-themes-load-vivendi))

(provide 'my-gui)
;;; my-gui.el ends here

lisp/my-dev.el

este archivo contiene mi configuración de emacs para desarrollo, principalmente Python y Javascript, Markdown, también uso Magit para trabajar con git sin salir del editor.

;; my-defun.el  -- Emacs develop configuration  -*- lexical-binding: t -*-
;;; Code:
;; Markdown
(use-package markdown-mode
  :hook ((markdown-mode . turn-on-visual-line-mode))
  :mode (("README\\.md\\'" . gfm-mode)
    ("\\.md\\'" . markdown-mode)
    ("\\.markdown\\'" . markdown-mode))
  :init (setq markdown-command "/usr/local/bin/pandoc"))

;; Magit
(use-package magit
  :bind ("C-x g" . magit-status))

;; Git
(use-package git-timemachine)
(use-package ssh-agency)
(use-package git-gutter
  :config (global-git-gutter-mode 't))

;; Javascript
(use-package js2-mode
  :mode ("\\.js\\'" . js2-mode)
  :config (setq-default js2-ignored-warnings '("msg.extra.trailing.comma")))

(use-package rjsx-mode)

;; Docker
(use-package dockerfile-mode
  :mode ("Dockerfile\\'" . dockerfile-mode))

;; JSON
(use-package json-mode)

(use-package json-reformat
  :after json-mode
  :init (setq json-reformat:indent-width 2))

;; YAML
(use-package yaml-mode
  :mode (("\\.yml\\'" . yaml-mode)))

;; Python
(use-package pyvenv)

;; Code formatting
(use-package hindent)

;; Emmet
(use-package emmet-mode
  :bind (("C-c C-e" . emmet-expand-yas ))
  :hook ((sgml-mode . emmet-mode)
    (html-mode . emmet-mode)
    (css-mode . emmet-mode)))

;; Web Mode
(use-package web-mode
  :config
  (setq web-mode-markup-indent-offset 2
 web-mode-css-indent-offset 2
 web-mode-code-indent-offset 2))
  (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))

(provide 'my-dev)
;;; my-dev.el ends here

lisp/my-org.el

Este es mi santo grial, aquí seteo mi flujo Org-Mode y GTD, la cual, me permite mantenerme organizado(en otro post explicaré esto).

;; my-org.el  -- Emacs ORG configuration  -*- lexical-binding: t -*-
;;; Code:
;; ORG
(use-package org
  :bind (("C-c l" . org-store-link)
  ("C-c a" . org-agenda)
  ("C-c c" . org-capture))
  :hook (org-mode . org-indent-mode)
  :config
  (setq org-hide-emphasis-markers t)
  (setq org-icalendar-timezone "Chile/Continental")
  (setq org-confirm-babel-evaluate nil)
  (setq org-src-fontify-natively t)
  (setq org-src-tab-acts-natively t)
  (setq org-startup-indented t)
  (setq org-pretty-entities t)
  (setq org-startup-with-inline-images t)
  (setq org-image-actual-width '(300))
  (setq org-enforce-todo-dependencies t)
  (setq org-track-ordered-property-with-tag t)
  (setq org-export-backends '(ascii html latex md org texinfo))
  (setq org-tags-column 80)
  (setq org-log-done 'time)
  (setq org-use-fast-todo-selection t)
  (setq org-refile-use-outline-path t)
  (setq org-log-into-drawer "LOGBOOK")
  (setq org-tags-exclude-from-inheritance '("project"))
  (setq org-archive-location "~/GTD/archive/%s_archive.org::datetree/")
  (setq org-refile-targets '(("~/GTD/life.org" :maxlevel . 3)
        ("~/GTD/tickler.org" :maxlevel . 1)
        ("~/GTD/someday.org" :maxlevel . 2)))
  (setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "WAITING(w)" "|" "DONE(d)" "CANCELLED(c)")))
  (setq org-todo-keyword-faces '(("TODO" :foreground "#FF0000" :weight bold)
     ("NEXT" :foreground "#FFFF00" :weight bold)
     ("WAITING" :foreground "#E0FFFF" :weight bold)
     ("DONE" :foreground "#00FF00" :weight bold)
     ("CANCELLED" :foreground "#C0C0C0" :weight bold)))
  (setq org-tag-alist '((:startgroup . "Software")
   ("@jira" . ?j)("@emacs" . ?e)("@browser" . ?b)("@ide" . ?i)
   ("@audacity" . ?a)("@gimp" . ?g)
   (:endgroup . "Software")
   (:startgroup . "Hardware")
   ("@phone" . ?p)("@kindle" . ?k)
   (:endgroup . "Hardware")))
  (setq org-tag-faces '(("@jira" :foreground "#6495ED" :weight bold)
   ("@emacs" :foreground "#20B2AA" :weight bold)
   ("@browser" :foreground "#9ACD32" :weight bold)
   ("@ide" :foreground "#E6E6FA" :weight bold)
   ("@audacity" :foreground "#FFE4B5" :weight bold)
   ("@gimp" :foreground "#FFA500" :weight bold)
   ("@phone" :foreground "#FFEFD5" :weight bold)
   ("@kindle" :foreground "#BDB76B" :weight bold))))
;; Agenda
(use-package org-agenda
  :ensure nil
  :after org
  :config
  (setq org-agenda-dim-blocked-tasks nil)
  (setq org-agenda-compact-blocks t)
  (setq org-agenda-include-diary nil)
  (setq org-agenda-inhibit-startup t)
  (setq org-agenda-show-log t)
  (setq org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled)
  (setq org-agenda-span 1)
  (setq org-agenda-start-on-weekday 1)
  (setq org-deadline-warning-days 7)
  (setq org-agenda-sticky nil)
  (setq org-agenda-window-setup 'current-window)
  (setq org-agenda-tags-column -102)
  (setq org-agenda-time-grid '((daily today require-timed)))
  (setq org-agenda-use-tag-inheritance t)
  (setq org-agenda-persistent-filter t)
  (setq org-agenda-files '("~/GTD/inbox.org" "~/GTD/life.org" "~/GTD/tickler.org")))

;; Capture
(use-package org-capture
  :ensure nil
  :after org
  :config
  (setq org-capture-templates '(("i" "TODO [INBOX]" entry (file+headline "~/GTD/inbox.org" "INBOX") "** TODO %i%?")
    ("t" "Tickler" entry (file+headline "~/GTD/tickler.org" "TICKLER") "** %i%? \n %U")
    ("m" "Meeting" entry (file+headline "~/GTD/life.org" "MEETINGS") "** %i%? \n %U"))))

;; Bullets
(use-package org-superstar
  :after org
  :hook (org-mode . org-superstar-mode)
  :config)

;; TOC
(use-package toc-org
  :after org
  :hook (org-mode . toc-org-enable))

(provide 'my-org)
;;; my-org.el ends here

lisp/my-defun.el

Este archivo contiene funciones propias para setear principalmente comandos para acceso directo a mis ficheros .org:

;; my-defun.el  -- Emacs defun configuration  -*- lexical-binding: t -*-
;;; Code:
;; GTD file
(defun gtd()
  (interactive)
  (find-file "~/GTD/life.org"))

;; Inbox file
(defun inbox()
  (interactive)
  (find-file "~/GTD/inbox.org"))

;; Someday file
(defun someday()
  (interactive)
  (find-file "~/GTD/someday.org"))

;; Tickler file
(defun tickler()
  (interactive)
  (find-file "~/GTD/tickler.org"))

(provide 'my-defun)
;;; my-defun.el ends here

Pues hasta aquí este post con mi configuración de Emacs, si te ha servido favor compartelo y si tienes alguna duda puedes contactarme en mis redes sociales y con gusto te ayudo.

Hasta la próxima..


  1. Esta es una opinión personal y puede que no estes de acuerdo conmigo. ↩︎

  2. Siempre existe la opción de compilar desde los archivos fuentes, pero no es recomendable para alguien que no tiene experiencia en este proceso. ↩︎