Emacs config
Table of Contents
- 1. Vanilla Emacs setup
- 2. Theme and styles
- 3. Dired
- 4. Editor enhancements
- 4.1. Whitespace
- 4.2. Deactivate extended region in visual mode
- 4.3. Dark and transparent title bar in macOS
- 4.4. Share clipoard with OS
- 4.5. Highlight TODO, FIXME, etc
- 4.6. Auto fill mode
- 4.7. Load PATH environment
- 4.8. Editorconfig
- 4.9. Snippets
- 4.10. Wakatime
- 4.11. Highlight thing
- 4.12. Various changes
- 4.13. Reformatter
- 4.14. Vterm
- 4.15. Toggle terminal
- 4.16. iSpell
- 4.17. Tree sitter
- 5. Evil
- 6. IA models integration
- 7. Utils
- 8. Common packages
- 9. Emacs process list
- 10. Git backup
- 11. Meme
- 12. Orgmode
- 13. Denote
- 14. Latex
- 15. Git
- 16. Web
- 17. Miscellaneous
- 18. LSP
- 19. Programming languages
- 20. Writing
- 21. Custom functions
1 Vanilla Emacs setup
This is required to enable lexical binding in the resulting .el
file, which will be used by emacs at startup.
;; -*- lexical-binding: t -*-
(setq inhibit-startup-message t) (setq default-directory (expand-file-name "~/")) ;; Default font-family, this will used in all the function that manipulates font settings (defvar my/font-family "Iosevka") ;; 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")))) ;; Revert dired and other buffers when there are changes on disk (setq global-auto-revert-non-file-buffers t) ;; 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 ;; make prompt readonly (add-hook 'comint-mode-hook #'(lambda () (setq-local show-trailing-whitespace nil) (setq-local comint-prompt-read-only t))) ;; 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 14" my/font-family)))
(define-minor-mode big-font-mode "Switch between a regular font size and a presentation font size." :init-value nil :global t (if big-font-mode (progn ;; save current font size in a temp variable to be able to restore it ;; after this minor mode is disabled (setq big-font-mode--tmp (/ (face-attribute 'default :height) 10)) (set-frame-font (format "%s %d" my/font-family 26))) (set-frame-font (format "%s %d" my/font-family big-font-mode--tmp))))
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 (substitute-command-keys 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.
(with-eval-after-load 'term (define-key term-raw-map (kbd "M-]") 'next-buffer))
1.2 xref
Enable evil emacs state when entering a xref buffer
(with-eval-after-load 'xref
(add-hook 'xref-after-update-hook #'(lambda () (evil-emacs-state))))
1.3 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) ;; for elixir testing output test filename use black color which makes it ineligible (set-face-foreground 'ansi-color-bold "magenta") (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.
(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)
Custom function to select a base directory before running compilation. M-x compile
always use the base directory of the buffer from where it was called as default-directory
, this is awful when you want to run a project compilation command from a nested file buffer.
(defun my/compile () "Run compilation process but ask for a `default-directory' before." (interactive) (let ((default-directory (read-directory-name "Base directory: " (my/project-root))) (cmd (read-string "Compile command: "))) ;; we need to "export" this variable to be able to re-run `compile' command (setq compile-command cmd) (compile compile-command)))
1.4 Narrowing
(defun my/toggle-narrowing () "Toggle narrow on the selected region." (interactive) (if (buffer-narrowed-p) (widen) (if (region-active-p) (narrow-to-region (region-beginning) (region-end)) (user-error "No active selection"))))
1.5 Ediff
(with-eval-after-load 'ediff (setq ediff-split-window-function 'split-window-horizontally) ;; put ediff buffer in a buffer at the bottom instead of in a new frame (setq ediff-window-setup-function 'ediff-setup-windows-plain))
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.
This is required for GitHub notifications segment
(use-package async
:ensure t)
(use-package doom-modeline :ensure t :defer t :custom (doom-modeline-modal-icon nil) (doom-modeline-buffer-file-name-style 'relative-from-project) (doom-modeline-github t) (doom-modeline-github-interval (* 30 60)) :hook (after-init . doom-modeline-mode) (doom-modeline-mode . display-battery-mode))
2.3 Emoji support
(use-package unicode-fonts :ensure t :config (unicode-fonts-setup))
3 Dired
(with-eval-after-load "dired" (define-key dired-mode-map (kbd "C-c C-e") 'wdired-change-to-wdired-mode))
3.1 Nerd icons dired
(use-package nerd-icons-dired :ensure t :defer t :hook (dired-mode . nerd-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
Allow to visual mode work more like vim visual highlighting.
(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 Auto fill mode
Use auto-fill-mode
only for comments and only with programming buffers
(setq comment-auto-fill-only-comments t)
(add-hook 'prog-mode-hook #'(lambda ()
(auto-fill-mode 1)))
4.7 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.8 Editorconfig
(use-package editorconfig :ensure t :config (editorconfig-mode 1))
4.9 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.10 Wakatime
(use-package wakatime-mode :ensure t :if (executable-find "wakatime-cli") :init (setq wakatime-cli-path (executable-find "wakatime-cli")) :config (global-wakatime-mode))
4.11 Highlight thing
(use-package highlight-thing :ensure t :hook (prog-mode . highlight-thing-mode))
4.12 Various changes
Disable lock files
(setq create-lockfiles nil)
4.13 Reformatter
(use-package reformatter
:ensure t)
4.14 Vterm
(use-package vterm :ensure t :defer 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.15 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.16 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.17 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)
Run ispell in text nodes
(use-package tree-sitter-ispell :ensure t :defer t :bind (("C-x C-s" . tree-sitter-ispell-run-at-point)))
5 Evil
(defun my/find-file-under-cursor () "Check it the filepath under cursor is an absolute path otherwise open `project-find-file' 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)) (project-find-file)))))
(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"))) ;; Setup leader key only for `normal', `visual' and `motion' modes (evil-set-leader '(normal visual motion) (kbd "\\")) (evil-define-key nil 'global (kbd "<leader>SPC") #'(lambda () (interactive) (call-interactively #'execute-extended-command))) (evil-define-key nil 'global (kbd "<leader>a") #'(lambda () (interactive) (if (region-active-p) (my/grep-in-project (buffer-substring-no-properties (region-beginning) (region-end))) (my/grep-in-project (thing-at-point 'symbol))))) (evil-define-key nil 'global (kbd "<leader>A") 'my/grep-in-project) (evil-define-key nil 'global (kbd "<leader>ba") 'my/add-bookmark) (evil-define-key nil 'global (kbd "<leader>bb") 'my/bookmark-switch) (evil-define-key nil 'global (kbd "<leader>B") #'(lambda () (interactive) (call-interactively #'switch-to-buffer))) (evil-define-key nil 'global (kbd "<leader>c") 'vterm-toggle) (evil-define-key nil 'global (kbd "<leader>e") 'my/find-file-in-project) (evil-define-key nil 'global (kbd "<leader>f") 'find-file) (evil-define-key nil 'global (kbd "<leader>g") 'magit-status) (evil-define-key nil 'global (kbd "<leader>G") 'magit-file-dispatch) (evil-define-key nil 'global (kbd "<leader>i") 'consult-imenu) (evil-define-key nil 'global (kbd "<leader>hs") 'diff-hl-stage-current-hunk) (evil-define-key nil 'global (kbd "<leader>hk") 'diff-hl-revert-hunk) (evil-define-key nil 'global (kbd "<leader>k") 'kill-buffer) (evil-define-key nil 'global (kbd "<leader>l") 'display-line-numbers-mode) (evil-define-key nil 'global (kbd "<leader>n") 'evil-buffer-new) (evil-define-key nil 'global (kbd "<leader>N") 'my/toggle-narrowing) (evil-define-key nil 'global (kbd "<leader>pa") 'my/copy-abs-path) (evil-define-key nil 'global (kbd "<leader>pr") 'my/copy-relative-path) (evil-define-key nil 'global (kbd "<leader>q") 'consult-line) (evil-define-key nil 'global (kbd "<leader>r") 'my/replace-at-point-or-region) (evil-define-key nil 'global (kbd "<leader>R") 'recompile) (evil-define-key nil 'global (kbd "<leader>s") 'my/toggle-spanish-characters) (evil-define-key nil 'global (kbd "<leader>t") 'persp-switch) (evil-define-key nil 'global (kbd "<leader>w") 'my/toggle-maximize) (evil-define-key nil 'global (kbd "<leader>x") 'my/resize-window) (evil-define-key nil 'global (kbd "<leader>y") 'consult-yank-from-kill-ring) (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 (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-at-point-or-region () "Setup buffer replace string for word at point or active region using evil ex mode." (interactive) (let ((text (if (region-active-p) (buffer-substring-no-properties (region-beginning) (region-end)) (word-at-point)))) (evil-ex (concat "%s/" text "/")))) (use-package evil-matchit :ensure t :config (global-evil-matchit-mode 1))
6 IA models integration
Integration with different "backends", ollama
, openai
, and so on.
(use-package llm
:ensure t)
UI to interact with models, relies on llm
(use-package ellama :ensure t :custom (ellama-language "English") :config (require 'llm-ollama) (with-eval-after-load 'llm-ollama) (setopt ellama-provider (make-llm-ollama :host "localhost" :chat-model "zephyr")))
Custom functions to better management of models
(defun my/switch-ollama-provider () "Switch ollama provider by using the installed local models." (interactive) (let* ((raw-result (shell-command-to-string "ollama list | awk '{print $1}' | tail -n+2")) (choices (string-split (string-trim raw-result) "\n")) (choices (mapcar (lambda (choice) (car (string-split choice ":"))) choices)) (model (completing-read "Choose model" choices))) (setopt ellama-provider (make-llm-ollama :host "localhost" :chat-model model)) (message "Model %s configured as ollama provider." (propertize model 'face '(:foreground "magenta")))))
7 Utils
7.1 Which-key
(use-package which-key :ensure t :config (which-key-mode) (which-key-setup-minibuffer))
7.2 Auto pair
Complete parenthesis, square brackets, etc
Enable it globally and disable it just for org-mode
to avoid having a conflict with <s
(electric-pair-mode)
(add-hook 'org-mode-hook #'(lambda () (electric-pair-local-mode -1)))
7.3 Restclient
(use-package restclient :ensure t :defer t :mode (("\\.http\\'" . restclient-mode)) :bind (:map restclient-mode-map ("C-c C-h" . 'cloak-mode) ("C-c C-f" . 'json-pretty-print))) ;TODO: change to only apply json formatting when the content-type is application/json (use-package company-restclient :ensure t :after (restclient) :config (add-to-list 'company-backends 'company-restclient))
7.4 Rainbow delimiters
(use-package rainbow-delimiters :ensure t :hook (prog-mode . rainbow-delimiters-mode))
7.5 XML formatter
(reformatter-define xml-format :program "xmlformat" :group 'xml) (with-eval-after-load 'nxml-mode (define-key nxml-mode-map (kbd "C-c C-f") 'xml-format-buffer))
7.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))) (with-eval-after-load 'sql (add-hook 'sql-mode-hook 'flymake-sqlfluff-load) (add-hook 'sql-mode-hook 'flymake-mode) (define-key sql-mode-map (kbd "C-c C-f") 'my/format-sql))
7.7 SQL linter using sqlfluff
(use-package flymake-sqlfluff
:ensure t)
8 Common packages
Used in every major mode
8.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))
8.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." (when (functionp 'my/project-p) (flymake-mode 1))) (add-hook 'prog-mode-hook '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))
8.3 Direnv
Handle environment variables per buffer usiong a .envrc
file.
(use-package envrc :ensure t :config (envrc-global-mode) :bind (:map envrc-mode-map ("C-c C-h" . 'cloak-mode)))
8.4 Cloak mode
Hide values that match regex patterns in .envrc
and restclient
files
(use-package cloak-mode :ensure t :custom (cloak-mode-patterns '((envrc-file-mode . "[a-zA-Z0-9_]+[ \t]*=[ \t]*\\(.*+\\)$") (restclient-mode . "^:[^: ]+[ \t]*=[ \t]*\\(.+?\\)$"))) (cloak-mode-mask "🙈🙈🙈") :config (global-cloak-mode))
8.5 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))
8.6 Project.el
project.el
default prefix is C-x
(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 :straight (:type built-in) :bind (:map project-prefix-map ("D" . 'my/project-edit-direnv) ("d" . 'project-dired) ("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." (project-root (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 `project-find-file'." (interactive) (persp-switch (my/project-name)) (project-find-file)) (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))) ;; in case we're using eglot we shutdown its server (if (and (featurep 'eglot) (eglot-managed-p)) (eglot-shutdown (eglot-current-server))) (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)))
8.7 Completion
UI for completion
(use-package vertico :ensure t :init (vertico-mode) :custom ;; fixed height (vertico-resize nil) ;; show max 15 elements (vertico-count 15))
Load vertico-multiform
which is required for vertico-posframe
(use-package vertico-multiform :after vertico :straight nil :load-path "straight/repos/vertico/extensions/")
Vertico posframe, show all the candidates in a child-frame, it will activated only for GUI version.
(use-package vertico-posframe :ensure t :if (display-graphic-p) :init (vertico-posframe-mode 1))
Add annotations to results shown by vertico
(use-package marginalia :ensure :init (marginalia-mode))
Icons support
(use-package nerd-icons-completion :ensure t :after marginalia :config (nerd-icons-completion-mode) (add-hook 'marginalia-mode-hook #'nerd-icons-completion-marginalia-setup))
Enable better completion styles
(use-package orderless :ensure t :config (setq completion-styles '(orderless basic) completion-category-overrides '((file (styles basic partial-completion)))))
Disable orderless completion style in company to keep previous behaviour which I like, this was copied from orderless
documentation.
;; We follow a suggestion by company maintainer u/hvis: ;; https://www.reddit.com/r/emacs/comments/nichkl/comment/gz1jr3s/ (defun company-completion-styles (capf-fn &rest args) (let ((completion-styles '(basic partial-completion))) (apply capf-fn args))) (advice-add 'company-capf :around #'company-completion-styles)
Search into project source
(use-package consult
:ensure t)
Integration with yasnippets
(use-package consult-yasnippet :ensure t :defer t)
Helpers to search term at point and general search into project
(defun my/grep-in-project (&optional term) "Run grep in current project. If TERM is not nil it will be used as initial value." (interactive) (let* ((pattern (read-string "Pattern: " (or term ""))) ;; add an extra space to be able to start typing more filters (pattern (concat pattern " "))) (call-interactively #'(lambda () (interactive) (consult-ripgrep (my/project-root) pattern)))))
Integration with embark
(use-package embark :ensure t :bind ("C-;" . embark-act) :config ;; grep exported data can have a lot of white spaces so we don't want ;; them to be shown while editing their content (setq-mode-local embark-collect-mode show-trailing-whitespace nil)) (use-package embark-consult :ensure t :defer t) (defun my/edit-completing-results () "Use results origin to execute an action after export them with `embark-export'." (interactive) ;; call of `project-find-file' (when (cl-search "Find file in" (buffer-string)) (run-at-time 0 nil #'embark-export) (run-at-time 0 nil #'wdired-change-to-wdired-mode) (run-at-time 0 nil #'evil-normal-state)) ;; call of `consult-ripgrep' (when (cl-search "Ripgrep" (buffer-string)) ;; we use `run-at-time' to ensure all of these steps ;; will be executed in order (run-at-time 0 nil #'embark-export) (run-at-time 0 nil #'wgrep-change-to-wgrep-mode) (run-at-time 0 nil #'evil-normal-state))) (define-key minibuffer-mode-map (kbd "C-c C-e") #'my/edit-completing-results)
Edit grep buffer
(use-package wgrep :ensure t :custom (wgrep-auto-save-buffer t) :bind ("C-c C-c" . 'wgrep-finish-edit) ("C-c C-k" . 'wgrep-abort-changes))
9 Emacs process list
(defun my/kill-emacs-process () "Show a list of current Emacs processes and kill the selected one." (interactive) (let* ((names (mapcar #'process-name (process-list))) (process-name (completing-read "Choose process: " names))) (delete-process (get-process process-name)) (message "%s process killed" (propertize process-name 'face '(:foreground "magenta"))))) (global-set-key (kbd "C-x c p") 'my/kill-emacs-process)
10 Git backup
Save a backup on every save, also allow to recover any version of a file
(defvar my/backup-dir (expand-file-name "~/.git-backup")) (defun my/git-backup-versioning () "Save a version of the current file." (unless (featurep 'git-backup) (require 'git-backup)) (git-backup-version-file (executable-find "git") my/backup-dir '() (buffer-file-name))) (defun my/git-backup-run-action (command commit-hash) "Execute COMMAND with COMMIT-HASH using another defaults arguments." (apply command `(,(executable-find "git") ,my/backup-dir ,commit-hash ,(buffer-file-name)))) (defun my/git-backup-sort (completions) "Given COMPLETIONS define a custom sort function." (lambda (string pred action) (if (eq action 'metadata) '(metadata (display-sort-function . identity)) (complete-with-action action completions string pred)))) (defun my/git-backup () "Navigate in versions of the current file." (interactive) (unless (featurep 'git-backup) (require 'git-backup)) ;; for some reason an extra space after `%h|' is required to avoid an error when ;; the shell command is executed (let* ((candidates (git-backup-list-file-change-time (executable-find "git") my/backup-dir "%cI|%h| %ar" (buffer-file-name))) (selection (completing-read "Pick revision: " (my/git-backup-sort candidates))) (commit-hash (nth 1 (string-split selection "|"))) (action (completing-read "Choose action: " '("diff" "new buffer" "replace current buffer")))) (cond ((string-equal action "diff") (my/git-backup-run-action 'git-backup-create-ediff commit-hash)) ((string-equal action "new buffer") (my/git-backup-run-action 'git-backup-open-in-new-buffer commit-hash)) ((string-equal action "replace current buffer") (my/git-backup-run-action 'git-backup-replace-current-buffer commit-hash)) (t (message "Not valid option"))))) (use-package git-backup :ensure t :hook (after-save . my/git-backup-versioning))
11 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)))))
12 Orgmode
Configured variables:
org-latex-caption-above
puts table captions at the bottomorg-clock-persist
persists time even if emacs is closedorg-src-fontify-natively
enables syntax highlighting for code blocksorg-log-done
saves the timestamp when a task is doneorg-src-preserve-indentation
when ist
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-define-key nil org-mode-map (kbd "<leader>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)) (with-eval-after-load 'org (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) (add-hook 'org-mode-hook #'(lambda () ;; Since emacs 27 this is needed to use shortcuts like <s to create source blocks (unless (featurep 'org-tempo) (require 'org-tempo)) (org-indent-mode t))) (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) (plantuml . t) (emacs-lisp . t)))) (use-package htmlize :ensure t :after (org))
Fix error with TAB
in evil-mode
in org-mode
with org elements.
(defun my/org-tab () "Run `org-cycle' only at point of an org element." (interactive) (if (org-element-at-point) (org-cycle) (evil-jump-forward))) (with-eval-after-load 'org (define-key org-mode-map (kbd "<tab>") 'my/org-tab))
12.1 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)))
13 Denote
Note taking using denote
(use-package denote :ensure t :custom (denote-directory "~/Documents/wiki") :hook ((dired-mode . denote-dired-mode))) (defun my/wiki () "Open personal wiki and launch Dired." (interactive) (dired (expand-file-name "~/Documents/wiki")) (dired-hide-details-mode t))
14 Latex
(use-package auctex :ensure t :defer t) (use-package latex-preview-pane :ensure t :defer t)
15 Git
15.1 Git-link
Open selected region in remote repo page
(use-package git-link :ensure t :defer t)
15.2 Git modes
This pacakge includes gitignore-mode
, gitconfig-mode
and gitattributes-mode
(use-package git-modes :defer t :ensure t)
15.3 Magit
(defun my/magit-blame-quit () "Restore evil state after magit blame mode is closed." (evil-exit-emacs-state)) (use-package magit :ensure t :custom ;; restore previous window configuration after a buffer is closed (magit-bury-buffer-function 'magit-restore-window-configuration) ;; open magit status buffer in the whole frame (magit-display-buffer-function 'magit-display-buffer-fullframe-status-v1) :defer t :config (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))))
15.4 Magit delta
Use delta
tool to show diffs in magit
(use-package magit-delta :ensure t :if (executable-find "delta") :hook (magit-mode . magit-delta-mode))
15.5 Forge
(use-package forge :ensure t :after (magit closql) :config (add-hook 'forge-topic-mode-hook #'(lambda () (evil-emacs-state))))
15.6 Git diff-hl
(use-package diff-hl :ensure t :custom (diff-hl-show-staged-changes nil) ;; 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))
15.7 Timemachine
(use-package git-timemachine :ensure t :config (add-hook 'git-timemachine-mode-hook #'(lambda () (evil-emacs-state))))
15.8 Gist
(use-package gist :ensure t :defer t)
15.9 Linkode
(use-package linkode :ensure t :defer t)
16 Web
16.1 Web mode
(defun my/web-mode-hook () (emmet-mode) (rainbow-delimiters-mode-disable)) (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) ("\\.html.heex\\'" . web-mode) ("\\.hbs\\'" . web-mode)) :config (add-hook 'web-mode-hook 'my/web-mode-hook))
16.2 Emmet
(use-package emmet-mode
:ensure t)
16.3 Sass
(use-package sass-mode :ensure t :defer t)
16.4 Rainbow
(use-package rainbow-mode :ensure t :hook ((css-mode . rainbow-mode) (sass-mode . rainbow-mode) (scss-mode . rainbow-mode)))
17 Miscellaneous
(use-package writeroom-mode :ensure t) (use-package csv-mode :ensure t :defer t) (use-package jsonian :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))) (use-package flymake-yamllint :ensure t :defer t :hook (yaml-mode . flymake-mode) (yaml-mode . flymake-yamllint-setup)) (use-package yaml-pro :ensure t :defer t :hook (yaml-mode . yaml-pro-ts-mode)) (use-package hcl-mode :ensure t) ;; 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-force-searcher 'rg) (dumb-jump-selector 'completing-read))
helpful, enhance help functions
(use-package helpful :ensure t :defer t) ;; these function have autoload annotation so they will load `helpful' package when they are called ;; because we're defined just keybindings we can just use the symbol even the function is not loaded yet (global-set-key (kbd "C-h f") #'helpful-callable) (global-set-key (kbd "C-h v") #'helpful-variable) (global-set-key (kbd "C-h k") #'helpful-key)
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))) (with-eval-after-load 'evil (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)
18 LSP
(use-package eglot :ensure nil :defer t :straight (:type built-in) :bind (:map eglot-mode-map ("C-c C-d" . 'eldoc-doc-buffer) ("C-c C-s" . 'xref-find-references)) :config (setf (alist-get 'elixir-mode eglot-server-programs) `(,(expand-file-name "~/Code/oss/elixir-ls/release/language_server.sh"))))
In case we don't have eglot running we can relay on dump-jump
(defun my/goto-definition-dumb-jump-fallback () "Go to definition using eglot when is active otherwise use dumb-jump." (interactive) (if (and (featurep 'eglot) (eglot-managed-p)) (xref-find-definitions (thing-at-point 'symbol)) (dumb-jump-go)))
19 Programming languages
19.1 Shell scripts
(use-package flymake-shellcheck :ensure t :defer t :if (executable-find "shellcheck") :commands flymake-shellcheck-load :init (add-hook 'sh-mode-hook 'flymake-shellcheck-load))
19.2 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))
19.3 Python
For each virtual environment install the following packages:
pip install elpy jedi flake8 epc
For LSP support python-language-server
is required
pip python-language-server
Install flymake-ruff
(use-package flymake-ruff
:ensure t)
(reformatter-define python-ruff-format :program "ruff" :args '("format" "-") :group 'python) (reformatter-define python-sort-imports :program "ruff" :args '("--fix" "--select" "I001" "-") :group 'python) ;; we use elpy just to have `elpy-test' (use-package elpy :ensure t :defer t :custom ;; always print stdout when running tests with pytest (elpy-test-pytest-runner-command '("pytest" "-s" "-vv")) (elpy-shell-echo-input nil)) (setq python-shell-completion-native-enable nil) (defun my/ensure-elpy-is-loaded () "Check if `elpy' is loaded otherwise load it." (unless (featurep 'elpy) (require 'elpy))) (with-eval-after-load 'python (evil-define-key nil python-mode-map (kbd "<leader>d") 'my/goto-definition-dumb-jump-fallback) (define-key python-mode-map (kbd "C-c C-f") 'python-ruff-format-buffer) (define-key python-mode-map (kbd "C-c C-t") 'elpy-test) (define-key python-mode-map (kbd "C-c C-i") 'python-sort-imports-region) (add-hook 'python-mode-hook #'flymake-ruff-load) (add-hook 'python-mode-hook 'my/ensure-elpy-is-loaded))
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)))
19.4 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))
19.5 Elixir
To have support for LSP we need to compile elixir-ls and setup the generated release into eglot-server-programs
mix elixir_ls.release
In case project mix.exs
is not in root folder we need to tell elixir_ls
where is the correct location using a .dir-locals.el
file.
((elixir-mode . ((eglot-workspace-configuration . ((:elixirLS . (:projectDir "mix.exs dir/")))))))
(reformatter-define elixir-format :program "mix" :args '("format" "-") :group 'elixir) (defun my/elixir-format-buffer () "Format elixir buffers using eglot when is active otherwise use reformatter function." (interactive) ;; eglot formatter is preferred because it will use project .formatter.exs file ;; regular formatter generated by reformatted will ignore that file (if (and (featurep 'eglot) (eglot-managed-p)) (eglot-format-buffer) (elixir-format-buffer))) (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" . 'my/elixir-format-buffer)) :config (evil-define-key nil elixir-mode-map (kbd "<leader>d") 'my/goto-definition-dumb-jump-fallback))
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)) (mix-file (expand-file-name (concat (locate-dominating-file (buffer-file-name) "mix.exs") "mix.exs"))) (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))
19.6 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)))
19.7 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))
19.8 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))))
19.9 Lua
(use-package lua-mode :ensure t :bind (:map lua-mode-map ("C-c C-b" . compile) ("C-c C-f" . lua-format-buffer)))
Define formatter using StyLua
(reformatter-define lua-format :program "stylua" :args '("-") :group 'lua)
19.10 Javascript
We use default js-mode
because it has been improved in emacs 27.
19.10.1 Formattter
We need to use --stdin-filepath a.js
to tell prettier
to use js parser.
(reformatter-define js-format :program "npx" :args '("prettier" "--stdin-filepath" "a.js")) (with-eval-after-load 'js (evil-define-key nil js-mode-map (kbd "<leader>d") 'my/goto-definition-dumb-jump-fallback) (define-key js-mode-map (kbd "C-c C-f") 'js-format-buffer))
19.11 Typescript
(use-package typescript-mode :ensure t :defer t :mode "\\.tsx?\\'" :config (evil-define-key nil typescript-mode-map (kbd "<leader>d") 'my/goto-definition-dumb-jump-fallback))
19.12 Rust
Install rust analyzer, this should be installed when rustup-init
is executed but in case is not we can execute:
rustup component add rust-analyzer
Install rust source code, it is required by rust-analyzer
, in case it's not installed automatically
rustup component add rust-src
(use-package rust-mode :ensure t :if (executable-find "rustc") :config (evil-define-key nil rust-mode-map (kbd "<leader>d") 'my/goto-definition-dumb-jump-fallback))
(defun my/rust-run-file () "Compile and rust current file." (interactive) (unless (buffer-file-name) (user-error "Save file before")) (let* ((path (buffer-file-name)) (default-directory (file-name-directory path)) (filename (buffer-name)) (command (format "rustc %s && ./%s" filename (string-replace ".rs" "" filename)))) (compile command)))
19.13 Golang
Install dependencies: goimports
go install golang.org/x/tools/cmd/goimports@latest
Install gopls
to have LSP support using eglot
brew install gopls
(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)) :hook (go-mode . eglot-ensure) :config (setq gofmt-command "goimports") (evil-define-key nil go-mode-map (kbd "<leader>d") 'my/goto-definition-dumb-jump-fallback)) (use-package go-playground :ensure t :if (executable-find "go") :after go-mode :config (setq go-playground-basedir (expand-file-name "~/Code/golang/playgrounds")))
19.14 Common lisp
(defconst inferior-lisp-program (executable-find "sbcl")) (use-package sly :ensure t :defer t)
19.15 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-define-key nil clojure-mode-map (kbd "<leader>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))
19.16 Emacs lisp
Enable go to definition with \ d keybinding
(evil-define-key nil emacs-lisp-mode-map (kbd "<leader>d") 'xref-find-definitions) (evil-define-key nil lisp-interaction-mode-map (kbd "<leader>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)
Enable flymake
(add-hook 'emacs-lisp-mode-hook 'flymake-mode-on)
package-lint
, used for packages development
(use-package package-lint :ensure t :defer t)
19.17 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))
19.18 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-define-key nil dart-mode-map (kbd "<leader>d") 'xref-find-definitions))
19.18.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")))))))
19.19 F-sharp
(use-package fsharp-mode :ensure t :defer t :if (executable-find "dotnet") :config (evil-define-key nil fsharp-mode-map (kbd "<leader>d") 'fsharp-ac/gotodefn-at-point))
20 Writing
Custom functions to speed up writing process
20.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")))))))
21 Custom functions
Simple bookmarks management
(defvar my/bookmarks (make-hash-table :test 'equal)) (defun my/bookmark-switch () "Switch to selected bookmark." (interactive) (let* ((key (my/project-name)) (items (gethash key my/bookmarks (make-hash-table :test 'equal))) (options (hash-table-keys items)) (selected (completing-read "Pick buffer: " options)) (selected-buffer (gethash selected items))) (when selected-buffer (switch-to-buffer selected-buffer)))) (defun my/add-bookmark () "Add current buffer to bookmark list." (interactive) (let* ((project-key (my/project-name)) (buffer-key (buffer-name)) (items (gethash project-key my/bookmarks (make-hash-table :test 'equal)))) (puthash buffer-key (current-buffer) items) (puthash project-key items my/bookmarks) (message "%s added to bookmarks" buffer-key)))
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) (project-find-file) (call-interactively #'find-file))) (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)))
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))) (defun my/preview-buffer-in-xwidget-browser () "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) (xwidget-webkit-browse-url (format "file://%s" 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))))
Copy json text from clipboard in a new buffer and format it
(defun my/copy-and-format-json-from-clipboard () "Copy content from clipboard and format it in a new buffer." (interactive) (let ((buffer (generate-new-buffer "tmp.json"))) (with-current-buffer buffer (yank) (jsonian-mode) (json-pretty-print-buffer)) (set-window-buffer nil buffer)))
21.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 completing-read
(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)))