Emacs config
Table of Contents
- Vanilla Emacs setup
- Theme and styles
- Dired
- Editor enhancements
- Whitespace
- Deactivate extended region in visual mode
- Dark and transparent title bar in macOS
- Share clipoard with OS
- Highlight TODO, FIXME, etc
- Auto fill mode
- Load PATH environment
- Editorconfig
- Snippets
- Wakatime
- Highlight thing
- Various changes
- Reformatter
- Hydra for major modes
- Vterm
- Toggle terminal
- Jinx
- iSpell
- Tree sitter
- Evil
- IA models integration
- Utils
- Common packages
- Emacs process list
- Git backup
- Meme
- Orgmode
- Denote
- Latex
- Git
- Web
- Telegram
- Miscellaneous
- LSP
- Programming languages
- Writing
- Custom functions
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 "JetBrainsMono Nerd Font") ;; 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-face-attribute 'default nil :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) ;; disable mouse gestures to avoid scaling text by mistake (global-set-key (kbd "<pinch>") 'ignore) (global-set-key (kbd "<C-wheel-up>") 'ignore) (global-set-key (kbd "<C-wheel-down>") 'ignore)
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 ligatures, by default it has support for JetBrainsMono
.
(use-package ligature :ensure t :config ;; Enable ligatures in programming modes (ligature-set-ligatures 'prog-mode '("www" "**" "***" "**/" "*>" "*/" "\\\\" "\\\\\\" "{-" "::" ":::" ":=" "!!" "!=" "!==" "-}" "----" "-->" "->" "->>" "-<" "-<<" "-~" "#{" "#[" "##" "###" "####" "#(" "#?" "#_" "#_(" ".-" ".=" ".." "..<" "..." "?=" "??" ";;" "/*" "/**" "/=" "/==" "/>" "//" "///" "&&" "||" "||=" "|=" "|>" "^=" "$>" "++" "+++" "+>" "=:=" "==" "===" "==>" "=>" "=>>" "<=" "=<<" "=/=" ">-" ">=" ">=>" ">>" ">>-" ">>=" ">>>" "<*" "<*>" "<|" "<|>" "<$" "<$>" "<!--" "<-" "<--" "<->" "<+" "<+>" "<=" "<==" "<=>" "<=<" "<>" "<<" "<<-" "<<=" "<<<" "<~" "<~~" "</" "</>" "~@" "~-" "~>" "~~" "~~>" "%%")) (global-ligature-mode 't))
Adjust font size to screen resolution, increase font size for 4K screens
(when (and (display-graphic-p) (>= (x-display-pixel-width) 3840)) (set-face-attribute 'default nil :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-face-attribute 'default nil :font (format "%s 24" my/font-family))) (set-face-attribute 'default nil :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)))
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))
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))))
Occur
;; occur is part of replace.el file (with-eval-after-load 'replace (define-key occur-mode-map (kbd "C-c C-e") 'occur-edit-mode) ;; this will run every time search results are shown (setq occur-hook #'(lambda () (window-buffer) (select-window (get-buffer-window "*Occur*")) (evil-normal-state))))
Popper.el
Display some special buffers in a little window always at bottom
(use-package popper :ensure t :init (setq popper-reference-buffers '("\\*eldoc\\*" help-mode)) (popper-mode +1))
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))
Translate ANSI escape sequences into faces, for example to show colors.
(require 'ansi-color) (add-hook 'compilation-filter-hook 'ansi-color-compilation-filter)
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)))
Async shell commands
Helper function to run async-shell-command
with some tweaks.
(defun my/async-shell-command () "Run `async-shell-command' but at any location. By default it will use project root but this can be changed" (interactive) (let* ((default-directory (read-directory-name "Base directory: " (my/project-root))) (action-name (read-string "Action name: ")) (command (read-string "shell command: ")) (buffer-name (format "(%s)*%s*" (my/project-name) action-name))) (async-shell-command command (get-buffer-create buffer-name))))
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"))))
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))
Theme and styles
Doom theme
(use-package doom-themes :ensure t :config (load-theme 'doom-badger t))
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))
Emoji support
(use-package unicode-fonts :ensure t :config (unicode-fonts-setup))
Dired
(with-eval-after-load "dired" (add-hook 'dired-mode-hook 'evil-emacs-state) (define-key dired-mode-map (kbd "C-c C-e") 'wdired-change-to-wdired-mode))
Dired preview
(use-package dired-preview :ensure t :after dired :config (define-key dired-mode-map (kbd "P") #'(lambda () (interactive) (dired-preview-display-file (dired-file-name-at-point)))))
Nerd icons dired
(use-package nerd-icons-dired :ensure t :defer t :hook (dired-mode . nerd-icons-dired-mode))
Dired subtree
(use-package dired-subtree :ensure t :after dired :config (define-key dired-mode-map (kbd "<tab>") 'dired-subtree-toggle))
Editor enhancements
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))
Deactivate extended region in visual mode
Allow to visual mode work more like vim visual highlighting.
(set-face-attribute 'region nil :extend nil)
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)))
Share clipoard with OS
(use-package pbcopy
:ensure t)
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)
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)))
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)))
Editorconfig
(if (>= emacs-major-version 30) (use-package editorconfig :straight (:type built-in) :config (editorconfig-mode 1)) (use-package editorconfig :ensure t :config (editorconfig-mode 1)))
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))
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))
Highlight thing
(use-package highlight-thing :ensure t :hook (prog-mode . highlight-thing-mode))
Various changes
Disable lock files
(setq create-lockfiles nil)
Reformatter
(use-package reformatter
:ensure t)
Hydra for major modes
(use-package major-mode-hydra :ensure t :config (with-eval-after-load 'evil (evil-define-key nil 'global (kbd "<leader>\\") #'(lambda () (interactive) (major-mode-hydra))))) (use-package hydra-posframe :straight (hydra-posframe :type git :host github :repo "Ladicle/hydra-posframe") :hook (after-init . hydra-posframe-mode))
Vterm
Function to search into zsh history
(defun my/select-from-zsh-history () "Selectt one option from ~/.zsh_history file." (with-temp-buffer (insert-file-contents (expand-file-name "~/.zsh_history")) (let* ((raw-content (buffer-substring-no-properties (point-min) (point-max))) (lines (string-split raw-content "\n")) (choices (mapcar (lambda (line) (second (string-split line ";"))) lines))) (completing-read "Select command: " choices)))) (defun my/insert-from-zsh-history () "Search into zsh history and insert the selected choice into buffer." (interactive) (when-let ((selected-choice (my/select-from-zsh-history))) (vterm-insert selected-choice)))
(use-package vterm :ensure t :defer t :hook (vterm-mode . (lambda () (setq-local show-trailing-whitespace nil))) :mode-hydra (vterm-mode (:title (concat (nerd-icons-icon-for-buffer) " Vterm commands")) ("History" (("i" my/insert-from-zsh-history "Insert command from history")))) :custom (vterm-module-cmake-args "-DUSE_SYSTEM_LIBVTERM=yes") (vterm-always-compile-module t))
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))
Jinx
This require to have enchant library to be able to compile and load a module
(use-package jinx :ensure t :hook (emacs-startup . global-jinx-mode) :config (define-key jinx-mode-map (kbd "M-\\") 'jinx-correct))
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)))
Tree sitter
Incremental code parsing for better syntax highlighting
(use-package treesit-auto :ensure t :custom (treesit-auto-install t) :config (global-treesit-auto-mode))
Run ispell in text nodes
(use-package treesit-ispell :ensure t :defer t :bind (("C-x C-s" . treesit-ispell-run-at-point)))
Set maximum value for font-locking
in treesit
native syntax highlighting
(with-eval-after-load 'treesit (setq treesit-font-lock-level 4))
Install combobulate
just to have combobulate-query-builder
is like tree-sitter-query-builder
but using native treesit
package.
(use-package combobulate :straight (combobulate :type git :host github :repo "mickeynp/combobulate") :commands (combobulate-query-builder))
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) evil-want-keybinding nil ;; 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) (define-key evil-motion-state-map "gd" 'my/goto-definition-dumb-jump-fallback) (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") '(lambda () (interactive) (save-excursion) (with-current-buffer "*compilation*" (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-commentary :ensure t :after (evil) :config (evil-commentary-mode)) (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))
Evil collection
Allow to use default terminal keybinding in vterm
without losing evil features
(use-package evil-collection :ensure t :config (evil-collection-init '(vterm)))
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")))))
Utils
Which-key
(if (>= emacs-major-version 30) (use-package which-key :straight (:type built-in) :config (which-key-mode) (which-key-setup-minibuffer)) (use-package which-key :ensure t :config (which-key-mode) (which-key-setup-minibuffer)))
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)))
Restclient
(use-package restclient :ensure t :defer t :mode (("\\.http\\'" . restclient-mode)) :mode-hydra (restclient-mode (:title (concat (nerd-icons-icon-for-buffer) " restclient commands")) ("Format" ;; TODO: change to only apply json formatting when the content-type is application/json (("f" jsonian-format-region)) "Secrets" (("h" cloak-mode "Toggle cloak" :toggle t)) "Request" (("r" restclient-http-send-current-stay-in-window "Execute request") ("c" restclient-copy-curl-command "Copy CURL command"))))) (use-package company-restclient :ensure t :after (restclient) :config (add-to-list 'company-backends 'company-restclient))
Rainbow delimiters
(use-package rainbow-delimiters :ensure t :hook (prog-mode . rainbow-delimiters-mode))
XML formatter
(with-eval-after-load 'reformatter (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))
SQL formatter
Install pgformatter
using homebrew brew install pgformatter
(with-eval-after-load 'reformatter (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))
SQL linter using sqlfluff
(use-package flymake-sqlfluff
:ensure t)
Common packages
Used in every major mode
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))
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))
Direnv
Handle environment variables per buffer using a .envrc
file.
(use-package envrc :ensure t :config (envrc-global-mode))
Hydra commands, this have to be defined this way instead of using :mode-hydra
inside use-package
because
for some reason it mess up with environment variables loading
(with-eval-after-load 'major-mode-hydra (major-mode-hydra-define envrc-file-mode (:title (concat (nerd-icons-icon-for-buffer) " envrc commands")) ("Secrets" (("h" cloak-mode "Toggle cloak" :toggle t)))))
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))
Avy
(use-package avy :ensure t :config (with-eval-after-load 'evil (define-key evil-normal-state-map (kbd "SPC SPC") 'avy-goto-char-2)))
perspective.el
(use-package perspective :ensure t :custom ;; disable warnings (persp-suppress-no-prefix-key-warning t) :config (persp-mode) ;; 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))
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)))
Completion
UI for completion
(use-package vertico :ensure t :hook (after-init . vertico-mode) :custom ;; fixed height (vertico-resize nil) ;; show max 15 elements (vertico-count 15) :config ;; `C-;' will open embark and `o' with execute `find-file-other-window' (define-key vertico-map (kbd "C-<return>") (kbd "C-; o")))
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 :config ;; Use consult for completion inside minibuffer, for example when ;; searching for a file. (setq completion-in-region-function #'consult-completion-in-region))
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)))))
Switch fonts like consult-theme
(defun my/consult-font (font) "Replace current font with FONT from `font-family-list'." (interactive (list (let ((saved-font (symbol-name (font-get (face-attribute 'default :font) :family)))) (consult--read (font-family-list) :prompt "Font: " :require-match t :state (lambda (action font) (pcase action ('return (my/consult-font (or font saved-font))) ((and 'preview (guard font)) (my/consult-font font)))) )))) (when font ;; size doesn't change during scrolling so we can reuse that to ;; configure new selected font (set-face-attribute 'default nil :font (format "%s %d" font (font-get (face-attribute 'default :font) :size)))))
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))
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)
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))
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)))))
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)))) (with-eval-after-load 'evil (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 ;; use tectonic for export to PDF org-latex-pdf-process '("tectonic -X compile --outdir=%o -Z shell-escape %f") 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))
Org modern
(use-package org-modern :ensure t :custom (org-modern-fold-stars '(("▶" . "▼") ("▷" . "▽") ("▹" . "▿") ("▸" . "▾"))) :config (with-eval-after-load 'org (global-org-modern-mode)))
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)))
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))
Latex
(use-package auctex :ensure t :defer t) (use-package latex-preview-pane :ensure t :defer t)
Git
Git-link
Open selected region in remote repo page
(use-package git-link :ensure t :defer t)
Git modes
This pacakge includes gitignore-mode
, gitconfig-mode
and gitattributes-mode
(use-package git-modes :defer t :ensure t)
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))))
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))
Forge
(use-package forge :ensure t :after (magit closql) :config (add-hook 'forge-topic-mode-hook #'(lambda () (evil-emacs-state))))
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))
Timemachine
(use-package git-timemachine :ensure t :config (add-hook 'git-timemachine-mode-hook #'(lambda () (evil-emacs-state))))
Gist
(defun my/gist () "Create a public gist." (interactive) (my/create-gist t)) (defun my/private-gist () "Create a private gist." (interactive) (my/create-gist :false)) (defun my/create-gist (public) "Create a gist, given PUBLIC value it will set as private or public." (let* ((oauth-token (my/gist-get-oauth-token)) (content (my/gist-get-content)) (payload (make-hash-table)) (filename (or (and (buffer-file-name) (file-name-nondirectory (buffer-file-name))) (buffer-name))) (files (make-hash-table)) (file (make-hash-table))) (puthash "description" "" payload) (puthash "public" public payload) (puthash "content" content file) (puthash filename file files) (puthash "files" files payload) (let ((url-request-method "POST") (url-request-extra-headers `(("Content-Type" . "application/json") ("User-Agent" . "gist.el") ("Authorization" . ,(concat "Bearer " oauth-token)))) (url-request-data (json-encode payload))) (url-retrieve "https://api.github.com/gists" 'my/gist-handle-response)))) (defun my/gist-handle-response (response) "Process content of RESPONSE and extract link." (let* ((status-line (buffer-substring-no-properties (line-beginning-position) (line-end-position))) (status-code (nth 1 (split-string status-line " ")))) (unless (string-equal status-code "201") (user-error "There was a problem with GitHub API, try again later")) (search-forward "\n\n") (let* ((raw-response (buffer-substring-no-properties (point) (point-max))) (data (json-parse-string raw-response)) (gist-link (gethash "html_url" data))) (kill-new gist-link) (message "Paste created: %s" gist-link)))) (defun my/gist-get-oauth-token () "Fetch oauth token from ~/.authinfo." (let ((entry (car (auth-source-search :host "gist")))) (unless entry (user-error "GitHub oauth token must be in ~/.authinfo using 'gist' as host")) (funcall (plist-get entry :secret)))) (defun my/gist-get-content () "Create payload using current region or the whole buffer." (if (region-active-p) (buffer-substring-no-properties (region-beginning) (region-end)) (buffer-substring-no-properties (point-min) (point-max))))
Linkode
(use-package linkode :ensure t :defer t)
Web
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) ("\\.hbs\\'" . web-mode)) :config (add-hook 'web-mode-hook 'my/web-mode-hook))
Emmet
(use-package emmet-mode
:ensure t)
CSS mode
(use-package css-ts-mode :straight (:type built-in) :mode "\\.css\\'" :defer t)
Rainbow
(use-package rainbow-mode :ensure t :hook ((css-ts-mode . rainbow-mode) (sass-mode . rainbow-mode) (heex-ts-mode . rainbow-mode) (scss-mode . rainbow-mode)))
Telegram
In case on error when compiling tdlib
the first time we need to specify where it's installed, for macOS
we can specify:
(telega-server-libs-prefix "/opt/homebrew/Cellar/tdlib/HEAD-fd3154b")
The last part can change depending of the version installed
(use-package telega :ensure t :defer t :custom ;; enable markdown for code snippets (telega-chat-input-markups '("markdown2" "org" nil)) ;; use vertico for completion (telega-completing-read-function 'completing-read) :config ;; use shift enter to make multi line messages and enter to send it (define-key telega-chat-mode-map (kbd "S-<return>") #'newline) ;; disable copy message link when moving over text with evil-mode (define-key telega-msg-button-map (kbd "l") nil) ;; avoid showing blank spaces highlighted (add-hook 'telega-chat-mode-hook #'(lambda () (setq-local show-trailing-whitespace nil)))) (defun my/start-telega () "Start `telega' inside a new perspective and activate 'telega-mode-line-mode'" (interactive) (persp-switch "*telega*") (telega) (telega-mode-line-mode))
Miscellaneous
(use-package writeroom-mode :ensure t) (use-package csv-mode :ensure t :defer t) (defun my/json-format () "Format region or buffer." (interactive) (if (region-active-p) (jsonian-format-region (region-beginning) (region-end)) (jsonian-format-region (point-min) (point-max)))) (use-package jsonian :ensure t :defer t :mode (("\\.json\\'" . jsonian-mode)) :mode-hydra (jsonian-mode (:title (concat (nerd-icons-icon-for-buffer) " JSON commands")) ("Format" (("f" my/json-format))))) (use-package request :ensure t :defer t) (use-package graphql-mode :ensure t :defer t) (with-eval-after-load 'reformatter (reformatter-define terraform-format :program "terraform" :args '("fmt" "-") :group 'terraform)) (use-package terraform-mode :ensure t :defer t :bind (:map terraform-mode-map ("C-c C-f" . 'terraform-format-buffer))) (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)))) (defun my/k8s-delete () "Delete current yaml file to the current kubernetes context." (interactive) (let ((default-directory (file-name-directory buffer-file-name))) (compile (format "kubectl delete -f %s" buffer-file-name)))) (use-package yaml-ts-mode :straight (:type built-in) :mode "\\.ya?ml\\'" :mode-hydra ((:title (concat (nerd-icons-icon-for-buffer) " YAML Commands")) ("Kubernetes" (("a" my/k8s-apply) ("k" my/k8s-delete))))) (use-package flymake-yamllint :ensure t :defer t :hook (yaml-ts-mode . flymake-mode) (yaml-ts-mode . flymake-yamllint-setup)) (use-package yaml-pro :ensure t :defer t :hook (yaml-ts-mode . yaml-pro-ts-mode) :config (define-key yaml-pro-ts-mode-map (kbd "C-c C-f") 'yaml-pro-format) ;; this binding conflicts with org indirect mode (define-key yaml-pro-ts-mode-map (kbd "C-c '") nil)) (use-package hcl-mode :ensure t) ;; Used for gherkin files (.feature) (use-package feature-mode :ensure t :defer t) (use-package toml-ts-mode :straight (:type built-in) :mode "\\.toml\\'" :defer t) (use-package slint-mode :ensure t :hook (slint-mode . eglot-ensure) :mode-hydra ((:title (concat (nerd-icons-icon-for-buffer) " Slint Commands")) ("Format" (("f" eglot-format-buffer))))) (use-package nix-mode :ensure t :defer t :custom (nix-nixfmt-bin "nixpkgs-fmt") :mode-hydra ((:title (concat (nerd-icons-icon-for-buffer) " Nix commands")) ("Format" (("f" nix-format-buffer)))) :mode "\\.nix\\'") (use-package d2-mode :ensure t :defer t :mode "\\.d2\\'") (use-package ob-d2 :defer t :ensure t :config (with-eval-after-load 'org (org-babel-do-load-languages 'org-babel-load-languages '((d2 . t))))) (use-package mermaid-mode :ensure t :defer t) (defun my/preview-mermaid () "Render region inside a webit embebed browser." (interactive) (unless (region-active-p) (user-error "Select a region first")) (let* ((path (concat (make-temp-file (temporary-file-directory)) ".html")) (mermaid-code (buffer-substring-no-properties (region-beginning) (region-end)))) (save-excursion (with-temp-buffer (insert "<body> <pre class=\"mermaid\">") (insert mermaid-code) ;; js script copied from mermaid documentation (insert "</pre> <script type=\"module\"> import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true }); </script> </body>") (write-file path))) (xwidget-webkit-browse-url (format "file://%s" path)))) (use-package markdown-mode :ensure t :defer t :custom (markdown-fontify-code-blocks-natively t) ;; use different sizes for headings, like org-mode (markdown-header-scaling t) :config (add-hook 'markdown-mode-hook #'(lambda () (setq-local fill-column 120) (auto-fill-mode 1)))) (use-package edit-indirect :ensure t :defer t) (use-package dockerfile-ts-mode :straight (:type built-in) :defer t :mode (("\\Dockerfile\\'" . dockerfile-ts-mode) ("\\.dockerignore\\'" . dockerfile-ts-mode))) (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)
websockets client
(use-package websocket :ensure t :defer t)
LSP
(use-package eglot :ensure nil :defer t :straight (:type built-in) :bind (:map eglot-mode-map ("C-c C-d" . 'eldoc-doc-buffer) ("<M-return>" . 'eglot-code-actions) ("C-c C-s" . 'xref-find-references)) :config ;; tailwind LSP support (add-to-list 'eglot-server-programs '(((heex-ts-mode :language-id "html") (web-mode :language-id "html")) . ("tailwindcss-language-server" "--stdio"))) ;; elixir LSP server ;; we first look up for a lexical binary, this will be in the case ;; we're using nix, otherwise we relay on a cloned repository which ;; previously was compiled (setf (alist-get 'elixir-ts-mode eglot-server-programs) `(,(if (executable-find "lexical") "lexical" (expand-file-name "~/Code/oss/lexical/release/bin/start_lexical.sh")))))
In case we don't have eglot running we can relay on dumb-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)) (if (member major-mode '(emacs-lisp-mode lisp-interaction-mode)) (xref-find-definitions (thing-at-point 'symbol)) (dumb-jump-go))))
Programming languages
Shell scripts
(use-package flymake-shellcheck :ensure t :defer t :if (executable-find "shellcheck") :commands flymake-shellcheck-load :init (add-hook 'bash-ts-mode-hook 'flymake-shellcheck-load))
Bash formatter using shfmt
(with-eval-after-load 'reformatter (reformatter-define sh-format :program "shfmt" :args '("-i" "2" "-") :group 'sh)) (with-eval-after-load 'sh-script (define-key bash-ts-mode-map (kbd "C-c C-f") 'sh-format-buffer))
C
clang-format
is required for this, we can install it with brew install clang-format
(with-eval-after-load 'reformatter (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))
Python
For LSP support pyright
is required
brew install pyright
Install flymake-ruff
(use-package flymake-ruff
:ensure t)
Generate a pyrightconfig.json
file
(defun my/gen-pyright-config-file () "Generate a configuration file for PyRight." (interactive) (let* ((input-dir (read-directory-name "Virtualenv directory: ")) (venv-path (file-name-parent-directory input-dir)) (venv (file-name-base (replace-regexp-in-string "/$" "" input-dir))) (data (make-hash-table))) (puthash "venvPath" venv-path data) (puthash "venv" venv data) (with-temp-buffer (insert (json-encode data)) (write-file (file-name-concat venv-path "pyrightconfig.json")))))
(with-eval-after-load 'reformatter (reformatter-define python-ruff-format :program "ruff" :args '("format" "-") :group 'python) (reformatter-define python-sort-imports :program "ruff" :args '("check" "--fix" "--select" "I001" "-") :group 'python)) (setq python-shell-completion-native-enable nil) (use-package python-ts-mode :straight (:type built-in) :defer t :mode "\\.py\\'" :mode-hydra ((:title (concat (nerd-icons-icon-for-buffer) " Python commands")) ("Format" (("f" python-ruff-format-buffer) ("si" python-sort-imports-region)) "Testing" (("tt" pythontest-test-at-point) ("tf" pythontest-test-file) ("ta" pythontest-test-all)))) :hook (python-ts-mode . flymake-ruff-load))
Testing package
(use-package pythontest :ensure t :defer t :custom (pythontest-pytest-command "pytest -s -vv"))
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)))
Erlang
(use-package erlang-ts-mode :straight (erlang-ts-mode :type git :host github :repo "sebastiw/erlang-ts-mode"))
Elixir
LSP server building
To have support for LSP we need to compile lexical and setup the generated release into eglot-server-programs
. lexical must use minimal versions so it can be used by more types of projects, recommended versions are:
elixir: 1.13.4-otp-24 erlang: 24.3.4.12
Prepare release of lexical
MIX_ENV=prod mix package --path release
Compilation integration
Add regex to match mix test
execution output and be able to navigate between errors.
(with-eval-after-load 'compile (push 'mix compilation-error-regexp-alist) (push '(mix "^\\ \\ \\ \\ \\ \\([a-zA-Z/_/.]+\\):\\([0-9]+\\)$" 1 2) compilation-error-regexp-alist-alist))
Config
(with-eval-after-load 'reformatter (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-ts-mode :straight (:type built-in) :mode (("\\.ex\\'" . elixir-ts-mode) ("\\.exs\\'" . elixir-ts-mode) ("\\mix.lock\\'" . elixir-ts-mode)) :mode-hydra ((:title (concat (nerd-icons-icon-for-buffer) " Elixir Commands")) ("Format" (("f" my/elixir-format-buffer)) "Testing" (("tt" my/mix-run-test-at-point) ("tf" my/mix-run-test-file) ("ta" my/mix-run-test-all)))))
heex
templates support
(defun my/heex-wrap-text-in-get-text () "Convert region content into a tag using gettext." (interactive) (unless (region-active-p) (user-error "Must select some text first")) (save-excursion (let* ((text (buffer-substring-no-properties (region-beginning) (region-end))) (new-text (concat "<%= " "gettext(\"" text "\") %>"))) (kill-region (region-beginning) (region-end)) (insert new-text))))
(use-package heex-ts-mode :ensure t :hook (heex-ts-mode . emmet-mode) :mode-hydra ((:title (concat (nerd-icons-icon-for-buffer) " Heex Commands")) ("Format" (("f" my/heex-format-file)) "Text" (("wg" my/heex-wrap-text-in-get-text)))))
Set up hydra commands
(defun my/heex-format-file () "Format heex file using mix command." (interactive) ;; avoid to show *Async-Shell-Command* buffer (let* ((display-buffer-alist (list (cons "\\*Async Shell Command\\*.*" (cons #'display-buffer-no-window nil)))) (mix-file (expand-file-name (concat (locate-dominating-file (buffer-file-name) "mix.exs") "mix.exs"))) (default-directory (file-name-directory mix-file))) (async-shell-command (format "mix format %s" (buffer-file-name)) nil) (revert-buffer (current-buffer) :ignore-auto :no-confirm)))
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) ;; we need to get absolute path of mix binary because when running ;; on nix this path will be different per project, version, etc (let* ((mix-binary (executable-find "mix")) (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 %s test %s" mix-env mix-binary current-file))) ((string-equal scope "at-point") (compile (format "%s %s test %s:%s" mix-env mix-binary current-file current-line))) (t (compile (format "%s %s test" mix-env mix-binary)))))) (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))
(defun my/phx-insert-hero-icon () "Insert icon component in a Elixir Phoenix application." (interactive) (let* ((base-dir (expand-file-name (locate-dominating-file (buffer-file-name) "assets"))) (icons-dir (format "%sassets/vendor/heroicons/optimized/20/solid/" base-dir)) (option (completing-read "Select icon: " (directory-files icons-dir nil "svg"))) (no-extension (string-replace ".svg" "" (string-trim option) ))) (insert (format "<.icon name=\"hero-%s\" />" no-extension))))
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)))
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))
Haskell
Install haskell binaries hlint
and hindent
and make sure ~/.local/bin/
is loaded in PATH
.
stack install hlint stack install hindent
(with-eval-after-load 'reformatter (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))))
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
(with-eval-after-load 'reformatter (reformatter-define lua-format :program "stylua" :args '("-") :group 'lua))
Javascript
Linter using biome
(use-package flymake-biome
:ensure t)
(use-package js-ts-mode :straight (:type built-in) :defer t :mode "\\.jsx?\\'" :mode-hydra ((:title (concat (nerd-icons-icon-for-buffer) " Javascript Commands")) ("Format" (("f" js-format-buffer)))) :hook (js-ts-mode . flymake-biome-load))
Formattter
We need to use --stdin-file-path a.js
to tell biome
to use stdin
.
(with-eval-after-load 'reformatter (reformatter-define js-format :program "biome" :args '("format" "--stdin-file-path" "a.js" "--javascript-formatter-indent-style" "space")))
Typescript
(use-package typescript-ts-mode :straight (:type built-in) :defer t :mode "\\.tsx?\\'")
Zig
(use-package zig-mode :ensure t :custom (zig-format-on-save nil))
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
(with-eval-after-load 'reformatter (reformatter-define rust-format :program "rustfmt")) (use-package rust-ts-mode :straight (:type built-in) :mode "\\.rs\\'" :mode-hydra ((:title (concat (nerd-icons-icon-for-buffer) " Rust Commands")) ("Format" (("f" rust-format-buffer)))))
(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)))
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-ts-mode :straight (:type built-in) :hook (go-ts-mode . eglot-ensure) :mode (("\\.go\\'" . go-ts-mode)) :mode-hydra ((:title (concat (nerd-icons-icon-for-buffer) " Go Commands")) ("Format" (("f" gofmt)) "Execute" (("r" go-run)) "Testing" (("tt" go-test-current-test) ("tf" go-test-current-file) ("ta" go-test-current-project)))) :config ;; by default tab width is 8, that's too much space so we define 4 ;; only for go buffers (add-hook 'go-ts-mode-hook #'(lambda () (setq-local tab-width 4))) (setq gofmt-command "goimports")) ;; we install this package to have access to their helper functions (use-package go-mode :ensure t :after go-ts-mode) (use-package gotest :ensure t :after go-ts-mode)
Common lisp
(defconst inferior-lisp-program (executable-find "sbcl")) (use-package sly :ensure t :defer t)
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))) (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))
Emacs lisp
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)
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))
Dart
(with-eval-after-load 'reformatter (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)))
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")))))))
Writing
Custom functions to speed up writing process
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")))))))
Insert docsy link
(defun my/docsy-insert-ref () "Insert a link using docsy ref helper." (interactive) (let* ((filename (read-file-name "Select file: " (my/project-root))) (prefix-to-remove (concat (my/project-root) "content/")) (relative-path (string-replace prefix-to-remove "" filename))) (insert (format "{{< ref \"%s\" >}}" relative-path))))
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 nil :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))) ;; used to store all the configuration per perspective (defvar my/toggle-window-configurations (make-hash-table)) (defun my/toggle-maximize () "Toggle maximization of current window." (interactive) (let* ((key (persp-name (persp-curr))) (value (gethash key my/toggle-window-configurations))) (if (eq value nil) (progn (puthash key (current-window-configuration) my/toggle-window-configurations) (delete-other-windows)) (progn (set-window-configuration value) (puthash key nil my/toggle-window-configurations))))) (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: "))) (set-face-attribute 'default nil :font (format "%s %d" my/font-family size))))
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)))
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) (jsonian-format-region (point-min) (point-max))) (set-window-buffer nil buffer)))
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)))