Mi configuración de emacs

Tabla de contenido

Acerca de este documento

Este documento está hecho en org-mode y es tanto mi configuración de emacs como la explicación de esta.

La idea es que termine siendo más o menos un tutorial de emacs, extendido con los articulos del blog.

Uso doom, que es una configuración “opinionada” de emacs. Es similar a spacemacs, pero mucho más pequeño, modulado, insipirado a vim y facilita el que tu le añadas tus propios módulos. Se puede ver más en este articulo.

Para usar esta configuración tal cual sólo hay que clonar el repositorio en ~/.doom.d/config.org y seguir las instrucciones habituales de doom.

Configurar el init.el

Configurar el bind léxico

La verdad es que no termino de entender que es ni por que es bueno usarlo. En la wiki de emacs lo explican muy bien, pero doy para lo que doy.

;;; init.el -*- lexical-binding: t; -*-

Hacer más usable ivy

FLX ordena los resultados de M-x de acorde al último usado.

(use-package-hook! flx
  :pre-init
  (setq ivy-re-builders-alist
        '((counsel-ag . ivy--regex-plus)
          (counsel-rg . ivy--regex-plus)
          (counsel-pt . ivy--regex-plus)
          (counsel-grep-or-swiper . ivy--regex-plus)
          (read-file-name-internal . ivy--regex-fuzzy)
          (t . ivy--regex-plus))
        ivy-initial-inputs-alist nil))

Hacer más usable evil

(use-package-hook! evil
  :pre-init
  (setq evil-cross-lines t)
  (setq evil-move-cursor-back nil)
  (setq evil-want-fine-undo t))

Configuración principal de doom

Aquí es donde se define toda la configuración que no se modificará en este documento. Se puede ver información de estos módulos en la documentación de doom.

(doom! :completion
       (company +childframe )
       (ivy +fuzzy +icons)

       :tools
       (magit +forge)
       pass
       pdf
       make
       ansible
       (docker +lsp)
       (terraform +lsp)
       (lsp +peek)
       eval
       debugger
       (lookup +docsets +dictionary)
       (eval +overlay)

       :checkers
       (syntax +childframe)
       grammar

       :emacs
       (dired +icons)
       electric
       vc
       (undo +tree)

       :term
       vterm
       eshell

       :lang
       data
       emacs-lisp
       java
       markdown
       php
       ruby
       (javascript +lsp)
       (web +lsp )
       (sh +lsp)
       (go +lsp)
       (python +lsp)
       (org +brain +pretty +hugo)
       (java +lsp)
       (json +lsp)
       (yaml +lsp)

       :app
       calendar

       :editor
       (evil +everywhere)
       multiple-cursors
       snippets
       file-templates
       (format +onsave)
       fold
       word-wrap

       :config
       literate
       (default +bindings +smartparens)

       :ui
       doom
       doom-dashboard
       modeline
       doom-quit
       (popup +all +defaults +hacks)
       hl-todo
       nav-flash
       unicode
       window-select
       vc-gutter
       workspaces
       ophints
       fill-column
       )

Configuración Genérica

Seguridad de las conexiones

(setq network-security-level 'high)

Moverse por el buffer

Subir y bajar párrafos:

(map! :ne "M-p" 'backward-paragraph
      :ne "M-n" 'forward-paragraph)

Establecer nombre y correo

Al exportar en org-mode, por ejemplo, coge estos valores.

(setq user-full-name "drymer"
      user-mail-address "drymer [ EN ] autistici.org")

Lenguaje por defecto en emacs y org-mode

(setq current-language-environment "Spanish")
(setq org-export-default-language "es")

No seguir enlaces simbólicos

(setq vc-follow-symlinks nil)

Terminal

Establecer zsh como shell por defecto, aunque no lo uso demasiado.

(setq explicit-shell-file-name "/bin/zsh")
(setq shell-file-name explicit-shell-file-name)
(setenv "SHELL" shell-file-name)
(add-hook 'comint-output-filter-functions 'comint-strip-ctrl-m)

kill-this-buffer en vez de kill-buffer

No quiero que me pregunte si de verdad quiero matar un buffer, quiero que lo mate y punto.

(map! :ne "C-x k" 'kill-this-buffer)

Fuente: http://pragmaticemacs.com/emacs/dont-kill-buffer-kill-this-buffer-instead/

Ispell

Instalamos el diccionario de esta dirección y configuramos emacs para que lo use:

(setq ispell-dictionary "espanol")

Tipo de fuente

(setq doom-font (font-spec :family "DejaVu Sans Mono" :height 107))

Tema

(setq doom-theme 'doom-molokai)

Kill-ring-max

(setq kill-ring-max 500)

Fuente: http://puntoblogspot.blogspot.com/2018/10/kill-ring-max-is-thing.html?m=1

url accionables

(use-package goto-addr
  :hook ((compilation-mode . goto-address-mode)
         (prog-mode . goto-address-prog-mode)
         (eshell-mode . goto-address-mode)
         (shell-mode . goto-address-mode))
  :bind (:map goto-address-highlight-keymap
         ("<RET>" . goto-address-at-point)
         ("M-<RET>" . newline))
  :commands (goto-address-prog-mode
             goto-address-mode))

org-mode

org-mode merece tener la configuración a parte. Ahí va.

Agenda

Definir archivos de la agenda

(setq org-agenda-files '("~/Documentos/org/inbox.org" "~/Documentos/org/trabajo.org" "~/Documentos/org/index.org" "~/Documentos/org/casa.org"))

Comandos de Agenda

(after! org-super-agenda
  (setq org-agenda-custom-commands
        '(
          ("a" "Agenda" agenda ""
           ((org-super-agenda-groups
             '((:log t)  ; Automatically named "Log"
               (:name "Schedule"
                :time-grid t)
               (:name "Today"
                :scheduled today)
               (:habit t)
               (:name "Due today"
                :deadline today)
               (:name "Overdue"
                :deadline past)
               (:name "Due soon"
                :deadline future)
               (:name "Blocked..."
                :todo "BLOCKED"
                :order 98)
               (:name "Unimportant"
                :todo ("TOFOLLOW" "MAYBE" "CHECK" "TO-READ" "TO-WATCH")
                :order 100)
               (:name "Scheduled earlier"
                :scheduled past)))))
          ("g" . "General")
          ("gh" "Hecho hoy" agenda ""
           ((org-agenda-overriding-header "Done today")
            (org-agenda-span 1)
            (org-agenda-start-with-log-mode t)
            (org-agenda-log-mode-items '(clock closed))
            (org-agenda-archives-mode t)
            (org-agenda-show-log 'clockcheck)))
          ;; Casa
          ("c" . "Casa")
          ("ca" "Tareas del sprint actual o inmediatas"
           tags
           (concat "+personal+" daemons/active-sprint)
           ((org-agenda-remove-tags nil)
            (org-agenda-hide-tags-regexp "personal")
            (org-agenda-files '("~/Documentos/org/index.org"))))
          ;; Tareas por empezar sin asignar a un sprint
          ("ce" "Tareas sin asignar al sprint ni horario" tags
           (concat "-SCHEDULED={.}/!+personal-" daemons/active-sprint
                   "/INPROGRESS|TOFOLLOW|BLOCKED|TODO|NEXT|EVENT")
           ((org-agenda-remove-tags nil)
            (org-agenda-hide-tags-regexp "personal")
            (org-agenda-files '("~/Documentos/org/index.org"))))
          ("cf" "Tareas con fecha" tags "+personal+SCHEDULED={.}/!"
           ((org-agenda-remove-tags nil)
            (org-agenda-hide-tags-regexp "personal")
            (org-agenda-files '("~/Documentos/org/index.org"))))
          ("cs" "Tareas sin fecha del sprint" tags (concat "+personal+"
                                                           daemons/active-sprint
                                                           "-scrum")
           ((org-agenda-remove-tags nil)
            (org-agenda-hide-tags-regexp "personal")
            (org-agenda-skip-function (quote (org-agenda-skip-entry-if (quote scheduled))))
            (org-agenda-files '("~/Documentos/org/index.org"))))
          ("cn" "Tareas de la semana siguiente" agenda ""
           ((org-agenda-remove-tags nil)
            (org-agenda-hide-tags-regexp "personal")
            (org-agenda-span 7)
            (org-agenda-start-day "+7d")
            (org-agenda-files '("~/Documentos/org/index.org"))))
          ("cr" "Mostras radar" tags "+radar"
           ((org-agenda-files '("~/Documentos/brain/index.org"))
            (org-agenda-remove-tags nil)
            (org-agenda-hide-tags-regexp "radar")))
          ;; Reportes del trabajo
          ("w" . "Trabajo")
          ("wp" "Ver proyectos" tags "+trabajo/INPROGRESS|TOFOLLOW|BLOCKED|TODO|NEXT|EVENT|DONE|CANCELLED"
           ((org-super-agenda-mode)
            (org-agenda-remove-tags nil)
            (org-agenda-hide-tags-regexp "trabajo")
            (org-super-agenda-groups org-agenda-supergroup-projects)))
          ("wt" "Hecho en los últimos tres días" agenda ""
           ((org-agenda-overriding-header "Last three days")
            (org-super-agenda-mode)
            (org-super-agenda-groups org-agenda-supergroup-tags-log)
            (org-agenda-span 4)
            (org-agenda-start-day "-3d")
            (org-agenda-start-with-log-mode t)
            (org-agenda-log-mode-items '(clock closed))
            (org-agenda-clockreport-mode t)
            (org-agenda-archives-mode t)
            (org-agenda-show-log 'clockcheck)))
          ("wi" "Hecho en la última semana" agenda ""
           ((org-super-agenda-mode)
            (org-super-agenda-groups org-agenda-supergroup-tags-log)
            (org-agenda-span 7)
            (org-agenda-start-day "-7d")
            (org-agenda-start-with-log-mode t)
            (org-agenda-log-mode-items '(clock closed))
            (org-agenda-archives-mode t)
            (org-agenda-show-log 'clockcheck)))
          ("wa" "Tareas activas o posiblemente activas" tags-todo
           "+trabajo/TODO|INPROGRESS|TESTING|HOTFIX"
           ((org-super-agenda-mode)
            (org-super-agenda-groups org-agenda-supergroup-tags-active)))
          ("wb" "Tareas paradas" tags-todo "+trabajo/BLOCKED|TOFOLLOW"
           ((org-super-agenda-mode)
            (org-super-agenda-groups org-agenda-supergroup-tags-blocked)))
          ("h" "Habitos" agenda ""
           ((org-super-agenda-groups
             '((:name "Diario"
                :tag "diario")
               (:name "Semanal"
                :tag "semanal")
               (:name "Mensual"
                :tag "mensual")
               (:name "Trimestral"
                :tag "trimestral")))
            (org-agenda-files '("~/Documentos/org/habitos.org"))
            (org-habit-show-habits t)
            (org-agenda-span 1)
            (org-habit-preceding-days 5)
            (org-habit-following-days 5)
            (org-agenda-overriding-header "Habitos")))
          ("l" . "Archivo")
          ("la" "Buscar archivado" search ""
           ((org-agenda-files (file-expand-wildcards "~/Documentos/org/*.org_archive"))))
          ;; Mostrar tareas a reubicar
          ("r" "Reubicar" tags "+refile" ((org-agenda-remove-tags nil) (org-agenda-hide-tags-regexp "refile"))))))
(after! org-super-agenda
(setq org-agenda-custom-commands
      '(;; Casa
        ("g" . "General")
        ("ch" "Hecho hoy" agenda ""
         ((org-agenda-overriding-header "Done today")
          (org-agenda-span 1)
          (org-agenda-start-with-log-mode t)
          (org-agenda-log-mode-items '(clock closed))
          (org-agenda-archives-mode t)
          (org-agenda-show-log 'clockcheck)))
        ("c" . "Casa")
        ;; Reportes del trabajo
        ("w" . "Trabajo")
        ("wt" "Hecho en los últimos tres días" agenda ""
         ((org-agenda-overriding-header "Last three days")
          (org-super-agenda-mode)
          (org-super-agenda-groups org-agenda-supergroup-tags-log)
          (org-agenda-span 4)
          (org-agenda-start-day "-3d")
          (org-agenda-start-with-log-mode t)
          (org-agenda-log-mode-items '(clock closed))
          (org-agenda-clockreport-mode t)
          (org-agenda-archives-mode t)
          (org-agenda-show-log 'clockcheck)))
        ("wi" "Hecho en la última semana" agenda ""
         ((org-super-agenda-mode)
          (org-super-agenda-groups org-agenda-supergroup-tags-log)
          (org-agenda-span 7)
          (org-agenda-start-day "-7d")
          (org-agenda-start-with-log-mode t)
          (org-agenda-log-mode-items '(clock closed))
          (org-agenda-archives-mode t)
          (org-agenda-show-log 'clockcheck)))
        ("wn" "Siguientes tareas" tags-todo "+trabajo/NEXT"
         ((org-super-agenda-mode)
          (org-super-agenda-groups org-agenda-supergroup-tags-next)))
        ("wa" "Tareas activas o posiblemente activas" tags-todo
         "+trabajo/TODO|INPROGRESS|TESTING|HOTFIX"
         ((org-super-agenda-mode)
          (org-super-agenda-groups org-agenda-supergroup-tags-active)))
        ("wb" "Tareas paradas" tags-todo "+trabajo/BLOCKED|TOFOLLOW"
         ((org-super-agenda-mode)
          (org-super-agenda-groups org-agenda-supergroup-tags-blocked)))
        ("wt" "Tareas por hacer" tags "+trabajo/TODO"
         ((org-super-agenda-mode)
          (org-super-agenda-groups org-agenda-supergroup-tags-todo)))
        ("wd" "Tareas terminadas sin archivar" tags "+trabajo/DONE|CANCELLED"
         ((org-super-agenda-mode)
          (org-super-agenda-groups org-agenda-supergroup-tags-done)))
        ;; Mostrar tareas a reubicar
        ("r" "Reubicar" tags "+refile" ((org-agenda-remove-tags nil) (org-agenda-hide-tags-regexp "refile")))))
)

Referencias:

Capturas de notas

(setq org-capture-templates
      '(
        ("r" "Reuniones" entry (file "~/Documentos/org/inbox.org")
         (file "~/.doom.d/org-capture-templates/reuniones.org")
         :clock-in t :clock-resume t)
        ;; Para reuniones de seguimiento, tener el arbol partido en días
        ("rs" "Reuniones semanales" entry (file "~/Documentos/org/inbox.org")
         (file "~/.doom.d/org-capture-templates/reuniones-periodicas.org")
         :clock-in t :clock-resume t)
        ;; Para cuando voy a comer en el trabajo
        ("c" "Comida" entry
         (file+olp "~/Documentos/org/trabajo.org" "Meta" "Recurrentes" "Comidas")
         "* Comida %(emacswiki/insert-current-date) "
         :clock-in t :clock-resume t)
        ;; Meter fecha debajo de un header principal para no tener chorrocientos "Dailies"
        ("d" "Daily" entry (file+olp "~/Documentos/org/trabajo.org" "Meta" "Recurrentes" "Dailies")
         "* Daily %(emacswiki/insert-current-date)" :clock-in t :clock-resume t)
        ("a" "Atraco" entry (file "~/Documentos/org/inbox.org")
         "* %?\n" :clock-in t :clock-resume t)
        ("tr" "Tarea con reloj" entry (file "~/Documentos/org/inbox.org")
         "* %? %^G\n%U\n" :clock-in t :clock-resume t)
        ("tn" "Tarea simple" entry (file "~/Documentos/org/inbox.org")
         "* %? %^G\n%U\n")
        ;; Revisiones
        ("rsp" "Revisión Semanal Personal" entry (file+olp+datetree "~/Documentos/org/log.org" "Revisión Semanal")
         (file "~/.doom.d/org-capture-templates/personal-weekly-review.org"))
        ("rst" "Revisión Semanal Trabajo" entry (file+headline "~/Documentos/org/trabajo.org" "Revisión Semanal")
         (file "~/.doom.d/org-capture-templates/work-weekly-review.org"))
        ("sn" "Sprint Nuevo Trabajo" entry (file "~/Documentos/org/trabajo.org")
         (file "~/.doom.d/org-capture-templates/new-sprint.org"))
        ("sp" "Sprint Nuevo Personal" entry (file "~/Documentos/org/scrum.org")
         (file "~/.doom.d/org-capture-templates/new-personal-sprint.org"))
        ("e" "Estado de la vida" entry (file "~/Documentos/org/index.org")
         (file "~/.doom.d/org-capture-templates/estado-de-la-vida.org"))
        ("m" "Captura desde correo" entry (file "~/Documentos/org/inbox.org") "* %? %^G\n%U\n%a\n" :clock-in t :clock-resume t)
        ("log" "Log" entry (file+olp+datetree "~/Documentos/org/log.org" "Log")
         (file "~/.doom.d/org-capture-templates/log.org") :time-prompt t)
        ("L" "Protocol Link" entry (file+headline ,(concat org-directory "notes.org") "Inbox")
         "* %? [[%:link][%:description]] \nCaptured On: %U")    ("p" "Protocol" entry (file+headline "~/Documentos/org/inbox.org" "Links")
         "* %^{Title}\nSource: %u, %c\n #+BEGIN_QUOTE\n%i\n#+END_QUOTE\n\n\n%?")
        ("b" "Articulo" entry (file "~/Proyectos/BadDaemons/content-org/articulos.org")
         "* TODO %(read-string \"Insert title: \")\n:PROPERTIES:\n:EXPORT_FILE_NAME: %(org-hugo-slug (nth 4 (org-heading-components)))\n:END:")))

Referencias:

Capturar interrupciones rapidamente:

(map! :ne "<f12>" (lambda () (interactive) (org-capture nil "a")))

Estados de los objetos de las listas

Todas las secuencias anteriores al símbolo | son tareas que no se consideran terminadas, al contrario de las que estan después de este.

Los estados que tienen el símbolo @ son los que, al escogerlos, abren un buffer preguntando si se quiere añadir alguna nota respecto al cambio de estado. Las que tienen el símbolo !, en cambio, crean una estampa de tiempo, para dejar constancia de cuando se ha cambiado a ese estado.

(setq org-todo-keywords
      '((sequence "TODO(t)" "NEXT(n)" "INPROGRESS(p@/!)" "WAITING(w@/!)" "|" "DONE(d!)" "CANCELED(c@)")))

Refile

Mover un arbol debajo de otro del mismo fichero o de los archivos de las agendas.

(setq org-refile-targets '((nil :maxlevel . 10) (org-agenda-files . (:maxlevel . 10))))

Crear nodo si no existe:

;; Source: https://blog.aaronbieber.com/2017/03/19/organizing-notes-with-refile.html
(setq org-refile-use-outline-path 'file)
(setq org-outline-path-complete-in-steps nil)
(setq org-refile-allow-creating-parent-nodes t)

Configuración del calendario

(setq org-icalendar-timezone "Europe/Madrid")

Tareas repetitivas

Las tareas marcadas para repetirse, al marcarlas cómo DONE vuelven al estado TODO y añade un timestamp del dia y la hora.

(setq org-log-repeat "time")

Quitar tags de la agenda

(setq org-agenda-remove-tags t)

Alargar el historial del reloj

(setq org-clock-history-length 60)

Mostrar los clockin en la agenda

(setq org-agenda-clock-consistency-checks t)

Añadir timestamp al terminar tarea

(setq org-log-done 'time)

Archivado

(setq org-archive-mark-done t)

org-super-agenda

(package! org-super-agenda)
(use-package! org-super-agenda :config (org-super-agenda-mode))

punch-in y punch-out

Estas funciones tan útiles las he cogido de eof. Las funciones principales son eos/punch-in y eos/punch-out. La primera va a una tarea genérica que tengo en el fichero principal del trabajo y empieza el reloj. Después de hacer eso, cuando quiera, podré arrancar el reloj en la tarea que considere. Cuando acabe esta y pare el reloj, gracias a la función de punch-in, en vez de quedarme sin contabilizar el tiempo, el reloj se encenderá en la tarea genérica. Cuando termino, le doy a punch-out y se para el reloj definitivamente.

Esto es muy cómodo por que me permite contabilizar todo el tiempo que estoy delante del ordenador trabajando.

(setq bh/keep-clock-running nil)
(setq bh/organization-task-id nil)
(defun bh/find-project-task ()
  "Move point to the parent (project) task if any"
  (save-restriction
    (widen)
    (let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point))))
      (while (org-up-heading-safe)
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq parent-task (point))))
      (goto-char parent-task)
      parent-task)))

(defun bh/punch-in (arg)
  "Start continuous clocking and set the default task to the
selected task.  If no task is selected set the Organization task
as the default task."
  (interactive "p")
  (setq bh/keep-clock-running t)
  (if (equal major-mode 'org-agenda-mode)
      ;;
      ;; We're in the agenda
      ;;
      (let* ((marker (org-get-at-bol 'org-hd-marker))
             (tags (org-with-point-at marker (org-get-tags-at))))
        (if (and (eq arg 4) tags)
            (org-agenda-clock-in '(16))
          (bh/clock-in-organization-task-as-default)))
    ;;
    ;; We are not in the agenda
    ;;
    (save-restriction
      (widen)
                                        ; Find the tags on the current task
      (if (and (equal major-mode 'org-mode) (not (org-before-first-heading-p)) (eq arg 4))
          (org-clock-in '(16))
        (bh/clock-in-organization-task-as-default)))))

(defun bh/punch-out ()
  (interactive)
  (setq bh/keep-clock-running nil)
  (when (org-clock-is-active)
    (org-clock-out))
  (org-agenda-remove-restriction-lock))

(defun bh/clock-in-default-task ()
  (save-excursion
    (org-with-point-at org-clock-default-task
      (org-clock-in))))

(defun bh/clock-in-parent-task ()
  "Move point to the parent (project) task if any and clock in"
  (let ((parent-task))
    (save-excursion
      (save-restriction
        (widen)
        (while (and (not parent-task) (org-up-heading-safe))
          (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
            (setq parent-task (point))))
        (if parent-task
            (org-with-point-at parent-task
              (org-clock-in))
          (when bh/keep-clock-running
            (bh/clock-in-default-task)))))))

(defun bh/clock-in-organization-task-as-default ()
  (interactive)
  (org-with-point-at (org-id-find bh/organization-task-id 'marker)
    (org-clock-in '(16))))

(defun bh/clock-out-maybe ()
  (when (and bh/keep-clock-running
             (not org-clock-clocking-in)
             (marker-buffer org-clock-default-task)
             (not org-clock-resolving-clocks-due-to-idleness))
    (bh/clock-in-parent-task)))

(add-hook 'org-clock-out-hook 'bh/clock-out-maybe 'append)
(setq org-clock-out-remove-zero-time-clocks t)
(setq org-clock-out-when-done t)
(setq org-clock-perist t)
(setq org-clock-report-include-clocking-task t)
(setq org-clone-delete-id t)

                                        ; Exclude DONE state tasks from refile targets
(defun bh/verify-refile-target ()
  "Exclude todo keywords with a done state from refile targets"
  (not (member (nth 2 (org-heading-components)) org-done-keywords)))
(setq org-refile-target-verify-function 'bh/verify-refile-target)

;; Change tasks to INPROGRESS when clocking in
(setq org-clock-in-switch-to-state 'bh/clock-in-to-progress)

(defun bh/clock-in-to-progress (kw)
  "Based in bh/clock-in-to-next: Switch a task from
TODO to INPROGRESS when clocking in. Skips capture tasks,
projects, and subprojects. Switch projects and
subprojects from INPROGRESS back to TODO"
  (when (not (and (boundp 'org-capture-mode) org-capture-mode))
    (cond
     ((and (member (org-get-todo-state) (list "TODO"))
           (bh/is-task-p))
      "INPROGRESS")
     ((and (member (org-get-todo-state) (list "INPROGRESS"))
           (bh/is-project-p))
      "TODO"))))

;; Usado por bh/clock-in-to-progress
(defun bh/is-task-p ()
  "Any task with a todo keyword and no subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task (not has-subtask)))))

;; Usado por bh/clock-in-to-progress
(defun bh/is-subproject-p ()
  "Any task which is a subtask of another project"
  (let ((is-subproject)
        (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
    (save-excursion
      (while (and (not is-subproject) (org-up-heading-safe))
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq is-subproject t))))))

;; Usado por bh/clock-in-to-progress
(defun bh/is-project-p ()
  "Any task with a todo keyword subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task has-subtask))))

Desactivar festivos

(setq calendar-holidays nil)

org-clock-convenience

(package! org-clock-convenience)
(use-package! org-clock-convenience
  :after org
  :defer
  :config
  (defun dfeich/org-agenda-mode-fn ()
    (define-key org-agenda-mode-map
      (kbd "<S-up>") #'org-clock-convenience-timestamp-up)
    (define-key org-agenda-mode-map
      (kbd "<S-down>") #'org-clock-convenience-timestamp-down)
    (define-key org-agenda-mode-map
      (kbd "ö") #'org-clock-convenience-fill-gap)
    (define-key org-agenda-mode-map
      (kbd "é") #'org-clock-convenience-fill-gap-both))
  (add-hook 'org-agenda-mode-hook #'dfeich/org-agenda-mode-fn))

Edición, diseño y varios

Mostrar horas en vez dias

(setq org-duration-format 'h:mm)

Mostrar popups de org-mode más grandes

(set-popup-rule! "agenda" :size 0.50 :select t)

org-mru-clock

(package! org-mru-clock)
(require 'org-timer)
(require 'org-clock)
(use-package! org-mru-clock
  :config
  (setq org-clock-persist 'history)
  (setq org-mru-clock-how-many 200)
  (setq org-mru-clock-completing-read #'ivy-completing-read)
  (setq org-clock-persist t)
  (setq  org-mru-clock-include-entry-at-point nil)
  (org-clock-persistence-insinuate)
  (map!
   (:localleader
    :mode org-mode
    (:desc "clock stuff"
     :prefix "c"
     :desc "Clock in previous" :nv "c" #'org-mru-clock-in
     :desc "Clock select recent" :nv "j" #'org-mru-clock-select-recent-task))))

Mapear org-cycle

(after! evil-org
  (remove-hook 'org-tab-first-hook #'+org-cycle-only-current-subtree-h))

Mostrar solo las cabeceras

(setq org-startup-folded t)

Asignar keybinds

(after! org
  (map! [remap outline-toggle-children] #'org-cycle)
  (map! (:leader
         (:desc "brain stuff"
          :prefix "k"
          :desc "Brain refile" :n "r" #'org-brain-refile
          :desc "Brain visualize" :n "v" #'org-brain-visualize
          :desc "Brain cliplink resource" :n "l" #'org-brain-cliplink-resource
          :desc "Brain goto" :n "g" #'org-brain-goto
          :desc "Brain add friendship" :n "f" #'org-brain-add-friendship
          :desc "Brain add parent" :n "p" #'org-brain-add-parent))
        (:leader
         (:desc "link stuff"
          :prefix "l"
          :desc "Insert link" :nv "i" #'org-insert-link
          :desc "Store link" :nv "l" #'org-store-link
          :desc "Go to link" :nv "o" #'counsel-ace-link
          :desc "Cliplink" :nv "L" #'org-cliplink))
        (:localleader
         :mode org-mode
         (:desc "clock stuff"
          :prefix "c"
          :desc "Clock in" :nv "i" #'org-clock-in
          :desc "Clock out" :nv "o" #'org-clock-out
          :desc "Clock goto" :nv "g" #'org-clock-goto
          :desc "Clock resolve" :nv "r" #'org-resolve-clocks)
         :desc "Cycle TODOs" :nv "t" #'org-todo
         :desc "Set schedule" :nv "s" #'org-schedule
         :desc "Set deadline" :nv "d" #'org-deadline
         :desc "Refile" :nv "r" #'org-refile
         :desc "Copy" :nv "R" #'org-copy
         :desc "Narrow element" :nv "n" #'org-narrow-to-element
         :desc "Widen element" :nv "w" #'widen
         :desc "Todo to list" :nv "-" #'org-ctrl-c-minus
         :desc "List to TODO" :nv "*" #'org-ctrl-c-star
         :desc "Jump to anywhere" :nv "g" #'counsel-org-goto-all
         :desc "Ctrl-Ctrl" :nv "C" #'org-ctrl-c-ctrl-c)))

Misc

elisp

package-lint

Linter de problemas habituales de paquetes de melpa:

(package! package-lint)
(after! elisp
  (use-package! package-lint :defer t))

python-mode

(defun daemons/docker-tox ()
  (interactive)
  (let ((default-directory (projectile-project-root))
        (current-prefix-arg '(4)))
    (call-interactively (compile "docker-tox"))))

docker-tox es un script en bash que ejecuta una imagen de docker con varias versiones de python y tox. El contenido del script es este:

#!/bin/bash

function async_clean(){
    docker stop `docker ps | grep tox | awk '{print $1}' | tail -n+2` &
}
async_clean &> /dev/null
docker run -ti -v `pwd`:/tox/files/ registry.daemons.it/tox:latest tox "$@"

De esta forma, ejecuto tox-current-class una vez y el del tiempo ejecuto recompile.

compile-mode

Varias herramientas usan el modo compile, como molecule.el:

;; Fuente: https://stackoverflow.com/questions/13397737/ansi-coloring-in-compilation-mode
(use-package! compile
  :defer t
  :config
  (setq compilation-skip-threshold 2)
  (setq compilation-scroll-output 'next-error)
  (set-popup-rule! "*compilation*" :size 0.40 :quit nil)
  (defun colorize-compilation-buffer ()
    (let ((inhibit-read-only t))
      (ansi-color-apply-on-region (point-min) (point-max))))
  (add-hook 'compilation-filter-hook 'colorize-compilation-buffer)
  (setq compilation-scroll-output 'first-error))

ivy-mode / counsel-mode / swipper-mode

Cambiar un poco el formato de ivy.

(use-package! ivy
  :config
  (setq ivy-count-format "(%d/%d) ")
  (setq counsel-find-file-at-point t))

counsel-rg

Seguir enlaces simbólicos:

(setq counsel-rg-base-command "rg -L -S --no-heading --line-number --color never %s .")

undo-tree

Personalizaciones para hacer que sea más chuli y que guarde la historia de cada fichero en un sitio común.

(use-package! undo-tree
  :config
  (setq undo-tree-visualizer-timestamps t)
  (setq undo-tree-auto-save-history t)
  (setq undo-tree-history-directory-alist '(("." . "~/.emacs.d/undo-tree"))))

docker

Uso la configuración por defecto de doom, pero me gusta que no muera el buffer de compilación:

(set-popup-rule! "docker-build-output" :size 0.30 :select t)

go-mode

Uso el de doom, que necesita las siguientes dependencias:

go get -u github.com/motemen/gore
go get -u github.com/mdempsky/gocode
go get -u golang.org/x/tools/cmd/godoc
go get -u golang.org/x/tools/cmd/goimports
go get -u golang.org/x/tools/cmd/gorename
go get -u golang.org/x/tools/cmd/guru

Además quiero que se formatee al guardar y que use goimports en vez de gofmt.

(add-hook 'go-mode-hook 'gofmt-before-save)
(setq gofmt-command "goimports")

hl-todo

Añadir un nuevo TODO para las cosas feas.

(add-to-list 'hl-todo-keyword-faces
             '("HACK" . "DarkRed"))

vue-mode

(package! vue-mode)

magit-mode

Quiero usar commitizen en vez del commit pelado. Para ello tengo dos opciones, abrir una terminal que ejecute cz o usar una plantilla para los commits. La primera opción tiene la ventaja de que puede usar la configuración de commitizen del repositorio, que es lo bonico. Lo malo es que no he encontrado una forma de integralo bonito con magit. Por ello, de momento tiraré con la segunda opción, ya que no trabajo con más gente que use este plugin de git. En el caso de encontrarmelo, igual decidiré si creo varias plantillas o si intento mejorar la integración de magit con commitizen.

Plantilla en magit

(defun daemons/commitizen-template()
  "Expand a commitizen template."
  (yas-expand-snippet "${1:Select the type of change you are committing: $$(yas-choose-value '(\"fix\" \"feat\" \"docs\" \"style\" \"refactor\" \"perf\" \"test\" \"build\" \"ci\"))}(${2:Scope. Could be anything specifying place of the commit change (users, db, poll): )}): ${3:Subject. Concise description of the changes. Imperative, lower case and no final dot}

${4:Body. Motivation for the change and contrast this with previous behavior}

${5:Footer. Information about Breaking Changes and reference issues that this commit closes}"))

(add-hook 'git-commit-setup-hook #'daemons/commitizen-template)

Abrir cz en una terminal

(defun phalp/run-in-vterm (command)
  "Execute string COMMAND in a new vterm.

From: https://www.reddit.com/r/emacs/comments/ft84xy/run_shell_command_in_new_vterm/

Interactively, prompt for COMMAND with the current buffer's file
name supplied. When called from Dired, supply the name of the
file at point.

Like `async-shell-command`, but run in a vterm for full terminal features.

The new vterm buffer is named in the form `*foo bar.baz*`, the
command and its arguments in earmuffs.

When the command terminates, the shell remains open, but when the
shell exits, the buffer is killed."
  (interactive
   (list
    (let* ((f (cond (buffer-file-name)
                    ((eq major-mode 'dired-mode)
                     (dired-get-filename nil t))))
           (filename (concat " " (shell-quote-argument (and f (file-relative-name f))))))
      (read-shell-command "Terminal command: "
                          (cons filename 0)
                          (cons 'shell-command-history 1)
                          (list filename)))))
  (with-current-buffer (vterm (concat "*" command "*"))
    (vterm-send-string command)
    (vterm-send-return)))

(defun daemons/commitizen-gz() (interactive) (phalp/run-in-vterm "git cz commit; exit"))

vterm-mode

Hacer que entre en insert mode al lanzar una terminal:

(after! vterm-mode
  (add-hook 'vterm-mode-hook #'evil-insert-state))

evil-hardcore-mode

Hardcodear atajos de teclado:

(package! evil-hardcore-mode :recipe (:repo "https://gitlab.com/drymerisnothere/evil-hardcore-mode"))
(use-package! evil-hardcore-mode
  :init
  (setq evil-hardcore-my-bad-keybinds nil)
  (add-to-list 'evil-hardcore-my-bad-keybinds "C-c C-x C-i")
  (add-to-list 'evil-hardcore-my-bad-keybinds "C-c C-x C-o")
  (add-to-list 'evil-hardcore-my-bad-keybinds "C-c C-x C-z")
  (add-to-list 'evil-hardcore-my-bad-keybinds "C-c C-x C-j")
  (add-to-list 'evil-hardcore-my-bad-keybinds "C-c C-t")
  (add-to-list 'evil-hardcore-my-bad-keybinds "C-c C-s")
  (add-to-list 'evil-hardcore-my-bad-keybinds "C-c C-d")
  (add-to-list 'evil-hardcore-my-bad-keybinds "C-x 1")
  (add-to-list 'evil-hardcore-my-bad-keybinds "C-x 2")
  (add-to-list 'evil-hardcore-my-bad-keybinds "C-x 3")
  (add-to-list 'evil-hardcore-my-bad-keybinds "C-s")
  (add-to-list 'evil-hardcore-my-bad-keybinds "M-x")
  :config
  (global-evil-hardcore-mode))

Cambiar atajos de doom

Cambiar los atajos por unos que me gustan más.

(map!
      :nmvoi "C-a" #'evil-beginning-of-line
      :nmvoi "C-e" #'evil-end-of-line
      :nmvoi "C-d" #'evil-scroll-page-down
      :nmvoi "C-u" #'evil-scroll-page-up
)

Hacer gifs con camcorder.el

(package! camcorder)
(use-package camcorder
  :ensure t
  :config
  (setq camcorder-recording-command '("recordmydesktop" " --fps 100 --no-sound --windowid " window-id " -o " file)))

Deshabilitar numeros en los laterales

(setq doom-line-numbers-style nil)
(setq display-line-numbers-type nil)

En progreso / Pendiente

En esta sección van las cosas que no tengo muy provadas o tengo a medio configurar.

molecule

No se activa automaticamente.

(package! molecule)
(use-package! molecule)
  ;; :config (add-hook 'yaml-mode-hook 'molecule-mode))

Autenticación para terraform

Uso el modo de terraform que da doom, pero le he añadido una función cutre

(after! terraform-mode
  (setq daemons/terraform-prefix nil)

  (defun daemons/terraform-set-aws-prefix ()
    (interactive)
    (let ((aws-profile (shell-command-to-string "grep 'profile' $HOME/.aws/config | sed -e 's/.*profile //' | sed -e 's/\]$//'"))
          (terraform-profile))
      (if (equal current-prefix-arg '(4))
          (setq daemons/terraform-prefix nil))
      (if (not daemons/terraform-prefix)
          (progn
            (setq terraform-profile (completing-read "AWS profiles: " (split-string aws-profile)))
            (setq daemons/terraform-prefix (concat "asp " terraform-profile ";"))))))
  (add-hook 'terraform-mode-hook 'terraform-format-on-save-mode))

(map! :after terraform-mode
      :map terraform-mode-map
      :localleader
      :desc "terraform apply" "a" (λ! (let ((current-prefix-arg '(4))) (compile (concat daemons/terraform-prefix "terraform apply"))))
      :desc "terraform destroy" "d" (λ! (let ((current-prefix-arg '(4))) (compile (concat daemons/terraform-prefix "terraform apply"))))
      :desc "terraform init"  "i" (λ! (compile (concat daemons/terraform-prefix "terraform init")))
      :desc "terraform plan"  "p" (λ! (compile (concat daemons/terraform-prefix "terraform plan"))))