107 KiB
Vanilla Emacs Configuration
- Introduction
- Basic configuration
- Custom Elisp
- Package Management
- Keybinding Management
- Packages Configuration
- Keybindings
Introduction
STOP Read this first!
You just landed on my vanilla Emacs configuration. However, this URL was used until recently for my Spacemacs configuration. If you want my complete, working Emacs configuration, I recommend you to head over there. This document is still a work in progress!
After a couple of years using Spacemacs and a failed attempt at switching to DoomEmacs, I’m finally switching back to a vanilla configuration! Be aware though this document is still very much a work in progress document, lots of comments on existing configuration are missing, and lots of functionnalities are still not implemented. I’m still in the process of porting my Spacemacs configuration over here.
Basic configuration
Early Init
The early init file is the file loaded before anything else in Emacs. This is where I put some options in order to disable as quickly as possible some built-in features of Emacs before they can be even loaded, speeding Emacs up a bit.
(setq package-enable-at-startup nil
inhibit-startup-message t
frame-resize-pixelwise t ; fine resize
package-native-compile t) ; native compile packages
(scroll-bar-mode -1) ; disable scrollbar
(tool-bar-mode -1) ; disable toolbar
(tooltip-mode -1) ; disable tooltips
(set-fringe-mode 10) ; give some breathing room
(menu-bar-mode -1) ; disable menubar
Emacs Behavior
Editing Text in Emacs
I never want to keep trailing spaces in my files, which is why I’m doing this:
(add-hook 'before-save-hook #'whitespace-cleanup)
I don’t understand why some people add two spaces behind a full stop, I sure don’t. Let’s tell Emacs.
(setq sentence-end-double-space nil)
There is a minor mode in Emacs which allows to have a finer way of
jumping from word to word: global-subword-mode
. It detects if what
would usually be considered by Emacs a word can be understood as
several modes, as in camelCase words, and allows us to jump words on
this finer level.
(global-subword-mode 1)
Lastly, I want the default mode for Emacs to be Emacs Lisp.
(setq-default initial-major-mode 'emacs-lisp-mode)
Indentation
I don’t like tabs. They rarely look good, and if I need it I can
almost always tell Emacs to use them through a .dir-locals.el
file or
through the config file of my code formatter. So by default, let’s
disable them:
(setq-default indent-tabs-mode nil)
(add-hook 'prog-mode-hook (lambda () (setq indent-tabs-mode nil)))
Just to go on a little tangeant here: I don’t exactly hate tabs, but I find them really annoying when your text editor knows only them. Sure, for indentation they work great and they allow different people getting different settings in their text editor depending on their preferred tastes —some may prefer 2 spaces tabs, some may prefer 4 spaces tabs, some deranged people prefer 8 spaces tabs, and some monsters prefer 3!
But the thing is, once you indented your code and then you need alignment, tabs don’t work anymore! Or they may on your text editor but not on your coworker’s! (He’s the one using 3 spaces tabs by the way).
So, is the answer to use spaces instead of tabs, and screw peoples’ preferences in terms of tabs width? No, I say the answer is more moderate than that, and it might frighten or anger some of you at first: use both spaces and tabs. Now, before you lynch me on the main avenue in front of everyone, let me tell you absolutely no one should ever be mixing spaces and tabs for indentation, that would be absolutely terrible and would bring the worst of both worlds. What’s the best of both worlds then?
Tabs for indentation
Spaces for alignment
I haven’t found a way to automate that in Emacs yet aside from formatters’ config file, and tabs look bat in EmacsLisp anyways, so I’ll stick with spaces by default and change it where needed.
Programming Modes
First off, my definition of what makes a a “programming mode” doesn’t exactly
fit mine, so on top of prog-mode
, let’s add a few other modes.
Modes |
---|
prog-mode |
latex-mode |
Line Number
Since version 26, Emacs has a built-in capacity of displaying line numbers on the left-side of the buffer. This is a fantastic feature that should actually be the default for all programming modes.
(dolist (mode '(<<prog-modes-gen()>>))
(add-hook mode #'display-line-numbers-mode))
Folding code
Most programming languages can usually have their code folded, be it
code between curly braces, chunks of comments or code on another level
of indentation (Python, why…?). The minor-mode that enables that is
hs-minor-mode
, let’s enable it for all of these programming modes:
(dolist (mode '(<<prog-modes-gen()>>))
(add-hook mode #'hs-minor-mode))
Stay Clean, Emacs!
As nice as Emacs is, it isn’t very polite or clean by default: open a
file, and it will create backup files in the same directory. But then,
when you open your directory with your favorite file manager and see
almost all of your files duplicated with a ~
appended to the filename,
it looks really uncomfortable! This is why I prefer to tell Emacs to
keep its backup files to itself in a directory it only will acces.
(setq backup-directory-alist `(("." . ,(expand-file-name ".tmp/backups/" user-emacs-directory))))
It also loves to litter its init.el
with custom variables here and
there, but the thing is: I regenerate my init.el
each time I tangle
this file! How can I keep Emacs from adding stuff that will be almost
immediately lost? Did someone say custom file?
(setq-default custom-file (expand-file-name ".custom.el" user-emacs-directory))
(when (file-exists-p custom-file) ; Don’t forget to load it, we still need it
(load custom-file))
If we delete a file, we want it moved to the trash, not simply deleted.
(setq delete-by-moving-to-trash t)
Finally, the scatch buffer always has some message at its beginning, I don’t want it!
(setq-default initial-scratch-message nil)
Stay Polite, Emacs!
When asking for our opinion on something, Emacs loves asking us to answer by “yes” or “no”, but in full! That’s very rude! Fortunately, we can fix this.
(defalias 'yes-or-no-p 'y-or-n-p)
This will make Emacs ask us for either hitting the y
key for “yes”, or
the n
key for “no”. Much more polite!
Misc
Let’s raise Emacs undo memory to 10MB, and make Emacs auto-save our files by default.
(setq undo-limit 100000000
auto-save-default t)
(setq window-combination-resize t) ; take new window space from all other windows
Personal Information
Emacs needs to know its master! For various reasons by the way, some
packages rely of these variables to know who it is talking to or
dealing with, such as mu4e
which will guess who you are if you haven’t
set it up correctly.
(setq user-full-name "Lucien Cartier-Tilet"
user-real-login-name "Lucien Cartier-Tilet"
user-login-name "phundrak"
user-mail-address "lucien@phundrak.com")
Visual Configuration
The first visual setting in this section will activate the visible bell. What it does is I get a visual feedback each time I do something Emacs doesn’t agree with, like tring to go up a line when I’m already at the top of the buffer.
(setq visible-bell t)
It is nicer to see a cursor cover the actual space of a character.
(setq x-stretch-cursor t)
When text is ellipsed, I want the ellipsis marker to be a single character of three dots. Let’s make it so:
(setq truncate-string-ellipsis "…")
Modeline Modules
I sometimes use Emacs in fullscreen, meaning my usual taskbar will be hidden. This is why I want the current date and time to be displayed, in an ISO-8601 style, although not exactly ISO-8601 (this is the best time format, fight me).
(setq display-time-format "%Y-%m-%d %H:%M")
(display-time-mode 1) ; display time in modeline
Something my taskbar doesn’t have is a battery indicator. However, I want it enabled only if I am on a laptop or if a battery is available.
(let ((battery-str (battery)))
(unless (or (equal "Battery status not available" battery-str)
(string-match-p (regexp-quote "N/A") battery-str))
(display-battery-mode 1)))
This isn’t a modeline module per se, but we have an indicator of the current line in Emacs. And although it is useful, I also often wish to know which column I’m on. This can be activated like so:
(column-number-mode)
The following code is, as will several chunks of code in this config,
borrowed from TEC’s configuration. It hides the encoding information
of the file if the file itself is a regular UTF-8 file with \n
line
ending. Be aware the doom-modeline-buffer-encoding
variable is usabel
here only because I use the Doom modeline as seen below.
(defun modeline-contitional-buffer-encoding ()
"Hide \"LF UTF-8\" in modeline.
It is expected of files to be encoded with LF UTF-8, so only show
the encoding in the modeline if the encoding is worth notifying
the user."
(setq-local doom-modeline-buffer-encoding
(unless (and (memq (plist-get (coding-system-plist buffer-file-coding-system) :category)
'(coding-category-undecided coding-category-utf-8))
(not (memq (coding-system-eol-type buffer-file-coding-system) '(1 2))))
t)))
Now, let’s automate the call to this function in order to apply the modifications to the modeline each time we open a new file.
(add-hook 'after-change-major-mode-hook #'modeline-contitional-buffer-encoding)
Fonts
I don’t like the default font I usually have on my machines, I really don’t. I prefer Cascadia Code, as it also somewhat supports the IPA.
(defvar phundrak/default-font-size 90
"Default font size.")
(when (equal system-type 'gnu/linux)
(set-face-attribute 'default nil :font "Cascadia Code" :height phundrak/default-font-size))
Frame Title
This is straight-up copied from TEC’s configuration. See their comment on the matter.
(setq frame-title-format
'(""
"%b"
(:eval
(let ((project-name (projectile-project-name)))
(unless (string= "-" project-name)
(format (if (buffer-modified-p) " ◉ %s" " ● %s") project-name))))))
Nice Macros From Doom-Emacs
Doom-Emacs has some really nice macros that can come in really handy,
but since I prefer to rely on my own configuration, I’ll instead just
copy their code here. First we get the after!
macro:
(require 'cl)
(defmacro after! (package &rest body)
"Evaluate BODY after PACKAGE have loaded.
PACKAGE is a symbol or list of them. These are package names, not modes,
functions or variables. It can be:
- An unquoted package symbol (the name of a package)
(after! helm BODY...)
- An unquoted list of package symbols (i.e. BODY is evaluated once both magit
and git-gutter have loaded)
(after! (magit git-gutter) BODY...)
- An unquoted, nested list of compound package lists, using any combination of
:or/:any and :and/:all
(after! (:or package-a package-b ...) BODY...)
(after! (:and package-a package-b ...) BODY...)
(after! (:and package-a (:or package-b package-c) ...) BODY...)
Without :or/:any/:and/:all, :and/:all are implied.
This is a wrapper around `eval-after-load' that:
1. Suppresses warnings for disabled packages at compile-time
2. Supports compound package statements (see below)
3. Prevents eager expansion pulling in autoloaded macros all at once"
(declare (indent defun) (debug t))
(if (symbolp package)
(list (if (or (not (bound-and-true-p byte-compile-current-file))
(require package nil 'noerror))
#'progn
#'with-no-warnings)
;; We intentionally avoid `with-eval-after-load' to prevent eager
;; macro expansion from pulling (or failing to pull) in autoloaded
;; macros/packages.
`(eval-after-load ',package ',(macroexp-progn body)))
(let ((p (car package)))
(cond ((not (keywordp p))
`(after! (:and ,@package) ,@body))
((memq p '(:or :any))
(macroexp-progn
(cl-loop for next in (cdr package)
collect `(after! ,next ,@body))))
((memq p '(:and :all))
(dolist (next (cdr package))
(setq body `((after! ,next ,@body))))
(car body))))))
Custom Elisp
Dired functions
phundrak/open-marked-files
This function allows the user to open all marked files from a dired buffer as new Emacs buffers.
(defun phundrak/open-marked-files (&optional files)
"Open all marked FILES in dired buffer as new Emacs buffers."
(interactive)
(let* ((file-list (if files
(list files)
(if (equal major-mode "dired-mode")
(dired-get-marked-files)
(list (buffer-file-name))))))
(mapc (lambda (file-path)
(find-file file-path))
(file-list))))
xah/open-in-external-app
(defun xah/open-in-external-app (&optional file)
"Open FILE or dired marked FILE in external app.
The app is chosen from the user’s OS preference."
(interactive)
(let ((file-list (if file
(list file)
(if (equal major-mode "dired-mode")
(dired-get-marked-files)
(list (buffer-file-name)))))
(do-it-p (if (<= (length file-list) 5)
t
(y-or-n-p "Open more than 5 files? "))))
(when do-it-p
(mapc (lambda (file-path)
(let ((process-connection-type nil))
(start-process "" nil "xdg-open" file-path)))
file-list))))
xah/dired-sort
(defun xah/dired-sort ()
"Sort dired dir listing in different ways.
Prompt for a choice."
(interactive)
(let (sort-by arg)
(setq sort-by (completing-read "Sort by:" '("name" "size" "date" "extension")))
(pcase sort-by
("name" (setq arg "-ahl --group-directories-first"))
("date" (setq arg "-ahl -t --group-directories-first"))
("size" (setq arg "-ahl -S --group-directories-first"))
("extension" (setq arg "ahlD -X --group-directories-first"))
(otherwise (error "Dired-sort: unknown option %s" otherwise)))
(dired-sort-other arg)))
Switch between buffers
Two default shortcuts I really like from Spacemacs are SPC b m
and SPC
b s
, which bring the user directly to the *Messages*
buffer and the
*scratch*
buffer respectively. These functions do exactly this.
(defun switch-to-messages-buffer ()
"Switch to Messages buffer."
(interactive)
(switch-to-buffer "*Messages*"))
(defun switch-to-scratch-buffer ()
"Switch to Messages buffer."
(interactive)
(switch-to-buffer "*scratch*"))
Org Functions
Emphasize text
/ | <c> | <c> |
---|---|---|
Emphasis | Character | Character code |
Bold | * |
42 |
Italic | / |
47 |
Code | ~ |
126 |
(defun org-mode-emphasize-bold ()
"Emphasize as bold the current region.
See also `org-emphasize'."
(interactive)
(org-emphasize 42))
Handle new windows
The two functions below allow the user to not only create a new window to the right or below the current window (respectively), but also to focus the new window immediately.
(defun split-window-right-and-focus ()
(interactive)
(split-window-right)
(windmove-right)
(when (and (boundp 'golden-ratio-mode)
(symbol-value golden-ratio-mode))
(golden-ratio)))
(defun split-window-below-and-focus ()
(interactive)
(split-window-below)
(windmove-down)
(when (and (boundp 'golden-ratio-mode)
(symbol-value golden-ratio-mode))
(golden-ratio)))
phundrak/toggle-org-src-window-split
(defun phundrak/toggle-org-src-window-split ()
"This function allows the user to toggle the behavior of
`org-edit-src-code'. If the variable `org-src-window-setup' has
the value `split-window-right', then it will be changed to
`split-window-below'. Otherwise, it will be set back to
`split-window-right'"
(interactive)
(if (equal org-src-window-setup 'split-window-right)
(setq org-src-window-setup 'split-window-below)
(setq org-src-window-setup 'split-window-right))
(message "Org-src buffers will now split %s"
(if (equal org-src-window-setup 'split-window-right)
"vertically"
"horizontally")))
Package Management
Repositories
By default, only GNU’s repositories are available to the package managers of Emacs. I also want to use Melpa and org-mode’s repository, so let’s add them! Note that the Elpa repository has been renamed to the gnu repository due to the addition of another Elpa repository, nongnu, which will hosts packages that do not conform to the FSF’s copyright assignment. Both the gnu and the nonfree repositories are Elpa repositories now, and they were renamed in order to avoid any confusion between the two of them.
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
("org" . "https://orgmode.org/elpa/")
("gnu" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")))
Straight
For my package management, I prefer to use straight
(Github). This is
due to its capacity of integrating nicely with use-package
, which is
also supported by general
which I use for my keybindings (see below),
but also because with it I can specify where to retrieve packages that
are not on MELPA or ELPA but on Github and other online Git
repositories too.
(defvar bootstrap-version)
(defvar comp-deferred-compilation-deny-list ()) ; workaround, otherwise straight shits itself
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 5))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(package-initialize)
(unless package-archive-contents
(package-refresh-contents))
;; Initialize use-package on non-Linux platforms
(unless (package-installed-p 'use-package)
(package-install 'use-package))
(require 'use-package)
(setq use-package-always-ensure t)
Keybinding Management
Which-key
(use-package which-key
:straight (:build t)
:defer t
:init (which-key-mode)
:diminish which-key-mode
:config
(setq which-key-idle-delay 1))
General
General is an awesome package for managing keybindings. Not only is it
oriented towards keychords by default (which I love), but it also
provides some integration with evil so that we can declare keybindings
for certain states only! This is a perfect replacement for define-key
,
evil-define-key
, and any other function for defining keychords. And it
is also possible to declare a prefix for my keybindings! By default,
my keybinds that are not bound to a specific mode will be prefixed
with SPC
, but when I want to get more specific in terms of mode, I'll
prefix them with a comma (I’ve taken this habit from Spacemacs).
(use-package general
:defer t
:straight (:build t)
:init
(general-auto-unbind-keys))
Evil
(use-package evil
:straight (:build t)
:init
(progn
(setq evil-want-integration t
evil-want-keybinding nil
evil-want-C-u-scroll t
evil-want-C-i-jump nil)
(require 'evil-vars)
(evil-set-undo-system 'undo-tree))
:config
(evil-mode 1)
(setq evil-want-fine-undo t) ; more granular undo with evil
(evil-set-initial-state 'messages-buffer-mode 'normal)
(evil-set-initial-state 'dashboard-mode 'normal)
;; Use visual line motions even outside of visual-line-mode buffers
(evil-global-set-key 'motion "t" 'evil-next-visual-line)
(evil-global-set-key 'motion "s" 'evil-previous-visual-line)
(define-key evil-normal-state-map "c" nil)
(define-key evil-normal-state-map "C" nil)
(define-key evil-normal-state-map "t" nil)
(define-key evil-normal-state-map "T" nil)
(define-key evil-normal-state-map "s" nil)
(define-key evil-normal-state-map "S" nil)
(define-key evil-normal-state-map "r" nil)
(define-key evil-normal-state-map "R" nil)
(define-key evil-normal-state-map "h" nil)
(define-key evil-normal-state-map "H" nil)
(define-key evil-normal-state-map "j" nil)
(define-key evil-normal-state-map "J" nil)
(define-key evil-normal-state-map "k" nil)
(define-key evil-normal-state-map "K" nil)
(define-key evil-normal-state-map "l" nil)
(define-key evil-normal-state-map "L" nil)
(define-key evil-motion-state-map "h" 'evil-replace)
(define-key evil-motion-state-map "H" 'evil-replace-state)
(define-key evil-motion-state-map "j" 'evil-find-char-to)
(define-key evil-motion-state-map "J" 'evil-find-char-to-backward)
(define-key evil-motion-state-map "k" 'evil-substitute)
(define-key evil-motion-state-map "K" 'evil-smart-doc-lookup)
(define-key evil-motion-state-map "l" 'evil-change)
(define-key evil-motion-state-map "L" 'evil-change-line)
(define-key evil-motion-state-map "c" 'evil-backward-char)
(define-key evil-motion-state-map "C" 'evil-window-top)
(define-key evil-motion-state-map "t" 'evil-next-line)
(define-key evil-motion-state-map "T" 'evil-join)
(define-key evil-motion-state-map "s" 'evil-previous-line)
(define-key evil-motion-state-map "S" 'evil-lookup)
(define-key evil-motion-state-map "r" 'evil-forward-char)
(define-key evil-motion-state-map "R" 'evil-window-bottom))
(use-package evil-collection
:after evil
:straight (:build t)
:config
(evil-collection-init))
(use-package undo-tree
:defer t
:straight (:build t)
:init
(global-undo-tree-mode))
Hydra
(use-package hydra
:straight (:build t)
:defer t)
Hydras
The following hydra allows me to quickly zoom in and out in the current buffer.
(defhydra hydra-zoom ()
"Zoom current buffer."
("t" text-scale-increase "zoom in")
("s" text-scale-decrease "zoom out")
("0" text-scale-adjust "reset")
("q" nil "finished" :exit t))
This one allows me to quickly navigate between code blocks and interact with them. This code block was inspired by one you can find in Spacemacs.
(defhydra org-babel-transient ()
"
^Navigate^ ^Interact
^^^^^^^^^^^------------------------------------------
[_t_/_s_] navigate src blocs [_x_] execute src block
[_g_]^^ goto named block [_'_] edit src block
[_z_]^^ recenter screen [_q_] quit
"
("q" nil :exit t)
("t" org-babel-next-src-block)
("s" org-babel-previous-src-block)
("g" org-babel-goto-named-src-block)
("z" recenter-top-bottom)
("x" org-babel-execute-maybe)
("'" org-edit-special :exit t))
Similarly, this one is also inspired from Spacemacs and allows the
user to interact with the width of the buffer in writeroom
.
(defhydra writeroom-buffer-width ()
"Change the width of a `writeroom-mode' buffer."
("q" nil :exit t)
("t" writeroom-increase-width "enlarge")
("s" writeroom-decrease-width "shrink")
("r" writeroom-adjust-width "adjust"))
Another similar one is for mu4e-view-mode
that allows me to shrink or
grow the mu4e-headers
buffer when viewing an email.
(defhydra mu4e-headers-split-adjust-width ()
"Change the width of a `mu4e-headers' buffer."
("q" nil :exit t)
("t" mu4e-headers-split-view-shrink "shrink")
("s" mu4e-headers-split-view-grow "enlarge"))
Packages Configuration
Applications
Docker
(use-package docker
:defer t
:straight (:build t))
(use-package dockerfile-mode
:defer t
:straight (:build t)
:init
(put 'docker-image-name 'safe-local-variable #'stringp)
:mode "Dockerfile\\'")
Elfeed
Email - Mu4e
(setq message-signature nil
mail-signature nil)
(use-package mu4e
:after all-the-icons
:straight (:build t :location site)
:commands mu4e mu4e-compose-new
:bind (("C-x m" . mu4e-compose-new))
:init
(progn
(setq mu4e-completing-read-function 'completing-read
mu4e-use-fancy-chars t
mu4e-view-show-images t
message-kill-buffer-on-exit t
mu4e-org-support nil)
(let ((dir "~/Downloads/mu4e"))
(when (file-directory-p dir)
(setq mu4e-attachment-dir dir))))
:config
(progn
<<mu4e-keybindings>>
(setq mu4e-compose-signature nil)
(when (fboundp 'imagemagick-register-types)
(imagemagick-register-types))
(add-to-list 'mu4e-view-actions
'("View in browser" . mu4e-action-view-in-browser) t)
(require 'gnus-dired)
(setq gnus-dired-mail-mode 'mu4e-user-agent)
(add-hook 'mu4e-compose-mode-hook
(lambda () (use-hard-newlines t 'guess)))
(add-hook 'dired-mode-hook 'turn-on-gnus-dired-mode)
(add-hook 'mu4e-compose-mode-hook 'mml-secure-message-sign-pgpmime)
(setq mu4e-get-mail-command "mbsync -a"
mu4e-maildir "~/.mail"
mu4e-trash-folder "/Trash"
mu4e-refile-folder "/Archive"
mu4e-sent-folder "/Sent"
mu4e-drafts-folder "/Drafts"
mu4e-update-interval 60
mu4e-compose-format-flowed t
mu4e-view-show-addresses t
mu4e-sent-messages-behaviour 'sent
mu4e-hide-index-messages t
;; try to show images
mu4e-view-show-images t
mu4e-view-image-max-width 600
;; configuration for sending mail
message-send-mail-function #'smtpmail-send-it
smtpmail-stream-type 'starttls
message-kill-buffer-on-exit t ; close after sending
;; start with the first (default) context
mu4e-context-policy 'pick-first
;; compose with the current context, or ask
mu4e-compose-context-policy 'ask-if-none
;; use ivy
mu4e-completing-read-function #'ivy-completing-read
;; no need to ask
mu4e-confirm-quit t
mu4e-header-fields
'((:account . 12)
(:human-date . 12)
(:flags . 4)
(:from . 25)
(:subject)))
;; set mail user agent
(setq mail-user-agent 'mu4e-user-agent)
;; Use fancy icons
(setq mu4e-use-fancy-chars t
mu4e-headers-draft-mark `("D" . ,(all-the-icons-faicon "pencil":height 0.8))
mu4e-headers-flagged-mark `("F" . ,(all-the-icons-faicon "flag":height 0.8))
mu4e-headers-new-mark `("N" . ,(all-the-icons-faicon "rss":height 0.8))
mu4e-headers-passed-mark `("P" . ,(all-the-icons-faicon "check":height 0.8))
mu4e-headers-replied-mark `("R" . ,(all-the-icons-faicon "reply":height 0.8))
mu4e-headers-seen-mark `("S" . ,(all-the-icons-faicon "eye":height 0.8))
mu4e-headers-unread-mark `("u" . ,(all-the-icons-faicon "eye-slash":height 0.8))
mu4e-headers-trashed-mark `("T" . ,(all-the-icons-faicon "trash":height 0.8))
mu4e-headers-attach-mark `("a" . ,(all-the-icons-faicon "paperclip":height 0.8))
mu4e-headers-encrypted-mark `("x" . ,(all-the-icons-faicon "lock":height 0.8))
mu4e-headers-signed-mark `("s" . ,(all-the-icons-faicon "certificate":height 0.8)))
(setq mu4e-bookmarks
`((,(s-join " "
'("NOT flag:trashed"
"AND (maildir:/Inbox OR maildir:/Junk)"
"AND NOT to:CONLANG@LISTSERV.BROWN.EDU"
"AND NOT to:AUXLANG@LISTSERV.BROWN.EDU"
"AND NOT to:ateliers-emacs@framalistes.org"
"AND NOT to:ateliers-paris@emacs-doctor.com"
"AND NOT list:ateliers-emacs.framalistes.org"
"AND NOT list:ateliers-paris.emacs-doctor.com"))
"Inbox" ?i) ;; Inbox without the linguistics mailing lists
(,(s-join " "
'("NOT flag:trashed"
"AND (maildir:/Inbox OR maildir:/Junk)"
"AND (f:/.*up8\.edu|.*univ-paris8.*/"
"OR c:/.*up8\.edu|.*univ-paris8.*/"
"OR t:/.*up8\.edu|.*univ-paris8.*/)"))
"University" ?u) ;; University-related emails
(,(s-join " "
'("to:CONLANG@LISTSERV.BROWN.EDU"
"OR to:AUXLANG@LISTSERV.BROWN.EDU"))
"Linguistics" ?l) ;; linguistics mailing lists
(,(s-join " "
'("list:ateliers-emacs.framalistes.org"
"OR to:ateliers-paris@emacs-doctor.com"
"OR list:ateliers-paris.emacs-doctor.com"))
"Emacs" ?e) ;; Emacs mailing list
("maildir:/Sent" "Sent messages" ?s)
("flag:unread AND NOT flag:trashed" "Unread messages" ?U)
("date:today..now AND NOT flag:trashed" "Today's messages" ?t)
("date:7d..now AND NOT flag:trashed" "Last 7 days" ?w)
("date:1m..now AND NOT flag:trashed" "Last month" ?m)
("date:1y..now AND NOT flag:trashed" "Last year" ?y)
("flag:trashed AND NOT flag:trashed" "Trash" ?T)
("mime:image/* AND NOT flag:trashed" "Messages with images" ?p)))
;; Add a column to display what email account the email belongs to.
(add-to-list 'mu4e-header-info-custom
'(:account
:name "Account"
:shortname "Account"
:help "Which account this email belongs to"
:function
(lambda (msg)
(let ((maildir (mu4e-message-field msg :maildir)))
(format "%s" (substring maildir 1 (string-match-p "/" maildir 1)))))))
(setq smtpmail-smtp-server "mail.phundrak.com"
smtpmail-smtp-service 587
smtpmail-stream-type 'starttls
message-send-mail-function 'smtpmail-send-it)
(defun mu4e-action-open-as-pdf (msg)
"Export and open MSG as pdf."
(let* ((date (mu4e-message-field msg :date))
(infile (mu4e~write-body-to-html msg))
(outfile (format-time-string "/tmp/%Y-%m-%d-%H-%M-%S.pdf" date)))
(with-temp-buffer
(shell-command
(format "wkhtmltopdf %s %s" infile outfile) t))
(find-file outfile)))
(add-to-list 'mu4e-view-actions '("PDF view" . mu4e-action-open-as-pdf) t)))
;; Unbinding some stuff
(general-define-key
:keymaps '(mu4e-headers-mode-map mu4e-view-mode-map)
"s" nil)
(general-define-key
:states 'normal
:keymaps '(mu4e-headers-mode-map mu4e-view-mode-map)
"s" nil)
(general-define-key
:keymaps 'mu4e-view-mode-map
"SPC" nil
"S" nil
"r" nil
"c" nil)
(general-define-key
:keymaps 'mu4e-view-mode-map
:states 'normal
"SPC" nil
"S" nil
"r" nil
"c" nil
"gu" nil)
;; View
(general-define-key
:keymaps 'mu4e-view-mode-map
:states 'normal
)
(general-define-key
:states 'motion
:keymaps 'mu4e-view-mode-map
:prefix ","
"|" #'mu4e-view-pipe
"." '(mu4e-headers-split-adjust-width/body :wk "mu4e-headers width")
"a" '(nil :wk "attachments")
"a|" #'mu4e-view-pipe-attachment
"aa" #'mu4e-view-attachment-action
"ao" #'mu4e-view-open-attachment
"aO" #'mu4e-view-open-attachment-with
"c" '(nil :wk "compose")
"cc" #'mu4e-compose-new
"ce" #'mu4e-compose-edit
"cf" #'mu4e-compose-forward
"cr" #'mu4e-compose-reply
"cR" #'mu4e-compose-resend
"g" '(nil :wk "go to")
"gu" #'mu4e-view-go-to-url
"gX" #'mu4e-view-fetch-url
"l" #'mu4e-show-log
"m" '(nil :wk "mark")
"md" #'mu4e-view-mark-for-trash
"mD" #'mu4e-view-mark-for-delete
"mm" #'mu4e-view-mark-for-move
"mr" #'mu4e-view-mark-for-refile
"mR" #'mu4e-view-mark-for-read
"mu" #'mu4e-view-mark-for-unread
"mU" #'mu4e-view-mark-for-unmark
"t" '(nil :wk "thread")
"td" '((lambda ()
(interactive)
(mu4e-view-mark-thread '(trash)))
:wk "Mark as trash")
"tD" '((lambda ()
(interactive)
(mu4e-view-mark-thread '(delete)))
:wk "Mark as delete")
"tm" '((lambda ()
(interactive)
(mu4e-view-mark-thread '(move)))
:wk "Mark as move")
"tr" '((lambda ()
(interactive)
(mu4e-view-mark-thread '(refile)))
:wk "Mark as refile")
"tR" '((lambda ()
(interactive)
(mu4e-view-mark-thread '(read)))
:wk "Mark as read")
"tu" '((lambda ()
(interactive)
(mu4e-view-mark-thread '(unread)))
:wk "Mark as unread")
"tU" '((lambda ()
(interactive)
(mu4e-view-mark-thread '(unmark)))
:wk "Mark as unmark")
"T" '(nil :wk "toggle")
"Tc" #'mu4e-view-toggle-hide-cited
"Th" #'mu4e-view-toggle-html
"n" #'mu4e-view-headers-next
"N" #'mu4e-view-headers-next-unread
"p" #'mu4e-view-headers-prev
"P" #'mu4e-view-headers-prev-unread)
;; Headers
(general-define-key
:prefix ","
:keymaps 'mu4e-headers-mode-map
:states 'normal
"s" '(nil :wk "search")
"ss" #'swiper)
(general-define-key
:keymaps 'mu4e-headers-mode-map
:states 'motion
"t" #'evil-next-line
"s" #'evil-previous-line
"T" '((lambda ()
(interactive)
(mu4e-headers-mark-thread nil '(read)))
:wk "Mark as read"))
;; Message
(general-define-key
:states 'normal
:keymaps 'message-mode-map
:prefix ","
"," #'message-send-and-exit
"c" #'message-send-and-exit
"a" #'message-kill-buffer
"k" #'message-kill-buffer
"s" #'message-dont-send
"f" #'mml-attach-file)
(use-package org-msg
:after (org mu4e)
:straight (:build t)
:hook (mu4e-compose-pre . org-msg-mode)
:general (:keymaps 'org-msg-edit-mode-map
:prefix ","
:states 'normal
"," #'message-send-and-exit
"c" #'message-send-and-exit
"a" #'message-kill-buffer
"k" #'message-kill-buffer
"s" #'message-dont-send
"f" #'org-msg-attach)
:config
(progn
(defun my/org-msg-signature-convert (orig-fun &rest args)
"Tweak my signature when replying as plain/text only."
(let ((res (apply orig-fun args)))
(when (equal (cadr args) '(text))
(setf (alist-get 'signature res)
(replace-regexp-in-string "\n+" "\n" org-msg-signature)))
res))
(advice-add 'org-msg-composition-parameters
:around 'my/org-msg-signature-convert)
(setq org-msg-startup "inlineimages"
org-msg-default-alternatives '((new . (text html))
(reply-to-html . (text html))
(reply-to-text . (text)))
org-msg-convert-citation t
org-msg-greeting-name-limit 3
org-msg-signature (format "\n--\n#+begin_signature\n%s\n#+end_signature"
(with-temp-buffer
(insert-file-contents mail-signature-file)
(buffer-string))))))
(use-package mu4e-alert
:straight (:build t)
:after mu4e)
PDF Tools
(use-package pdf-tools
:defer t
:magic ("%PDF" . pdf-view-mode)
:straight (:build t)
:mode (("\\.pdf\\'" . pdf-view-mode))
:config
(progn
(with-eval-after-load 'pdf-view
(setq pdf-view-midnight-colors '("#d8dee9" . "#2e3440")))
(general-define-key
:keymaps 'pdf-view-mode-map
"SPC" nil)
(general-define-key
:states 'normal
:keymaps 'pdf-view-mode-map
"SPC" nil
"y" #'pdf-view-kill-ring-save
"t" #'evil-collection-pdf-view-next-line-or-next-page
"s" #'evil-collection-pdf-view-previous-line-or-previous-page))
(general-define-key
:states 'motion
:keymaps 'pdf-view-mode-map
:prefix "SPC"
"a" '(nil :which-key "annotations")
"aD" #'pdf-annot-delete
"at" #'pdf-annot-attachment-dired
"ah" #'pdf-annot-add-highlight-markup-annotation
"al" #'pdf-annot-list-annotations
"am" #'pdf-annot-markup-annotation
"ao" #'pdf-annot-add-strikeout-markup-annotation
"as" #'pdf-annot-add-squiggly-markup-annotation
"at" #'pdf-annot-add-text-annotation
"au" #'pdf-annot-add-underline-markup-annotation
"f" '(nil :which-key "fit")
"fw" #'pdf-view-fit-width-to-window
"fh" #'pdf-view-fit-height-to-window
"fp" #'pdf-view-fit-page-to-window
"s" '(nil :which-key "slice/search")
"sb" #'pdf-view-set-slice-from-bounding-box
"sm" #'pdf-view-set-slice-using-mouse
"sr" #'pdf-view-reset-slice
"ss" #'pdf-occur
"o" 'pdf-outline
"m" 'pdf-view-midnight-minor-mode)
:hook
(pdf-tools-enabled . pdf-view-midnight-minor-mode))
(use-package pdf-view-restore
:after pdf-tools
:hook (pdf-view-mode . pdf-view-restore-mode))
(setq pdf-view-restore-filename (expand-file-name ".tmp/pdf-view-restore"
user-emacs-directory))
Screenshot
(use-package screenshot
:defer t
:straight (screenshot :build t
:type git
:host github
:repo "tecosaur/screenshot"))
Shells
Shell-pop
Shell-pop allows the user to easily call for a new shell in a pop-up buffer.
(use-package shell-pop
:defer t
:straight (:build t)
:custom
(shell-pop-default-directory "/home/phundrak")
(shell-pop-shell-type (quote ("eshell" "*eshell*" (lambda nil (eshell shell-pop-term-shell)))))
(shell-pop-window-size 30)
(shell-pop-full-span nil)
(shell-pop-window-position "bottom")
(shell-pop-autocd-to-working-dir t)
(shell-pop-restore-window-configuration t)
(shell-pop-cleanup-buffer-at-process-exit t))
VTerm
(use-package vterm
:defer t
:straight t
:config
(setq vterm-shell "/usr/bin/fish"))
XWidgets Webkit Browser
(general-define-key
:keymaps 'xwidget-webkit-mode-map
:states 'normal
"<mouse-4>" #'xwidget-webkit-scroll-down-line
"<mouse-5>" #'xwidget-webkit-scroll-up-line
"c" #'xwidget-webkit-scroll-backward
"t" #'xwidget-webkit-scroll-up-line
"s" #'xwidget-webkit-scroll-down-line
"r" #'xwidget-webkit-scroll-forward
"h" #'xwidget-webkit-goto-history
"j" nil
"k" nil
"l" nil
"H" nil
"L" nil
"T" #'xwidget-webkit-scroll-up
"S" #'xwidget-webkit-scroll-down)
(general-define-key
:keymaps 'xwidget-webkit-mode-map
:states 'normal
:prefix ","
"b" #'xwidget-webkit-back
"f" #'xwidget-webkit-forward
"r" #'xwidget-webkit-reload)
Wttr.in
(use-package wttrin
:defer t
:straight (wttrin :build t
:type git
;; :host github
;; :repo "Phundrak/emacs-wttrin"
:local-repo "~/fromGIT/emacs-packages/emacs-wttrin"
)
:config
(setq wttrin-default-cities '("Aubervilliers" "Paris" "Lyon" "Nonières"
"Saint Agrève")
wttrin-use-metric t))
Autocompletion
Code Autocompletion
(use-package company
:straight (:build t)
:defer t
:hook (company-mode . evil-normalize-keymaps)
:init (global-company-mode)
:config
(setq company-minimum-prefix-length 2
company-toolsip-limit 14
company-tooltip-align-annotations t
company-require-match 'never
company-global-modes '(not erc-mode message-mode help-mode gud-mode)
company-frontends
'(company-pseudo-tooltip-frontend ; always show candidates in overlay tooltip
company-echo-metadata-frontend) ; show selected candidate docs in echo area
;; Buffer-local backends will be computed when loading a major
;; mode, so only specify a global default here.
company-backends '(company-capf)
;; These auto-complete the current selection when
;; `company-auto-complete-chars' is typed. This is too
;; magical. We already have the much more explicit RET and
;; TAB.
company-auto-complete nil
company-auto-complete-chars nil
;; Only search the current buffer for `company-dabbrev' (a
;; backend that suggests text you open buffers). This prevents
;; Company from causing lag once you have a lot of buffers
;; open.
company-dabbrev-other-buffers nil
;; Make `company-dabbrev' fully case-sensitive, to improve UX
;; with domai-specific words with particular casing.
company-dabbrev-ignore-case nil
company-dabbrev-downcase nil))
(use-package company-dict
:after company
:straight (:build t)
:config
(setq company-dict-dir (expand-file-name "dicts" user-emacs-directory)))
(use-package company-box
:straight (:build t)
:after (company all-the-icons)
:config
(setq company-box-show-single-candidate t
company-box-backends-colors nil
company-box-max-candidates 50
company-box-icons-alist 'company-box-icons-all-the-icons
company-box-icons-all-the-icons
(let ((all-the-icons-scale-factor 0.8))
`((Unknown . ,(all-the-icons-material "find_in_page" :face 'all-the-icons-purple))
(Text . ,(all-the-icons-material "text_fields" :face 'all-the-icons-green))
(Method . ,(all-the-icons-material "functions" :face 'all-the-icons-red))
(Function . ,(all-the-icons-material "functions" :face 'all-the-icons-red))
(Constructor . ,(all-the-icons-material "functions" :face 'all-the-icons-red))
(Field . ,(all-the-icons-material "functions" :face 'all-the-icons-red))
(Variable . ,(all-the-icons-material "adjust" :face 'all-the-icons-blue))
(Class . ,(all-the-icons-material "class" :face 'all-the-icons-red))
(Interface . ,(all-the-icons-material "settings_input_component" :face 'all-the-icons-red))
(Module . ,(all-the-icons-material "view_module" :face 'all-the-icons-red))
(Property . ,(all-the-icons-material "settings" :face 'all-the-icons-red))
(Unit . ,(all-the-icons-material "straighten" :face 'all-the-icons-red))
(Value . ,(all-the-icons-material "filter_1" :face 'all-the-icons-red))
(Enum . ,(all-the-icons-material "plus_one" :face 'all-the-icons-red))
(Keyword . ,(all-the-icons-material "filter_center_focus" :face 'all-the-icons-red))
(Snippet . ,(all-the-icons-material "short_text" :face 'all-the-icons-red))
(Color . ,(all-the-icons-material "color_lens" :face 'all-the-icons-red))
(File . ,(all-the-icons-material "insert_drive_file" :face 'all-the-icons-red))
(Reference . ,(all-the-icons-material "collections_bookmark" :face 'all-the-icons-red))
(Folder . ,(all-the-icons-material "folder" :face 'all-the-icons-red))
(EnumMember . ,(all-the-icons-material "people" :face 'all-the-icons-red))
(Constant . ,(all-the-icons-material "pause_circle_filled" :face 'all-the-icons-red))
(Struct . ,(all-the-icons-material "streetview" :face 'all-the-icons-red))
(Event . ,(all-the-icons-material "event" :face 'all-the-icons-red))
(Operator . ,(all-the-icons-material "control_point" :face 'all-the-icons-red))
(TypeParameter . ,(all-the-icons-material "class" :face 'all-the-icons-red))
(Template . ,(all-the-icons-material "short_text" :face 'all-the-icons-green))
(ElispFunction . ,(all-the-icons-material "functions" :face 'all-the-icons-red))
(ElispVariable . ,(all-the-icons-material "check_circle" :face 'all-the-icons-blue))
(ElispFeature . ,(all-the-icons-material "stars" :face 'all-the-icons-orange))
(ElispFace . ,(all-the-icons-material "format_paint" :face 'all-the-icons-pink))))))
Ivy
My main menu package is ivy
which I use as much as possible –I’ve
noticed helm
can be slow, very slow in comparison to ivy
so I’ll use
the latter as much as possible. Actually, only ivy
is installed for
now. I could have used ido
too, but I find it to be a bit too
restricted in terms of features compared to ivy
.
(use-package ivy
:straight (:build t)
:defer t
:diminish
:bind (("C-s" . swiper)
:map ivy-minibuffer-map
("TAB" . ivy-alt-done)
("C-l" . ivy-alt-done)
("C-t" . ivy-next-line)
("C-s" . ivy-previous-line)
:map ivy-switch-buffer-map
("C-t" . ivy-next-line)
("C-s" . ivy-previous-line)
("C-l" . ivy-done)
("C-d" . ivy-switch-buffer-kill)
:map ivy-reverse-i-search-map
("C-t" . ivy-next-line)
("C-s" . ivy-previous-line)
("C-d" . ivy-reverse-i-search-kill))
:config
(ivy-mode 1)
(setq ivy-wrap t
ivy-height 17
ivy-sort-max-size 50000
ivy-fixed-height-minibuffer t
ivy-read-action-functions #'ivy-hydra-read-action
ivy-read-action-format-function #'ivy-read-action-format-columns
projectile-completion-system 'ivy
ivy-on-del-error-function #'ignore
ivy-use-selectable-prompt t))
There is also prescient.el
that offers some nice features when
coupled with ivy
, guess what was born out of it? ivy-prescient
, of
course!
(use-package ivy-prescient
:after ivy
:straight (:build t))
I warned you I’d use too much all-the-icons
, I did!
(use-package all-the-icons-ivy
:straight (:build t)
:after (ivy all-the-icons)
:hook (after-init . all-the-icons-ivy-setup))
A buffer popping at the bottom of the screen is nice and all, but have you considered a floating buffer in the center of your frame?
(use-package ivy-posframe
:defer t
:hook (ivy-mode . ivy-posframe-mode)
:straight (ivy-posframe :build t
:type git
:host github
:repo "tumashu/ivy-posframe")
:config
(setq ivy-fixed-height-minibuffer nil
ivy-posframe-border-width 10
ivy-posframe-parameters
`((min-width . 90)
(min-height . ,ivy-height))))
Finally, let’s make ivy
richer:
(use-package ivy-rich
:straight (:build t)
:after ivy
:init
(ivy-rich-mode 1))
Counsel
(use-package counsel
:straight (:build t)
:defer t
:bind (("M-x" . counsel-M-x)
("C-x b" . counsel-ibuffer)
("C-x C-f" . counsel-find-file)
:map minibuffer-local-map
("C-r" . 'counsel-minibuffer-history)))
Yasnippet
(use-package yasnippet
:defer t
:straight (:build t)
:init
(yas-global-mode))
(use-package yasnippet-snippets
:after yasnippet
:straight (:build t))
(use-package yatemplate
:after yasnippet
:straight (:build t))
(use-package ivy-yasnippet
:after (ivy yasnippet)
:straight (:build t))
Editing
Evil Nerd Commenter
(use-package evil-nerd-commenter
:after evil
:straight (:build t))
Evil Surround
This package allows its user to surround regions with pairs of characters easily.
Parinfer
Don’t let the name of the package fool you! parinfer-rust-mode
is not
a parinfer
mode for rust-mode
, but a mode for parinfer-rust
. parinfer
was a project for handling parenthesis and other double markers in a
much more intuitive way when writing Lisp code. However, it is now out
of date (last commit was on January 2nd, 2019) and the repository has
since been archived. New implementations then appeared, one of them is
parinfer-rust
, obviously written in Rust, around which
parinfer-rust-mode
is built.
(use-package parinfer-rust-mode
:defer t
:diminish parinfer-rust-mode
:hook emacs-lisp-mode common-lisp-mode scheme-mode
:init
(setq parinfer-rust-auto-download t
parinfer-rust-library-directory (concat user-emacs-directory
"parinfer-rust/")))
string-edit
string-edit
is a cool package that allows the user to write naturally
a string and get it automatically escaped for you. No more manually
escaping your strings!
(use-package string-edit
:defer t
:straight (:build t))
Writeroom
On the other hand, writeroom
allows the user to enter a
distraction-free mode of Emacs, and I like that!
(use-package writeroom-mode
:defer t
:straight (:build t))
Emacs built-ins
Dired
Dired is Emacs’ built-in file manager. It’s really great, and replaces any graphical file manager for me most of the time because:
- I am not limited to x tabs or panes
- All actions can be done with keybindings
- I get a consistent behavior between Dired and Emacs, since it’s the same thing.
However, the only thing I lack still is thumbnails. In any case, I need still to make some tweaks in order to make it usable for me.
(use-package dired
:straight (:type built-in)
:defer t
:general
(:keymaps 'dired-mode-map
:states 'normal
"(" #'dired-hide-details-mode
"n" #'evil-next-line
"p" #'evil-previous-line)
:config
(setq dired-dwim-target t))
Dired-x stands for “dired extra” which provides a couple more features that are for some reason not included in dired yet.
(use-package dired-x
:straight (:type built-in)
:after dired
:commands (dired-jump dired-jump-other-window dired-omit-mode)
:general
(:keymaps 'dired-mode-map
:states 'normal
"«" #'dired-omit-mode))
dired-du
provides the user with the option to be able to see the size
of directories as they are, rather than just the size of their
inode. However, I will not enable it by default as it can take some
time to get the actual size of a directory.
(use-package dired-du
:after dired
:straight (:build t)
:general
(:keymaps 'dired-mode-map
:states 'normal
"»" #'dired-du-mode))
This package on the other hand provides Git information to the user in
the current dired buffer in a similar way to how Github and Gitea
display the latest commit message and its age about a file in the file
tree. And by default, I want dired-git-info
to hide file details.
(use-package dired-git-info
:after dired
:general
(:keymaps 'dired-mode-map
:states 'normal
")" #'dired-git-info-mode)
:hook (dired-after-reading . dired-git-info-auto-enable)
:config
(setq dgi-auto-hide-details-p t))
Diredfl makes dired colorful, and much more readable in my opinion.
(use-package diredfl
:straight (:build t)
:after dired
:init
(diredfl-global-mode 1))
And let’s add some fancy icons in dired!
(use-package all-the-icons-dired
:straight (:build t)
:hook (dired-mode . all-the-icons-dired-mode))
(use-package image-dired+
:after image-dired
:init (image-diredx-adjust-mode 1))
Eshell
Eshell is a built-in shell available from Emacs which I use almost as often as fish. Some adjustments are necessary to make it fit my taste though.
(general-define-key
:keymaps 'eshell-mode-map
:states 'insert
"C-a" #'eshell-bol
"C-e" #'end-of-line)
Aliases
First, let’s declare our list of “dumb” aliases we’ll use in Eshell. You can find them here.
(setq eshell-aliases-file (expand-file-name "eshell-aliases" user-emacs-directory))
A couple of other aliases will be defined through custom Elisp functions, but first I’ll need a function for concatenating a shell command into a single string:
(defun phundrak/concatenate-shell-command (&rest command)
"Concatenate an eshell `COMMAND' into a single string.
All elements of `COMMAND' will be joined in a single
space-separated string."
(string-join command " "))
I’ll also declare some aliases here, such as open
and openo
that
respectively allow me to open a file in Emacs, and same but in another
window.
(defalias 'open 'find-file)
(defalias 'openo 'find-file-other-window)
As you see, these were not declared in my dedicated aliases file but
rather were declared programmatically. This is because I like to keep
my aliases file for stuff that could work too with other shells were
the syntax a bit different, and aliases related to Elisp are kept
programmatically. I’ll also declare list-buffers
an alias of ibuffer
because naming it that way kind of makes more sense to me.
(defalias 'list-buffers 'ibuffer)
I still have some stupid muscle memory telling me to open emacs
, vim
or nano
in Eshell, which is stupid: I’m already inside Emacs and I
have all its power available instantly. So, let’s open each file
passed to these commands.
(defun eshell/emacs (&rest file)
"Open each `FILE' and kill eshell.
Old habits die hard."
(when file
(dolist (f (reverse file))
(find-file f t))))
Finally, I’ll declare mkcd
which allows the simultaneous creation of a
directory and moving into this newly created directory. And of course,
it will also work if the directory also exists or if parent
directories don’t, similarly to the -p
option passed to mkdir
.
(defun eshell/mkcd (dir)
"Create the directory `DIR' and move there.
If the directory `DIR' doesn’t exist, create it and its parents
if needed, then move there."
(mkdir dir t)
(cd dir))
Custom Functions
When I’m in Eshell, sometimes I wish to open multiple files at once in
Emacs. For this, when I have several arguments for find-file
, I want
to be able to open them all at once. Let’s modify find-file
like so:
(defadvice find-file (around find-files activate)
"Also find all files within a list of files. This even works recursively."
(if (listp filename)
(cl-loop for f in filename do (find-file f wildcards))
ad-do-it))
I also want to be able to have multiple instances of Eshell opened at
once. For that, I declared the function eshell-new
that does exactly
that.
(defun eshell-new ()
"Open a new instance of eshell."
(interactive)
(eshell 'N))
Environment Variables
Some environment variables need to be correctly set so Eshell can
correctly work. I would like to set two environment variables related
to Dart development: the DART_SDK
and ANDROID_HOME
variables.
(setenv "DART_SDK" "/opt/dart-sdk/bin")
(setenv "ANDROID_HOME" (concat (getenv "HOME") "/Android/Sdk/"))
The EDITOR
variable also needs to be set for git commands, especially the
yadm
commands.
(setenv "EDITOR" "emacsclient -c -a emacs")
Finally, for some specific situations I need SHELL
to be set to
something more standard than fish:
(setenv "SHELL" "/bin/sh")
Visual configuration
I like to have at quick glance some information about my machine when I fire up a terminal. I haven’t found anything that does that the way I like it, so I’ve written a package!
(use-package eshell-info-banner
:defer t
:straight (eshell-info-banner :build t
:type git
:host github
:repo "phundrak/eshell-info-banner.el")
:hook (eshell-banner-load . eshell-info-banner-update-banner)
:config
(setq eshell-info-banner-width 80
eshell-info-banner-partition-prefixes '("/dev" "zroot" "tank")))
Tramp
Tramp is an Emacs built-in package that allows the user to connect to
various hosts using various protocols, such as ssh
and
rsync
. However, I have some use-case for Tramp which are not
supported natively. I will describe them here.
(require 'tramp)
Yadm
yadm
is a git wrapper made to easily manage your dotfiles. It has
loads of features I don’t use (the main one I like but don’t use is
its Jinja-like host and OS-aware syntax), but unfortunately Magit
doesn’t play nice with it. Tramp to the rescue, and this page explains
how! Let’s just insert in my config this code snippet:
(add-to-list 'tramp-methods
'("yadm"
(tramp-login-program "yadm")
(tramp-login-args (("enter")))
(tramp-login-env (("SHELL") ("/bin/sh")))
(tramp-remote-shell "/bin/sh")
(tramp-remote-shell-args ("-c"))))
I’ll also create a fuction for connecting to this new Tramp protocol:
(defun my/yadm ()
"Manage my dotfiles through TRAMP."
(interactive)
(magit-status "/yadm::"))
EXWM
(defvar phundrak/exwm-enabled (and (seq-contains command-line-args "--use-exwm")))
(use-package xelb
:straight (:build t))
(use-package exwm
:straight (:build t)
:after (xelb)
:init
(setq mouse-autoselect-window nil
focus-follows-mouse t
exwm-workspace-warp-cursor t)
:config
(setq exwm-input-global-keys
`(([?\s-r] . exwm-reset)
([?\s-w] . exwm-workspace-switch)
,@(mapcar (lambda (i)
`(,(kbd (format "s-%s" (car i))) .
(lambda ()
(interactive)
(exwm-workspace-switch-create ,(cdr i)))))
'(("\"" . 1)
("«" . 2)
("»" . 3)
("(" . 4)
(")" . 5)
("@" . 6)
("+" . 7)
("-" . 8)
("/" . 9)
("*" . 0)))))
(add-hook 'exwm-update-class-hook
(lambda ()
(exwm-workspace-rename-buffer exwm-class-name)))
(require 'exwm-config)
(exwm-config-default)
(exwm-enable))
(use-package desktop-environment
:if phundrak/exwm-enabled
:after exwm
:straight (:build t))
Making my life easier
Bufler
Bufler is a package that organizes and lists buffers in a much better
way than how they are usually sorted. You can easily and quickly find
buffers by their group, not only by their name, and THIS is great
news! Also, no helm
please! And for some reasons the keybindings are
borked by default, so let’s redefine them, and let’s also rebind SPC
to p
since it would conflict with my main general
prefix.
(use-package bufler
:straight (bufler :build t
:files (:defaults (:exclude "helm-bufler.el")))
:defer t
:general
(:keymaps 'bufler-list-mode-map
:states 'normal
"?" #'hydra:bufler/body
"g" #'bufler
"f" #'bufler-list-group-frame
"F" #'bufler-list-group-make-frame
"N" #'bufler-list-buffer-name-workspace
"k" #'bufler-list-buffer-kill
"p" #'bufler-list-buffer-peek
"s" #'bufler-list-buffer-save
"RET" #'bufler-list-buffer-switch))
Helpful
As the name tells, helpful
is a really helpful package which greatly
enhances a couple of built-in functions from Emacs, namely:
Vanilla Emacs Function | Helpful Function | Comment |
---|---|---|
describe-function |
helpful-callable |
Only interactive functions |
describe-function |
helpful-function |
Only actual functions (including interactive) |
describe-function |
helpful-macro |
|
describe-command |
helpful-command |
|
describe-key |
helpful-key |
|
describe-variable |
helpful-variable |
(use-package helpful
:straight (:build t)
:after (counsel ivy)
:custom
(counsel-describe-function-function #'helpfull-callable)
(counsel-describe-variable-function #'helpfull-variable)
:bind
([remap describe-function] . counsel-describe-function)
([remap describe-command] . helpful-command)
([remap describe-variable] . counsel-describe-variable)
([remap describe-key] . helpful-key))
Org-mode
(use-package org
:straight (org :build t
:type git
:host nil
:repo "https://code.orgmode.org/bzg/org-mode.git")
:defer t
:commands (orgtbl-mode)
:hook (org-mode . visual-line-mode)
:hook (org-mode . org-num-mode)
:init
(auto-fill-mode -1)
:general
(:states 'normal
:keymaps 'org-mode-map
"RET" 'org-open-at-point)
(:states 'normal
:prefix ","
:keymaps 'org-mode-map
"RET" #'org-ctrl-c-ret
"*" #'org-ctrl-c-star
"," #'org-ctrl-c-ctrl-c
"'" #'org-edit-special
"-" #'org-ctrl-c-minus
"a" #'org-agenda
"c" #'org-capture
"l" #'org-store-link
"p" #'org-priority
"b" '(:ignore t :wk "babel")
"b." #'org-babel-transient/body
"bb" #'org-babel-execute-buffer
"bc" #'org-babel-check-src-block
"bC" #'org-babel-tangle-clean
"be" #'org-babel-execute-maybe
"bf" #'org-babel-tangle-file
"bn" #'org-babel-next-src-block
"bo" #'org-babel-open-src-block-result
"bp" #'org-babel-previous-src-block
"br" #'org-babel-remove-result-one-or-many
"bR" #'org-babel-goto-named-result
"bt" #'org-babel-tangle
"bi" #'org-babel-view-src-block-info
"d" '(:ignore t :wk "dates")
"dd" #'org-deadline
"ds" #'org-schedule
"dt" #'org-time-stamp
"dT" #'org-time-stamp-inactive
"e" '(:ignore t :wk "export")
"ee" #'org-export-dispatch
"i" '(:ignore t :wk "insert")
"ib" #'org-insert-structure-template
"id" #'org-insert-drawer
"ie" #'org-set-effort
"if" #'org-footnote-new
"ih" #'org-insert-heading
"ii" #'org-insert-item
"il" #'org-insert-link
"in" #'org-add-note
"ip" #'org-set-property
"is" #'org-insert-subheading
"it" #'org-set-tags-command
"j" '(:ignore t :wk "jump")
"ja" #'counsel-org-goto-all
"jh" #'counsel-org-goto
"t" '(:ignore t :wk "tables")
"ta" #'org-table-align
"te" #'org-table-eval-formula
"tf" #'org-table-field-info
"th" #'org-table-convert
"tl" #'org-table-recalculate
"tp" #'org-plot/gnuplot
"ts" #'org-table-sort-lines
"tw" #'org-table-wrap-region
"tN" #'org-table-create-with-table.el
"tc" #'org-table-previous-field
"tr" #'org-table-next-field
"tC" #'org-table-move-column-left
"tT" #'org-table-move-row-down
"tS" #'org-table-move-row-up
"tR" #'org-table-move-column-right
"td" '(:ignore t :wk "delete")
"tdc" #'org-table-delete-column
"tdr" #'org-table-kill-row
"ti" '(:ignore t :wk "insert")
"tic" #'org-table-insert-column
"tih" #'org-table-insert-hline
"tir" #'org-table-insert-row
"tiH" #'org-table-hline-and-move
"tt" '(:ignore t :wk "toggle")
"ttf" #'org-table-toggle-formula-debugger
"tto" #'org-table-toggle-coordinate-overlays
"T" '(:ignore t :wk "toggle")
"Tc" #'org-toggle-checkbox
"Ti" #'org-toggle-inline-images
"Tl" #'org-latex-preview
"Tn" #'org-num-mode
"Ts" 'phundrak/toggle-org-src-window-split
"Tt" #'org-show-todo-tree
"TT" #'org-todo)
(:states 'normal
:keymaps 'org-src-mode-map
:prefix ","
"'" #'org-edit-src-exit
"k" #'org-edit-src-abort))
(after! org
(setq org-hide-leading-stars nil
org-superstar-leading-bullet ?\s
org-hide-macro-markers t
org-ellipsis " ⤵"
org-image-actual-width 550
org-redisplay-inline-images t
org-display-inline-images t
org-startup-with-inline-images "inlineimages"
org-pretty-entities t
org-fontify-whole-heading-line t
org-fontify-done-headline t
org-fontify-quote-and-verse-blocks t
org-startup-indented t
org-startup-align-all-tables t
org-use-property-inheritance t
org-list-allow-alphabetical t
org-M-RET-may-split-line nil
org-src-window-setup 'split-window-below
org-src-fontify-natively t
org-src-tab-acts-natively t
org-log-done 'time
org-directory "~/org"
org-default-notes-file (expand-file-name "notes.org" org-directory))
<<org-agenda-files>>
<<org-behavior-electric>>
<<org-use-sub-superscripts>>
<<org-latex-compiler>>
<<org-latex-listings>>
<<org-latex-default-packages>>
<<org-export-latex-hyperref-format>>
<<org-latex-pdf-process>>
<<org-re-reveal-root>>
<<org-html-validation>>
<<org-latex-classes>>
<<org-publish-projects>>)
(use-package evil-org
:straight (:build t)
:after org
:hook (org-mode . evil-org-mode)
:config
;; (dolist (key '("h" "j" "k" "l"))
;; (define-key org-mode-map (kbd (format "M-%s" key)) 'undefined))
(setq-default evil-org-movement-bindings
'((up . "s")
(down . "t")
(left . "c")
(right . "r")))
(evil-org-set-key-theme '(textobjects navigation calendar additional shift operators))
(require 'evil-org-agenda)
(evil-org-agenda-set-keys))
(use-package org-contrib
:defer t
:after org
:straight (:build t))
(use-package conlanging
:straight (conlanging :build t
:type git
:host nil
:repo "https://labs.phundrak.com/phundrak/conlanging.el")
:after org
:defer t)
Since very recently, the contrib/lisp/
directory of org moved out of
the main repository to this repository. On the other hand,
contrib/scripts/
moved to the worg repository, but I don’t need
it. The main reason I want org-contrib
is due to ox-extra
that allow
the usage of the :ignore:
tag in org.
(use-package org-contrib
:straight (:build t)
:init
(require 'ox-extra)
(ox-extras-activate '(latex-header-blocks ignore-headlines)))
Agenda
(setq-default org-agenda-files (list "~/org/agenda" "~/org/notes.org"))
Behavior
A useful package I like is toc-org
which creates automatically a table
of contents. My main usage for this however is not just to create a
table of content of my files to quickly jump around my file (I have
counsel-org-goto
for that), but it is for creating table of contents
for org files that will be hosted and viewable on Github.
(use-package toc-org
:after org
:straight (:build t)
:init
(add-to-list 'org-tag-alist '("TOC" . ?T))
:hook (org-mode . toc-org-enable)
:hook (markdown-mode . toc-org-enable))
electric-mode
also bothers me a lot when editing org files, so let’s deactivate it:
(add-hook 'org-mode-hook (lambda ()
(interactive)
(electric-indent-local-mode -1)))
Babel
A package I use from time to time is ob-latex-as-png
which allows me
to easily convert a LaTeX snippet into a PNG, regardless of the
exporter I use afterwards. Its installation is pretty simple:
(use-package ob-latex-as-png
:defer t
:straight (:build t))
File export
I want to disable by default behavior of ^
and _
for only one
character, making it compulsory to use instead ^{}
and _{}
respectively. This is due to my frequent usage of the underscore in my
org files as a regular character and not a markup one, especially when
describing phonetics evolution. So, let’s disable it:
(setq org-use-sub-superscripts (quote {}))
(use-package htmlize
:straigh (:build t)
:defer t)
LaTeX
When it comes to exports, I want the LaTeX and PDF exports to be done with XeLaTeX only. This implies the modification of the following variable:
(setq org-latex-compiler "xelatex")
I also want to get by default minted
for LaTeX listings so I can have
syntax highlights:
(setq org-latex-listings 'minted)
The default packages break my LaTeX exports: for some reasons, images
are not loaded and exported in PDFs, so I needed to redifine the
default packages excluding the one that broke my exports. I also added
a default package, minted
for syntax highlighting.
(setq org-latex-default-packages-alist '(("" "graphicx" t)
("T1" "fontspec" t ("pdflatex"))
("" "longtable" nil)
("" "wrapfig" nil)
("" "rotating" nil)
("normalem" "ulem" t)
("" "amsmath" t)
("" "textcomp" t)
("" "amssymb" t)
("" "capt-of" nil)
("" "minted" nil)
("" "hyperref" nil)))
By the way, reference links in LaTeX should be written in this format:
(setq org-export-latex-hyperref-format "\\ref{%s}")
When it comes to the export itself, the latex file needs to be processed several times through XeLaTeX in order to get some references right. Don’t forget to also run bibtex!
(setq org-latex-pdf-process
'("xelatex -8bit -shell-escape -interaction nonstopmode -output-directory %o %f"
"bibtex %b"
"xelatex -8bit -shell-escape -interaction nonstopmode -output-directory %o %f"
"xelatex -8bit -shell-escape -interaction nonstopmode -output-directory %o %f"))
Finally, org-mode is supposed to automatically clean logfiles after it exports an org file to LaTeX. However, it misses a few, so I need to add their extension like so:
(dolist (ext '("bbl" "lot"))
(add-to-list 'org-latex-logfiles-extensions ext t))
HTML
For Reveal.JS exports, I need to set where to find the framework by default:
(setq org-re-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js")
On HTML exports, Org-mode tries to include a validation link for the exported HTML. Let’s disable that since I never use it.
(setq org-html-validation-link nil)
LaTeX formats
I currently have two custom formats for my Org-mode exports: one for general use
(initialy for my conlanging files, hence its conlang
name), and one for beamer
exports.
Below is the declaration of the conlang
LaTeX class:
'("conlang"
"\\documentclass{book}"
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
And here is the declaration of the beamer
class:
`("beamer"
,(concat "\\documentclass[presentation]{beamer}\n"
"[DEFAULT-PACKAGES]"
"[PACKAGES]"
"[EXTRA]\n")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
Both these classes have to be added to org-latex-classes
like so:
(eval-after-load "ox-latex"
'(progn
(add-to-list 'org-latex-classes
<<org-latex-class-conlang>>
)
(add-to-list 'org-latex-classes
<<org-latex-class-beamer>>
)))
Projects
Another great features of Org-mode is the Org projects that allow the user to easily publish a bunch of org files to a remote location. Here is the current declaration of my projects, which will be detailed later:
<<org-proj-config-setup>>
<<org-proj-lang-setup>>
(setq org-publish-project-alist
`(
<<org-proj-config-html>>
<<org-proj-config-static>>
<<org-proj-config>>
<<org-proj-lang-html>>
<<org-proj-lang-pdf>>
<<org-proj-lang-static>>
<<org-proj-lang>>))
Configuration website
This is my configuration for exporting my dotfiles to my website in a web format only. No PDFs or anything, just HTML. Please note that I do not use that often anymore, I much prefer the automatic script that I have which deploys through my Drone instance my website on git pushes.
And before we get into the actual configuration, I would like to introduce a couple of variables. This is a bit more verbose than if I declared everything manually, but now I can change all three values at the same time without a hasle.
(defvar phundrak//projects-config-target
"/rsync:Tilo:~/www/phundrak.com/config"
"Points to where exported files for config.phundrak.com should be put")
(defvar phundrak//projects-config-source
"~/org/config/"
"Points to where the sources for config.phundrak.com are")
(defvar phundrak//projects-config-language
"en"
"Language of config.phundrak.com")
(defvar phundrak//projects-config-recursive
t
"Defines whether subdirectories should be parsed for config.phundrak.com")
Now, here is my configuration. In this snippet, my org files located in my source directory get exported in the HTML format and published to my target directory on my remote server through RSYNC via TRAMP. A sitemap is automatically generated, which comes in handy with the online sitemap that is available through the navigation bar.
("config-website-org"
:base-directory ,phundrak//projects-config-source
:base-extension "org"
:publishing-directory ,phundrak//projects-config-target
:recursive ,phundrak//projects-config-recursive
:language ,phundrak//projects-config-language
:publishing-function org-html-publish-to-html
:headline-levels 5
:auto-sitemap t
:auto-preamble t)
We also have the component for all the static files needed to run the website (mostly images tbh).
("config-website-static"
:base-directory ,phundrak//projects-config-source
:base-extension "png\\|jpg\\|gif\\|webp\\|svg\\|jpeg\\|ttf\\|woff\\|txt\\|epub\\|md"
:publishing-directory ,phundrak//projects-config-target
:recursive ,phundrak//projects-config-recursive
:language ,phundrak//projects-config-language
:publishing-function org-publish-attachment)
The project is then defined like so:
("config-website"
:components ("config-website-org"
"config-website-static"))
Linguistics website
My linguistics website is made out of three projects. As for the previous project, let’s declare the common values for these.
(defvar phundrak//projects-conlanging-target
"/rsync:Tilo:~/www/phundrak.com/langue/"
"Points to where exported files for langue.phundrak.com should be put")
(defvar phundrak//projects-conlanging-source
"~/Documents/conlanging/content/"
"Points to where the sources for langue.phundrak.com are")
(defvar phundrak//projects-conlanging-language
"fr"
"Language of langue.phundrak.com")
(defvar phundrak//projects-conlanging-recursive
t
"Defines whether subdirectories should be parsed for langue.phundrak.com")
The first component is the one generating the HTML files from the org files.
("langue-phundrak-com-org"
:base-directory ,phundrak//projects-conlanging-source
:base-extension "org"
:exclude "\\./\\(CONTRIB\\|README\\|head\\|temp\\|svg-ink\\).*"
:publishing-directory ,phundrak//projects-conlanging-target
:recursive ,phundrak//projects-conlanging-recursive
:language ,phundrak//projects-conlanging-language
:publishing-function org-html-publish-to-html
:headline-levels 5
:auto-sitemap t
:auto-preamble t)
We also have the component for the LaTeX and PDF part of the website:
("langue-phundrak-com-pdf"
:base-directory ,phundrak//projects-conlanging-source
:base-extension "org"
:exclude "\\./\\(CONTRIB\\|README\\|index\\|head\\|temp\\|svg-ink\\).*"
:publishing-directory ,phundrak//projects-conlanging-target
:recursive ,phundrak//projects-conlanging-recursive
:language ,phundrak//projects-conlanging-language
:publishing-function org-latex-publish-to-pdf
:headline-levels 5
:auto-preamble t)
And lastly, we have the component for all the static files needed to run the website:
("langue-phundrak-com-static"
:base-directory ,phundrak//projects-conlanging-source
:base-extension "png\\|jpg\\|gif\\|webp\\|svg\\|jpeg\\|ttf\\|woff\\|txt\\|epub"
:publishing-directory ,phundrak//projects-conlanging-target
:recursive ,phundrak//projects-conlanging-recursive
:language ,phundrak//projects-conlanging-language
:publishing-function org-publish-attachment)
The project is then defined like so:
("langue-phundrak-com"
:components ("langue-phundrak-com-org"
"langue-phundrak-com-static"
"langue-phundrak-com-pdf"))
Visual Configuration
While most modes of Emacs are dedicated to development, and therefore
are much more comfortable with a fixed-pitch font, more literary modes
such as org-mode are much more enjoyable if you have a variable pitch
font enabled. BUT, these modes can also require some fixed-pitch fonts
for some elements of the buffer, such as code blocks with
org-mode. mixed-pitch
comes to the rescue!
(use-package mixed-pitch
:after org
:straight t
:hook
(org-mode . mixed-pitch-mode)
:config
(add-hook 'org-agenda-mode-hook (lambda () (mixed-pitch-mode -1))))
I have an issue with org-mode’s emphasis markers: I find them ugly. I
can of course hide them if I simply set org-hide-emphasis-markers
to
t
, but it makes editing hard since I never know whether I am before or
after the emphasis marker when editing near the beginning/end of an
emphasized region. org-appear
fixes this issue so that it shows the
emphasis markers only when the cursor is in the emphasized region,
otherwise they will remain hidden! Very cool!
(use-package org-appear
:after org
:straight (:build t)
:hook (org-mode . org-appear-mode)
:config
(setq org-appear-autoemphasis t
org-hide-emphasis-markers t
org-appear-autolinks t
org-appear-autoentities t
org-appear-autosubmarkers t)
(run-at-time nil nil #'org-appear--set-elements))
Tired of seeing lots of actual stars *
in your headers, and you want a
fancier remplacement? Or you are still using org-bullets
but just
found out how out-of-date and abandoned it is? (Last commit was on
September 18th, 2014… damn…) Search no more, org-superstar
will take
care of that for you!
(use-package org-superstar
:after org
:straight (:build t)
:hook (org-mode . org-superstar-mode)
:config
(setq org-superstar-leading-bullet ?\s
org-superstar-leading-fallback ?\s
org-hide-leading-stars nil
org-superstar-todo-bullet-alist
'(("TODO" . 9744)
("[ ]" . 9744)
("DONE" . 9745)
("[X]" . 9745))))
org-fancy-priorities
change the priority of an org element such such
as #A
to anything user-defined. Let’s make this anything all-the-icons
icons!
(use-package org-fancy-priorities
:after (org all-the-icons)
:straight (:build t)
:hook (org-mode . org-fancy-priorities-mode)
:hook (org-agenda-mode . org-fancy-priorities-mode)
:config
(setq org-fancy-priorities-list `(,(all-the-icons-faicon "flag" :height 1.1 :v-adjust 0.0)
,(all-the-icons-faicon "arrow-up" :height 1.1 :v-adjust 0.0)
,(all-the-icons-faicon "square" :height 1.1 :v-adjust 0.0))))
Finally, org-sticky-header-mode
reminds you where you are in your org
files at the top of your window.
(use-package org-sticky-header
:straight (:build t)
:after org
:hook (org-mode . org-sticky-header-mode))
Project Management
Magit
Magit is an awesome wrapper around Git for Emacs! Very often, I go from disliking to really hating Git GUI clients because they often obfuscate which Git commands are used to make things happen. Such a thing doesn’t happen with Magit, it’s pretty transparent but it still provides some awesome features and visualizations of what you are doing and what Git is doing! In short, I absolutely love it!
(use-package magit
:straight (:build t)
:defer t
:custom
(magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)
:config
(progn
(general-define-key
:keymaps '(git-rebase-mode-map)
"C-t" #'evil-next-line
"C-s" #'evil-previous-line)
(general-define-key
:keymaps 'git-rebase-mode-map
:state 'normal
:prefix ","
"," #'with-editor-finish
"k" #'with-editor-cancel
"a" #'with-editor-cancel)))
Alphapapa also created an awesome package for Magit: magit-todos which display in the Magit buffer a list of TODOs found in the current project to remind you of what to do next.
(use-package magit-todos
:straight (:build t)
:after magit
:config
(setq magit-todos-ignore-case t))
Forge
NOTE: Make sure to configure a GitHub token before using this package!
(use-package forge
:after magit
:straight (:build t))
Projectile
(use-package projectile
:straight (:build t)
:defer t
:diminish projectile-mode
:config (projectile-mode)
:custom ((projectile-completion-system 'ivy))
:bind-keymap
("C-c p" . projectile-command-map)
:init
;; NOTE: Set this to the folder where you keep your Git repos!
(setq projectile-switch-project-action #'projectile-dired))
(use-package counsel-projectile
:straight (:build t)
:after (counsel projectile)
:config (counsel-projectile-mode))
Programming languages
DSLs
DSLs, or Domain Specific Languages, are languages dedicated to some very tasks, such as configuration languages or non-general programming such as SQL.
Caddy
Caddy (or Caddyserver) is a web server akin to Nginx or Apache which I find much easier to configure that the latter two, plus it has built-in support for automatically generating SSL certificates with Letsencrypt! Automatic HTTPS, what more do you want?
All that is nice and all, but Emacs doesn’t support the syntax of
Caddy files natively, so let’s install caddyfile-mode
:
(use-package caddyfile-mode
:defer t
:straight (:build t)
:mode (("Caddyfile\\'" . caddyfile-mode)
("caddy\\.conf\\'" . caddyfile-mode)))
Gnuplot
This package is a front-end and major mode for the programming language Gnuplot. Let’s make some beautiful graphs, shall we?
(use-package gnuplot
:straight (:build t)
:defer t)
Nginx
Nginx is another webserver, older and more mature than Caddy. A couple of packages are required in order to be able to properly work with Nginx configuration files. First, we need the correct mode for editing Nginx configuration files.
(use-package nginx-mode
:straight (:build t)
:defer t)
We then also have an autocompletion package that adds to company
the
Nginx syntax.
(use-package company-nginx
:straight (:build t)
:defer t
:config
(add-hook 'nginx-mode-hook (lambda ()
(add-to-list 'company-backends #'company-nginx))))
Shells
Aside from Eshell, my main shell on my machine is fish (see my fish config), therefore I need a mode for it.
(use-package fish-mode
:straight (:build t)
:defer t)
Yaml
(use-package yaml-mode
:defer t
:straight (:build t)
:mode "\\.yml\\'"
:mode "\\.yaml\\'")
Flycheck
(use-package flycheck
:straight (:build t)
:defer t
:init
(global-flycheck-mode)
:config
(setq flycheck-emacs-lisp-load-path 'inherit)
;; Rerunning checks on every newline is a mote excessive.
(delq 'new-line flycheck-check-syntax-automatically)
;; And don’t recheck on idle as often
(setq flycheck-idle-change-delay 2.0)
;; For the above functionality, check syntax in a buffer that you
;; switched to on briefly. This allows “refreshing” the syntax check
;; state for several buffers quickly after e.g. changing a config
;; file.
(setq flycheck-buffer-switch-check-intermediate-buffers t)
;; Display errors a little quicker (default is 0.9s)
(setq flycheck-display-errors-delay 0.2))
(use-package flycheck-popup-tip
:straight (:build t)
:after flycheck
:hook (flycheck-mode . flycheck-popup-tip-mode)
:config
(setq flycheck-popup-tip-error-prefix "X ")
(after! evil
(add-hook 'evil-insert-state-entry-hook
#'flycheck-popup-tip-delete-popup)
(add-hook 'evil-replace-state-entry-hook
#'flycheck-popup-tip-delete-popup)))
(use-package flycheck-posframe
:straight (:build t)
:hook (flycheck-mode . flycheck-posframe-mode)
:config
(setq flycheck-posframe-warning-prefix "! "
flycheck-posframe-info-prefix "··· "
flycheck-posframe-error-prefix "X "))
General Programming Languages
EmacsLisp
This package displays the function’s arglist or variable’s docstring in the echo area at the bottom of the frame. Quite useful indeed.
(use-package eldoc
:defer t
:after company
:init
(eldoc-add-command 'company-complete-selection
'company-complete-common
'company-capf
'company-abort))
Let’s also declare some Elisp-dedicated keybindings, prefixed by a comma.
(general-define-key
:states 'motion
:keymaps 'emacs-lisp-mode-map
:prefix ","
"'" #'ielm
"c" '(emacs-lisp-byte-compile :which-key "Byte compile")
"e" '(nil :which-key "eval")
"eb" #'eval-buffer
"ed" #'eval-defun
"ee" #'eval-last-sexp
"er" #'eval-region
"h" '(nil :which-key "help")
"hh" #'helpful-at-point
"t" '(nil :wk "toggle")
"tP" '(nil :wk "parinfer")
"tPs" #'parinfer-rust-switch-mode
"tPd" #'parinfer-rust-mode-disable
"tPp" #'parinfer-rust-toggle-paren-mode)
Visual Configuration
Dashboard
(use-package dashboard
:straight (:build t)
:ensure t
:after all-the-icons
:config
(setq dashboard-banner-logo-title "Phundrak’s Vanilla Emacs"
dashboard-startup-banner 'logo
dashboard-center-content t
dashboard-show-shortcuts t
dashboard-set-navigator t
dashboard-set-heading-icons t
dashboard-set-file-icons t
initial-buffer-choice (lambda () (get-buffer "*dashboard*"))
dashboard-projects-switch-function 'counsel-projectile-switch-project-by-name)
(setq dashboard-navigator-buttons
`(((,(all-the-icons-faicon "language" :height 1.1 :v-adjust 0.0)
"Linguistics website"
""
(lambda (&rest _) (browse-url "https://langue.phundrak.com")))
(,(all-the-icons-faicon "firefox" :height 1.1 :v-adjust 0.0)
"Config Website"
""
(lambda (&rest _) (browse-url "https://config.phundrak.com"))))
((,(all-the-icons-octicon "git-branch" :height 1.1 :v-adjust 0.0)
"Dotfiles sources"
""
(lambda (&rest _) (browse-url "https://labs.phundrak.com/phundrak/dotfiles")))
("!" "Issues" "Show issues" (lambda (&rest _)
(browse-url "https://labs.phundrak.com/phundrak/dotfiles/issues"))
warning))
((,(all-the-icons-faicon "level-up" :height 1.1 :v-adjust 0.0)
"Update packages"
""
(lambda (&rest _) (progn
(require 'straight)
(straight-pull-all)))))))
(setq dashboard-items '((recents . 15)
(projects . 10)))
(dashboard-setup-startup-hook)
:init
(add-hook 'after-init-hook 'dashboard-refresh-buffer))
Modeline
(use-package doom-modeline
:straight (:build t)
:defer t
:init (doom-modeline-mode 1)
:custom ((doom-modeline-height 15)))
Theme
(use-package doom-themes
:straight (:build t)
:defer t
:init (load-theme 'doom-nord t))
Icons? Did someone say icons?
YES! ALL OF THEM!
Ahem…
The package all-the-icons
allows us to use a wide variety of icons in
Emacs for various purposes, wherever we want, and THAT is GREAT! I’ll
(ab)use this feature in my config, be warned! NOTE: The first time a
configuration with all-the-icons
is loaded on a machine, the needed
fonts might not be available, so you’ll need to install them with the
command M-x all-the-icons-install-fonts
.
(use-package all-the-icons
:defer t)
prettify-symbols-mode
is also a nifty feature of Emacs, and it is
built-in! With that, I can replace strings of my choice by another
character of my choice!
(dolist (symbol '(("lambda" . 955)
("mapc" . 8614)))
(add-to-list 'prettify-symbols-alist symbol))
Let’s enable this mode for any programming mode:
(add-hook 'emacs-lisp-mode-hook #'prettify-symbols-mode)
Rainbow Delimiters
(use-package rainbow-delimiters
:straight (:build t)
:defer t
:hook (prog-mode . rainbow-delimiters-mode))
Y’all want some more COLORS?
It is possible to make info buffers much more colorful (and imo easier to read) with this simple package:
(use-package info-colors
:straight (:build t)
:commands info-colors-fnontify-node
:hook (Info-selection . info-colors-fontify-node)
:hook (Info-mode . mixed-pitch-mode))
Misc
avy
avy
is a really convenient way of jumping around, but I’ll need some
configuration to make it bépo-compatible.
(use-package avy
:defer t
:straight (:build t)
:config
(setq avy-keys '(?a ?u ?i ?e ?c ?t ?s ?r ?n))
:general
(:states 'normal
"gl" #'avy-goto-line))
Calc
Let’s give calc-mode
some better defaults.
(setq calc-angle-mode 'rad
calc-symbolic-mode t)
Elcord
What’s the point of using Emacs if you can’t tell everyone?
(use-package elcord
:straight (:built t)
:defer t
:config
(setq elcord-use-major-mode-as-main-icon t
elcord-refresh-rate 5
elcord-display-elapsed nil))
ivy-quick-find-files.el
This package is a small utility package I’ve written in order to quickly find files across my filesystem.
(use-package ivy-quick-find-files
:defer t
:straight (ivy-quick-find-files :type git
:host github
:repo "phundrak/ivy-quick-find-files.el"
:build t)
:config
(setq ivy-quick-find-files-program 'fd
ivy-quick-find-files-dirs-and-exts '(("~/org" . "org")
("~/Documents/conlanging" . "org")
("~/Documents/university" . "org"))))
Winum
Winum allows Emacs to associate windows with a specific number and navigate through these windows by directly refering to their associated number! This allows for faster window configuration than just going to the frame above, then left, left, and up.
(use-package winum
:straight (:build t)
:init (winum-mode))
Keybindings
Undefining some stuff to make keybind prefixes work correctly.
(general-define-key
:keymaps '(backtrace-mode-map diff-minor-mode-map magit-mode-map
rmail-mode-map evil-motion-state-map dired-mode-map
epa-key-list-mode-map special-mode-map splash-screen-keymap
undo-tree-visualizer-mode-map magit-blame-read-only-mode-map
org-agenda-keymap org-agenda-mode-map git-rebase-mode-map
Buffer-menu-mode-map custom-mode-map gfm-view-mode-map
electric-help-map image-mode-map magit-diff-mode-map)
"SPC" nil)
(general-define-key
:keymaps 'evil-motion-state-map
"," nil)
(general-define-key
:keymaps 'evil-insert-state-map
"C-t" nil)
(general-define-key
:keymaps '(diff-mode-map help-mode-map image-mode-map
dired-mode-map Man-mode-map eww-mode-map magit-mode-map
debugger-mode-map dired-mode-map custom-mode-map
eshell-mode-map)
:states 'normal
"SPC" nil)
(general-define-key
:keymaps 'magit-mode-map
:states 'visual
"SPC" nil)
(general-define-key
:keymaps '(diff-mode-map org-agenda-keymap org-agenda-mode-map)
:states 'motion
"SPC" nil)
(general-define-key
:keymaps 'eshell-mode-map
:states 'normal
"c" #'evil-backward-char
"t" #'evil-next-line
"s" #'evil-previous-line
"r" #'evil-forward-char)
(general-define-key
:keymaps 'evil-insert-state-map
"U" nil
"C-a" nil
"C-y" nil
"C-e" nil)
(general-define-key
:states 'normal
"U" #'evil-redo
"C-a" #'beginning-of-line
"C-e" #'end-of-line
"C-y" #'yank)
(general-define-key
:states 'normal
:prefix "SPC"
"SPC" '(counsel-M-x :wk "M-x")
"'" #'shell-pop
"a" '(nil :wk "apps")
"ac" #'calc
"ad" #'docker
"ae" #'eww
"at" #'tetris
"aw" #'wttrin
"aC" #'calendar
"as" '(nil :wk "shells")
"ase" #'eshell-new
"asv" #'vterm
"b" '(nil :wk "buffers")
"bb" #'bufler-switch-buffer
"bB" #'bury-buffer
"bl" #'bufler
"bd" #'kill-this-buffer
"bD" #'kill-buffer
"bh" #'dashboard-refresh-buffer
"bm" #'switch-to-messages-buffer
"br" #'counsel-buffer-or-recentf
"bs" #'switch-to-scratch-buffer
"c" '(nil :wk "code")
"cl" #'evilnc-comment-or-uncomment-lines
"e" '(nil :wk "email")
"ec" #'mu4e-compose-new
"em" #'mu4e
"f" '(nil :wk "files")
"fc" '((lambda ()
(interactive)
(find-file (concat (getenv "HOME") "/org/config/emacs.org")))
:wk "Config file")
"ff" #'counsel-find-file
"fF" #'ivy-quick-find-files
"fh" #'hexl-find-file
"fr" #'counsel-recentf
"fs" #'save-buffer
"g" '(nil :wk "git")
"gc" #'magit-clone
"gd" #'magit-dispatch
"gi" #'magit-init
"gs" #'magit-status
"gy" #'my/yadm
"gS" #'magit-stage-file
"gU" #'magit-unstage-file
"gf" '(nil :wk "file")
"gfd" #'magit-diff
"gfc" #'magit-file-checkout
"gfl" #'magit-file-dispatch
"gfF" #'magit-find-file
"h" '(nil :wk "help")
"hk" #'which-key-show-top-level
"hd" '(nil :wk "describe")
"hdc" #'describe-char
"hdf" #'helpful-callable
"hdk" #'helpful-key
"hdv" #'helpful-variable
"i" '(nil :wk "insert")
"iy" #'ivy-yasnippet
"j" '(nil :wk "jump")
"jd" #'dired-jump
"jD" #'dired-jump-other-window
"p" '(nil :wk "project")
"p!" #'projectile-run-shell-command-in-root
"p&" #'projectile-run-async-shell-command-in-root
"pb" #'counsel-projectile-switch-to-buffer
"pc" #'counsel-projectile
"pd" #'counsel-projectile-find-dir
"pe" #'projectile-edit-dir-locals
"pf" #'counsel-projectile-find-file
"pg" #'projectile-find-tag
"pk" #'project-kill-buffers
"pp" #'counsel-projectile-switch-project
"pt" #'ivy-magit-todos
"pv" #'projectile-vc
"t" '(nil :wk "toggles")
"tt" #'counsel-load-theme
"ti" '(nil :wk "input method")
"tit" #'toggle-input-method
"tis" #'set-input-mode
"u" #'universal-argument
"U" #'undo-tree-visualize
"w" '(nil :wk "windows")
"w-" #'split-window-below-and-focus
"w/" #'split-window-right-and-focus
"w$" #'winum-select-window-by-number
"w0" '(winum-select-window-0-or-10 :wk nil)
"w1" '(winum-select-window-1 :wk nil)
"w2" '(winum-select-window-2 :wk nil)
"w3" '(winum-select-window-3 :wk nil)
"w4" '(winum-select-window-4 :wk nil)
"w5" '(winum-select-window-5 :wk nil)
"w6" '(winum-select-window-6 :wk nil)
"w7" '(winum-select-window-7 :wk nil)
"w8" '(winum-select-window-8 :wk nil)
"w9" '(winum-select-window-9 :wk nil)
"wb" '((lambda ()
(interactive)
(progn
(kill-this-buffer)
(delete-window)))
:wk "Kill buffer and window")
"wd" #'delete-window
"wo" #'other-window
"wD" #'delete-other-windows
"ww" '(nil :wk "writeroom")
"www" #'writeroom-mode
"wwb" #'writeroom-buffer-width/body
"wc" #'evil-window-left
"wt" #'evil-window-down
"ws" #'evil-window-up
"wr" #'evil-window-right
"T" '(nil :wk "text")
"Tz" #'hydra-zoom/body
"Tu" #'downcase-region
"TU" #'upcase-region
"Te" #'string-edit-at-point
"q" '(nil :wk "quit")
"qf" #'delete-frame
"qq" #'save-buffers-kill-terminal
"qQ" #'kill-emacs)