Emacs config

Table of Contents

1 Vanilla Emacs setup

(setq inhibit-startup-message t)
(setq default-directory (expand-file-name "~/"))

;; Hide vertical scroll bar
(scroll-bar-mode -1)

;; Default font-family, this will used in all the function that manipulates font settings
(defvar my/font-family "Iosevka")

;; Hide toolbar
(tool-bar-mode -1)

;; Change backups directory to emacs folder
;; this avoid to save backup files in the same directory of the original files
(setq backup-directory-alist `(("." . ,(concat user-emacs-directory
                                               "backups"))))

;; Hide the bell in the center of screen
(setq ring-bell-function 'ignore)
(column-number-mode t)
(global-hl-line-mode 1)

(when (member my/font-family (font-family-list))
  (set-frame-font (format "%s 13" my/font-family)))

(global-set-key (kbd "C-x -") 'my/decrease-font-height)
(global-set-key (kbd "C-x =") 'my/increase-font-height)
(global-set-key (kbd "C-x +") 'my/increase-font-height)

;; Navigate through buffers
(global-set-key (kbd "M-[") 'previous-buffer)
(global-set-key (kbd "M-]") 'next-buffer)

;; Fix unicode errors
(setenv "LANG" "en_US.UTF-8")
(setenv "LC_ALL" "en_US.UTF-8")
(setenv "LC_CTYPE" "en_US.UTF-8")

;; Fix size of scroll
(setq scroll-step 1
      scroll-conservatively  10000)

;; Show a marker when the line has empty characters at the end
(setq-default show-trailing-whitespace t)

;; disable `show-trailing-whitespace' for modes based in comint-mode
(add-hook 'comint-mode-hook #'(lambda () (setq-local show-trailing-whitespace nil)))

;; Avoid close emacs by mistake
(global-unset-key (kbd "C-x C-c"))

(defalias 'yes-or-no-p 'y-or-n-p)
(defalias 'run-elisp 'ielm)

;; place custom code generated for emacs in a separate file
(defconst custom-file (expand-file-name ".customize.el" user-emacs-directory))
(load custom-file :noerror)

Show a message with emacs startup time

(defun my/display-startup-time ()
  (message "🚀 Emacs loaded in %s with %d garbage collections."
           (float-time (time-subtract after-init-time before-init-time))
           gcs-done))

(add-hook 'emacs-startup-hook #'my/display-startup-time)

Setup fira code symbols mode.

(when (member "Fira Code" (font-family-list))
  (load-file (expand-file-name "~/.emacs.d/fira-code-mode.el"))
  (require 'fira-code-mode)
  (add-hook 'prog-mode-hook 'fira-code-mode))

Adjust font size to screen resolution, increase font size for 4K screens

(when (and (display-graphic-p) (>= (x-display-pixel-width) 3840))
  (set-frame-font (format "%s 16" my/font-family)))

Parrot mode FTW!

(defun my/parrot-animate-when-compile-success (buffer result)
  (if (string-match "^finished" result)
      (parrot-start-animation)))

(use-package parrot
  :ensure t
  :config
  (parrot-mode)
  (add-hook 'before-save-hook 'parrot-start-animation)
  (add-to-list 'compilation-finish-functions 'my/parrot-animate-when-compile-success))

Open scratch buffer

(defun my/scratch-buffer()
  "Switch to scratch buffer."
  (interactive)
  (let ((buffer (get-buffer "*scratch*")))
    (if buffer
        (switch-to-buffer buffer)
      (switch-to-buffer (get-buffer-create "*scratch*"))
      (insert initial-scratch-message))
    (lisp-interaction-mode)))

1.1 Ansi term

For some reason ansi-term doesn't respect the global keybinding for M-] so this have to be setup in term-raw-map as well.

(eval-after-load "term"
  '(define-key term-raw-map (kbd "M-]") 'next-buffer))

1.2 Compilation

Disable h key-binding, this has a conflict with evil-mode left navigation key-binding.

(with-eval-after-load 'compile
  ;; set cursor to follow compilation output
  (setq compilation-scroll-output t)
  (define-key compilation-mode-map (kbd "g") nil)
  (define-key compilation-mode-map (kbd "r") 'recompile)
  (define-key compilation-mode-map (kbd "h") nil))

Allow to show color characters in the compilation buffer.

copied from https://stackoverflow.com/questions/3072648/cucumbers-ansi-colors-messing-up-emacs-compilation-buffer/3072831#3072831

(require 'ansi-color)

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

2 Theme and styles

2.1 Dracula

(use-package dracula-theme
  :ensure t
  :config
  (load-theme 'dracula t)
  (set-face-foreground 'font-lock-variable-name-face "gray"))

2.2 Doom modeline

Enable display-battery-mode after doom-modeline is loaded.

(use-package doom-modeline
  :ensure t
  :defer t
  :custom
  (doom-modeline-modal-icon nil)
  :hook
  (after-init . doom-modeline-mode)
  (doom-modeline-mode . display-battery-mode))

2.3 All the icons

(use-package all-the-icons
  :ensure t)

2.4 Emoji support

(use-package unicode-fonts
  :ensure t
  :config
  (unicode-fonts-setup))

3 Dired

(eval-after-load "dired"
  '(define-key dired-mode-map (kbd "C-c C-e") 'wdired-change-to-wdired-mode))

3.1 All the icons dired

(use-package all-the-icons-dired
  :ensure t
  :defer t
  :hook (dired-mode . all-the-icons-dired-mode))

3.2 Dired subtree

(use-package dired-subtree
  :ensure t
  :after dired
  :config
  (define-key dired-mode-map (kbd "<tab>") 'dired-subtree-toggle))

4 Editor enhancements

4.1 Whitespace

Show special markers for tab and endline characters in prog-mode

(use-package whitespace-mode
  :custom
  (whitespace-style '(tab-mark newline-mark))
  (whitespace-display-mappings '((newline-mark ?\n    [?¬ ?\n]  [?$ ?\n])
                                 (tab-mark     ?\t    [?» ?\t] [?\\ ?\t])))
  :hook
  (prog-mode . whitespace-mode))

4.2 Deactivate extended region in visual mode

This allow to visual mode work more like vim visual highlighting.

(unless (version< emacs-version "27")
  (set-face-attribute 'region nil :extend nil))

4.3 Dark and transparent title bar in macOS

(when (memq window-system '(mac ns))
  (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
  (add-to-list 'default-frame-alist '(ns-appearance . dark)))

4.4 Share clipoard with OS

(use-package pbcopy
  :ensure t)

4.5 Highlight TODO, FIXME, etc

(defun my/highlight-todo-like-words ()
  (font-lock-add-keywords
   nil `(("\\<\\(FIXME\\|TODO\\)"
          1 font-lock-warning-face t))))

(add-hook 'prog-mode-hook 'my/highlight-todo-like-words)

4.6 Load PATH environment

exec-path-from-shell by default uses ("-l" "-i") when starts a new shell to get the PATH, -i option was removed to open a non interactive shell so it can be faster at startup.

(use-package exec-path-from-shell
  :ensure t
  :custom
  (exec-path-from-shell-arguments '("-l"))
  (exec-path-from-shell-check-startup-files nil)
  :config
  (when (memq window-system '(mac ns))
    (exec-path-from-shell-initialize)))

4.7 Editorconfig

(use-package editorconfig
  :ensure t
  :config
  (editorconfig-mode 1))

4.8 Snippets

(use-package yasnippet
  :ensure t
  :hook ((prog-mode . yas-minor-mode)
         (conf-mode . yas-minor-mode)
         (text-mode . yas-minor-mode)
         (snippet-mode . yas-minor-mode)))

(use-package yasnippet-snippets
  :ensure t
  :after (yasnippet))

4.9 Wakatime

(use-package wakatime-mode
  :ensure t
  :if (executable-find "wakatime")
  :init
  (setq wakatime-cli-path (executable-find "wakatime"))
  :config
  (global-wakatime-mode))

4.10 Highlight thing

(use-package highlight-thing
  :ensure t
  :hook
  (prog-mode . highlight-thing-mode))

4.11 Various changes

Disable lock files

(setq create-lockfiles nil)

4.12 Reformatter

(use-package reformatter
  :ensure t)

4.13 Vterm

(use-package vterm
  :ensure t
  :hook
  (vterm-mode . (lambda ()
                  (setq-local show-trailing-whitespace nil)))
  :custom
  (vterm-module-cmake-args "-DUSE_SYSTEM_LIBVTERM=yes")
  (vterm-always-compile-module t))

4.14 Toggle terminal

'project use always the same terminal per project, this way we avoid to create a new terminal for each call to vterm-toggle. 'reset-window-configration yes, it's suppose to be configration, for some reason it was defined like this instead of configuration

Also for easy access insert mode is activated right away after vterm is shown

(use-package vterm-toggle
  :ensure t
  :custom
  (vterm-toggle-scope 'project)
  (vterm-toggle-hide-method 'reset-window-configration)
  :hook
  (vterm-toggle-show . evil-insert-state))

4.15 iSpell

Avoid check spelling in markdown code blocks

(with-eval-after-load 'ispell
  (setq ispell-program-name "aspell")
  (add-to-list 'ispell-skip-region-alist
               '("^```" . "^```")))

When editing a commit message ispell should ignore lines that start with #, these lines are diff details about the commit.

(defun my/setup-ispell-for-commit-message ()
  "Setup `ispell-skip-region-alist' to avoid lines starting with #.
  This way diff code will be ignored when ispell run."
  (setq-local ispell-skip-region-alist (cons '("^#" . "$") ispell-skip-region-alist)))

4.16 Tree sitter

Incremental code parsing for better syntax highlighting

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

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

5 Evil

(defun my/find-file-under-cursor ()
  "Check it the filepath under cursor is an absolute path otherwise open helm and insert the filepath."
  (interactive)
  (let ((file-path (thing-at-point 'filename t)))
    (if (file-name-absolute-p file-path)
        (find-file-at-point file-path)
      (minibuffer-with-setup-hook (lambda ()
                                    (insert file-path))
        (helm-ls-git-ls)))))

(use-package evil
  :ensure t
  :init
  (setq evil-emacs-state-cursor '("white" box)
        evil-normal-state-cursor '("green" box)
        evil-visual-state-cursor '("orange" box)
        evil-insert-state-cursor '("red" bar)
        ;; use emacs-28 undo system
        evil-undo-system 'undo-redo)
  :config
  (evil-mode 1)
  (modify-syntax-entry ?_ "w")
  (define-key evil-normal-state-map (kbd "C-p") 'diff-hl-previous-hunk)
  (define-key evil-normal-state-map (kbd "C-n") 'diff-hl-next-hunk)
  (define-key evil-normal-state-map "gf" 'my/find-file-under-cursor)
  (add-hook 'prog-mode-hook #'(lambda ()
                                (modify-syntax-entry ?_ "w")))

  (face-spec-set
   'evil-ex-substitute-matches
   '((t :foreground "red"
        :strike-through t
        :weight bold)))

  (face-spec-set
   'evil-ex-substitute-replacement
   '((t
      :foreground "green"
      :weight bold))))

(use-package evil-nerd-commenter
  :ensure t
  :after (evil)
  :config
  (evilnc-default-hotkeys)
  (global-set-key (kbd "C-\-") 'evilnc-comment-operator)
  ;; avoid to auto-setup of keybindings
  (setq evilnc-use-comment-object-setup nil))

(use-package evil-surround
  :ensure t
  :after (evil)
  :config
  (global-evil-surround-mode 1))

(defun my/replace-word-at-point ()
  "Setup buffer replace string for word at point using evil ex mode."
  (interactive)
  (evil-ex (concat "%s/" (word-at-point) "/")))

(use-package evil-leader
  :ensure t
  :after (evil)
  :config
  (global-evil-leader-mode)
  (evil-leader/set-key
    "SPC" 'helm-M-x
    "a" 'my/helm-ag-with-default-term
    "A" 'my/helm-ag-without-default-term
    "b" 'helm-buffers-list
    "c" 'vterm-toggle
    "e" 'my/find-file-in-project
    "f" 'find-file
    "g" 'my/magit-status
    "G" 'magit-file-dispatch
    "i" 'imenu
    "hk" 'diff-hl-revert-hunk
    "k" 'kill-buffer
    "l" 'display-line-numbers-mode
    "n" 'evil-buffer-new
    "pa" 'my/copy-abs-path
    "pr" 'my/copy-relative-path
    "q" 'helm-swoop
    "r" 'my/replace-word-at-point
    "R" 'recompile
    "s" 'my/toggle-spanish-characters
    "t" 'persp-switch
    "w" 'my/toggle-maximize
    "x" 'my/resize-window
    "y" 'helm-show-kill-ring))

(use-package evil-matchit
  :ensure t
  :config (global-evil-matchit-mode 1))

6 Utils

6.1 Which-key

(use-package which-key
  :ensure t
  :config
  (which-key-mode)
  (which-key-setup-minibuffer))

6.2 Autopair

(use-package autopair
  :ensure t
  :config
  (autopair-global-mode))

6.3 Restclient

(use-package restclient
  :ensure t
  :defer t
  :mode (("\\.http\\'" . restclient-mode))
  :bind (:map restclient-mode-map
              ("C-c C-f" . json-mode-beautify))) ;TODO: change to only apply json formatting when the content-type is application/json

(use-package restclient-helm
  :ensure t
  :after (restclient))

(use-package company-restclient
  :ensure t
  :after (restclient)
  :config
  (add-to-list 'company-backends 'company-restclient))

6.4 Rainbow delimiters

(use-package rainbow-delimiters
  :ensure t
  :hook
  (prog-mode . rainbow-delimiters-mode))

6.5 XML formatter

(reformatter-define xml-format
  :program "xmlformat"
  :group 'xml)

(eval-after-load "nxml-mode"
  '(define-key nxml-mode-map (kbd "C-c C-f") 'xml-format-buffer))

6.6 SQL formatter

Install pgformatter using homebrew brew install pgformatter

(reformatter-define sql-format
  :program "pg_format")

(defun my/format-sql ()
  "Format active region otherwise format the entire buffer."
  (interactive)
  (if (region-active-p)
      (sql-format-region (region-beginning) (region-end))
    (sql-format-buffer)))

(eval-after-load "sql"
  '(define-key sql-mode-map (kbd "C-c C-f") 'my/format-sql))

7 Common packages

Used in every major mode

7.1 Company

(use-package company
  :ensure t
  :init
  (setq company-idle-delay 0.1
        company-tooltip-limit 10
        company-minimum-prefix-length 3)
  :hook (after-init . global-company-mode)
  :config
  (define-key company-active-map (kbd "C-n") 'company-select-next)
  (define-key company-active-map (kbd "C-p") 'company-select-previous))

7.2 Flymake

Only activate flymake for actual projects and for prog-mode

(defun my/setup-flymake ()
  "Activate flymake only if we are inside a project."
  (if (my/project-p)
      (flymake-mode-on)))

(add-hook 'prog-mode 'my/setup-flymake)

(with-eval-after-load "flymake"
  (define-key flymake-mode-map (kbd "M-n") 'flymake-goto-next-error)
  (define-key flymake-mode-map (kbd "M-p") 'flymake-goto-prev-error))

7.3 Direnv

This allow to update environment using .envrc file.

(use-package direnv
  :ensure t
  :defer t)

7.4 perspective.el

(use-package perspective
  :ensure t
  :config
  (persp-mode)
  ;; change default font-face color to be aligned with doom-mode-line
  (set-face-foreground 'persp-selected-face "green")
  ;; setup vim tab like key-bindings
  (define-key evil-normal-state-map (kbd "gt") 'persp-next)
  (define-key evil-normal-state-map (kbd "gT") 'persp-prev))

7.5 Project.el

project.el default prefix is C-x, we need to pull project.el instead using the included package because emacs 27 project.el version doesn't have all the functionality I need.

(defun my/project-edit-dir-locals ()
  "Edit .dir-locals.el file in project root."
  (interactive)
  (find-file (expand-file-name ".dir-locals.el" (my/project-root))))

(defun my/project-edit-direnv ()
  "Edit .envrc file in project root."
  (interactive)
  (find-file (expand-file-name ".envrc" (my/project-root))))

(use-package project
  :bind (:map project-prefix-map
              ("D" . 'my/project-edit-direnv)
              ("e" . 'my/project-edit-dir-locals)
              ("k" . 'my/project-kill-buffers)
              ("n" . 'my/project-open-new-project)
              ("p" . 'my/project-switch)))

Define helper functions to be used by other packages

(defun my/project-root ()
  "Return project root path."
  (cdr (project-current)))

(defun my/project-p ()
  (project-current))

(defun my/project-name ()
  "Get project name extracting latest part of project path."
  (if (my/project-p)
      (second (reverse (split-string (my/project-root) "/")))
    nil))

perspective.el integration, a new perspective should be "attached" to a project so it's easy to switch between them.

(defun my/project-switch ()
  "Switch to a project and trigger switch action."
  (interactive)
  ;; make sure all the projects list is available to be used
  (project--ensure-read-project-list)
  (let* ((projects (mapcar 'car project--list))
         (choice (completing-read "Switch to project: " projects))
         (default-directory choice))
    ;; `default-directory' must be defined so `project.el' can know is in a new project
    (my/project-switch-action)))

(defun my/project-switch-action ()
  "Switch to a new perspective which name is project's name and open `helm-ls-git-ls'."
  (interactive)
  (persp-switch (my/project-name))
  (helm-ls-git-ls))

(defun my/project-kill-buffers ()
  "Kill all the related buffers to the current project and delete its perspective as well."
  (interactive)
  (let* ((project-name (my/project-name))
         (project (project-current))
         (buffers-to-kill (project--buffers-to-kill project)))
    (when (yes-or-no-p (format "Kill %d buffers in %s?" (length buffers-to-kill) (my/project-root)))
      (mapc #'kill-buffer buffers-to-kill)
      (persp-kill project-name))))

(defun my/project-open-new-project ()
  "Open a project for the first time and add it to `project.el' projects list."
  (interactive)
  (let* ((project-path-abs (read-directory-name "Enter project root: "))
         ;; we need to define `default-directory' to be able to get the new project when `project-current' is called
         (default-directory (replace-regexp-in-string (expand-file-name "~") "~" project-path-abs)))
    (project-remember-project (project-current))
    (my/project-switch-action)))

7.6 Helm

(use-package helm
  :ensure t
  :custom
  (helm-M-x-use-completion-styles nil)
  (helm-split-window-inside-p t)
  :bind (:map helm-map
              ("<tab>" . 'helm-execute-persistent-action))
  :config
  (helm-mode 1))

(with-eval-after-load 'helm
  (add-to-list 'display-buffer-alist
               '("\\`\\*helm.*\\*\\'"
                 (display-buffer-in-side-window)
                 (inhibit-same-window . t)
                 (window-height . 0.4))))

Icons support, it uses all-the-icons package.

(use-package helm-icons
  :after helm
  :custom
  (helm-icons-provider 'all-the-icons)
  :config
  (helm-icons-enable))

Helm util packages

(defun my/helm-ag-with-default-term ()
  (interactive)
  (let ((helm-ag-insert-at-point 'word))
    (helm-ag-project-root)))

(defun my/helm-ag-without-default-term ()
  (interactive)
  (let ((helm-ag-insert-at-point nil))
    (helm-ag-project-root)))

(use-package helm-ag
  :ensure t
  :defer t)

(use-package helm-ls-git
  :ensure t
  :defer t)

(use-package helm-swoop
  :ensure t
  :defer t)

7.7 Neotree

(defun my/neotree-toggle ()
  "Custom function with some tweaks to be aplied when neotree opens."
  (interactive)
  (if (and (my/project-p) (not (neo-global--window-exists-p)))
      (neotree-dir (my/project-root))
    (neotree-toggle)))

(use-package neotree
  :ensure t
  :straight (neotree
             :type git
             :host github
             :repo "jaypei/emacs-neotree"
             :branch "dev")
  :custom
  (neo-window-fixed-size nil)
  (neo-fit-to-contents t)
  (neo-theme 'icons)
  (neo-autorefresh nil)
  (neo-vc-integration '(face))
  :bind (([f3] . 'my/neotree-toggle)
         :map neotree-mode-map
         ("C-w l" . 'evil-window-right)
         ("C-c C-h" . 'neotree-hidden-file-toggle)
         ("C-c C-r" . 'neotree-rename-node)))

(with-eval-after-load 'evil
  (evil-set-initial-state 'neotree-mode 'emacs))

8 Meme

This package requires to have svg support in emacs, this feature relies on librsvg at compilation time

(defun my/meme-from-clipboard ()
  "Create a meme using an image from clipboard"
  (interactive)
  (unless (executable-find "pngpaste")
    (user-error "please install pngpaste"))

  (let* ((filepath (make-temp-file "clipboard" nil ".png"))
         (command (format "pngpaste %s" filepath))
         (command-stdout (shell-command-to-string command)))
    ;; pngpaste returns "" when found a valid image in the clipboard
    (unless (string-equal command-stdout "")
      (user-error (string-trim command-stdout)))

    (switch-to-buffer (get-buffer-create "*meme*"))
    (meme-mode)
    (meme--setup-image filepath)))

(use-package imgur
  :ensure t
  :defer t
  :straight (imgur
             :type git
             :host github
             :repo "myuhe/imgur.el"))

(use-package meme
  :ensure t
  :defer t
  :commands (meme-mode meme)
  :straight (meme
             :type git
             :host github
             :repo "larsmagne/meme")
  :config
  ;; fix to be able to read images, straight.el put files in a different directory so we have to
  ;; move them to the right one
  (let ((images-dest-dir (concat user-emacs-directory "straight/build/meme/images"))
        (images-source-dir (concat user-emacs-directory "straight/repos/meme/images")))
    (unless (file-directory-p images-dest-dir)
      (shell-command (format "cp -r %s %s" images-source-dir images-dest-dir)))))

9 Orgmode

Configured variables:

  • org-latex-caption-above puts table captions at the bottom
  • org-clock-persist persists time even if emacs is closed
  • org-src-fontify-natively enables syntax highlighting for code blocks
  • org-log-done saves the timestamp when a task is done
  • org-src-preserve-indentation when is t avoid to insert a left indentation in source blocks
(defun my/org-insert-image-from-clipboard ()
  "Insert image from clipboard using an org tag"
  (interactive)
  (let* ((image-name (read-string "Filename: " "image.png"))
         (images-folder "./images")
         (image-path (format "%s/%s" images-folder image-name)))
    (unless (file-directory-p images-folder)
      (shell-command (format "mkdir -p %s" images-folder)))
    (shell-command (format "pngpaste %s" image-path))
    (insert (format "[[file:%s]]" image-path))))

(evil-leader/set-key-for-mode 'org-mode "mii" 'my/org-insert-image-from-clipboard)

When I read books on Apple Books and I want to insert some quote Apple Books insert some text I don't want in my notes, this function delete that and just insert the meaning part using org quote syntax.

(defun my/org-insert-quote-from-apple-books ()
  "Take quote from clipboard and remove all the unnecesary text and insert
    an org quote in the current position"
  (interactive)
  (let* ((raw-value (current-kill 0 t))
         (tmp (second (split-string raw-value "“")))
         (quote-value (car (split-string tmp "”"))))
    (insert "#+begin_quote\n")
    (insert (concat quote-value "\n"))
    (insert "#+end_quote\n")))

(defvar my/org-src-block-tmp-window-configuration nil)

(defun my/org-edit-special (&optional arg)
  "Save current window configuration before a org-edit buffer is open."
  (setq my/org-src-block-tmp-window-configuration (current-window-configuration)))

(defun my/org-edit-src-exit ()
  "Restore the window configuration that was saved before org-edit-special was called."
  (set-window-configuration my/org-src-block-tmp-window-configuration))

(eval-after-load "org"
  `(progn
     (setq org-latex-caption-above nil
           org-clock-persist 'history
           org-src-fontify-natively t
           org-src-preserve-indentation t
           org-log-done t)
     (org-clock-persistence-insinuate)

     ;; this is needed to use shortcuts like <s to create source blocks
     (unless (version< emacs-version "27")
       (require 'org-tempo))

     (add-hook 'org-mode-hook (lambda ()
                                (org-indent-mode t)
                                (autopair-mode -1)))

     (advice-add 'org-edit-special :before 'my/org-edit-special)
     (advice-add 'org-edit-src-exit :after 'my/org-edit-src-exit)

     (org-babel-do-load-languages 'org-babel-load-languages
                                  '((python . t)
                                    (shell . t)
                                    (lisp . t)
                                    (sql . t)
                                    (dot . t)
                                    (emacs-lisp . t)))))

(use-package htmlize
  :ensure t
  :after (org))

9.1 Org-ref

(use-package org-ref
  :ensure t
  :defer t
  :init
  (setq org-latex-pdf-process (list "latexmk -shell-escape -bibtex -f -pdf %f")))

9.2 Org tree slide

A tool to show org file as an slideshow

hide-mode-line hide the modeline to allow to have a clean screen while using org-tree-slide-mode

(use-package hide-mode-line
  :ensure t)

Some tweaks to have a better looking while presenting slides

(defun my/org-tree-slide-setup ()
  (org-display-inline-images)
  (hide-mode-line-mode 1))

(defun my/org-tree-slide-end ()
  (org-display-inline-images)
  (hide-mode-line-mode 0))

(use-package org-tree-slide
  :ensure t
  :defer t
  :custom
  (org-image-actual-width nil)
  (org-tree-slide-activate-message "Presentation started!")
  (org-tree-slide-deactivate-message "Presentation finished!")
  :hook ((org-tree-slide-play . my/org-tree-slide-setup)
         (org-tree-slide-stop . my/org-tree-slide-end))
  :bind (:map org-tree-slide-mode-map
              ("C-<" . org-tree-slide-move-previous-tree)
              ("C->" . org-tree-slide-move-next-tree)))

10 Latex

(use-package auctex
  :ensure t
  :defer t)

(use-package latex-preview-pane
  :ensure t
  :defer t)

11 Git

11.1 Git-link

Open selected region in remote repo page

(use-package git-link
  :ensure t
  :defer t)

11.2 Gitignore-mode

(use-package gitignore-mode
  :defer t
  :ensure t)

11.3 Magit

(defconst my/magit-register ?m)

(defun my/magit-status()
  (interactive)
  (set-register my/magit-register (current-window-configuration))
  (magit-status)
  (delete-other-windows))

(defun my/magit-status-exit (&optional kill-buffer)
  "Restore windows configuration after magit status buffer is closed."
  (interactive)
  (let ((magit-buffer-name (format "magit: %s" (my/project-name)))
        (register-value (get-register my/magit-register)))
    (if (and register-value (string-equal magit-buffer-name (buffer-name)))
        (set-window-configuration register-value))))

(defun my/magit-blame-quit ()
  "Restore evil state after magit blame mode is closed."
  (evil-exit-emacs-state))

(use-package magit
  :ensure t
  :defer t
  :config
  (advice-add 'magit-mode-bury-buffer :after 'my/magit-status-exit)
  (advice-add 'magit-blame-quit :after 'my/magit-blame-quit)
  (add-hook 'git-commit-mode-hook 'my/setup-ispell-for-commit-message)
  (add-hook 'magit-blame-mode-hook
            (lambda ()
              (evil-emacs-state))))

11.4 Forge

(use-package forge
  :ensure t
  :after magit
  :config
  (add-hook 'forge-topic-mode-hook (lambda ()
                                     (evil-emacs-state))))

11.5 Git diff-hl

(use-package diff-hl
  :ensure t
  ;; for some reason the :hook form doesn't work so we have to use :init
  :init
  (add-hook 'magit-pre-refresh-hook 'diff-hl-magit-pre-refresh)
  (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh)
  (add-hook 'dired-mode-hook 'diff-hl-dired-mode)
  :config
  (global-diff-hl-mode))

11.6 Timemachine

(use-package git-timemachine
  :ensure t
  :config
  (add-hook 'git-timemachine-mode-hook (lambda ()
                                         (evil-emacs-state))))

11.7 Gist

(use-package gist
  :ensure t
  :defer t)

11.8 Linkode

(use-package linkode
  :ensure t
  :defer t)

12 Web

12.1 Web mode

(defun my/web-mode-hook ()
  (emmet-mode)
  (rainbow-delimiters-mode-disable)
  (autopair-mode -1))

(use-package web-mode
  :ensure t
  :custom
  (web-mode-enable-current-element-highlight t)
  (web-mode-enable-current-column-highlight t)
  :mode (("\\.html\\'" . web-mode)
         ("\\.html.eex\\'" . web-mode)
         ("\\.html.leex\\'" . web-mode)
         ("\\.hbs\\'" . web-mode))
  :config
  (add-hook 'web-mode-hook 'my/web-mode-hook))

12.2 Emmet

(use-package emmet-mode
  :ensure t)

12.3 Sass

(use-package sass-mode
  :ensure t
  :defer t)

12.4 Rainbow

(use-package rainbow-mode
  :ensure t
  :hook
  ((css-mode . rainbow-mode)
   (sass-mode . rainbow-mode)
   (scss-mode . rainbow-mode)))

13 Miscellaneous

(use-package writeroom-mode
  :ensure t)

(use-package csv-mode
  :ensure t
  :defer t)

(use-package json-mode
  :ensure t
  :defer t)

(use-package request
  :ensure t
  :defer t)

(use-package graphql-mode
  :ensure t
  :defer t)

(defun my/k8s-apply ()
  "Apply current yaml file to the current kubernetes context."
  (interactive)
  (let ((default-directory (file-name-directory buffer-file-name)))
    (compile (format "kubectl apply -f %s" buffer-file-name))))

(use-package yaml-mode
  :ensure t
  :bind (:map yaml-mode-map
              ("C-c C-c" . 'my/k8s-apply)))

;; Used for gherkin files (.feature)
(use-package feature-mode
  :ensure t
  :defer t)

(use-package toml-mode
  :ensure t
  :defer t)

(use-package nix-mode
  :ensure t
  :defer t
  :mode "\\.nix\\'")

(use-package markdown-mode
  :ensure t
  :defer t)

(use-package edit-indirect
  :ensure t
  :defer t)

(use-package dockerfile-mode
  :ensure t
  :defer t)

(use-package dumb-jump
  :ensure t
  :defer t
  :custom
  (dumb-jump-selector 'helm))

Use ESC key instead C-g to close and abort

Copied from somewhere

(defun minibuffer-keyboard-quit ()
  "Abort recursive edit.
  In Delete Selection mode, if the mark is active, just deactivate it;
  then it takes a second \\[keyboard-quit] to abort the minibuffer."
  (interactive)
  (if (and delete-selection-mode transient-mark-mode mark-active)
      (setq deactivate-mark  t)
    (when (get-buffer "*Completions*") (delete-windows-on "*Completions*"))
    (abort-recursive-edit)))

(eval-after-load "evil"
  '(progn
     (define-key evil-normal-state-map [escape] 'keyboard-quit)
     (define-key evil-visual-state-map [escape] 'keyboard-quit)))

(define-key minibuffer-local-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-ns-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-completion-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-must-match-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-isearch-map [escape] 'minibuffer-keyboard-quit)
(global-set-key [escape] 'evil-exit-emacs-state)

Emacs Start Up Profiler

(use-package esup
  :ensure t)

14 LSP

(use-package lsp-mode
  :ensure t
  :defer t
  :init
  (setq lsp-prefer-capf t)
  ;; 10Mb LSP consume large payloads so a higher value is required
  (setq read-process-output-max (* 10 1024 1024)))

15 Programming languages

15.1 C

clang-format is required for this, we can install it with brew install clang-format

(reformatter-define c-format
  :program "clang-format")

(with-eval-after-load 'cc-mode
  (define-key c-mode-map (kbd "C-c C-f") 'c-format-buffer))

15.2 Python

For each virtual environment install the following packages:

pip install elpy jedi flake8 importmagic autopep8 yapf epc isort
(reformatter-define python-black-format
  :program "black"
  :args '("-")
  :group 'python)

(defun my/python-format-code ()
  (interactive)
  (if (executable-find "black")
      (python-black-format-buffer)
    (elpy-format-code)))

(use-package elpy
  :ensure t
  :hook (python-mode . elpy-enable)
  :custom
  (elpy-shell-echo-input . nil)
  :config
  (evil-leader/set-key-for-mode 'python-mode "d" 'elpy-goto-definition)
  (define-key elpy-mode-map (kbd "C-c C-f") 'my/python-format-code)
  (setq elpy-rpc-python-command "python")
  (add-hook 'elpy-mode-hook
            (lambda ()
              (highlight-indentation-mode -1)))) ; Remove vertical line

(setq python-shell-completion-native-enable nil)

(use-package py-isort
  :ensure t
  :after (elpy)
  :init
  (defun my/sort-imports ()
    (interactive)
    (if (region-active-p)
        (py-isort-region)
      (message "Select a region before to call isort")))
  :bind (:map elpy-mode-map
              ("C-c C-i" . my/sort-imports)))

Show a list of the available django commands and run the selected one using a compilation buffer.

(defun my/run-django-command ()
  "Run a django command."
  (interactive)
  (let* ((python-bin (concat (getenv "VIRTUAL_ENV") "/bin/python"))
         (manage-py-file (concat (my/project-root) "manage.py"))
         (default-directory (my/project-root))
         (raw-help (shell-command-to-string (concat python-bin " " manage-py-file " help")))
         (splited-lines (split-string raw-help "\n"))
         (options (seq-filter '(lambda (line) (cl-search "    " line)) splited-lines))
         (selection (completing-read "Pick django command: " (mapcar 'string-trim options)))
         (command (concat python-bin " " manage-py-file " " selection)))
    (compile command)))

15.3 Erlang

Clone erlang source code into ~/Code/erlang/src/

git clone https://github.com/erlang/otp.git ~/Code/erlang/src/

(use-package erlang
  :ensure t
  :defer t
  :if (executable-find "erl")
  :config
  (setq erlang-root-dir (expand-file-name "~/Code/erlang/src"))
  (require 'erlang-start))

15.4 Elixir

(reformatter-define elixir-format
  :program "mix"
  :args '("format" "-")
  :group 'elixir)

(use-package elixir-mode
  :ensure t
  :bind (:map elixir-mode-map
              ("C-c C-t" . 'my/mix-run-test-at-point)
              ("C-c C-f" . elixir-format-buffer))
  :config
  (evil-leader/set-key-for-mode 'elixir-mode "d" 'dumb-jump-go))

Custom functions to run elixir tests.

elixir-extra-test-env can be set up on .dir-locals.el

(defun my/mix-run-test (&optional scope)
  "Run elixir test for the given SCOPE."
  (interactive)
  (let* ((current-file (buffer-file-name))
         (current-line (line-number-at-pos))
         (possible-mix-paths `(,(concat (my/project-root) "mix.exs")
                               ,(concat (my/project-root) "src/mix.exs")))
         (mix-file (car (seq-filter 'file-exists-p possible-mix-paths)))
         (default-directory (file-name-directory mix-file))
         (extra-env (if (boundp 'elixir-extra-test-env) elixir-extra-test-env ""))
         (mix-env (concat "MIX_ENV=test " extra-env)))

    (cond
     ((string-equal scope "file")
      (compile (format "%s mix test %s" mix-env current-file)))

     ((string-equal scope "at-point")
      (compile (format "%s mix test %s:%s" mix-env current-file current-line)))

     (t
      (compile (format "%s mix test" mix-env))))))


(defun my/mix-run-test-file ()
  "Run mix test over the current file."
  (interactive)
  (my/mix-run-test "file"))

(defun my/mix-run-test-at-point ()
  "Run mix test at point."
  (interactive)
  (my/mix-run-test "at-point"))

(defun my/mix-run-test-all ()
  "Run mix test at point."
  (interactive)
  (my/mix-run-test))

15.5 LFE

(use-package lfe-mode
  :ensure t
  :if (executable-find "lfe")
  :bind (:map lfe-mode-map
              ("C-c C-c" . lfe-eval-buffer))
  :init
  (defun lfe-eval-buffer ()
    "Send current buffer to inferior LFE process."
    (interactive)
    (if (eq (get-buffer-window "*inferior-lfe*") nil)
        (run-lfe nil))
    (lfe-eval-region (point-min) (point-max) nil)))

15.6 Elm

Install Elm

npm -g install elm elm-format elm-oracle

(use-package elm-mode
  :ensure t
  :if (executable-find "elm")
  :bind (:map elm-mode-map
              ("C-c C-d" . elm-oracle-doc-at-point))
  :config
  (add-hook 'elm-mode-hook #'elm-oracle-setup-completion)
  (add-to-list 'company-backends 'company-elm))

15.7 Haskell

Install haskell binaries hlint and hindent and make sure ~/.local/bin/ is loaded in PATH.

stack install hlint
stack install hindent

(reformatter-define haskell-format
  :program "hindent"
  :group 'haskell)

(use-package haskell-mode
  :ensure t
  :bind (:map haskell-mode-map
              ("C-c C-f" . haskell-format-buffer)
              ("C-c C-l" . haskell-process-load-file)))

(defun my/run-hlint ()
  "Run  hlint over the current project."
  (interactive)
  (let ((default-directory (my/project-root)))
    (compile "hlint .")))

(defun my/run-hlint-buffer ()
  "Run  hlint over the current buffer."
  (interactive)
  (let* ((current-file (buffer-file-name))
         (default-directory (my/project-root)))
    (compile (concat "hlint " current-file))))

15.8 Lua

(use-package lua-mode
  :ensure t
  :bind (:map lua-mode-map
              ("C-c C-b" . compile)))

(use-package company-lua
  :ensure t
  :config
  (add-to-list 'company-backends 'company-lua))

15.9 Javascript

We use default js-mode because it has been improved in emacs 27.

Formattter

Put this script in some $PATH location like ~/.local/bin. This script is needed because prettier can't read code from stdin :/

#!/bin/bash -

tmp="$(mktemp).js"
while read line
do
    echo $line >> $tmp
done < /dev/stdin

npx prettier $tmp

(reformatter-define js-format
  :program "fixprettier.sh")

(with-eval-after-load 'js
  (evil-leader/set-key-for-mode 'js-mode "d" 'dumb-jump-go)
  (define-key js-mode-map (kbd "C-c C-f") 'js-format-buffer))


15.10 Typescript

(use-package typescript-mode
  :ensure t
  :defer t
  :config
  (evil-leader/set-key-for-mode 'typescript-mode "d" 'dumb-jump-go))

15.11 Rust

Clone rust source code into ~/Code/rust/src/

git clone https://github.com/rust-lang/rust.git ~/Code/rust/src/

Install dependencies

cargo install rustfmt
cargo install racer

(use-package rust-mode
  :ensure t
  :if (executable-find "rustc"))

(use-package cargo
  :ensure t
  :if (executable-find "cargo")
  :after rust-mode
  :bind (:map cargo-minor-mode-map
              ("C-c C-t" . cargo-process-test)
              ("C-c C-b" . cargo-process-build)
              ("C-c C-c" . cargo-process-run))
  :config
  (add-hook 'rust-mode-hook 'cargo-minor-mode))

(use-package racer
  :ensure t
  :if (executable-find "racer")
  :after rust-mode
  :custom
  (racer-rust-src-path "~/Code/rust/src/src")
  :hook ((rust-mode . racer-mode)
         (racer-mode . eldoc-mode)
         (racer-mode . company-mode))
  :config
  (evil-leader/set-key-for-mode 'rust-mode "d" 'racer-find-definition))

15.12 Golang

Install dependencies: godef, goimports, gocode

go get github.com/rogpeppe/godef
go get golang.org/x/tools/cmd/goimports
go get github.com/mdempsky/gocode

(use-package go-mode
  :ensure t
  :if (executable-find "go")
  :bind (:map go-mode-map
              ("C-c C-t" . go-test-current-file)
              ("C-c C-c" . go-run)
              ("C-c C-f" . gofmt))
  :config
  (setq gofmt-command "goimports")
  (evil-leader/set-key-for-mode 'go-mode "d" 'godef-jump))

(use-package company-go
  :ensure t
  :if (executable-find "gocode")
  :after go-mode
  :config
  (add-to-list 'company-backends 'company-go))

(use-package go-eldoc
  :ensure t
  :if (executable-find "gocode")
  :after go-mode
  :config
  (add-hook 'go-mode-hook 'go-eldoc-setup))

(use-package go-playground
  :ensure t
  :if (executable-find "go")
  :after go-mode
  :config
  (setq go-playground-basedir (expand-file-name "~/Code/golang/playgrounds")))

15.13 Common lisp

(defconst inferior-lisp-program (executable-find "sbcl"))

(use-package sly
  :ensure t
  :defer t)

15.14 Clojure

(defun my/clj-format-code ()
  "Format clojure code using cider commands."
  (interactive)
  (if (region-active-p)
      (cider-format-region (region-beginning) (region-end))
    (cider-format-buffer)))

(defun my/cider-repl-reset ()
  "Call (reset) in the active repl and return to the position where was called."
  (interactive)
  (save-window-excursion
    (cider-insert-in-repl "(reset)" t)))

(use-package cider
  :ensure t
  :bind (:map cider-mode-map
              ("C-c C-f" . my/clj-format-code)
              ("C-c C-r" . my/cider-repl-reset)))

(evil-leader/set-key-for-mode 'clojure-mode "d" 'cider-find-var)

(with-eval-after-load 'evil
  (evil-set-initial-state 'cider-stacktrace-mode 'emacs))

(use-package clj-refactor
  :ensure t
  :after cider
  :bind (:map clojure-mode-map
              ("C-c C-a" . cljr-add-project-dependency))
  :hook (clojure . clj-refactor))

15.15 Emacs lisp

Enable go to definition with \ d keybinding

(evil-leader/set-key-for-mode 'emacs-lisp-mode "d" 'xref-find-definitions)
(evil-leader/set-key-for-mode 'lisp-interaction-mode "d" 'xref-find-definitions)

Disable indentation with tabs for emacs-lisp-mode

(defun my/emacs-lisp-hook-setup ()
  (setq indent-tabs-mode nil))

(add-hook 'emacs-lisp-mode-hook 'my/emacs-lisp-hook-setup)

15.16 OCaml

(use-package tuareg
  :ensure t
  :defer t)

(use-package merlin
  :ensure t
  :hook ((tuareg-mode caml-mode) . merlin-mode))

(use-package merlin-eldoc
  :ensure t
  :hook ((reason-mode tuareg-mode caml-mode) . merlin-eldoc-setup))

15.17 Dart

(reformatter-define dart-format
  :program "dart"
  :args '("format")
  :group 'dart)

(defun my/dart-run-file ()
  "Execute the code of the current file."
  (interactive)
  (compile (format "dart %s" (buffer-file-name))))

(use-package dart-mode
  :ensure t
  :if (or (executable-find "dart") (executable-find "flutter"))
  :bind (:map dart-mode-map
              ("C-c C-f" . dart-format-buffer)
              ("C-c C-c" . my/dart-run-file))
  :config
  (evil-leader/set-key-for-mode 'dart-mode "d" 'xref-find-definitions))

(use-package lsp-dart
  :ensure t
  :hook (dart-mode . lsp))

15.17.1 Flutter

(defun my/flutter-goto-logs-buffer()
  "Go to buffer logs buffer."
  (interactive)
  (let ((buffer (get-buffer flutter-buffer-name)))
    (unless buffer
      (user-error "flutter is not running."))
    (switch-to-buffer buffer)
    (goto-line (point-max))))

(use-package flutter
  :ensure t
  :after dart-mode
  :bind (:map dart-mode-map
              ("C-c C-r" . #'flutter-run-or-hot-reload)
              ("C-c C-l" . #'my/flutter-goto-logs-buffer))
  :hook (dart-mode . flutter-test-mode)
  :custom
  ;; sdk path will be the parent-parent directory of flutter cli
  (flutter-sdk-path (directory-file-name
                     (file-name-directory
                      (directory-file-name
                       (file-name-directory (executable-find "flutter")))))))

15.18 F-sharp

(use-package fsharp-mode
  :ensure t
  :defer t
  :if (executable-find "dotnet")
  :config
  (evil-leader/set-key-for-mode 'fsharp-mode "d" 'fsharp-ac/gotodefn-at-point))

16 Writing

Custom functions to speed up writing process

16.1 Hugo

Insert org-link image using clipboard value, if the current file is blog/demo.org it will place the resulting image into static/images/blog/demo/image.png.

(defun my/hugo-insert-image-from-clipboard ()
  "Use clipoard image and put it in a generated images folder for the current file."
  (interactive)
  (let* ((absolute-path (buffer-file-name))
         (splitted (reverse (split-string absolute-path "/")))
         (filename (replace-regexp-in-string ".org" "" (car splitted)))
         (dir (nth 1 splitted))
         (base-image-path (concat (my/project-root) "static/images"))
         (result-image-dir (format "%s/%s/%s" base-image-path dir filename))
         (result-image-name (read-string "Filename: " "image.png"))
         (full-path-result-image (format "%s/%s" result-image-dir result-image-name)))

    (shell-command (format "mkdir -p %s" result-image-dir))
    (shell-command (format "pngpaste %s" full-path-result-image))
    (insert (format "[[file:%s]]" (car (cdr (split-string full-path-result-image "static")))))))

17 Custom functions

Manage window configurations, allows to save a "snapshot" of the current windows configuration. Also allows to restore a saved "snapshot".

(defvar my/window-snapshots '())

(defun my/save-window-snapshot ()
  "Save the current window configuration into `window-snapshots` alist."
  (interactive)
  (let ((key (read-string "Enter a name for the snapshot: ")))
    (setf (alist-get key my/window-snapshots) (current-window-configuration))
    (message "%s window snapshot saved!" key)))

(defun my/get-window-snapshot (key)
  "Given a KEY return the saved value in `window-snapshots` alist."
  (let ((value (assoc key my/window-snapshots)))
    (cdr value)))

(defun my/restore-window-snapshot ()
  "Restore a window snapshot from the window-snapshots alist."
  (interactive)
  (let* ((snapshot-name (completing-read "Choose snapshot: " (mapcar #'car my/window-snapshots)))
         (snapshot (my/get-window-snapshot snapshot-name)))
    (if snapshot
        (set-window-configuration snapshot)
      (message "Snapshot %s not found" snapshot-name))))

Manipulate frame font height.

(defun my/change-font-height (delta)
  "Use DELTA to increase/decrease the frame font height."
  (let* ((current-height (face-attribute 'default :height))
         (new-height (+ current-height delta)))
    (set-face-attribute 'default (selected-frame) :height new-height)))

(defun my/decrease-font-height ()
  "Decrease font height by 10."
  (interactive)
  (my/change-font-height -10))

(defun my/increase-font-height ()
  "Increase font height by 10."
  (interactive)
  (my/change-font-height +10))

(defun my/find-file-in-project ()
  "Custom find file function."
  (interactive)
  (if (my/project-p)
      (helm-ls-git-ls)
    (helm-for-files)))

(defun my/fold-buffer-when-is-too-big (max-lines)
  "Fold buffer is max lines if grater than as MAX-LINES."
  (if (> (count-lines (point-min) (point-max)) max-lines)
      (hs-hide-all)))

(defun my/toggle-maximize ()
  "Toggle maximization of current window."
  (interactive)
  (let ((register ?w))
    (if (eq (get-register register) nil)
        (progn
          (set-register register (current-window-configuration))
          (delete-other-windows))
      (progn
        (set-window-configuration (get-register register))
        (set-register register nil)))))

(defun my/venv-workon (name)
  "Active virtualenv NAME only is not setup yet."
  (unless pyvenv-virtual-env
    (pyvenv-workon name)))

(defun my/config-file ()
  "Open config file."
  (interactive)
  (find-file (expand-file-name "~/.emacs.d/bootstrap.org")))

(defun my/toggle-spanish-characters ()
  "Enable/disable alt key to allow insert spanish characters."
  (interactive)
  (if (eq ns-alternate-modifier 'meta)
      (setq ns-alternate-modifier nil)
    (setq ns-alternate-modifier 'meta)))

(defun my/change-font-size()
  "Change frame font size."
  (interactive)
  (let* ((size (read-number "New size: "))
         (font (format "%s %d" my/font-family size)))
    (set-frame-font font)))

(defun my/find-tag ()
  "Allow find a tag if the TAGS file exists, otherwise ask for create the file."
  (interactive)
  (if (my/project-p)
      (let
          ((tags-file-path (concat (my/project-root) "TAGS")))
        (if (f-exists-p tags-file-path)
            (helm-etags-select t)
          (if (yes-or-no-p "Do you want generate a TAGS file?")
              (progn
                (my/gen-etags-file (my/project-root))
                (helm-etags-select t)))))
    (message "You are not in a project.")))

(defun my/force-build-tags ()
  "Force the build of the TAGS file."
  (interactive)
  (if (my/project-p)
      (my/gen-etags-file (my/project-root))
    (message "You are not in a project.")))

(defun my/gen-etags-file (root-path)
  "Generate etags file for the ROOT-PATH folder."
  (let
      ((pattern (read-string "Enter pattern of files to be used: ")))
    (cd root-path)
    (shell-command (format "find . -name \"%s\" | etags -" pattern))))

Function to extract clocks from org buffer and filter them by month

(defun my/collect-clocks ()
  "Collect all the clocks of current buffer."
  (org-element-map (org-element-parse-buffer) 'clock
    (lambda (clock) clock)))

(defun my/filter-clocks-by-month (clocks month)
  "Filter CLOCKS using MONTH value."
  (seq-filter '(lambda (clock)
                 (eq (org-element-property :month-end (org-element-property :value clock)) month)) clocks))

(defun my/org-filter-clocks-report ()
  "Create a buffer with the tasks filtered by month."
  (interactive)
  (let* ((month (read-number "Insert month: "))
         (clocks (my/collect-clocks))
         (filtered-clocks (my/filter-clocks-by-month clocks month))
         (buffer (get-buffer-create "*clocks report*")))
    (switch-to-buffer buffer)
    (org-mode)
    (insert "* Report\n")
    (seq-map '(lambda (clock)
                (insert (format "CLOCK: %s\n" (org-element-property :raw-value (org-element-property :value clock))))) filtered-clocks)
    (org-clock-display)))

Copy absolute and relative path to clipboard

(defun my/copy-abs-path ()
  "Copy absolute path of the buffer to clipboard"
  (interactive)
  (if buffer-file-name
      (progn
        (kill-new buffer-file-name)
        (message (format "%s copied to clipboard" buffer-file-name)))
    (message "File not saved yet")))

(defun my/copy-relative-path ()
  "Copy relative path of the buffer to clipboard"
  (interactive)
  (if (and (my/project-p) buffer-file-name)
      (let ((path (file-relative-name buffer-file-name (my/project-root))))
        (kill-new path)
        (message (format "%s copied to clipboard" path)))
    (message "File not saved yet or not inside project")))

Create a temp file with the current buffer content and render it with eww.

(defun my/preview-buffer-in-eww ()
  "Preview buffer content in EWW."
  (interactive)
  (let* ((temp-file (make-temp-name (temporary-file-directory)))
         (path (concat temp-file ".html")))
    (write-file path)
    (kill-buffer)
    (eww-open-file path)))

Resize window: allow create a "resize mode" and use hjkl to increase/decrease width/height of the current window

(defun my/resize-window ()
  "Resize window using j k h l keys."
  (interactive)
  (let ((keys-map '((?h . evil-window-decrease-width)
                    (?j . evil-window-decrease-height)
                    (?k . evil-window-increase-height)
                    (?l . evil-window-increase-width)))
        (overlay (make-overlay (point-min) (point-max) (window-buffer))))
    (let ((is-reading t))
      (overlay-put overlay 'face '((t (:foreground "gray40"))))
      (while is-reading
        (let ((action (alist-get (read-key) keys-map)))
          (if action
              (apply action '(1))
            (setq is-reading nil)
            (delete-overlay overlay)))))))

Kill the current buffer and delete the related file

(defun my/delete-close-file ()
  "Delete the current file and kill its buffer."
  (interactive)
  (when buffer-file-name
    (delete-file buffer-file-name)
    (kill-buffer)))

Generate daily report for work.

(defun my/daily-template ()
  "Create a markdown formatter daily report."
  (interactive)
  (let* ((day (format-time-string "%A"))
         (prev-label-text (if (equal day "Monday") "Viernes" "Ayer"))
         (prev (read-string (concat prev-label-text ": ")))
         (today (read-string "Hoy: "))
         (problems (read-string "Impedimentos: ")))
    (kill-new (format "*%s*: %s\n*Hoy*: %s\n*Impedimentos*: %s" prev-label-text prev today problems))))

17.1 MacOS

Functions to open Finder using current file or current project.

(defun my/open-finder-at (path)
  "Open Finder app with the given PATH."
  (let* ((finder (executable-find "open"))
         (command (format "%s %s" finder path)))
    (shell-command command)))

(defun my/open-project-in-finder ()
  "Open current project in Finder app."
  (interactive)
  (if (my/project-p)
      (my/open-finder-at (my/project-root))
    (message "There is no active project.")))

(defun my/open-current-file-in-finder ()
  "Open current file in Finder."
  (interactive)
  (let ((file (buffer-file-name)))
    (if file
        (my/open-finder-at (file-name-directory file))
      (message "Buffer has not been saved yet!"))))

Open current file with an macOS app. Installed macOS apps will be listed using helm

(defun my/macos-open-file-with ()
  "Open current file with and macOS installed app."
  (interactive)
  (let* ((apps-list (directory-files "/Applications" nil "\\.app$"))
         (selected-app (completing-read "Choose an application: " apps-list)))
    (shell-command (format "open %s -a '%s'" (buffer-file-name) selected-app))))

Open the current file with macOS open command. This will open the file with the default app configured for the type of file.

(defun my/macos-open-current-file ()
  (interactive)
  (shell-command (format "open %s" (buffer-file-name))))

Save image from clipboard to path.

(defun my/save-image-from-clipboard ()
  "Save image from clipboard to the given path."
  (interactive)
  (unless (executable-find "pngpaste")
    (user-error "Install pngpaste to continue"))
  (let* ((path (read-file-name ""))
         (command (format "pngpaste %s" path)))
    (shell-command command)
    (kill-new path)))

Author: Erick Navarro

Created: 2021-05-15 Sat 17:29

Validate