Skip to content

Latest commit

 

History

History
2116 lines (2051 loc) · 81.7 KB

config.org

File metadata and controls

2116 lines (2051 loc) · 81.7 KB

GNU Emacs Configuration

Table of Content

About

My personal emacs configuration.

This configuration is inspired by the following repositories:

Core

This section contains the core packages and configurations needed to get started with GNU Emacs.

Defaults

Sensible default values that I use.

(setq user-emacs-directory (expand-file-name (format "%s/emacs" "~/.config")))
(setq-default
 initial-major-mode 'text-mode                    ; Start *scratch* in text mode
 ad-redefinition-action 'accept                   ; Silence warnings for redefinition
 byte-compile-warnings '(cl-functions)            ; Remove pesky CL warning
 cursor-in-non-selected-windows nil               ; Hide the cursor in inactive windows
 display-time-default-load-average nil            ; Don't display load average
 help-window-select t                             ; Focus new help windows when opened
 load-prefer-newer t                              ; Prefer the newest version of a file
 read-process-output-max (* 1024 1024)            ; Increase the amount of data reads from the process
 scroll-conservatively most-positive-fixnum       ; Always scroll by one line
 select-enable-clipboard t                        ; Merge system's and Emacs' clipboard
 user-full-name "Sohaib Mahmood"                  ; Set the full name of the current user
 user-mail-address "[email protected]"     ; Set the email address of the current user
 find-file-visit-truename t                       ; Always follow the symlinks
 view-read-only t                                 ; Always open read-only buffers in view-mode
 save-interprogram-paste-before-kill t            ; Save to kill-ring copying something from outside emacs
 confirm-kill-processes nil                       ; Don't prompt about sub-processes when exiting emacs
 confirm-kill-emacs 'y-or-n-p)                    ; Confirm to kill session
(fset 'yes-or-no-p 'y-or-n-p)                     ; Replace yes/no prompts with y/n
;; Hard set emacs configuration directory

Functions

Some useful global functions that we will use across the configuration

(defun emacs.d (path)
  "Return the full path pointing to user-emacs-directory"
  (expand-file-name path user-emacs-directory))
(defun my/mkdir (dir-path)
  "Make directory in DIR-PATH if it doesn't exist."
  (unless (file-exists-p dir-path)
    (make-directory dir-path t)))
(defun my/location ()
  "Return 'home' if system-name starts with 'sm-', otherwise return 'work'."
  (if (string-match-p "^sm-" (system-name))
      "home"
    "work"))
(defun my/reload-config ()
  "Reload init file, which will effectively reload everything"
  (interactive)
  (load-file (expand-file-name "init.el" user-emacs-directory)))
(global-set-key (kbd "<f5>") 'my/reload-config)

(defun my/revert-buffer-no-confirm ()
  "Revert buffer without confirmation."
  (interactive)
  (revert-buffer :ignore-auto :noconfirm))

Packages

To manage packages, I use the use-package + straight combo.

Package Sources

To install packages, it is useful to configure the package sources.

(setq package-archives '(("elpa" . "https://elpa.gnu.org/packages/")
                         ("melpa" . "https://melpa.org/packages/")
                         ("nongnu" . "https://elpa.nongnu.org/nongnu/")))

Package Management

Contrary to what some users might think, use-package is not a package manager. To download and use packages that are not available in package sources, I use straight replacing the default package.el.The snippet below takes care of installing straight.

;; Disable package.el in favor of straight.el
(setq package-enable-at-startup nil)
;; Bootstrap straight
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name
        "straight/repos/straight.el/bootstrap.el"
        (or (bound-and-true-p straight-base-dir)
            user-emacs-directory)))
      (bootstrap-version 7))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

Package Configuration

To manage the package configurations with use-package, you must install it with the following code snippet.

;; Integrates `straight' directly into the `use-package' package through the
;; `:straight' expression.
(straight-use-package 'use-package)
(setq use-package-always-ensure nil)
(setq use-package-verbose 'debug)
;; From this point on we should be able to use `use-package
(use-package straight
  :custom
  (straight-use-package-by-default t)
  :config
  (use-package delight :ensure t))

Libraries

A useful Emacs string manipulation library.

(use-package s)

Environment

By default not all environment variables are copied to Emacs. This package ensures proper syncronization between the two.

(use-package exec-path-from-shell
  :custom
  (epg-pinentry-mode 'loopback)
  (exec-path-from-shell-variables '("PATH" "SHELL" "GOPATH"))
  :config
  (setenv "SSH_AUTH_SOCK" (string-chop-newline (shell-command-to-string "gpgconf --list-dirs agent-ssh-socket")))
  (exec-path-from-shell-initialize))

Having a good terminal emulator is vital to my flow of using Emacs. I found the eat package to be the best in this regard.

(use-package eat
  :config
  (add-hook 'eat-mode-hook (lambda () (setq-local global-hl-line-mode nil)))
  (define-key eat-semi-char-mode-map (kbd "M-o") nil)
  (define-key eat-semi-char-mode-map (kbd "M-u") nil)
  :bind
  ("C-c t" . eat))

Authentication

I use a GPG key stored in a Yubikey for most of my authentication

(use-package epa-file
  :straight (:type built-in)
  :init
  (epa-file-enable))

XDG Base Directory Specification

To keep the user’s home and the ~/.config/emacs folder as clean as possible, I follow the XDG base directory specification. Be careful that GNU Emacs will not create the appropriate folders if they do not exist. Therefore, it is necessary to create them yourself.

(defvar xdg-state (getenv "XDG_STATE_HOME")
  "The XDG bin base directory.")

(defvar xdg-cache (getenv "XDG_CACHE_HOME")
  "The XDG cache base directory.")

(defvar xdg-config (getenv "XDG_CONFIG_HOME")
  "The XDG config base directory.")

(defvar xdg-data (getenv "XDG_DATA_HOME")
  "The XDG data base directory.")

(defvar xdg-lib (getenv "XDG_LIB_HOME")
  "The XDG lib base directory.")

No Littering

The default paths used to store configuration files and persistent data are not consistent across Emacs packages. This isn’t just a problem with third-party packages but even with built-in packages. The following package helps sort that out.

 (use-package no-littering
	:init
	(setq no-littering-etc-directory (expand-file-name (format "%s/emacs/etc/" xdg-data)))
	(setq no-littering-var-directory (expand-file-name (format "%s/emacs/var/" xdg-data)))
	:config
	(let ((dir (expand-file-name (format "%s/emacs/etc/" xdg-cache))))
	  (my/mkdir dir)
	  (setq url-cookie-file dir))
	(setq custom-file (no-littering-expand-etc-file-name "custom.el"))
	(when (file-exists-p custom-file) (load custom-file))
	(let ((dir (no-littering-expand-var-file-name "lock-files/")))
	  (my/mkdir dir)
	  (setq lock-file-name-transforms `((".*" ,dir t)))))

Saving Configuration

A good practice is to use an .org file to modify your GNU Emacs configuration with org-mode and to load this configuration via an .el file. This way you can maintain an org-mode configuration and still get a faster load.

Using the async package and the org-babel-tangle command, the code below will executes org-babel-tangle asynchronously when config.org is saved, to update the config.el file. From then on, you only need to add a add the my/config-tangle function to the after-save hook and specify the loading of the config.el file into the init.el file.

 (use-package async
	:hook (after-save . my/config-tangle)
	:preface
	(defvar config-file (expand-file-name "config.org" user-emacs-directory)
	  "The configuration file.")
	(defvar config-last-change (nth 5 (file-attributes config-file))
	  "The last modification time of the configuration file.")
	(defvar show-async-tangle-results nil
	  "Keep *emacs* async buffers around for later inspection.")
	(defun my/config-tangle ()
	  "Tangle the org file asynchronously."
	  (when (my/config-updated)
		(setq config-last-change
			  (nth 5 (file-attributes config-file)))
		(my/async-babel-tangle config-file)))
	(defun my/config-updated ()
	  "Check if the configuration file has been updated since the last time."
	  (time-less-p config-last-change
				   (nth 5 (file-attributes config-file))))
	(defun my/async-babel-tangle (org-file)
	  "Tangle the org file asynchronously."
	  (let ((init-tangle-start-time (current-time))
			(file (buffer-file-name))
			(async-quiet-switch "-q"))
		(async-start
		 `(lambda ()
			(require 'org)
			(org-babel-tangle-file ,org-file))
		 (unless show-async-tangle-results
		   `(lambda (result)
			  (if result
				  (message "[✓] %s successfully tangled (%.2fs)"
						   ,org-file
						   (float-time (time-subtract (current-time)
													  ',init-tangle-start-time)))
				(message "[✗] %s as tangle failed." ,org-file))))))))

Appearance

A color scheme and decent font not only helps beautify emacs but also helps readablity

Defaults

Sensible default values that I use.

(setq-default
 inhibit-startup-screen t                         ; Disable start-up screen
 initial-scratch-message ""                       ; Empty the initial *scratch* buffer
 make-pointer-invisible t                         ; Hide mouse pointer when typing
 cursor-type 'bar)                                ; Cursor type should be bar not block
(blink-cursor-mode 1)                             ; Blink the cursor
(column-number-mode 1)                            ; Show the column number
(global-hl-line-mode)                             ; Hightlight current line
(show-paren-mode 1)                               ; Show the parent
(when window-system                               ; No menu/scroll/tool bars
  (menu-bar-mode -1)
  (scroll-bar-mode -1)
  (tool-bar-mode -1)
  (tooltip-mode -1))

Theme

I switch themes quite often but usually I prefer high contrast dark themes.

(use-package doom-themes
  :if (and (display-graphic-p) (string= (my/location) "home"))
  :custom
  (doom-themes-enable-bold t)
  (doom-themes-enable-italic t)
  :config
  (doom-themes-visual-bell-config)
  :init
  (load-theme 'doom-homage-black t))

Since we do not do things by halves, it is also interesting to visually differentiate “real” buffers (e.g., buffers that contain our work) from “unreal” buffers (e.g., popups) by giving the latter a darker color. From then on, solar-mode is the ideal package.

(use-package solaire-mode
  :defer 0.1
  :custom (solaire-mode-remap-fringe t)
  :config (solaire-global-mode))

Fonts

Spending most of our time on GNU Emacs, it is important to use a font that will make our reading easier. JetBrainsMono is one of the best monospaced font. Since we are going to install NerdIcons we might as well install the NerdFont version.

(set-face-attribute 'default nil :font "JetBrainsMonoNerdFont 14")
(set-fontset-font t 'latin "Noto Sans")

Icons

To integrate icons with the modeline and other packages, nerd-icons is my icons package of choice.

(use-package nerd-icons
  :custom
  (nerd-icons-font-family "JetBrains Mono Nerd Font"))

Menus

GNU Emacs has so many commands per mode that it is tedious to remember all the keybindings for quick access. Fortunately, hydra allows you to create menu commands and on the basis of a popup, display the commands you have associated with it.

 (use-package hydra)
 (use-package major-mode-hydra
	:after hydra
	:preface
	(defun with-mdicon (icon str &optional height v-adjust face)
	  "Display an icon from nerd-icons."
	  (s-concat (nerd-icons-mdicon icon :v-adjust (or v-adjust 0) :height (or height 1) :face face) " " str))
	(defun with-faicon (icon str &optional height v-adjust face)
	  "Display an icon from Font Awesome icon."
	  (s-concat (nerd-icons-faicon icon ':v-adjust (or v-adjust 0) :height (or height 1) :face face) " " str))
	(defun with-fileicon (icon str &optional height v-adjust face)
	  "Display an icon from the Atom File Icons package."
	  (s-concat (nerd-icons-flicon icon :v-adjust (or v-adjust 0) :height (or height 1) :face face) " " str))
	(defun with-octicon (icon str &optional height v-adjust face)
	  "Display an icon from the GitHub Octicons."
	  (s-concat (nerd-icons-octicon icon :v-adjust (or v-adjust 0) :height (or height 1) :face face) " " str)))

Modeline

Telephone Line is a minimal and customizable modeline and seems to work best with nyan mode

(use-package telephone-line
  :config
  (setq telephone-line-lhs
        '((accent . (telephone-line-vc-segment
                     telephone-line-erc-modified-channels-segment))
          (nil    . (telephone-line-buffer-segment
                     telephone-line-nyan-segment))))
  (setq telephone-line-rhs
        '((nil    . (telephone-line-misc-info-segment))
          (accent . (telephone-line-major-mode-segment))
          (evil   . (telephone-line-airline-position-segment))))
  (telephone-line-mode 1))

Must have eye candy =)

(use-package nyan-mode
:config
(nyan-mode))

De-clutter major/minor buffer information into a single menu

(use-package minions
  :config
  (minions-mode 1))

Dashboard

Organization is even more important in the 21st century than it was before. What could be better than launching GNU Emacs with a dashboard that lists the tasks of the week with org-agenda and a list of projects we have recently contributed to with projectile. To our delight the dashboard package offers these features and more.

(use-package dashboard
  :custom
  (dashboard-banner-logo-title "Get Busy Living Or Get Busy Dying!")
  (dashboard-center-content t)
  (dashboard-items '((agenda)
                     (projects . 3)
                     (recents   . 3)))
  (dashboard-set-file-icons t)
  (dashboard-set-footer nil)
  (dashboard-set-heading-icons t)
  (dashboard-set-navigator t)
  (dashboard-startup-banner 'logo)
  :config (dashboard-setup-startup-hook))

Management

Section dedicated to managing buffers, windows, the minibuffer, files and projects on GNU Emacs to provide a more pleasant experience.

Buffers

Buffers can quickly become a mess to manage. To manage them better, I use the ibuffer built-in package instead of buffer-menu, to have a nicer visual interface with a syntax color. I also include additional functions from Emacs Redux that I have found useful. In addition, some buffers may contain useful temporary information that should not be killed by accident. I make sure to set the buffers scratch and Messages to read-only.

(use-package ibuffer
  :straight (:type built-in)
  :preface
  (defvar protected-buffers '("*scratch*" "*Messages*")
    "Buffer that cannot be killed.")
  (defun my/protected-buffers ()
    "Protect some buffers from being killed."
    (dolist (buffer protected-buffers)
      (with-current-buffer buffer
        (emacs-lock-mode 'kill))))
  (defun my/rename-file-and-buffer ()
    "Rename the current buffer and file it is visiting."
    (interactive)
    (let ((filename (buffer-file-name)))
      (if (not (and filename (file-exists-p filename)))
          (message "Buffer is not visiting a file!")
        (let ((new-name (read-file-name "New name: " filename)))
          (cond
           ((vc-backend filename) (vc-rename-file filename new-name))
           (t
            (rename-file filename new-name t)
            (set-visited-file-name new-name t t)))))))
  (defun my/delete-file-and-buffer ()
    "Kill the current buffer and deletes the file it is visiting."
    (interactive)
    (let ((filename (buffer-file-name)))
      (when filename
        (if (vc-backend filename)
            (vc-delete-file filename)
          (progn
            (delete-file filename)
            (message "Deleted file %s" filename)
            (kill-buffer))))))
  (defun my/kill-other-buffers ()
    "Kill other buffers except current one and protected buffers."
    (interactive)
    (eglot-shutdown-all)
    (mapc 'kill-buffer
          (cl-remove-if
           (lambda (x)
             (or
              (eq x (current-buffer))
              (member (buffer-name x) protected-buffers)))
           (buffer-list)))
    (delete-other-windows))
  :bind (([remap kill-buffer] . kill-this-buffer))
  :init (my/protected-buffers))

In addition we can override eamcs default mechanism for making buffer name unique

(use-package uniquify
  :straight (:type built-in)
  :config
  (setq uniquify-buffer-name-style 'forward)
  (setq uniquify-separator "/")
  (setq uniquify-after-kill-buffer-p t)
  (setq uniquify-ignore-buffers-re "^\\*"))

We can add a menu for buffer options as well

(pretty-hydra-define hydra-buffer
  (:hint nil :forein-keys warn :quit-key "q" :title (with-faicon "nf-fa-buffer" "Buffers" 1 -0.05))
  ("Buffer"
   (("a" ibuffer "all")
    ("r" my/rename-file-and-buffer "rename")
    ("d" my/delete-file-and-buffer "delete")
    ("o" my/kill-other-buffers "only")
    ("s" sudo-edit-current-file "sudo"))))
(global-set-key (kbd "C-c b") 'hydra-buffer/body)
(global-unset-key (kbd "C-x C-b"))

Windows

Most of the time, I want to split a window and put the focus on it to perform an action. By default GNU Emacs does not give the focus to this new window. I have no idea why this is not the default behavior, but we can easily set this behavior.

	  (use-package window
		:straight (:type built-in)
		:preface
		(defun my/hsplit-last-window ()
		  "Focus to the last created horizontal window."
		  (interactive)
		  (split-window-horizontally)
		  (other-window 1))
		(defun my/vsplit-last-window ()
		  "Focus to the last created vertical window."
		  (interactive)
		  (split-window-vertically)
		  (other-window 1))
		(defun my/toggle-fullscreen-window ()
		  "Maximize buffer"
		  (interactive)
		  (if (= 1 (length (window-list)))
			  (jump-to-register '_)
			(progn
			  (window-configuration-to-register '_)
			  (delete-other-windows))))
		(defun my/transpose-windows ()
		  "Transpose two windows.  If more or less than two windows are visible, error."
		  (interactive)
		  (unless (= 2 (count-windows))
			(error "There are not 2 windows."))
		  (let* ((windows (window-list))
				 (w1 (car windows))
				 (w2 (nth 1 windows))
				 (w1b (window-buffer w1))
				 (w2b (window-buffer w2)))
			(set-window-buffer w1 w2b)
			(set-window-buffer w2 w1b)))
		:bind (("C-x 2" . my/vsplit-last-window)
			   ("C-x 3" . my/hsplit-last-window)
			   ("M-u" . my/toggle-fullscreen-window)))

The way I move between several windows in GNU Emacs is by indicating the number of the window I want to move to. Most people use ace-window, but I prefer switch-window which displays the window number while hiding its content. I find this behavior more convenient than moving from window to window to get to the one we are looking for.

(use-package switch-window
  :bind (("M-o" . switch-window)))

There are times when I would like to bring back a windows layout with their content. With the winner-undo and winner-redo commands from the built-in winner package, I can easily do that.

(use-package winner
  :straight (:type built-in)
  :config (winner-mode))

We can add a menu for window options as well

 (pretty-hydra-define hydra-windows
	(:hint nil :forein-keys warn :quit-key "q" :title (with-faicon "nf-fa-windows" "Windows" 1 -0.05))
	("Window"
	 (("b" balance-windows "balance")
	  ("c" centered-window-mode "center")
	  ("t" my/transpose-windows "transpose")
	  ("i" enlarge-window "heighten")
	  ("j" shrink-window-horizontally "narrow")
	  ("k" shrink-window "lower")
	  ("u" winner-undo "undo")
	  ("r" winner-redo "redo")
	  ("l" enlarge-window-horizontally "widen")
	  ("s" switch-window-then-swap-buffer "swap" :color teal))
	 "Zoom"
	 (("-" text-scale-decrease "out")
	  ("+" text-scale-increase "in")
	  ("=" (text-scale-increase 0) "reset"))))
 (global-set-key (kbd "C-c w") 'hydra-windows/body)

Minibuffer

Completion

Having a good minibuffer experience is important on GNU Emacs since it is one of the elements we will frequently interact with. We start with vertico which is a vertical completion system that is very performant and minimalistic.

(use-package vertico
  :init (vertico-mode)
  :custom (vertico-cycle t)
  :custom-face (vertico-current ((t (:background "#1d1f21")))))

To enable richer annotations (e.g., summary documentation of the functions and variables, as well as having the size and the last consultation of the files) for minibuffer completions, marginalia is awesome.

(use-package marginalia
  :after vertico
  :init (marginalia-mode)
  :custom
  (marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil)))

By default, vertico sorts the candidates according to their history position, then by length and finally by alphabetical. To improves searching across completion (e.g., by filter expressions separated by spaces), you should use orderless (or prescient).

(use-package orderless
  :custom
  (completion-category-defaults nil)
  (completion-category-overrides '((file (styles . (partial-completion)))))
  (completion-styles '(orderless)))

We can enhance buffer/minibuffer completion by adding a small completion popup with a neat package called corfu.

(use-package corfu
  :custom
  (corfu-cycle t)
  (corfu-auto t)
  (corfu-auto-prefix 1)
  (corfu-auto-delay 0)
  (corfu-quit-no-match 'separator)
  (corfu-preview-current t)
  (corfu-on-exact-match nil)
  :config
  (setq completion-cycle-threshold 3)
  (setq tab-always-indent 'complete))
(use-package nerd-icons-corfu
  :init
  (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))

And finally, which-key helps show available commands in the minibuffer

(use-package which-key
  :init
  (which-key-mode))

Actions

consult is used for search and navigation but a lot of it’s actions start from the minibuffer

(use-package consult
  :init
  (global-unset-key (kbd "C-s"))
  :custom
  (consult-yank-rotate 1)
  :bind (("C-s S" . isearch-forward)
         ("C-s R" . isearch-backward)
         ("C-s s" . consult-line)
         ("C-s l" . consult-goto-line)
         ("C-s i" . consult-imenu)
         ("C-s r" . consult-ripgrep)
         ("C-s f" . consult-fd)
         ("M-y"   . consult-yank-from-kill-ring)
         ("C-x b" . consult-buffer)))

embark is great if like me you like to interact directly with your files (e.g., for renaming, deleting and copying) through your completion system without having to go through dired.

 (use-package embark
	:bind ("C-." . embark-act))
 (use-package embark-consult
   :bind ("C-." . embark-act))

Files

Backups

It is important to have file backups available with GNU Emacs. The following configuration forces a backup on every save of a file.

 (setq-default
  vc-make-backup-files t                           ; backup versioned files
  version-control t                                ; version numbers for backup files
  kept-new-versions 100                            ; Number of newest versions to keep
  kept-old-versions 100                            ; Number of oldest versions to keep
  delete-old-versions t                            ; Don't ask to delete excess backup versions
  backup-by-copying t)                             ; Copy all files, don't rename them
 (defun force-backup-of-buffer ()
	"Make a special 'per session' backup at the first save of each emacs session and a per-save backup on each subsequent save."
	(let* ((per-save-dir (expand-file-name (file-name-as-directory (format "%s/emacs/backups/per-save/" xdg-data))))
		   (per-session-dir (expand-file-name (file-name-as-directory (format "%s/emacs/backups/per-session/" xdg-data)))))
	  (my/mkdir per-save-dir)
	  (my/mkdir per-session-dir)
	  (setq backup-directory-alist `(("." . ,per-save-dir)))
	  (when (not buffer-backed-up)
		(let ((backup-directory-alist `(("." . ,per-session-dir)))
			  (kept-new-versions 3))
		  (backup-buffer)))
	  (let ((buffer-backed-up nil))
		(backup-buffer))))
 (add-hook 'before-save-hook 'force-backup-of-buffer)

backup-walker allows us to view the diff between backups and selectively restore one

(use-package backup-walker)

Saving

Let’s change where Emacs stores auto-saves

(setq-default
 auto-save-default t                           ; Enable auto-save
 auto-save-timeout 30                          ; Auto-save if idle for 30 seconds
 auto-save-interval 300)                       ; Auto-save after having typed 300 characters
(let ((dir (expand-file-name (file-name-as-directory (expand-file-name (format "%s/emacs/auto-save/" xdg-cache))))))
  (my/mkdir dir)
  (setq auto-save-file-name-transforms `((".*" ,dir t))))

Sometimes you may want to discard your changes to a file and revert to the saved version of this file.

(use-package autorevert
  :straight (:type built-in)
  :delight auto-revert-mode
  :bind ("C-x R" . revert-buffer)
  :custom (auto-revert-verbose nil)
  :config (global-auto-revert-mode))

There are times when it is necessary to remember a command. The savehist built-in package allows you to save commands in a file so that you can run them again later.

(use-package savehist
  :straight (:type built-in)
  :custom
  (history-delete-duplicates t)
  (history-length 25)
  (savehist-file (expand-file-name (format "%s/emacs/history" xdg-cache)))
  :config (savehist-mode))

Recent

It is also useful to have easy access to recently modified files.

(use-package recentf
  :straight (:type built-in)
  :bind ("C-x r" . recentf-open-files)
  :init (recentf-mode)
  :custom
  (recentf-exclude (list "/scp:"
                         "/ssh:"
                         "/sudo:"
                         "/tmp/"
                         "~$"
                         "COMMIT_EDITMSG"))
  (recentf-max-menu-items 15)
  (recentf-max-saved-items 200)
  (recentf-save-file (expand-file-name (format "%s/emacs/recentf" xdg-cache)))
  ;; Save recent files every 5 minutes to manage abnormal output.
  :config (run-at-time nil (* 5 60) 'recentf-save-list))

Trash

I’d like files to be trashed instead of permanently deleted

(defun empty-trash ()
  "Empty the trash"
  (interactive)
  (shell-command "trash-empty -f"))

(setq trash-directory (expand-file-name (format "%s/Trash/files" xdg-data)))
(setq delete-by-moving-to-trash t)

Dired

Dirvish is an improved version built on Emacs’s builtin file manager Dired. The following tools are also recommended to use with dirvish:

  • fd as a faster alternative to find
  • imagemagick for image preview
  • poppler | pdf-tools for pdf preview
  • ffmpegthumbnailer for video preview
  • mediainfo for audio/video metadata generation
  • tar and unzip for archive files preview
(use-package dirvish
  :init
  (dirvish-override-dired-mode)
  :custom
  (dirvish-quick-access-entries
   '(("h" "~/"                                          "Home")
     ("d" "~/dump/"                                     "Downloads")
     ("w" "~/workstation"                               "Workstation")
     ("p" "~/media/pictures/"                           "Pictures")
     ("m" "/mnt/"                                       "Drives")
     ("t" "~/.local/share/Trash/files/"                 "TrashCan")
     ("r" "/"                                           "Root")))
  :config
  (setf dirvish-reuse-session nil)
  ;; (setq dirvish-mode-line-format
  ;;       '(:left (sort symlink) :right (omit yank index)))
  (setq dirvish-use-header-line 'global)
  (setq dirvish-header-line-format
        '(:left (path) :right (free-space))
        dirvish-mode-line-format
        '(:left (sort file-time " " file-size symlink) :right (omit yank index)))
  (setq dirvish-attributes
    '(vc-state subtree-state nerd-icons collapse git-msg file-time file-size))
  (setq dirvish-subtree-state-style 'nerd)
  (setq dired-listing-switches
        "-l --almost-all --human-readable --group-directories-first --no-group")
  :bind
  (("<f1>" . dirvish-side)
   :map dirvish-mode-map
   ("M-p" . dired-up-directory)
   ("M-n" . dired-find-file)
   ("M-d" . empty-trash)
   ("a"   . dirvish-quick-access)
   ("f"   . dirvish-file-info-menu)
   ("y"   . dirvish-yank-menu)
   ("N"   . dirvish-narrow)
   ("^"   . dirvish-history-last)
   ("h"   . dirvish-history-jump)
   ("s"   . dirvish-quicksort)
   ("TAB" . dirvish-subtree-toggle)
   ("M-f" . dirvish-history-go-forward)
   ("M-b" . dirvish-history-go-backward)))

Menu

We can add a menu for file options as well

(defun my/find-file-with-default-path (path)
  (interactive)
  (let ((file-name-as-directory path))
    (call-interactively 'find-file)))
(pretty-hydra-define hydra-file
  (:hint nil :color teal :quit-key "q" :title (with-octicon "nf-oct-file_symlink_file" "Files" 1 -0.05))
  ("Sandbox"
   (("sp" (find-file "~/workstation/projects/sandbox/python/main.py") "python")
    ("sc" (find-file "~/workstation/projects/sandbox/c/main.c") "C")
    ("sb" (find-file "~/workstation/projects/sandbox/bash/main.sh") "bash")
    ("sg" (find-file "~/workstation/projects/sandbox/go/main.go") "go")
    ("sr" (find-file "~/workstation/projects/sandbox/rust/main.rs") "rust")
    ("sd" (find-file "~/workstation/projects/sandbox/docker/Dockerfile") "docker")
    ("sv" (find-file "~/workstation/projects/sandbox/vagrant/Vagrantfile") "vagrant"))
   "Config"
   (("cbb" (find-file "~/.bashrc") "bashrc")
    ("cba" (find-file (format "%s/bash/bash_alias" xdg-state)) "bash alias")
    ("cbe" (find-file (format "%s/bash/bash_environment" xdg-state)) "bash env")
    ("cbf" (find-file (format "%s/bash/bash_function" xdg-state)) "bash func")
    ("ce" (find-file (format "%s/emacs/config.org" xdg-config)) "emacs")
    ("ci" (find-file (format "%s/i3/config" xdg-config)) "i3")
    ("cp" (find-file (format "%s/polybar/config.ini" xdg-config)) "polybar")
    ("ca" (find-file (format "%s/alacritty/alacritty.toml" xdg-config)) "alacritty")
    ("cg" (find-file (format "%s/git/config" xdg-config)) "git")
    ("cs" (find-file (format "%s/ssh/config" xdg-config)) "ssh")
    ("cz" (find-file (format "%s/zathura/zathurarc" xdg-config)) "zathura"))))
(global-set-key (kbd "C-x f") 'find-file)
(global-set-key (kbd "C-c f") 'hydra-file/body)

Projects

I have found the built-in project.el a suitable replacement now for projectile.

(use-package project
  :straight (:type built-in)
  :config
  (customize-set-variable 'project-find-functions (list #'project-try-vc #'my/project-try-local))
  (cl-defmethod project-root ((project (head local)))
    "Return root directory of current PROJECT."
    (cdr project))
  (defun my/project-try-local (dir)
    "Determine if DIR is a non-Git project.
     DIR must include a .project file to be considered a project."
    (let ((root (locate-dominating-file dir ".project")))
      (when root
        (cons 'local root))))
  (defun my/project-save-all-buffers (&optional proj arg)
    "Save all file-visiting buffers in PROJ without asking."
    (interactive)
    (let* ((proj (or proj (project-current)))
           (buffers (project-buffers (project-current))))
      (dolist (buf buffers)
        ;; Act on base buffer of indirect buffers, if needed.
        (with-current-buffer (or (buffer-base-buffer buf) buf)
          (when (and (buffer-file-name buf)   ; Ignore all non-file-visiting buffers.
                     (buffer-modified-p buf)) ; Ignore all unchanged buffers.
            (let ((buffer-save-without-query t))  ; Save silently.
              (save-buffer arg)))))))
  :bind (("C-x p" . project-switch-project)))

We can add a menu for project options as well

(pretty-hydra-define hydra-project
  (:hint nil :color teal :quit-key "q" :title (with-faicon "nf-fa-rocket" "Projectile" 1 -0.05))
  ("Buffers"
   (("b" project-switch-to-buffer "list")
    ("k" project-kill-buffers "kill all")
    ("S" my/project-save-all-buffers "save"))
   "Find"
   (("d" project-find-dir "directory")
    ("D" project-dired "root")
    ("f" project-find-file "file"))
   "Other"
   (("i" projectile-invalidate-cache "reset cache"))
   "Search"
   (("r" project-query-replace-regexp "regexp replace")
    ("s" consult-ripgrep "search"))))
  (global-set-key (kbd "C-c p") 'hydra-project/body)

Editing

Typing or manipulating text is my primary purpose for using Emacs so let’s improve on that.

Defaults

Some sensible defaults for editing

(setq-default
 fill-column 80                                   ; Set width for automatic line breaks
 tab-width 4                                      ; Set width for tabs
 kill-ring-max 128                                ; Maximum length of kill ring
 mark-ring-max 128                                ; Maximum length of mark ring
 kill-do-not-save-duplicates t                    ; Remove duplicates from kill ring
 require-final-newline t)                         ; Always add new line to end of file
(delete-selection-mode t)                         ; Typing will replace a selected region
(set-default-coding-systems 'utf-8)               ; Default to utf-8 encoding
(prefer-coding-system 'utf-8)
(set-language-environment 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)

Deletion

(use-package simple
  :straight (:type built-in)
  :delight (auto-fill-function)
  :preface
  (defun my/kill-region-or-line ()
    "When called interactively with no active region, kill the whole line."
    (interactive)
    (if current-prefix-arg
        (progn
          (kill-new (buffer-string))
          (delete-region (point-min) (point-max)))
      (progn (if (use-region-p)
                 (kill-region (region-beginning) (region-end) t)
               (kill-region (line-beginning-position) (line-beginning-position
                                                       2))))))
  (defun my/delete-surround-at-point--find-brackets (pos)
    "Return a pair of buffer positions for the opening & closing bracket positions.
Or nil when nothing is found."
    (save-excursion
      (goto-char pos)
      (when
          (or
           (when
               (and
                (eq (syntax-class (syntax-after pos)) 4)
                (= (logand (skip-syntax-backward "/\\") 1) 0))
             (forward-char 1)
             (if (and (ignore-errors (backward-up-list 1) t) (eq (point) pos))
                 t
               (goto-char pos)
               nil))
           (ignore-errors (backward-up-list 1) t))
        (list (point)
              (progn
                (forward-list)
                (1- (point)))))))
  (defun my/delete-surround-at-point ()
    "https://emacs.stackexchange.com/a/54679"
    (interactive)
    (let ((range (my/delete-surround-at-point--find-brackets (point))))
      (unless range
        (user-error "No surrounding brackets"))
      (pcase-let ((`(,beg ,end) range))
        (let ((lines (count-lines beg end))
              (beg-char (char-after beg))
              (end-char (char-after end)))
          (save-excursion
            (goto-char end)
            (delete-char 1)
            (goto-char beg)
            (delete-char 1))
          (message
           "Delete surrounding \"%c%c\"%s" beg-char end-char
           (if (> lines 1)
               (format " across %d lines" lines)
             ""))))))
  :hook ((before-save . delete-trailing-whitespace)
         ((prog-mode text-mode) . turn-on-auto-fill))
  :bind (("C-M-d" . my/delete-surround-at-point)
         ([remap kill-region] . my/kill-region-or-line))
  :custom (set-mark-command-repeat-pop t))

Finally, I also like is to be able to delete every consecutive space characters when a space character is deleted. The hungry-delete package allows this behavior.

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

Visual Clarity

smart-comment allows for faster commenting and marking comments for deletion

(use-package smart-comment
  :bind ("M-;" . smart-comment))

Managing parentheses can be painful. One of the first things you want to do is to change the appearance of the highlight of the parentheses pairs.

(use-package faces
  :straight (:type built-in)
  :custom (show-paren-delay 0)
  :config
  (set-face-background 'show-paren-match "#161719")
  (set-face-bold 'show-paren-match t)
  (set-face-foreground 'show-paren-match "#ffffff"))

We can colour nested parentheses with the rainbow-delimiters package.

(use-package rainbow-delimiters
  :config
  (show-paren-mode 1))

We also want to match pairs properly

 (use-package smartparens
	:delight
	:hook ((minibuffer-setup-hook . turn-on-smartparens-strict-mode))
	:config
	(require 'smartparens-config)
	:bind (("C-M-b" . sp-backward-sexp)
		   ("C-M-f" . sp-forward-sexp)
		   ("M-(" . sp-wrap-round)
		   ("M-[" . sp-wrap-curly))
	:custom (sp-escape-quotes-after-insert nil))

Navigation

Let’s start with some useful functions that improve on existing Emacs navigation

(use-package navigation
  :straight (:type built-in)
  :preface
  (defun my/smarter-move-beginning-of-line (arg)
    "Move point back to indentation of beginning of line."
    (interactive "^p")
    (setq arg (or arg 1))
    (when (/= arg 1)
      (let ((line-move-visual nil))
        (forward-line (1- arg))))
    (let ((orig-point (point)))
      (back-to-indentation)
      (when (= orig-point (point))
        (move-beginning-of-line 1))))
  (defun my/smart-kill-whole-line (&optional arg)
    "A simple wrapper around `kill-whole-line' that respects indentation."
    (interactive "P")
    (kill-whole-line arg)
    (back-to-indentation))
  (defun my/move-line-up ()
    "Move up the current line."
    (interactive)
    (transpose-lines 1)
    (forward-line -2)
    (indent-according-to-mode))
  (defun my/move-line-down ()
    "Move down the current line."
    (interactive)
    (forward-line 1)
    (transpose-lines 1)
    (forward-line -1)
    (indent-according-to-mode))
  (defun my/smart-open-line-below ()
    "Insert an empty line after the current line.
        Position the cursor at its beginning, according to the current mode."
    (interactive)
    (move-end-of-line nil)
    (newline-and-indent))
  (defun my/smart-open-line-above ()
    "Insert an empty line above the current line.
      Position the cursor at it's beginning, according to the current mode."
    (interactive)
    (move-beginning-of-line nil)
    (newline-and-indent)
    (forward-line -1)
    (indent-according-to-mode))
  (defun my/smart-kill-line-backwards ()
    "Insert an empty line above the current line.
      Position the cursor at it's beginning, according to the current mode."
    (interactive)
    (kill-line 0)
    (indent-according-to-mode))
  (defun my/unpop-to-mark ()
    "Unpop off mark ring. Does nothing if mark ring is empty."
    (interactive)
    (when mark-ring
      (let ((pos (marker-position (car (last mark-ring)))))
        (if (not (= (point) pos))
            (goto-char pos)
          (setq mark-ring (cons (copy-marker (mark-marker)) mark-ring))
          (set-marker (mark-marker) pos)
          (setq mark-ring (nbutlast mark-ring))
          (goto-char (marker-position (car (last mark-ring))))))))
  :bind (("M-p" . my/move-line-up)
         ("M-n" . my/move-line-down)
         ("C-a" . my/smarter-move-beginning-of-line)
         ("C-<return>" . my/smart-open-line-below)
         ("M-<return>" . my/smart-open-line-above)
         ("M-<backspace>" . my/smart-kill-line-backwards)
         ("C-<" . pop-global-mark)
         ("C->" . my/unpop-to-mark)
         ([remap kill-whole-line] . my/smart-kill-whole-line)))

Avy is an amazing package that allows one to go truly mouseless and navigate via links

(use-package avy
  :config
  (defun avy-action-embark (pt)
    (unwind-protect
        (save-excursion
          (goto-char pt)
          (embark-act))
      (select-window
       (cdr (ring-ref avy-ring 0))))
    t)
  (setf (alist-get ?. avy-dispatch-alist) 'avy-action-embark)
  :bind
  ("C-s a" . avy-goto-char-timer))

Search and Replace

Anzu shows your replace changes live as you type which is neat.

(use-package anzu
  :config
  (set-face-attribute 'anzu-mode-line nil
                      :foreground "yellow" :weight 'bold)
  (custom-set-variables
   '(anzu-mode-lighter "")
   '(anzu-deactivate-region t)
   '(anzu-search-threshold 1000)
   '(anzu-replace-threshold 50)
   '(anzu-replace-to-string-separator " => "))
  :bind (([remap query-replace] . anzu-query-replace)
         ([remap query-replace-regexp] . anzu-query-replace-regexp)))

Linguistics

Jinx powered by libenchant seems to be the best spell checker today

(use-package jinx
  :bind ([remap ispell-word] . jinx-correct))

Occasionally, I would like to have a summary of a term directly on GNU Emacs, before that I would like to know more about this term. The wiki-summary package allows this behavior.

 (use-package wiki-summary
	:commands (wiki-summary wiki-summary-insert)
	:preface
	(defun my/format-summary-in-buffer (summary)
	  "Given a summary, sticks it in the *wiki-summary* buffer and displays
	   the buffer."
	  (let ((buf (generate-new-buffer "*wiki-summary*")))
		(with-current-buffer buf
		  (princ summary buf)
		  (fill-paragraph)
		  (goto-char (point-min))
		  (view-mode))
		(pop-to-buffer buf)))
	:config
	(advice-add 'wiki-summary/format-summary-in-buffer
				:override #'my/format-summary-in-buffer))

This function saves me time to find the definition of a word

(defun google-current-word ()
"Search the current word on Google using browse-url."
(interactive)
(let ((word (thing-at-point 'word)))
  (if word
      (browse-url (concat "https://www.duckduckgo.com/search?q=" word))
    (message "No word found at point."))))

We can add a menu for language options as well

(pretty-hydra-define hydra-lang
  (:hint nil :color teal :quit-key "q" :title (with-faicon "nf-fa-magic" "Spelling" 1 -0.05))
  ("Spell Check"
   (("<" jinx-previous "previous" :color pink)
    (">" jinx-next "next" :color pink)
    ("w" jinx-correct-word "word")
    ("a" jinx-correct-all "all")
    ("m" jinx-mode "mode" :toggle t))
   "Lanaguage"
   (("g" google-current-word "google")
    ("l" jinx-languages "language")
    ("s" wiki-summary "wiki"))
   "Word"
   (("u" upcase-dwim "upcase")
    ("d" downcase-dwim "downcase")
    ("c" capitalize-dwim "capitalize"))))
(global-set-key (kbd "C-c j") 'hydra-lang/body)

Multi-Edit

multiple-cursors is a package that allows you to edit multiple lines at once and is really nifty once you kinda get the hang of it.

(use-package multiple-cursors)
(pretty-hydra-define hydra-multiple-cursors
  (:hint nil :color teal :quit-key "q" :title (with-mdicon "nf-md-multicast" "Multiple Cursors" 1 -0.05))
  ("Multilpe Cursors"
   (("r" mc/mark-all-in-region-regexp "regex"))))
(global-set-key (kbd "C-c m") 'hydra-multiple-cursors/body)

Undo/Redo

Vundo is a really cool package that show your undo history and allows you traverse this tree

(use-package vundo
  :config
  (setq vundo-glyph-alist vundo-ascii-symbols)
  :bind (("C-x u" . vundo)))

undo-fu allows us to incerase our undo history

(use-package undo-fu
  :bind (:map global-map
              ("C-/" . undo-fu-only-undo)
              ("C-?" . undo-fu-only-redo))
  :config
  (setq undo-limit 67108864) ; 64mb.
  (setq undo-strong-limit 100663296) ; 96mb.
  (setq undo-outer-limit 1006632960)) ; 960mb

undo-fu-session saves your undo history between sessions

(use-package undo-fu-session
  :init
  (undo-fu-session-global-mode 1)
  :custom
  (undo-fu-session-directory (expand-file-name (format "%s/emacs/undo-fu-session" xdg-cache))))

Sudo

The following package re-opens a file with sudo rights

(use-package sudo-edit)

Development

Tree-sitter

Tree-sitter (as far as my understanding goes) is basically the LSP of languages but for syntax. It replaces all the different “x-language-mode” to a common one which aims to provide all the benefits such as code highlighting and structural editing.

Currently though there is no clean way to implement tree-sitter for all languages so this package helps to alleviate some of those paint points.

(use-package treesit-auto
  :custom
  (treesit-auto-install 'prompt)
  :config
  (setq treesit-auto-langs '(python))
  (global-treesit-auto-mode))

Version Control

No surprises here magit is the best git interface to ever exist.

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

To make sure that the summary and the body of the commits respect the conventions, the git-commit package from magit is perfect.

(use-package git-commit
  :straight (:type built-in)
  :preface
  (defun my/git-commit-auto-fill-everywhere ()
    "Ensure that the commit body does not exceed 72 characters."
    (setq fill-column 72)
    (setq-local comment-auto-fill-only-comments nil))
  :hook (git-commit-mode . my/git-commit-auto-fill-everywhere)
  :custom (git-commit-summary-max-length 50))

Finally, I like to know the modified lines of a file while I edit it.

(use-package git-gutter
  :defer 0.3
  :delight
  :config (global-git-gutter-mode))

We can add a menu for git options as well

 (pretty-hydra-define hydra-magit
	(:hint nil :color teal :quit-key "q" :title (with-octicon "nf-oct-mark_github" "Magit" 1 -0.05))
	("Action"
	 (("b" magit-blame "blame")
	  ("c" magit-clone "clone")
	  ("i" magit-init "init")
	  ("l" magit-log-buffer-file "commit log (current file)")
	  ("L" magit-log-current "commit log (project)")
	  ("s" magit-status "status"))))
 (global-set-key (kbd "C-c g") 'hydra-magit/body)

Language Server

Emacs has a lot of great LSP clients namely eglot, lsp-mode and lsp-bridge. Eglot is my choice currently as it is built-in to emacs (29+) and also seems to be the more minimalist option.

(use-package eglot
  :custom
  (fset #'jsonrpc--log-event #'ignore)
  (eglot-events-buffer-size 0)
  (eglot-sync-connect nil)
  (eglot-connect-timeout nil)
  (eglot-autoshutdown t)
  (eglot-send-changes-idle-time 3)
  (flymake-no-changes-timeout 5)
  (eldoc-echo-area-use-multiline-p nil)
  (setq eglot-ignored-server-capabilities '( :documentHighlightProvider)))
(use-package eglot-booster
  :after eglot
  :straight (eglot-booster :type git :host github :repo "jdtsmith/eglot-booster")
  :config
  (eglot-booster-mode))

Linters/Documentation

Eldoc shows function arguments in the echo area

(use-package eldoc
  :after eglot
  :straight (:type built-in)
  :config
  (with-eval-after-load 'eglot
    (add-hook 'eglot-managed-mode-hook
              (lambda ()
                ;; Show flymake diagnostics first.
                (setq eldoc-documentation-functions
                      (cons #'flymake-eldoc-function
                            (remove #'flymake-eldoc-function eldoc-documentation-functions)))
                ;; Show all eldoc feedback.
                (setq eldoc-documentation-strategy #'eldoc-documentation-compose)))))

Eglot has built-in support for flymake so that is what we will use as our syntax checker

(use-package flymake
  :straight (:type built-in)
  :config
  (custom-set-variables
   '(help-at-pt-timer-delay 1)
   '(help-at-pt-display-when-idle '(flymake-overlay))))

And a menu for flymake Eglot has built-in support for flymake so that is what we will use as our syntax checker

(pretty-hydra-define hydra-flymake
  (:hint nil :color teal :quit-key "q" :title (with-faicon "nf-fa-plane" "Flymake" 1 -0.05))
  ("Checker"
   (("s" flymake-start "syntax")
    ("m" flymake-mode "mode" :toggle t))
   "Errors"
   (("<" flymake-goto-prev-error "previous" :color pink)
    (">" flymake-goto-next-error "next" :color pink)
    ("b" flymake-show-buffer-diagnostics "buffer")
    ("p" flymake-show-project-diagnostics "project")
    ("l" flymake-switch-to-log-buffer "log"))
   "Other"
   (("c" consult-flymake "consult"))))
(global-set-key (kbd "C-c e") 'hydra-flymake/body)

Snippets

Yasnippet is the most popular snippet package but I wanted a more minmalistic package and tempel seems to be just that

(use-package tempel
  :commands (tempel-expand)
  :bind (("M-+" . tempel-expand)
         ("M-*" . tempel-insert)
         (:map tempel-map (("C-n" . tempel-next)
                           ("C-p" . tempel-previous))))
  :config
  (setq-default tempel-path (expand-file-name (format "%s/snippets/*.eld" user-emacs-directory))))

These are some snippets available globally

fundamental-mode
(today (format-time-string "%Y-%m-%d"))
(NOW (format-time-string "%Y-%m-%d %a %H:%M"))
(yesterday (format-time-string "%Y-%m-%d" (time-subtract nil (* 24 60 60))))
(tomorrow (format-time-string "%Y-%m-%d" (time-add nil (* 24 60 60))))

Virtualization

I use docker quite extensively so the following package is a staple for me

(use-package docker)

Org

Org short for organization of my life mainly by using org-mode

Defaults

Org Mode is a really good package for note taking and organization.

(use-package org
  :straight (:type built-in)
  :mode (("\\.org\\'" . org-mode))
  :bind (:map org-mode-map
              ("C-M-p" . org-shiftmetaleft)
              ("C-M-n" . org-shiftmetaright)
              ("M-<return>" . org-meta-return))
  :hook
  (org-mode . org-indent-mode)
  (org-mode . visual-line-mode)
  (org-mode . jinx-mode)
  :custom
  (org-directory "~/org")
  (org-archive-location "~/org/archives/%s::")
  (org-confirm-babel-evaluate nil)
  (org-log-done 'time)
  (org-return-follows-link t)
  (org-hide-emphasis-markers t)
  :config
  (add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode)))
(use-package org-contrib)

If like me you want to automatically update the tables of contents of your org files, toc-org is the ideal package. To automate these tables of contents, you only need to use the :TOC: tag in the first heading of these tables of contents.

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

We can add a menu for org options as well

(pretty-hydra-define hydra-org
  (:hint nil :color teal :quit-key "q" :title (with-faicon "nf-fa-pen" "Org" 1 -0.05))
  ("Action"
   (("A" my/org-archive-done-tasks "archive")
    ("a" org-agenda "agenda")
    ("c" org-capture "capture")
    ("l" my/org-open-current-ledger "ledger")
    ("d" org-decrypt-entry "decrypt")
    ("i" org-insert-link-global "insert-link")
    ("j" org-capture-goto-last-stored "jump-capture")
    ("k" org-cut-subtree "cut-subtree")
    ("o" org-open-at-point-global "open-link")
    ("r" org-refile "refile")
    ("s" org-store-link "store-link")
    ("t" org-show-todo-tree "todo-tree"))))
(global-set-key (kbd "C-c o") 'hydra-org/body)

Faces

Let’s add prettier bullets in Org Mode with org-superstar.

(use-package org-faces
  :straight (:type built-in)
  :custom
  (org-todo-keyword-faces
    (quote (("TODO" :foreground "red" :weight bold)
            ("NEXT" :foreground "blue" :weight bold)
            ("DONE" :foreground "forest green" :weight bold)
            ("WAITING" :foreground "orange" :weight bold)
            ("LATER" :foreground "magenta" :weight bold)
            ("NOPE" :foreground "grey" :weight bold)))))

Let’s add prettier bullets in Org Mode with org-superstar.

(use-package org-superstar
  :hook
  (org-mode . org-superstar-mode)
  :config
  (dolist (face '((org-level-1 . 1.35)
                  (org-level-2 . 1.3)
                  (org-level-3 . 1.2)
                  (org-level-4 . 1.1)
                  (org-level-5 . 1.1)
                  (org-level-6 . 1.1)
                  (org-level-7 . 1.1)
                  (org-level-8 . 1.1))))
  (setq org-superstar-remove-leading-stars t)
  (setq org-superstar-headline-bullets-list '("" "" "" ""  "" ""  ""  "" )))

Agenda

Nowadays, it is crucial to be organized. Even more than before. That is why it is important to take the time to make a configuration that is simple to use and that makes your life easier. The org-agenda built-in package allows me to be organized in my daily tasks. As a result, I can use my time to the fullest.

(use-package org-agenda
  :straight (:type built-in)
  :bind (:map org-agenda-mode-map
              ("C-n" . org-agenda-next-item)
              ("C-p" . org-agenda-previous-item)
              ("j" . org-agenda-goto)
              ("X" . my/org-agenda-mark-done-next)
              ("x" . my/org-agenda-mark-done))
  :custom
  (org-agenda-files '("~/org/todos")))

Capture

Org-capture templates saves you a lot of time when adding new entries. I use it to quickly record tasks, ledger entries, notes and other semi-structured information.

(use-package org-capture
  :straight (:type built-in)
  :custom
  (org-todo-keywords
   (quote ((sequence "TODO(t)""|" "NEXT(n)""|" "DONE(d)")
           (sequence "WAITING(w@/!)" "LATER(l@/!)""|" "NOPE(x@/!)"))))
  (org-capture-templates
   `(("p" "Personal To-Do"
      entry (file+headline "~/org/todos/personal.org" "General Tasks")
      "* TODO [#B] %?\n:Created: %T\n "
      :empty-lines 0))))

Note Taking

(use-package org-roam
  :custom
  (org-roam-directory (file-truename "~/org/notes"))
  :config
  ;; If you're using a vertical completion framework, you might want a more informative completion interface
  (setq org-roam-node-display-template (concat "${title:*} " (propertize "${tags:10}" 'face 'org-tag)))
  (org-roam-db-autosync-mode))

And a menu for org-roam

(pretty-hydra-define hydra-notes
  (:hint nil :color teal :quit-key "q" :title (with-octicon "nf-oct-pencil" "Notes" 1 -0.05))
  ("Notes"
   (("c" org-roam-dailies-capture-today "capture")
    ("C" org-roam-dailies-capture-tomorrow "capture tomorrow")
    ("g" org-roam-graph "graph")
    ("f" org-roam-node-find "find")
    ("i" org-roam-node-insert "insert"))
   "Go To"
   ((">" org-roam-dailies-goto-next-note "next note")
    ("<" org-roam-dailies-goto-previous-note "previous note")
    ("d" org-roam-dailies-goto-date "date")
    ("t" org-roam-dailies-goto-today "today")
    ("T" org-roam-dailies-goto-tomorrow "tomorrow")
    ("y" org-roam-dailies-goto-yesterday "yesterday"))))
(global-set-key (kbd "C-c n") 'hydra-notes/body)

Time Management

Islam

Hijri Calendar

By default GNU Emacs fills in too many dates and most of the ones I am interested in are not included

(use-package holidays
  :straight (:type built-in)
  :custom
  (holiday-bahai-holidays nil)
  (holiday-hebrew-holidays nil)
  (holiday-oriental-holidays nil)
  (holiday-general-holidays nil)
  (holiday-local-holidays nil)
  (holiday-islamic-holidays
   '((holiday-fixed 3 11 "Ramadan")
     (holiday-fixed 4 10 "Eid ul-Fitr")
     (holiday-fixed 6 13 "Hajj")
     (holiday-fixed 6 16 "Arafah")
     (holiday-fixed 6 28 "Eid ul-Adha"))))

Prayer

(use-package awqat
  :straight (awqat :type git :host github :repo "zkry/awqat")
  :commands (awqat-display-prayer-time-mode
             awqat-times-for-day)
  :init
  (awqat-display-prayer-time-mode)
  :custom
  (calendar-latitude 45.350912)
  (calendar-longitude -75.9529472)
  (awqat-mode-line-format "   ${prayer} (${hours}h${minutes}m) ")
  :config
  (setq awqat-asr-hanafi nil)
  (awqat-set-preset-midnight)
  (awqat-set-preset-isna))

Money Management

Good money management is a skill to be acquired as soon as possible. Fortunately for us, Ledger allows you to have a double-entry accounting system directly from the UNIX command line. To use Ledger with GNU Emacs, you need to the ledger-mode package.

(use-package ledger-mode
    :mode ("\\.\\(dat\\|ledger\\)\\'")
    :preface
    (defun my/ledger-save ()
      "Clean the ledger buffer at each save."
      (interactive)
      (ledger-mode-clean-buffer)
      (save-buffer))
    :bind (:map ledger-mode-map
                ("C-x C-s" . my/ledger-save))
    :hook (ledger-mode . ledger-flymake-enable)
    :custom
    (ledger-clear-whole-transactions t))

Org (7.01+) has built-in support for ledger entries using Babel.

(org-babel-do-load-languages
 'org-babel-load-languages
 '((ledger . t)))

We can also define a function to open the current years ledger file

(defun my/org-open-current-ledger ()
  "Open the ledger file corresponding to the current year."
  (interactive)
  (let* ((current-year (format-time-string "%Y"))
         (ledger-file (format "~/org/ledger/%s.org.gpg" current-year)))
    (find-file ledger-file)))

Languages

Defaults

Sensible default programming modes that I use.

(add-hook 'prog-mode-hook #'corfu-mode)
(add-hook 'prog-mode-hook #'display-line-numbers-mode)
(add-hook 'prog-mode-hook #'eldoc-mode)
(add-hook 'prog-mode-hook #'flymake-mode)
(add-hook 'prog-mode-hook #'smartparens-mode)
(add-hook 'prog-mode-hook #'jinx-mode)
(add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
(remove-hook 'prog-mode-hook 'turn-on-auto-fill)

Yaml

Yaml development environment.

Config

(use-package yaml-mode
  :mode (("\\.yml\\'" . yaml-mode)
         ("\\.yaml\\'" . yaml-mode))
  :interpreter ("yaml" . yaml-mode)
  :hook
  (yaml-mode . yaml-pro-mode)
  :preface
  (defun my/yaml-format ()
    "Compile current buffer file with yaml."
    (interactive)
    (compile (format "yamlfmt %s" buffer-file-name)))
  (defun my/yaml-check ()
    "Compile current buffer file with yaml."
    (interactive)
    (compile (format "yamllint %s" buffer-file-name))))

Snippets

Yaml specific snippets for tempel

Menu

We can add a menu for python specific functions

(pretty-hydra-define hydra-yaml
  (:hint nil :forein-keys warn :quit-key "q" :title (with-octicon "nf-oct-terminal" "yaml" 1 -0.05))
  ("Format"
   (("f" my/yaml-format "format")
    ("c" my/yaml-check "check"))))
(with-eval-after-load "yaml-mode"
  (define-key yaml-mode-map (kbd "C-c r") 'hydra-yaml/body))

Python

Python development environment.

Config

(use-package python
  :straight (:type built-in)
  :mode (("\\.py\\'" . python-ts-mode))
  :interpreter ("python" . python-ts-mode)
  :hook
  (python-ts-mode . eglot-ensure)
  :init
  (add-to-list 'eglot-server-programs '((python-ts-mode) . ("pyright-langserver" "--stdio")))
  :preface
  (defun my/pyrightconfig-write (virtualenv)
    "From https://robbmann.io/posts/emacs-eglot-pyrightconfig/
     Write a `pyrightconfig.json' file at the Git root of a project
     with `venvPath' and `venv' set to the absolute path of
     `virtualenv'.  When run interactively, prompts for a directory to
     select."
    (interactive "DEnv: ")
    ;; Naming convention for venvPath matches the field for pyrightconfig.json
    (let* ((venv-dir (tramp-file-local-name (file-truename virtualenv)))
           (venv-file-name (directory-file-name venv-dir))
           (venvPath (file-name-directory venv-file-name))
           (venv (file-name-base venv-file-name))
           (base-dir (vc-git-root default-directory))
           (out-file (expand-file-name "pyrightconfig.json" base-dir))
           (out-contents (json-encode (list :venvPath venvPath :venv venv))))
      (with-temp-file out-file (insert out-contents))
      (message (concat "Configured `" out-file "` to use environment `" venv-dir))))
  (defun my/python-venv-setup ()
    "Install .pyvenv virtual environment at the root of the project.
Additionally installed libraries from requirements.txt if it exists."
    (interactive)
    (let* ((base-dir (vc-git-root default-directory)) (venv-dir (concat base-dir ".venv")))
      (progn
        (save-window-excursion
          (shell-command (s-concat "python3 -m venv " venv-dir))
          (when (file-exists-p (concat base-dir "requirements.txt"))
            (shell-command (s-concat "source " venv-dir "/bin/activate && pip3 install -r " base-dir "requirements.txt")))
          (my/pyrightconfig-write venv-dir)))
      (message (concat "Created " venv-dir))))
  (defun my/python-run ()
    "Compile current buffer file with python."
    (interactive)
    (compile (format "python3 %s" buffer-file-name)))
  (defun my/python-format ()
    "Compile current buffer file with python."
    (interactive)
    (compile (format "ruff format %s" buffer-file-name))
    (my/revert-buffer-no-confirm))
  (defun my/python-check ()
    "Compile current buffer file with python."
    (interactive)
    (compile (format "ruff check %s" buffer-file-name)))
  :config
  ;; Error message support for pyright in a *Compilation* buffer
  (with-eval-after-load 'compile
    (add-to-list 'compilation-error-regexp-alist-alist
                 '(pyright "^[[:blank:]]+\\(.+\\):\\([0-9]+\\):\\([0-9]+\\).*$" 1 2 3))
    (add-to-list 'compilation-error-regexp-alist 'pyright))
  (setq python-check-command "NO_COLOR=1 ruff check"))
;; ruff mode to format on save
(use-package reformatter)
(use-package ruff-format
  :after reformatter
  :hook
  (python-ts-mode . ruff-format-on-save-mode))

Snippets

Python specific snippets for tempel

python-mode python-ts-mode
(__contains__ "def __contains__(self, el):" n> p n> "pass")
(__enternn__ "def __enter__(self):" n> p n> "return self")
(__eq__ "def __eq__(self, other):" n> "return self." p " == other." q)
(__exit__ "def __exit__(self, type, value, traceback):" n> p n> "pass")
(__getitem__ "def __len__(self):" n> p n> "pass")
(__iter__ "def __iter__(self):" n> "return " q)
(__new__ "def __new__(mcs, name, bases, dict):" n> p n> "return type.__new__(mcs, name, bases, dict)")
(__setitem__ "__all__ = [" n> p n> "]")
(arg "parser.add_argument('-" p "', '--" p "'," n> p ")")
(arg_positional "parser.add_argument('" p "', " p ")")
(assert "assert " q)
(assertEqual "self.assertEqual(" p ", " p ")")
(assertFalse "self.assertFalse(" p ")")
(assertIn "self.assertIn(" p ", " p ")")
(assertNotEqual "self.assertNotEqual(" p ", " p ")")
(assertRaises "assertRaises(" p ", " p ")")
(assertRaises-with "with self.assertRaises(" p "):" n> q)
(assertTrue "self.assertTrue(" p ")")
(celery_pdb "from celery.contrib import rdb; rdb.set_trace()")
(class "class " p":" n> "def __init__(self" p "):" n> q)
(classmethod "@classmethod" n> "def " p "(cls, " p "):" n> q)
(def_decorator "def " p "(func):" n> p n> "def _" p "(*args, **kwargs):" n> p n> "ret = func(*args, **kwargs)" n> p n> "return ret" n n> "return _" q)
(def_function "def " p "(" p "):" n> q)
(doc "\"\"\"" p "\"\"\"")
(doctest ">>> " p n> q)
(for "for " p " in " p ":" n> q)
(from "from " p " import " q)
(function_docstring "def " p "(" p "):" n> "\"\"\"" p "\"\"\"" n> q)
(if "if " p ":" n> q)
(ife "if " p ":" n> p n> "else:" n> q)
(ifmain "if __name__ == '__main__':" n> q)
(ig "# type: ignore" q)
(imp "import " q)
(fimp "from " p " import " q)
(init "def __init__(self" p "):" n> q)
(init_docstring "def __init__(self" p "):" n> "\"\"\"" p "\"\"\"" n> q)
(interact "import code; code.interact(local=locals())")
(ipdb_trace "import ipdb; ipdb.set_trace()")
(lambda "lambda " p ": " q)
(list "[" p " for " p " in " p "]")
(logger_name "logger = logging.getLogger(__name__)")
(logging "logger = logging.getLogger(\"" p "\")" n> "logger.setLevel(logging." p ")")
(main "def main():" n> q)
(metaclass "__metaclass__ = type")
(method "def " p "(self" p "):" n> q)
(method_docstring "def " p "(self" p "):" n> "\"\"\"" p "\"\"\"" n> q)
(not_impl "raise NotImplementedError")
(np "import numpy as np" n> q)
(parse_args "def parse_arguments():" n> "parser = argparse.ArgumentParser(description='" p "')" n> p n> "return parser.parse_args()")
(pd "import pandas as pd" n> q)
(tf "import tensorflow as tf" n> q)
(tr & "import " p "; " p ".set_trace()" q)
(parser "parser = argparse.ArgumentParser(description='" p "')" n> q)
(pass "pass")
(p "print(\"" p "\")")
(pf "print(f\"" p "\")")
(prop "def " p "():"
      n> "doc = \"\"\"" p "\"\"\""
      n> "def fget(self):"
      n> "return self._" p
      n> n> "def fset(self, value):"
      n> "self._" p " = value"
      n> n> "def fdel(self):"
      n> "del self._" p
      n> "return locals()"
      n> p " = property(**" p "())")
(reg p " = re.compile(r\"" p "\")")
(__repr__ "def __repr__(self):" n> q)
(return "return " q)
(script "#!/usr/bin/env python" n n> "def main():" n> "pass" n n> "if __name__ == '__main__':" n> "main()")
(self "self." q)
(self_without_dot "self")
(selfassign "self." p " = " q)
(setdef p ".setdefault(" p ", []).append(" p ")")
(setup "from setuptools import setup" n n> "package = '" p "'" n> "version = '" p "'" n n> "setup(name=package," n> "version=version," n> "description=\"" p "\"," n> "url='" p "'" p ")")
(shebang_line "#!/usr/bin/env python" n> q)
(size "sys.getsizeof(" p ")")
(static "@staticmethod" n> "def " p "(" p "):" n> q)
(__str__ "def __str__(self):" n> q)
(super "super(" p ", self)." p "(" p ")")
(test_class "class Test" p "(" p "):" n> q)
(test_file "import unittest" n> "from " p " import *" n> p n> "if __name__ == '__main__':" n> "unittest.main()")
(trace "import pdb; pdb.set_trace()")
(try "try:" n> p n> "except " p ":" n> q)
(tryelse "try:" n> p n> "except " p ":" n> p n> "else:" n> q)
(__unicode__ "def __unicode__(self):" n> q)
(utf-8_encoding "# -*- coding: utf-8 -*-")
(while "while " p ":" n> q)
(with "with " p p ":" n> q)
(with_statement "from __future__ import with_statement")

Menu

We can add a menu for python specific functionsn

 (pretty-hydra-define hydra-python
	(:hint nil :forein-keys warn :quit-key "q" :title (with-faicon "nf-fa-python" "Python" 1 -0.05))
	("Venv"
	 (("v" my/pyrightconfig-write "set venv")
	  ("V" my/python-venv-setup "create venv"))
	 "Run"
	 (("r" my/python-run "run"))
	 "Format"
	 (("f" my/python-format "format")
	  ("c" my/python-check "check"))))
 (with-eval-after-load "python"
	(define-key python-ts-mode-map (kbd "C-c r") 'hydra-python/body))

C/C++

C/C++ development environment.

Config

(use-package c
  :straight (:type built-in)
  :mode (("\\.c" . c-ts-mode))
  :interpreter ("c" . c-ts-mode)
  :hook
  (c++-ts-mode . eglot-ensure)
  :init
  (add-to-list 'eglot-server-programs '((c-mode c-ts-mode) . ("clangd" "-j=8" "--log=error" "--malloc-trim" "--background-index" "--clang-tidy" "--all-scopes-completion" "--completion-style=detailed" "--pch-storage=memory" "--header-insertion=never" "--header-insertion-decorators=0")))
  :preface
  (defun my/c-run ()
    "Compile current buffer file with c."
    (interactive)
    (compile (format "clang -Wall %s -o %s.out && ./%s.out" buffer-file-name)))
  (defun my/c-cpp-format ()
    "Compile current buffer file with c."
    (interactive)
    (compile (format "clang-format %s" buffer-file-name)))
  (defun my/c-cpp-check ()
    "Compile current buffer file with c."
    (interactive)
    (compile (format "clang-check %s" buffer-file-name))))
(use-package c++
  :straight (:type built-in)
  :mode (("\\.cc\\'" . c++-ts-mode)
         ("\\.cpp\\'" . c++-ts-mode))
  :interpreter ("c++" . c++-ts-mode)
  :hook
  (c++-ts-mode . eglot-ensure)
  :init
  (add-to-list 'eglot-server-programs '((c++-mode cc-mode c++-ts-mode) . ("clangd" "-j=8" "--log=error" "--malloc-trim" "--background-index" "--clang-tidy" "--all-scopes-completion" "--completion-style=detailed" "--pch-storage=memory" "--header-insertion=never" "--header-insertion-decorators=0")))
  :preface
  (defun my/cpp-run ()
    "Compile current buffer file with c."
    (interactive)
    (compile (format "clang++ -Wall -std=c++17 %s -o %s.out && ./%s.out" buffer-file-name)))
  :config
  (add-to-list 'eglot-server-programs '((c-ts-mode c++-ts-mode c-mode c++-mode) . ("clangd" "-j=8" "--log=error" "--malloc-trim" "--background-index" "--clang-tidy" "--all-scopes-completion" "--completion-style=detailed" "--pch-storage=memory" "--header-insertion=never" "--header-insertion-decorators=0"))))

Snippets

C specific snippets for tempel


Menu

We can add a menu for c and c++ specific functions

 (pretty-hydra-define hydra-c
	(:hint nil :forein-keys warn :quit-key "q" :title (with-mdicon "nf-md-language_c" "C" 1 -0.05))
	("Run"
	 (("r" my/c-run "run"))
	 "Format"
	 (("f" my/c-cpp-format "format")
	  ("c" my/c-cpp-check "check"))))
 (pretty-hydra-define hydra-cpp
	(:hint nil :forein-keys warn :quit-key "q" :title (with-mdicon "nf-md-language_c" "C++" 1 -0.05))
	("Run"
	 (("r" my/cpp-run "run"))
	 "Format"
	 (("f" my/c-cp--format "format")
	  ("c" my/c-cpp-check "check"))))
 (with-eval-after-load "c-ts-mode"
	(define-key c-ts-mode-map (kbd "C-c r") 'hydra-c/body))
 (with-eval-after-load "c-ts-mode"
	(define-key c++-ts-mode-map (kbd "C-c r") 'hydra-cpp/body))

Go

Golang development environment.

Config

(use-package go-mode
  :commands go-mode
  :mode ("\\.go\\'" . go-mode)
  :interpreter ("go" . go-mode)
  :hook
  (go-mode . eglot-ensure)
  (go-mode . go-eldoc-setup)
  (before-save . gofmt-before-save)
  :preface
  (defun my/go-run ()
    "Compile current buffer file with go."
    (interactive)
    (compile (format "go run %s" buffer-file-name)))
  (defun my/go-format ()
    "Format current buffer file with goimports."
    (interactive)
    (compile (format "goimports -w %s && gofumpt -w %s" buffer-file-name buffer-file-name))
    (my/revert-buffer-no-confirm))
  (defun my/go-check ()
    "Check current buffer file with goimports."
    (interactive)
    (compile (format "gofumpt -e %s" buffer-file-name)))
  :config
  (add-hook 'go-mode-hook
            (lambda ()
              (setq-local eglot-workspace-configuration
                          '((:gopls .
                                    ((staticcheck . t)
                                     (matcher . "CaseSensitive")))))))
  :custom
  (gofmt-command "goimports"))

(use-package go-eldoc
  :requires go-mode
  :hook go-mode)

Snippets

Go specific snippets for tempel

go-mode go-ts-mode
(imp "import " q)
(impn "import (" n> q n ")")
(pr "fmt.Printf(\"\\n" p "\\n%#v\\n\", " q ")")
(pl "fmt.Println(" q ")")
(db "Debug.Printf(\"\\n" p "\\n\\n%#v\\n\", " q ")")
(dl "Debug.Println(" q ")")
(lf "log.Printf(\"\\n%#v\\n\", " q ")")
(ln "log.Println(" q ")")
(stt "type " p " struct {" n> q n "}")
(inf "type " p " interface {" n> q n "}")
(cnt "const " p " = " q )
(cnst "const (" n> p " = " q n ")")
(vr "var " p " " q)
(mp "map[" p "]" q)
(if "if " p " {" n> p n "}" > q)
(el "if " p " {" n> p n "} else {" > n> p n "}" > q)
(elif "if " p " {" n> p n "} else if " > p " {" n> p n "}" > q)
(ifen "if err != nil {" n> q n "}" >)
(ifer "if err != " p " {" n> q n "}" >)
(sel "select {" n> "case " p ":" n> q n "}" >)
(swch "switch " p " {" n> "case " p ":" q n "}" >)
(fr "for " p "{" n> q n "}" >)
(rng "for " p ", " p " := range " p " {" n> q n "}" >)
(fnc "func " p "(" p ") {" n> q n "}" >)
(mn "func main() {" n> q n "}")
(in "func init() {" n> q n "}")
(tst "func Test" p " (t *testing.T) { " n> q n "}")

Menu

We can add a menu for python specific functions

 (pretty-hydra-define hydra-go
	(:hint nil :forein-keys warn :quit-key "q" :title (with-mdicon "nf-md-language_go" "Go" 1 -0.05))
	("Run"
	 (("r" my/go-run "run"))
	 "Format"
	 (("f" my/go-format "format")
	  ("c" my/go-check "check"))))
 (with-eval-after-load "go-mode"
	(define-key go-mode-map (kbd "C-c r") 'hydra-go/body))

Rust

Rust development environment.

Config

(use-package rust-mode
  :commands rust-mode
  :mode ("\\.rs\\'" . rust-mode)
  :interpreter ("rust" . rust-mode)
  :hook
  (rust-mode . eglot-ensure)
  :preface
  (defun my/rust-run ()
    "Compile current buffer file with rust."
    (interactive)
    (compile (format "rustc %s" buffer-file-name)))
  (defun my/rust-format ()
    "Format current buffer file with rustimports."
    (interactive)
    (compile (format "rustfmt --color never %s" buffer-file-name buffer-file-name))
    (my/revert-buffer-no-confirm))
  (defun my/rust-check ()
    "Check current buffer file with rustimports."
    (interactive)
    (compile (format "rustfmt --color never --check %s" buffer-file-name)))
  :custom
  (rust-format-save t))

Snippets

Rust specific snippets for tempel

rust-mode rust-ts-mode

Menu

We can add a menu for python specific functions

(pretty-hydra-define hydra-rust
  (:hint nil :forein-keys warn :quit-key "q" :title (with-mdicon "nf-md-language_rust" "Rust" 1 -0.05))
  ("Run"
   (("r" my/rust-run "run"))
   "Format"
   (("f" my/rust-format "format")
    ("c" my/rust-check "check"))))
(with-eval-after-load "rust-mode"
  (define-key rust-mode-map (kbd "C-c r") 'hydra-rust/body))

Bash

Bash development environment.

Config

(use-package sh
  :straight (:type built-in)
  :mode (("\\.sh\\'" . bash-ts-mode))
  :interpreter ("sh" . bash-ts-mode)
  :hook
  (bash-ts-mode . eglot-ensure)
  :init
  (add-to-list 'eglot-server-programs '((bash-ts-mode) . ("bash-language-server" "start")))
  :preface
  (defun my/sh-run ()
    "Compile current buffer file with sh."
    (interactive)
    (compile (format "bash %s" buffer-file-name)))
  (defun my/sh-format ()
    "Compile current buffer file with sh."
    (interactive)
    (compile (format "shfmt -w %s" buffer-file-name)))
  (defun my/sh-check ()
    "Compile current buffer file with sh."
    (interactive)
    (compile (format "shellcheck %s" buffer-file-name))))

Snippets

Bash specific snippets for tempel

sh-mode bash-ts-mode
(! & "#!/usr/bin/env bash" n q)
(if "if [ " p " ]" n "  then " p n "fi" q)
(ife "if [ " p " ]" n "  then " p n "else" p n "fi" q)
(cs "case " p " in" n> p " )" n> p n> ";;" n> q n "esac")
(fr "for " p " in " p n "do" n> q n "done")

Menu

We can add a menu for python specific functions

 (pretty-hydra-define hydra-sh
	(:hint nil :forein-keys warn :quit-key "q" :title (with-octicon "nf-oct-terminal" "sh" 1 -0.05))
	("Run"
	 (("r" my/sh-run "run"))
	 "Format"
	 (("f" my/sh-format "format")
	  ("c" my/sh-check "check"))))
 (with-eval-after-load "sh-script"
	(define-key bash-ts-mode-map (kbd "C-c r") 'hydra-sh/body))