8503 lines
297 KiB
Org Mode
8503 lines
297 KiB
Org Mode
#+title: Emacs Configuration
|
||
#+setupfile: headers
|
||
#+options: auto-id:t
|
||
#+html_head: <meta name="description" content="Phundrak’s Emacs Configuration" />
|
||
#+html_head: <meta property="og:title" content="Phundrak’s Emacs Configuration" />
|
||
#+html_head: <meta property="og:description" content="Phundrak’s Emacs Configuration Detailed" />
|
||
#+property: header-args:emacs-lisp :mkdirp yes :lexical t :exports code
|
||
#+property: header-args:emacs-lisp+ :tangle ~/.config/emacs/init.el
|
||
#+property: header-args:emacs-lisp+ :mkdirp yes :noweb no-export
|
||
|
||
#+include: img/emacs.svg export html
|
||
|
||
* Introduction
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Introduction7gzhel6184j0
|
||
:END:
|
||
After a couple of years using Spacemacs and a failed attempt at
|
||
switching to DoomEmacs, I’m finally switching back to a vanilla
|
||
configuration! Why? Because I got tired of the framework getting in my
|
||
way when I wanted to do stuff. I’m sure this is more applicable to
|
||
Spacemacs than DoomEmacs since the latter has nice macros written to
|
||
easily add new packages and configure them, such as ~package!~, ~after!~,
|
||
and others. But ultimately, I wanted to have a system I designed
|
||
entirely, with the keybinds I want, the packages I want.
|
||
|
||
Aso, why Emacs? You know this famous quote:
|
||
#+begin_quote
|
||
Emacs is a great operating system, it just lacks a good text editor.
|
||
#+end_quote
|
||
|
||
It’s actually pretty true in my opinion. Emacs is basically a Lisp
|
||
machine with a default text editor, programmed with EmacsLisp, a
|
||
general-purpose programming language. Therefore, if you want to do
|
||
something in Emacs, with enough Elisp you can do it --- if it’s not in
|
||
Emacs already, that is.
|
||
|
||
#+attr_html: :alt Dammit Emacs… :loading lazy
|
||
#+caption: [[https://xkcd.com/378/][XKCD n°378]]: Real Programmers
|
||
[[file:./img/real_programmers.png]]
|
||
|
||
* A Warning Before You Proceed
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: A-Warning-Before-You-Proceed-mgyar9i0ucj0
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
This configuration makes heavy use of the [[https://orgmode.org/manual/Noweb-Reference-Syntax.html][noweb]] syntax. This means if
|
||
you encounter some code that looks ~<<like-this>>~, org-mode will
|
||
replace this snippet with another code snippet declared elsewhere in
|
||
my configuration. If you see some code that looks ~<<like-this()>>~,
|
||
some generating code will run and replace this piece of text with the
|
||
text generated. A quick example:
|
||
#+begin_src elisp
|
||
(defun hello ()
|
||
<<generate-docstring()>>
|
||
<<print-hello>>)
|
||
#+end_src
|
||
|
||
Will instead appear as
|
||
#+begin_src emacs-lisp :noweb yes
|
||
(defun hello ()
|
||
<<generate-docstring()>>
|
||
<<print-hello>>)
|
||
#+end_src
|
||
|
||
This is because I have the block of code below named
|
||
~generate-docstring~ which generates an output, which replaces its noweb
|
||
tag. You can recognize noweb snippets generating code with the
|
||
parenthesis. Often, such blocks aren’t visible in my HTML exports, but
|
||
you can still see them if you open the actual org source file.
|
||
#+name: generate-docstring
|
||
#+begin_src emacs-lisp
|
||
(concat "\""
|
||
"Print \\\"Hello World!\\\" in the minibuffer."
|
||
"\"")
|
||
#+end_src
|
||
|
||
On the other hand, noweb snippets without parenthesis simply replace
|
||
the snippet with the equivalent named code block. For instance the one
|
||
below is named ~print-hello~ and is placed as-is in the target source
|
||
block.
|
||
#+name: print-hello
|
||
#+begin_src emacs-lisp
|
||
(message "Hello World!")
|
||
#+end_src
|
||
|
||
* Basic Configuration
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configurationzt3iel6184j0
|
||
:END:
|
||
** Early Init
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Early-Inityj7iel6184j0
|
||
:header-args:emacs-lisp: :tangle ~/.config/emacs/early-init.el :mkdirp yes
|
||
:header-args:emacs-lisp+: :exports code :results silent :lexical t
|
||
:END:
|
||
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.
|
||
#+begin_src emacs-lisp :mkdirp yes
|
||
(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
|
||
(blink-cursor-mode 0) ; disable blinking cursor
|
||
(setq gc-cons-threshold (* 1024 1024 1024))
|
||
#+end_src
|
||
|
||
** Emacs Behavior
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Emacs-Behavior6gbiel6184j0
|
||
:END:
|
||
*** Editing Text in Emacs
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Emacs-Behavior-Editing-Text-in-Emacsy2fiel6184j0
|
||
:END:
|
||
I *never* want to keep trailing spaces in my files, which is why I’m
|
||
doing this:
|
||
#+begin_src emacs-lisp
|
||
(add-hook 'before-save-hook #'whitespace-cleanup)
|
||
#+end_src
|
||
|
||
I don’t understand why some people add two spaces behind a full stop,
|
||
I sure don’t. Let’s tell Emacs.
|
||
#+begin_src emacs-lisp
|
||
(setq-default sentence-end-double-space nil)
|
||
#+end_src
|
||
|
||
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
|
||
Emacs usually considers a word can be understood as several words, as
|
||
in camelCase words, and allows us to jump words on this finer level.
|
||
#+begin_src emacs-lisp
|
||
(global-subword-mode 1)
|
||
#+end_src
|
||
|
||
Changing half my screen each time my cursor goes too high or too low
|
||
is not exactly ideal. Fortunately, if we set ~scroll-conservatively~
|
||
high enough we can have the cursor stay on top or at the bottom of the
|
||
screen while the text scrolls progressively.
|
||
#+begin_src emacs-lisp
|
||
(setq scroll-conservatively 1000)
|
||
#+end_src
|
||
|
||
Lastly, I want the default mode for Emacs to be Emacs Lisp.
|
||
#+begin_src emacs-lisp
|
||
(setq-default initial-major-mode 'emacs-lisp-mode)
|
||
#+end_src
|
||
|
||
**** Indentation
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Emacs-Behavior-Editing-Text-in-Emacs-Indentationauiiel6184j0
|
||
:END:
|
||
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:
|
||
#+begin_src emacs-lisp
|
||
(setq-default indent-tabs-mode nil)
|
||
(add-hook 'prog-mode-hook (lambda () (setq indent-tabs-mode nil)))
|
||
#+end_src
|
||
|
||
Just to go on a little tangent 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?
|
||
#+begin_center
|
||
/Tabs for indentation/
|
||
|
||
/Spaces for alignment/
|
||
#+end_center
|
||
|
||
I haven’t found a way to automate that in Emacs yet aside from
|
||
formatters’ config file, and tabs look bat in EmacsLisp anyway, so
|
||
I’ll stick with spaces by default and change it where needed.
|
||
|
||
*** Programming Modes
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Emacs-Behavior-Programming-Modesfnmiel6184j0
|
||
:END:
|
||
First off, my definition of what makes a “programming mode” doesn’t exactly
|
||
fit mine, so on top of ~prog-mode~, let’s add a few other modes.
|
||
#+name: line-number-modes-table
|
||
| Modes |
|
||
|------------|
|
||
| prog-mode |
|
||
| latex-mode |
|
||
|
||
#+name: prog-modes-gen
|
||
#+header: :cache yes :exports none :tangle no
|
||
#+begin_src emacs-lisp :var modes=line-number-modes-table
|
||
(mapconcat (lambda (mode) (format "%s-hook" (car mode)))
|
||
modes
|
||
" ")
|
||
#+end_src
|
||
|
||
**** Line Number
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Emacs-Behavior-Programming-Modes-Line-Numbermcqiel6184j0
|
||
:END:
|
||
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.
|
||
|
||
#+begin_src emacs-lisp
|
||
(dolist (mode '(<<prog-modes-gen()>>))
|
||
(add-hook mode #'display-line-numbers-mode))
|
||
#+end_src
|
||
|
||
**** Folding code
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Emacs-Behavior-Programming-Modes-Folding-code16uiel6184j0
|
||
:END:
|
||
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:
|
||
#+begin_src emacs-lisp
|
||
(dolist (mode '(<<prog-modes-gen()>>))
|
||
(add-hook mode #'hs-minor-mode))
|
||
#+end_src
|
||
|
||
*** Stay Clean, Emacs!
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Emacs-Behavior-Stay-Clean-Emacs7wxiel6184j0
|
||
:END:
|
||
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 access.
|
||
#+begin_src emacs-lisp
|
||
(setq backup-directory-alist `(("." . ,(expand-file-name ".tmp/backups/"
|
||
user-emacs-directory))))
|
||
#+end_src
|
||
|
||
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/?
|
||
#+begin_src emacs-lisp
|
||
(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))
|
||
#+end_src
|
||
|
||
If we delete a file, we want it moved to the trash, not simply deleted.
|
||
#+begin_src emacs-lisp
|
||
(setq delete-by-moving-to-trash t)
|
||
#+end_src
|
||
|
||
Finally, the scatch buffer always has some message at its beginning, I
|
||
don’t want it!
|
||
#+begin_src emacs-lisp
|
||
(setq-default initial-scratch-message nil)
|
||
#+end_src
|
||
|
||
*** Stay Polite, Emacs!
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Emacs-Behavior-Stay-Polite-Emacszp1jel6184j0
|
||
:END:
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(defalias 'yes-or-no-p 'y-or-n-p)
|
||
#+end_src
|
||
|
||
This will make Emacs ask us for either hitting the ~y~ key for “yes”, or
|
||
the ~n~ key for “no”. Much more polite!
|
||
|
||
It is also very impolite to keep a certain version of a file in its
|
||
buffer when said file has changed on disk. Let’s change this behavior:
|
||
#+begin_src emacs-lisp
|
||
(global-auto-revert-mode 1)
|
||
#+end_src
|
||
|
||
Much more polite! Note that if the buffer is modified and its changes
|
||
haven’t been saved, it will not automatically revert the buffer and
|
||
your unsaved changes won’t be lost. Very polite!
|
||
|
||
*** Misc
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Emacs-Behavior-Misc9j5jel6184j0
|
||
:END:
|
||
Let’s raise Emacs undo memory to 10MB, and make Emacs auto-save our
|
||
files by default.
|
||
#+begin_src emacs-lisp
|
||
(setq undo-limit 100000000
|
||
auto-save-default t)
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(setq window-combination-resize t) ; take new window space from all other windows
|
||
#+end_src
|
||
|
||
** Personal Information
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Personal-Informationi59jel6184j0
|
||
:END:
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(setq user-full-name "Lucien Cartier-Tilet"
|
||
user-real-login-name "Lucien Cartier-Tilet"
|
||
user-login-name "phundrak"
|
||
user-mail-address "lucien@phundrak.com")
|
||
#+end_src
|
||
|
||
** Visual Configuration
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Visual-Configurationzvcjel6184j0
|
||
:END:
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(setq visible-bell t)
|
||
#+end_src
|
||
|
||
It is nicer to see a cursor cover the actual space of a character.
|
||
#+begin_src emacs-lisp
|
||
(setq x-stretch-cursor t)
|
||
#+end_src
|
||
|
||
When text is ellipsed, I want the ellipsis marker to be a single
|
||
character of three dots. Let’s make it so:
|
||
#+begin_src emacs-lisp
|
||
(setq truncate-string-ellipsis "…")
|
||
#+end_src
|
||
|
||
With Emacs 29.0.50 onwards, a new frame parameter exists:
|
||
~alpha-background~. Unlike ~alpha~, this frame parameter only makes Emacs’
|
||
background transparent, excluding images and text.
|
||
#+begin_src emacs-lisp
|
||
(add-to-list 'default-frame-alist '(alpha-background . 0.9))
|
||
#+end_src
|
||
|
||
*** Modeline Modules
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Visual-Configuration-Modeline-Modules9kgjel6184j0
|
||
:END:
|
||
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).
|
||
#+begin_src emacs-lisp
|
||
(setq display-time-format "%Y-%m-%d %H:%M")
|
||
(display-time-mode 1) ; display time in modeline
|
||
#+end_src
|
||
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(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)))
|
||
#+end_src
|
||
|
||
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:
|
||
#+begin_src emacs-lisp
|
||
(column-number-mode)
|
||
#+end_src
|
||
|
||
The following code is, as will several chunks of code in this config,
|
||
borrowed from [[https://tecosaur.github.io/emacs-config/#theme-modeline][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.
|
||
#+begin_src emacs-lisp
|
||
(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)))
|
||
#+end_src
|
||
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(add-hook 'after-change-major-mode-hook #'modeline-contitional-buffer-encoding)
|
||
#+end_src
|
||
|
||
*** Fonts
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Visual-Configuration-Fontsxfkjel6184j0
|
||
:END:
|
||
I don’t like the default font I usually have on my machines, I really
|
||
don’t. I prefer [[https://github.com/microsoft/cascadia-code][Cascadia Code]], as it also somewhat supports the [[https://www.internationalphoneticassociation.org/][IPA]].
|
||
#+begin_src emacs-lisp
|
||
(defvar phundrak/default-font-size 90
|
||
"Default font size.")
|
||
|
||
(defvar phundrak/default-font-name "Cascadia Code"
|
||
"Default font.")
|
||
|
||
(defun my/set-font ()
|
||
(when (find-font (font-spec :name phundrak/default-font-name))
|
||
(set-face-attribute 'default nil
|
||
:font phundrak/default-font-name
|
||
:height phundrak/default-font-size)))
|
||
|
||
(my/set-font)
|
||
(add-hook 'server-after-make-frame-hook #'my/set-font)
|
||
#+end_src
|
||
|
||
*** Frame Title
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Visual-Configuration-Frame-Titlej7ojel6184j0
|
||
:END:
|
||
This is straight-up copied from [[https://tecosaur.github.io/emacs-config/config.html#window-title][TEC]]’s configuration. See their comment
|
||
on the matter.
|
||
#+begin_src emacs-lisp
|
||
(setq frame-title-format
|
||
'(""
|
||
"%b"
|
||
(:eval
|
||
(let ((project-name (projectile-project-name)))
|
||
(unless (string= "-" project-name)
|
||
(format (if (buffer-modified-p) " ◉ %s" " ● %s - Emacs") project-name))))))
|
||
#+end_src
|
||
|
||
** A better custom variable setter
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-Configuration-A-better-custom-variable-setter-56z4ni61lhj0
|
||
:END:
|
||
Something people often forget about custom variables in Elisp is they
|
||
can have a custom setter that will run some code if we set the
|
||
variable properly with ~customize-set-variable~, so ~setq~ shouldn’t be
|
||
the user’s choice by default. But repeatedly writing
|
||
~customize-set-variable~ can get tiring and boring. So why not take the
|
||
best of both world and create ~csetq~, a ~setq~ that uses
|
||
~customize-set-variable~ under the hood while it keeps a syntax similar
|
||
to the one ~setq~ uses?
|
||
#+begin_src emacs-lisp
|
||
(defmacro csetq (&rest forms)
|
||
"Bind each custom variable FORM to the value of its VAL.
|
||
|
||
FORMS is a list of pairs of values [FORM VAL].
|
||
`customize-set-variable' is called sequentially on each pairs
|
||
contained in FORMS. This means `csetq' has a similar behaviour as
|
||
`setq': each VAL expression are evaluated sequentially, i.e. the
|
||
first VAL is evaluated before the second, and so on. This means
|
||
the value of the first FORM can be used to set the second FORM.
|
||
|
||
The return value of `csetq' is the value of the last VAL.
|
||
|
||
\(fn [FORM VAL]...)"
|
||
(declare (debug (&rest sexp form))
|
||
(indent 1))
|
||
;; Check if we have an even number of arguments
|
||
(when (= (mod (length forms) 2) 1)
|
||
(signal 'wrong-number-of-arguments (list 'csetq (1+ (length forms)))))
|
||
;; Transform FORMS into a list of pairs (FORM . VALUE)
|
||
`(progn ,@(cl-loop for (form value) on forms by 'cddr
|
||
collect `(customize-set-variable ',form ,value))))
|
||
#+end_src
|
||
|
||
I first got inspired by [[https://oremacs.com/2015/01/17/setting-up-ediff/][this blog article]] (archived article, just in
|
||
case) but it seems the code snippet no longer works properly, so not
|
||
only did I have to modify it to make it work with an arbitrary amount
|
||
of arguments (as long as it’s pairs of variables and their value), but
|
||
I also had to make the code simply work.
|
||
|
||
* Custom Elisp
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Custom-Elispksvjel6184j0
|
||
:END:
|
||
** Dired functions
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Custom-Elisp-Dired-functionsm8zjel6184j0
|
||
:END:
|
||
*** ~phundrak/open-marked-files~
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Custom-Elisp-Dired-functions-phundrak-open-marked-filesdw2kel6184j0
|
||
:END:
|
||
This function allows the user to open all marked files from a dired
|
||
buffer as new Emacs buffers.
|
||
#+begin_src emacs-lisp
|
||
(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))))
|
||
#+end_src
|
||
|
||
*** ~xah/open-in-external-app~
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Custom-Elisp-Dired-functions-xah-open-in-external-appnm6kel6184j0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(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))))
|
||
#+end_src
|
||
|
||
*** ~xah/dired-sort~
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Custom-Elisp-Dired-functions-xah-dired-sort9fakel6184j0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(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)))
|
||
#+end_src
|
||
|
||
** Switch between buffers
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Custom-Elisp-Switch-between-buffersp4ekel6184j0
|
||
:END:
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(defun switch-to-messages-buffer ()
|
||
"Switch to Messages buffer."
|
||
(interactive)
|
||
(switch-to-buffer (messages-buffer)))
|
||
|
||
(defun switch-to-scratch-buffer ()
|
||
"Switch to Messages buffer."
|
||
(interactive)
|
||
(switch-to-buffer "*scratch*"))
|
||
#+end_src
|
||
|
||
** Screenshots
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Custom-Elisp-Screenshots-l9bkib013aj0
|
||
:END:
|
||
Since Emacs27, it is possible for Emacs to take screenshots of itself
|
||
in various formats. I’m mainly interested by the SVG and PNG format,
|
||
so I’ll only write functions for these. It isn’t really redundant with
|
||
the ~screenshot.el~ package used [[#Packages-Configuration-Applications-Screenshot96d1fl6184j0][here]] since these functions take a
|
||
screenshot of Emacs as a whole rather than of a code snippet.
|
||
|
||
First, we have a general function which is a slight modification of
|
||
the function shared by Alphapapa in [[https://www.reddit.com/r/emacs/comments/idz35e/emacs_27_can_take_svg_screenshots_of_itself/g2c2c6y/][this Reddit comment]]. It has been
|
||
modified so it is possible to pass the function an argument for the
|
||
format the screenshot will be taken as, and if ~type~ is ~nil~ the user
|
||
can still chose it.
|
||
#+begin_src emacs-lisp
|
||
(defun self-screenshot (&optional type)
|
||
"Save a screenshot of type TYPE of the current Emacs frame.
|
||
As shown by the function `', type can weild the value `svg',
|
||
`png', `pdf'.
|
||
|
||
This function will output in /tmp a file beginning with \"Emacs\"
|
||
and ending with the extension of the requested TYPE."
|
||
(interactive)
|
||
(let* ((type (if type type
|
||
(intern (completing-read "Screenshot Type: "
|
||
'(png svg pdf postscript)))))
|
||
(extension (pcase type
|
||
('png ".png")
|
||
('svg ".svg")
|
||
('pdf ".pdf")
|
||
('postscript ".ps")
|
||
(otherwise (error "Cannot export screenshot of type %s" otherwise))))
|
||
(filename (make-temp-file "Emacs-" nil extension))
|
||
(data (x-export-frames nil type)))
|
||
(with-temp-file filename
|
||
(insert data))
|
||
(kill-new filename)
|
||
(message filename)))
|
||
#+end_src
|
||
|
||
I used this function to take the screenshots you can see in this
|
||
document.
|
||
|
||
** Handle new windows
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Custom-Elisp-Handle-new-windows-f1za1rl02ej0
|
||
:END:
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(defun split-window-right-and-focus ()
|
||
"Spawn a new window right of the current one and focus it."
|
||
(interactive)
|
||
(split-window-right)
|
||
(windmove-right))
|
||
|
||
(defun split-window-below-and-focus ()
|
||
"Spawn a new window below the current one and focus it."
|
||
(interactive)
|
||
(split-window-below)
|
||
(windmove-down))
|
||
|
||
(defun kill-buffer-and-delete-window ()
|
||
"Kill the current buffer and delete its window."
|
||
(interactive)
|
||
(progn
|
||
(kill-this-buffer)
|
||
(delete-window)))
|
||
#+end_src
|
||
|
||
** Extend ~add-to-list~
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Custom-Elisp-Extend-add-to-list-eh2325605gj0
|
||
:END:
|
||
One function I find missing regarding ~add-to-list~ is ~add-all-to-list~
|
||
so I can add multiple elements to a list at once. Instead, with
|
||
vanilla Emacs, I have to repeatedly call ~add-to-list~. That’s not very
|
||
clean. Let’s declare this missing function:
|
||
#+begin_src emacs-lisp
|
||
(defun add-all-to-list (list-var elements &optional append compare-fn)
|
||
"Add ELEMENTS to the value of LIST-VAR if it isn’t there yet.
|
||
|
||
ELEMENTS is a list of values. For documentation on the variables
|
||
APPEND and COMPARE-FN, see `add-to-list'."
|
||
(let (return)
|
||
(dolist (elt elements return)
|
||
(setq return (add-to-list list-var elt append compare-fn)))))
|
||
#+end_src
|
||
|
||
* Package Management
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Package-Managementqpwkel6184j0
|
||
:END:
|
||
** Repositories
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Package-Management-Repositoriesab0lel6184j0
|
||
:END:
|
||
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 stock /elpa/ repository is renamed to
|
||
/gnu/ 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 are renamed here in order to avoid any confusion between the
|
||
two of them. Melpa is a community-maintained repository which contains
|
||
an absurd amount of Emacs packages.
|
||
#+begin_src emacs-lisp
|
||
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
|
||
("gnu" . "https://elpa.gnu.org/packages/")
|
||
("nongnu" . "https://elpa.nongnu.org/nongnu/")))
|
||
#+end_src
|
||
|
||
** Straight
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Package-Management-Straightry3lel6184j0
|
||
:END:
|
||
For my package management, I prefer to use ~straight~ ([[https://github.com/raxod502/straight.el][Github]]). This is
|
||
due to its capacity of integrating nicely with ~use-package~, which also
|
||
supports ~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.
|
||
First, let’s bootstrap straight.
|
||
#+begin_src emacs-lisp
|
||
(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))
|
||
#+end_src
|
||
|
||
Now, we can refresh our package list in order to be able to install
|
||
stuff.
|
||
#+begin_src emacs-lisp
|
||
(package-initialize)
|
||
(unless package-archive-contents
|
||
(package-refresh-contents))
|
||
|
||
#+end_src
|
||
|
||
From time to time, I fork some packages either because I’m trying to
|
||
implement something new in said package, or because the package is
|
||
unmaintained and I want to continue developing it a bit more. Straight
|
||
provides a nice feature for using forks of a package with its ~:fork~
|
||
option. If set to ~t~, then straight will attempt to retrieve the
|
||
package with the same name but with a different username on the same
|
||
host. This username is retrieved through the following variable:
|
||
#+begin_src emacs-lisp
|
||
(setq straight-host-usernames
|
||
'((github . "Phundrak")
|
||
(gitlab . "Phundrak")))
|
||
#+end_src
|
||
|
||
The huge advantage of straight is it clones through git the packages
|
||
it installs. This means development can be done directly on the
|
||
downloaded package. However, Forge (a Magit extension for interacting
|
||
with websites such as Github, Gitlab, and such) interacts by default
|
||
with the forge described by the ~origin~ remote, which isn’t necessarily
|
||
the one I want Forge to interact with by default. Therefore, all
|
||
default remotes are named ~straight~ so it won’t collide with my regular
|
||
development flow.
|
||
#+begin_src emacs-lisp
|
||
(setq straight-vc-git-default-remote-name "straight")
|
||
#+end_src
|
||
|
||
We finally come to the ~use-package~ installation. This is done like so:
|
||
#+begin_src emacs-lisp
|
||
(straight-use-package '(use-package :build t))
|
||
(setq use-package-always-ensure t)
|
||
#+end_src
|
||
|
||
* Keybinding Management
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinding-Management728lel6184j0
|
||
:END:
|
||
** Which-key
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinding-Management-Which-keymsblel6184j0
|
||
:END:
|
||
Which key is, I think, one of my favorite quality of life package.
|
||
When you begin a keybind, Emacs will show you all keybinds you can
|
||
follow the first one with in order to form a full keychord. Very
|
||
useful when you have a lot of keybinds and don’t remember exactly what
|
||
is what.
|
||
#+begin_src emacs-lisp
|
||
(use-package which-key
|
||
:straight (:build t)
|
||
:defer t
|
||
:init (which-key-mode)
|
||
:diminish which-key-mode
|
||
:config
|
||
(setq which-key-idle-delay 1))
|
||
#+end_src
|
||
|
||
** General
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinding-Management-Generalycflel6184j0
|
||
:END:
|
||
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,
|
||
all keybinds will be prefixed with ~SPC~ and keybinds related to a
|
||
specific mode (often major modes) will be prefixed by a comma ~,~ (and
|
||
by ~C-SPC~ and ~M-m~ respectively when in ~insert-mode~ or ~emacs-mode~). You
|
||
can still feel some influence from my Spacemacs years here.
|
||
#+begin_src emacs-lisp
|
||
(use-package general
|
||
:straight (:build t)
|
||
:init
|
||
(general-auto-unbind-keys)
|
||
:config
|
||
(general-create-definer phundrak/undefine
|
||
:keymaps 'override
|
||
:states '(normal emacs))
|
||
(general-create-definer phundrak/evil
|
||
:states '(normal))
|
||
(general-create-definer phundrak/leader-key
|
||
:states '(normal insert visual emacs)
|
||
:keymaps 'override
|
||
:prefix "SPC"
|
||
:global-prefix "C-SPC")
|
||
(general-create-definer phundrak/major-leader-key
|
||
:states '(normal insert visual emacs)
|
||
:keymaps 'override
|
||
:prefix ","
|
||
:global-prefix "M-m"))
|
||
#+end_src
|
||
|
||
#+name: general-keybindings-gen
|
||
#+header: :tangle no :exports none :results value :cache yes
|
||
#+begin_src emacs-lisp :var table=keybinds-windows prefix=""
|
||
(mapconcat (lambda (line)
|
||
(let* ((key (car line))
|
||
(function (cadr line))
|
||
(comment (caddr line)))
|
||
(format "\"%s%s\" %s"
|
||
prefix
|
||
key
|
||
(if (string= "" comment)
|
||
(if (or (string= "" function)
|
||
(string= "nil" function))
|
||
"nil"
|
||
(concat "#'" function))
|
||
(format "'(%s :wk %s)"
|
||
(if (or (string= "" function)
|
||
(string= "nil" function))
|
||
":ignore t"
|
||
function)
|
||
(if (or (string= "none" comment)
|
||
(string= "nil" comment))
|
||
"t"
|
||
(concat "\"" comment "\"")))))))
|
||
table
|
||
"\n")
|
||
#+end_src
|
||
|
||
** Evil
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinding-Management-Eviljg30fl6184j0
|
||
:END:
|
||
Evil emulates most of vim’s keybinds, because let’s be honest here,
|
||
they are much more comfortable than Emacs’.
|
||
#+begin_src emacs-lisp
|
||
(use-package evil
|
||
:straight (:build t)
|
||
:after (general)
|
||
:init
|
||
(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-undefine-keys>>
|
||
<<evil-bepo>>
|
||
(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))
|
||
#+end_src
|
||
|
||
I want to undefine some of the default keybinds of Evil because it
|
||
does not match my workflow. Namely, I use the space key and the comma
|
||
as leaders for my keybinds, and I’m way too used to Emacs’ ~C-t~, ~C-a~,
|
||
~C-e~, and ~C-y~.
|
||
#+name: evil-undefine-keys
|
||
#+begin_src emacs-lisp :tangle no
|
||
(evil-global-set-key 'motion "t" 'evil-next-visual-line)
|
||
(evil-global-set-key 'motion "s" 'evil-previous-visual-line)
|
||
|
||
(general-define-key
|
||
:keymaps 'evil-motion-state-map
|
||
"SPC" nil
|
||
"," nil)
|
||
(general-define-key
|
||
:keymaps 'evil-insert-state-map
|
||
"C-t" nil)
|
||
(general-define-key
|
||
:keymaps 'evil-insert-state-map
|
||
"U" nil
|
||
"C-a" nil
|
||
"C-y" nil
|
||
"C-e" nil)
|
||
#+end_src
|
||
|
||
Something else that really bugs me is I use the bépo layout, which is
|
||
not at all like the qwerty layout. For instance, ~hjkl~ becomes ~ctsr~.
|
||
Thus, I need some bépo-specific changes.
|
||
#+name: evil-bepo
|
||
#+begin_src emacs-lisp :tangle no
|
||
(dolist (key '("c" "C" "t" "T" "s" "S" "r" "R" "h" "H" "j" "J" "k" "K" "l" "L"))
|
||
(general-define-key :states 'normal key nil))
|
||
|
||
(general-define-key
|
||
:states 'motion
|
||
"h" 'evil-replace
|
||
"H" 'evil-replace-state
|
||
"j" 'evil-find-char-to
|
||
"J" 'evil-find-char-to-backward
|
||
"k" 'evil-substitute
|
||
"K" 'evil-smart-doc-lookup
|
||
"l" 'evil-change
|
||
"L" 'evil-change-line
|
||
|
||
"c" 'evil-backward-char
|
||
"C" 'evil-window-top
|
||
"t" 'evil-next-line
|
||
"T" 'evil-join
|
||
"s" 'evil-previous-line
|
||
"S" 'evil-lookup
|
||
"r" 'evil-forward-char
|
||
"R" 'evil-window-bottom)
|
||
#+end_src
|
||
|
||
This package enables and integrates Evil into a lot of different
|
||
modes, such as org-mode, dired, mu4e, etc. Again, I need some
|
||
additional code compared to most people due to the bépo layout.
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-collection
|
||
:after evil
|
||
:straight (:build t)
|
||
:config
|
||
;; bépo conversion
|
||
(defun my/bépo-rotate-evil-collection (_mode mode-keymaps &rest _rest)
|
||
(evil-collection-translate-key 'normal mode-keymaps
|
||
;; bépo ctsr is qwerty hjkl
|
||
"c" "h"
|
||
"t" "j"
|
||
"s" "k"
|
||
"r" "l"
|
||
;; add back ctsr
|
||
"h" "c"
|
||
"j" "t"
|
||
"k" "s"
|
||
"l" "r"))
|
||
(add-hook 'evil-collection-setup-hook #'my/bépo-rotate-evil-collection)
|
||
(evil-collection-init))
|
||
#+end_src
|
||
~undo-tree~ is my preferred way of undoing and redoing stuff. The main
|
||
reason is it doesn’t create a linear undo/redo history, but rather a
|
||
complete tree you can navigate to see your complete editing history.
|
||
One of the two obvious things to do are to tell Emacs to save all its
|
||
undo history fies in a dedicated directory, otherwise we’d risk
|
||
littering all of our directories. The second thing is to simply
|
||
globally enable its mode.
|
||
#+begin_src emacs-lisp
|
||
(use-package undo-tree
|
||
:defer t
|
||
:straight (:build t)
|
||
:custom
|
||
(undo-tree-history-directory-alist
|
||
`(("." . ,(expand-file-name (file-name-as-directory "undo-tree-hist")
|
||
user-emacs-directory))))
|
||
:init
|
||
(global-undo-tree-mode)
|
||
:config
|
||
<<undo-tree-ignore-text-properties>>
|
||
<<undo-tree-compress-files>>
|
||
(setq undo-tree-visualizer-diff t
|
||
undo-tree-auto-save-history t
|
||
undo-tree-enable-undo-in-region t
|
||
undo-limit (* 800 1024)
|
||
undo-strong-limit (* 12 1024 1024)
|
||
undo-outer-limit (* 128 1024 1024)))
|
||
#+end_src
|
||
|
||
An interesting behavior from DoomEmacs is to compress the history
|
||
files with ~zstd~ when it is present on the system. Not only do we enjoy
|
||
much smaller files (according to DoomEmacs, we get something like 80%
|
||
file savings), Emacs can load them much faster than the regular files.
|
||
Sure, it uses more CPU time uncompressing these files, but it’s
|
||
insignificant and it’s still faster than loading a heavier file.
|
||
#+name: undo-tree-compress-files
|
||
#+begin_src emacs-lisp :tangle no
|
||
(when (executable-find "zstd")
|
||
(defun my/undo-tree-append-zst-to-filename (filename)
|
||
"Append .zst to the FILENAME in order to compress it."
|
||
(concat filename ".zst"))
|
||
(advice-add 'undo-tree-make-history-save-file-name
|
||
:filter-return
|
||
#'my/undo-tree-append-zst-to-filename))
|
||
#+end_src
|
||
|
||
** Hydra
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinding-Management-Hydra0970fl6184j0
|
||
:END:
|
||
[[https://github.com/abo-abo/hydra][Hydra]] is a simple menu creator for keybindings.
|
||
#+begin_src emacs-lisp
|
||
(use-package hydra
|
||
:straight (:build t)
|
||
:defer t)
|
||
#+end_src
|
||
|
||
*** Hydras
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybinding-Management-Hydra-Hydrasvya0fl6184j0
|
||
:END:
|
||
The following hydra allows me to quickly zoom in and out in the
|
||
current buffer.
|
||
#+begin_src emacs-lisp
|
||
(defhydra hydra-zoom ()
|
||
"
|
||
^Zoom^ ^Other
|
||
^^^^^^^--------------------------
|
||
[_t_/_s_] zoom in/out [_q_] quit
|
||
[_0_]^^ reset zoom
|
||
"
|
||
("t" text-scale-increase "zoom in")
|
||
("s" text-scale-decrease "zoom out")
|
||
("0" text-scale-adjust "reset")
|
||
("q" nil "finished" :exit t))
|
||
#+end_src
|
||
|
||
Similarly, this one is also inspired from Spacemacs and allows the
|
||
user to interact with the width of the buffer in ~writeroom~.
|
||
#+begin_src emacs-lisp
|
||
(defhydra writeroom-buffer-width ()
|
||
"
|
||
^Width^ ^Other
|
||
^^^^^^^^-----------------------
|
||
[_t_] enlarge [_r_/_0_] adjust
|
||
[_s_] shrink [_q_]^^ quit
|
||
"
|
||
("q" nil :exit t)
|
||
("t" writeroom-increase-width "enlarge")
|
||
("s" writeroom-decrease-width "shrink")
|
||
("r" writeroom-adjust-width "adjust")
|
||
("0" writeroom-adjust-width "adjust"))
|
||
#+end_src
|
||
|
||
Another similar one is for ~mu4e-view-mode~ that allows me to shrink or
|
||
grow the ~mu4e-headers~ buffer when viewing an email.
|
||
#+begin_src emacs-lisp
|
||
(defhydra mu4e-headers-split-adjust-width ()
|
||
"
|
||
^Zoom^ ^Other
|
||
^^^^^^^---------------------------------
|
||
[_t_/_s_] shrink/enlarge view [_q_] quit
|
||
"
|
||
("q" nil :exit t)
|
||
("t" mu4e-headers-split-view-shrink "shrink")
|
||
("s" mu4e-headers-split-view-grow "enlarge"))
|
||
#+end_src
|
||
|
||
Similarly still, this one allows me to manage the size my Emacs
|
||
windows.
|
||
#+begin_src emacs-lisp
|
||
(defhydra windows-adjust-size ()
|
||
"
|
||
^Zoom^ ^Other
|
||
^^^^^^^-----------------------------------------
|
||
[_t_/_s_] shrink/enlarge vertically [_q_] quit
|
||
[_c_/_r_] shrink/enlarge horizontally
|
||
"
|
||
("q" nil :exit t)
|
||
("c" shrink-window-horizontally)
|
||
("t" enlarge-window)
|
||
("s" shrink-window)
|
||
("r" enlarge-window-horizontally))
|
||
#+end_src
|
||
|
||
This one allows me to manipulate my Emacs frames’ background
|
||
transparency.
|
||
#+begin_src emacs-lisp
|
||
(defun my/transparency-round (val)
|
||
"Round VAL to the nearest tenth of an integer."
|
||
(/ (round (* 10 val)) 10.0))
|
||
|
||
(defun my/increase-frame-alpha-background ()
|
||
"Increase current frame’s alpha background."
|
||
(interactive)
|
||
(set-frame-parameter nil
|
||
'alpha-background
|
||
(my/transparency-round
|
||
(min 1.0
|
||
(+ (frame-parameter nil 'alpha-background) 0.1))))
|
||
(message "%s" (frame-parameter nil 'alpha-background)))
|
||
|
||
(defun my/decrease-frame-alpha-background ()
|
||
"Decrease current frame’s alpha background."
|
||
(interactive)
|
||
(set-frame-parameter nil
|
||
'alpha-background
|
||
(my/transparency-round
|
||
(max 0.0
|
||
(- (frame-parameter nil 'alpha-background) 0.1))))
|
||
(message "%s" (frame-parameter nil 'alpha-background)))
|
||
|
||
(defhydra my/modify-frame-alpha-background ()
|
||
"
|
||
^Transparency^ ^Other^
|
||
^^^^^^^^^^^^^^------------------------
|
||
[_t_] decrease transparency [_q_] quit
|
||
[_s_] increase transparency
|
||
"
|
||
("q" nil :exit t)
|
||
("s" my/decrease-frame-alpha-background)
|
||
("t" my/increase-frame-alpha-background))
|
||
#+end_src
|
||
|
||
* Packages Configuration
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configurationije0fl6184j0
|
||
:END:
|
||
** Autocompletion
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Autocompletionr8n1fl6184j0
|
||
:END:
|
||
*** Code Autocompletion
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Autocompletion-Code-Autocompletion4no1fl6184j0
|
||
:END:
|
||
Company is, in my opinion, the best autocompleting engine for Emacs,
|
||
and it is one of the most popular if not /the/ most popular.
|
||
#+begin_src emacs-lisp
|
||
(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
|
||
company-backends '(company-capf)
|
||
company-auto-commit nil
|
||
company-auto-complete-chars nil
|
||
company-dabbrev-other-buffers nil
|
||
company-dabbrev-ignore-case nil
|
||
company-dabbrev-downcase nil))
|
||
#+end_src
|
||
|
||
This package is a backend for company. It emulates
|
||
~ac-source-dictionary~ by proposing text related to the current
|
||
major-mode.
|
||
#+begin_src emacs-lisp
|
||
(use-package company-dict
|
||
:after company
|
||
:straight (:build t)
|
||
:config
|
||
(setq company-dict-dir (expand-file-name "dicts" user-emacs-directory)))
|
||
#+end_src
|
||
|
||
On the other hand, ~company-box~ is a Company front-end which offers
|
||
colors, icons, documentation and so on. Very nice.
|
||
|
||
Declaring all the icons for the variable
|
||
~company-box-icons-all-the-icons~ is quite verbose in Elisp, so I do it
|
||
with an org-table.
|
||
#+name: company-box-icons
|
||
| Type | Icon | Color |
|
||
|---------------+--------------------------+--------|
|
||
| Unknown | find_in_page | purple |
|
||
| Text | text_fields | green |
|
||
| Method | functions | red |
|
||
| Function | functions | red |
|
||
| Constructor | functions | red |
|
||
| Field | functions | red |
|
||
| Variable | adjust | blue |
|
||
| Class | class | red |
|
||
| Interface | settings_input_component | red |
|
||
| Module | view_module | red |
|
||
| Property | settings | red |
|
||
| Unit | straighten | red |
|
||
| Value | filter_1 | red |
|
||
| Enum | plus_one | red |
|
||
| Keyword | filter_center_focus | red |
|
||
| Snippet | short_text | red |
|
||
| Color | color_lens | red |
|
||
| File | insert_drive_file | red |
|
||
| Reference | collections_bookmark | red |
|
||
| Folder | folder | red |
|
||
| EnumMember | people | red |
|
||
| Constant | pause_circle_filled | red |
|
||
| Struct | streetview | red |
|
||
| Event | event | red |
|
||
| Operator | control_point | red |
|
||
| TypeParameter | class | red |
|
||
| Template | short_text | green |
|
||
| ElispFunction | functions | red |
|
||
| ElispVariable | check_circle | blue |
|
||
| ElispFeature | stars | orange |
|
||
| ElispFace | format_paint | pink |
|
||
|
||
#+name: gen-company-box-icons
|
||
#+headers: :tangle no :noweb yes :exports none :cache yes
|
||
#+header: :wrap "src emacs-lisp :exports none :tangle no"
|
||
#+begin_src emacs-lisp :var table=company-box-icons
|
||
(mapconcat (lambda (row)
|
||
(format "(%s . ,(all-the-icons-material \"%s\" :face 'all-the-icons-%s))"
|
||
(car row)
|
||
(cadr row)
|
||
(caddr row)))
|
||
table
|
||
"\n")
|
||
#+end_src
|
||
|
||
#+RESULTS[8ebf4bb3f7f354571a5d42cf58f8b9ba847ba028]: gen-company-box-icons
|
||
#+begin_src emacs-lisp :exports none :tangle no
|
||
(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))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(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))
|
||
`(
|
||
<<gen-company-box-icons()>>))))
|
||
#+end_src
|
||
|
||
*** Ivy
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Autocompletion-Ivy84q1fl6184j0
|
||
:END:
|
||
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~.
|
||
#+begin_src emacs-lisp
|
||
(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)
|
||
("C-u" . ivy-scroll-up-command)
|
||
("C-d" . ivy-scroll-down-command)
|
||
: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))
|
||
#+end_src
|
||
|
||
There is also [[https://github.com/raxod502/prescient.el][~prescient.el~]] that offers some nice features when
|
||
coupled with ~ivy~, guess what was born out of it? ~ivy-prescient~, of
|
||
course!
|
||
#+begin_src emacs-lisp
|
||
(use-package ivy-prescient
|
||
:after ivy
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
I warned you I’d use too much ~all-the-icons~, I did!
|
||
#+begin_src emacs-lisp
|
||
(use-package all-the-icons-ivy
|
||
:straight (:build t)
|
||
:after (ivy all-the-icons)
|
||
:hook (after-init . all-the-icons-ivy-setup))
|
||
#+end_src
|
||
|
||
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?
|
||
#+begin_src emacs-lisp
|
||
(use-package ivy-posframe
|
||
:defer t
|
||
:after (:any ivy helpful)
|
||
:hook (ivy-mode . ivy-posframe-mode)
|
||
:straight (:build t)
|
||
:init
|
||
(ivy-posframe-mode 1)
|
||
:config
|
||
(setq ivy-fixed-height-minibuffer nil
|
||
ivy-posframe-border-width 10
|
||
ivy-posframe-parameters
|
||
`((min-width . 90)
|
||
(min-height . ,ivy-height))))
|
||
#+end_src
|
||
|
||
Finally, let’s make ~ivy~ richer:
|
||
#+begin_src emacs-lisp
|
||
(use-package ivy-rich
|
||
:straight (:build t)
|
||
:after ivy
|
||
:init
|
||
(ivy-rich-mode 1))
|
||
#+end_src
|
||
|
||
*** Counsel
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Autocompletion-Counselorr1fl6184j0
|
||
:END:
|
||
I could almost merge this chapter with the previous one since counsel
|
||
is a package that provides loads of completion functions for ivy. The
|
||
ones I find most useful are ~counsel-M-x~ and ~counsel-find-file~.
|
||
#+begin_src emacs-lisp
|
||
(use-package counsel
|
||
:straight (:build t)
|
||
:after recentf
|
||
:defer t
|
||
:after ivy
|
||
: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)))
|
||
#+end_src
|
||
|
||
*** Yasnippet
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Autocompletion-Yasnippet68t1fl6184j0
|
||
:END:
|
||
Yasnippet allows you to insert some pre-made code by just typing a few
|
||
characters. It can even generate some string with Elisp expressions
|
||
and ask the user for some input in some precise places.
|
||
#+begin_src emacs-lisp
|
||
(use-package yasnippet
|
||
:defer t
|
||
:straight (:build t)
|
||
:init
|
||
(yas-global-mode)
|
||
:hook ((prog-mode . yas-minor-mode)
|
||
(text-mode . yas-minor-mode)))
|
||
#+end_src
|
||
|
||
Of course, yasnippet wouldn’t be as awesome as it is without premade
|
||
snippets.
|
||
#+begin_src emacs-lisp
|
||
(use-package yasnippet-snippets
|
||
:defer t
|
||
:after yasnippet
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
Similarly, yatemplate offers premade files rather than just strings.
|
||
That’s still yasnippet by the way.
|
||
#+begin_src emacs-lisp
|
||
(use-package yatemplate
|
||
:defer t
|
||
:after yasnippet
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
And finally, with ivy you can chose your snippets from a menu if
|
||
you’re not sure or if you don’t remember what your snippet is.
|
||
#+begin_src emacs-lisp
|
||
(use-package ivy-yasnippet
|
||
:defer t
|
||
:after (ivy yasnippet)
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/leader-key
|
||
:infix "i"
|
||
:packages 'ivy-yasnippet
|
||
"y" #'ivy-yasnippet))
|
||
#+end_src
|
||
|
||
** Applications
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications94i0fl6184j0
|
||
:END:
|
||
*** Bitwarden
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Bitwarden-cjme4wv0rdj0
|
||
:END:
|
||
This package is still a very much work in progress one I’m developing
|
||
in order to interact with Bitwarden in Emacs with the help of the
|
||
[[https://github.com/bitwarden/cli][Bitwarden CLI]]. Use at your own risks.
|
||
#+begin_src emacs-lisp
|
||
(use-package bitwarden
|
||
:defer t
|
||
:straight (bitwarden :build t
|
||
:type git
|
||
:host nil
|
||
:repo "https://labs.phundrak.com/phundrak/bitwarden.el"))
|
||
#+end_src
|
||
|
||
*** Docker
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Docker5ul0fl6184j0
|
||
:END:
|
||
Docker is an awesome tool for reproducible development environments.
|
||
Due to this, I absolutely need a mode for editing Dockerfiles.
|
||
#+begin_src emacs-lisp
|
||
(use-package dockerfile-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:hook (dockerfile-mode . lsp-deferred)
|
||
:init
|
||
(put 'docker-image-name 'safe-local-variable #'stringp)
|
||
:mode "Dockerfile\\'")
|
||
#+end_src
|
||
|
||
The ~docker~ package also provides interactivity with Docker and
|
||
docker-compose from Emacs.
|
||
#+begin_src emacs-lisp
|
||
(use-package docker
|
||
:defer t
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
*** Elfeed
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Elfeedoip0fl6184j0
|
||
:END:
|
||
Elfeed is a nice Atom and RSS reader for Emacs. The only thing I want
|
||
to change for now is the default search filter: I want to see not only
|
||
unread news but read news as well, a bit like my emails; and where the
|
||
database is to be stored.
|
||
#+begin_src emacs-lisp
|
||
(use-package elfeed
|
||
:defer t
|
||
:straight (:build t)
|
||
:config
|
||
<<elfeed-open-youtube-with-mpv>>
|
||
:custom
|
||
((elfeed-search-filter "@6-months-ago")
|
||
(elfeed-db-directory (expand-file-name ".elfeed-db"
|
||
user-emacs-directory))))
|
||
#+end_src
|
||
|
||
I don’t want YouTube videos to be open with my web browser when I
|
||
invoke ~elfeed-show-visit~, so I’ll advise this function so I can modify
|
||
the behavior of said function. Oh, and I already made [[#Packages-Configuration-Misc-Ytplay-wxm9weq0r4j0][a neat package]]
|
||
for playing YouTube videos and friends through [[https://ytdl-org.github.io/youtube-dl/][youtube-dl]] or its
|
||
superior fork [[https://github.com/yt-dlp/yt-dlp][yt-dlp]] in mpv.
|
||
#+name: elfeed-open-youtube-with-mpv
|
||
#+begin_src emacs-lisp
|
||
(defun my/elfeed-filter-youtube-videos (orig-fun &rest args)
|
||
"Open with mpv the video leading to PATH"
|
||
(let ((link (elfeed-entry-link elfeed-show-entry)))
|
||
(when link
|
||
(if (string-match-p ".*youtube\.com.*watch.*" link)
|
||
;; This is a YouTube video, open it with mpv
|
||
(progn
|
||
(require 'ytplay)
|
||
(ytplay link))
|
||
(apply orig-fun args)))))
|
||
|
||
(advice-add 'elfeed-show-visit :around #'my/elfeed-filter-youtube-videos)
|
||
#+end_src
|
||
|
||
A future improvement to be made is to let the user chose the
|
||
resolution of the video before it is launched. I may not always have
|
||
the best internet connection, and viewing 4K videos on a 1080p display
|
||
is not something very useful.
|
||
|
||
Elfeed-goodies is a package which enhances the Elfeed experience.
|
||
Aside from running its setup command as soon as possible, I also set
|
||
in this code block all my keybinds for Elfeed here.
|
||
#+begin_src emacs-lisp
|
||
(use-package elfeed-goodies
|
||
:defer t
|
||
:after elfeed
|
||
:commands elfeed-goodies/setup
|
||
:straight (:build t)
|
||
:init
|
||
(elfeed-goodies/setup)
|
||
:general
|
||
(phundrak/undefine
|
||
:keymaps '(elfeed-show-mode-map elfeed-search-mode-map)
|
||
:packages 'elfeed
|
||
"DEL" nil
|
||
"s" nil)
|
||
(phundrak/evil
|
||
:keymaps 'elfeed-show-mode-map
|
||
:packages 'elfeed
|
||
<<general-keybindings-gen(table=elfeed-keybinds-show-mode)>>)
|
||
(phundrak/evil
|
||
:keymaps 'elfeed-search-mode-map
|
||
:packages 'elfeed
|
||
<<general-keybindings-gen(table=elfeed-keybinds-search-mode)>>)
|
||
(phundrak/major-leader-key
|
||
:keymaps 'elfeed-search-mode-map
|
||
:packages 'elfeed
|
||
<<general-keybindings-gen(table=elfeed-keybinds-search-mode-prefixed)>>))
|
||
#+end_src
|
||
|
||
Last but not least, my Elfeed configuration is stored in an org file
|
||
thanks to ~elfeed-org~.
|
||
#+begin_src emacs-lisp
|
||
(use-package elfeed-org
|
||
:defer t
|
||
:after elfeed
|
||
:straight (:build t)
|
||
:init
|
||
(elfeed-org)
|
||
:config
|
||
(setq rmh-elfeed-org-files '("~/org/elfeed.org")))
|
||
#+end_src
|
||
|
||
**** Keybinds
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Elfeed-Keybinds-9oeijjs0baj0
|
||
:END:
|
||
First, here are the keybinds for Elfeed’s ~elfeed-show-mode~. They
|
||
aren’t prefixed by ~SPC~ like most of my keybinds, a direct keypress
|
||
will directly launch the function.
|
||
#+name: elfeed-keybinds-show-mode
|
||
| Key | Function | Comment |
|
||
|-----+-------------------------------+---------|
|
||
| + | elfeed-show-tag | |
|
||
| - | elfeed-show-untag | |
|
||
| « | elfeed-show-prev | |
|
||
| » | elfeed-show-next | |
|
||
| b | elfeed-show-visit | |
|
||
| C | elfeed-kill-link-url-at-point | |
|
||
| d | elfeed-show-save-enclosure | |
|
||
| l | elfeed-show-next-link | |
|
||
| o | elfeed-goodies/show-ace-link | |
|
||
| q | elfeed-kill-buffer | |
|
||
| S | elfeed-show-new-live-search | |
|
||
| u | elfeed-show-tag--unread | |
|
||
| y | elfeed-show-yank | |
|
||
|
||
Same thing, different mode, here are my keybinds for
|
||
~elfeed-search-mode~.
|
||
#+name: elfeed-keybinds-search-mode
|
||
| Key | Function | Comment |
|
||
|-----+-----------------------------+---------|
|
||
| « | elfeed-search-first-entry | |
|
||
| » | elfeed-search-last-entry | |
|
||
| b | elfeed-search-browse-url | |
|
||
| f | | filter |
|
||
| fc | elfeed-search-clear-filter | |
|
||
| fl | elfeed-search-live-filter | |
|
||
| fs | elfeed-search-set-filter | |
|
||
| u | | update |
|
||
| us | elfeed-search-fetch | |
|
||
| uS | elfeed-search-update | |
|
||
| uu | elfeed-update | |
|
||
| uU | elfeed-search-update--force | |
|
||
| y | elfeed-search-yank | |
|
||
|
||
I have some additional keybinds for ~elfeed-search-mode~, but these one
|
||
are prefixed with ~,~ (and ~M-m~).
|
||
#+name: elfeed-keybinds-search-mode-prefixed
|
||
| Key | Function | Comment |
|
||
|-----+--------------------------------+---------|
|
||
| c | elfeed-db-compact | |
|
||
| t | | tag |
|
||
| tt | elfeed-search-tag-all-unread | |
|
||
| tu | elfeed-search-untag-all-unread | |
|
||
| tT | elfeed-search-tag-all | |
|
||
| tU | elfeed-search-untag-all | |
|
||
|
||
*** Email
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Email9dt0fl6184j0
|
||
:END:
|
||
**** Basic configuration
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Email-Basic-configurationf7w0fl6184j0
|
||
:END:
|
||
As seen below, I use ~org-msg~ to compose my emails, which includes by
|
||
default my signature. Therefore, there is no need for Emacs itself to
|
||
know about it since I don’t want it to include it a second time after
|
||
~org-msg~ already did.
|
||
#+begin_src emacs-lisp
|
||
(setq message-signature nil
|
||
mail-signature nil)
|
||
#+end_src
|
||
|
||
**** Mu4e
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Email-Mu4e5kx0fl6184j0
|
||
:END:
|
||
Mu4e is a very eye-pleasing email client for Emacs, built around ~mu~
|
||
and which works well with ~mbsync~ (found in Arch’s ~isync~ package). For
|
||
me, the main advantage of mu4e is it has a modern interface for
|
||
emailing, and quite straightforward. I tried a couple of other email
|
||
clients for Emacs, and I even was for some time a Gnus user, but in
|
||
the end, mu4e really works best for me. Below you’ll find my
|
||
configuration for the ~mu4e~ package itself.
|
||
#+begin_src emacs-lisp
|
||
(use-package mu4e
|
||
:after all-the-icons
|
||
:straight (:build t :location site)
|
||
:commands mu4e mu4e-compose-new
|
||
:init
|
||
(csetq mu4e-completing-read-function 'completing-read
|
||
mu4e-use-fancy-chars t
|
||
message-kill-buffer-on-exit t
|
||
mu4e-org-support nil)
|
||
(let ((dir (concat (getenv "HOME") "/Downloads/mu4e")))
|
||
(when (file-directory-p dir)
|
||
(csetq mu4e-attachment-dir dir)))
|
||
(defmacro mu4e-view-mode--prepare ()
|
||
`(lambda () (visual-line-mode 1)))
|
||
:gfhook ('mu4e-view-mode-hook (mu4e-view-mode--prepare))
|
||
:config
|
||
(require 'mu4e-view)
|
||
(with-eval-after-load 'mm-decode
|
||
(add-to-list 'mm-discouraged-alternatives "text/html")
|
||
(add-to-list 'mm-discouraged-alternatives "text-richtext"))
|
||
|
||
(add-hook 'mu4e-view-mode-hook (lambda () (setq truncate-lines nil)))
|
||
(add-hook 'mu4e-headers-mode-hook (lambda () (setq truncate-lines t)))
|
||
|
||
<<mu4e-keybindings-undef>>
|
||
<<mu4e-keybindings-view>>
|
||
<<mu4e-keybindings-view-no-prefix>>
|
||
<<mu4e-keybindings-header>>
|
||
<<mu4e-keybindings-header-no-leader>>
|
||
<<mu4e-keybindings-message>>
|
||
|
||
<<mu4e-mail-service>>
|
||
<<mu4e-mail-on-machine>>
|
||
<<mu4e-no-signature>>
|
||
<<mu4e-bookmarks>>
|
||
<<mu4e-maildirs>>
|
||
|
||
(when (fboundp 'imagemagick-register-types)
|
||
(imagemagick-register-types))
|
||
|
||
(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 'mu4e-compose-mode-hook 'mml-secure-message-sign-pgpmime)
|
||
|
||
(setq mu4e-change-filenames-when-moving t
|
||
mu4e-update-interval 60
|
||
mu4e-compose-format-flowed t
|
||
mu4e-view-show-addresses t
|
||
mu4e-sent-messages-behaviour 'sent
|
||
mu4e-hide-index-messages t
|
||
mu4e-view-show-images t ; try to show images
|
||
mu4e-view-image-max-width 600
|
||
message-send-mail-function #'smtpmail-send-it ; how to send an email
|
||
smtpmail-stream-type 'starttls
|
||
message-kill-buffer-on-exit t ; close after sending
|
||
mu4e-context-policy 'pick-first ; start with first (default) context
|
||
mu4e-compose-context-policy 'ask-if-none ; compose with current context, or ask
|
||
mu4e-completing-read-function #'ivy-completing-read ; use ivy
|
||
mu4e-confirm-quit t ; no need to ask
|
||
mu4e-header-fields '((:account . 12)
|
||
(:human-date . 12)
|
||
(:flags . 4)
|
||
(:from . 25)
|
||
(:subject)))
|
||
|
||
;; set mail user agent
|
||
(setq mail-user-agent 'mu4e-user-agent)
|
||
|
||
<<mu4e-fancy-marks>>
|
||
<<mu4e-vertical-split>>
|
||
<<mu4e-headers-mode>>
|
||
|
||
(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))))
|
||
#+end_src
|
||
|
||
Quick sidenote: on ArchLinux, you’ll need to install either ~mu~ or
|
||
~mu-git~ from the AUR in order to use mu4e. I also have a ~.desktop~ file
|
||
so I can open mu4e directly from my program picker. It uses the shell
|
||
script ~emacsmail~ I’ve written [[file:bin.org::#Emacsmail-afffb7cd][here]].
|
||
#+header: :mkdirp yes
|
||
#+begin_src conf-desktop :tangle ~/.local/share/applications/mu4e.desktop
|
||
[Desktop Entry]
|
||
Name=Mu4e
|
||
GenericName=Mu4e
|
||
Comment=Maildir Utils for Emacs
|
||
MimeType=x-scheme-handler/mailto;
|
||
Exec=/home/phundrak/.local/bin/emacsmail %U
|
||
Icon=emacs
|
||
Type=Application
|
||
Terminal=false
|
||
Categories=Network;Email;TextEditor
|
||
StartupWMClass=Gnus
|
||
Keywords=Text;Editor;
|
||
#+end_src
|
||
|
||
***** Basic configuration
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Email-Mu4e-Basic-configurationfxy0fl6184j0
|
||
:END:
|
||
First, let’s inform Emacs how it can send emails, using which service
|
||
and how. In my case, I use my own mail server.
|
||
#+name: mu4e-mail-service
|
||
#+begin_src emacs-lisp :tangle no
|
||
(setq smtpmail-smtp-server "mail.phundrak.com"
|
||
smtpmail-smtp-service 587
|
||
smtpmail-stream-type 'starttls
|
||
message-send-mail-function 'smtpmail-send-it)
|
||
#+end_src
|
||
|
||
We also need to inform it on where my emails are stored on my machine,
|
||
and how to retrieve them.
|
||
#+name: mu4e-mail-on-machine
|
||
#+begin_src emacs-lisp :tangle no
|
||
(setq mu4e-get-mail-command "mbsync -a"
|
||
mu4e-root-maildir "~/Mail"
|
||
mu4e-trash-folder "/Trash"
|
||
mu4e-refile-folder "/Archive"
|
||
mu4e-sent-folder "/Sent"
|
||
mu4e-drafts-folder "/Drafts")
|
||
#+end_src
|
||
|
||
In the same vein of [[*Basic configuration][this bit of configuration]], I do not want mu4e to
|
||
insert my mail signature, ~org-msg~ already does that.
|
||
#+name: mu4e-no-signature
|
||
#+begin_src emacs-lisp :tangle no
|
||
(setq mu4e-compose-signature nil)
|
||
#+end_src
|
||
|
||
***** Bookmarks
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Email-Mu4e-Bookmarkszo11fl6184j0
|
||
:END:
|
||
In mu4e, the main focus isn’t really mail directories such as your
|
||
inbox, your sent messages and such, but instead you manipulate
|
||
bookmarks which will show you emails depending on tags. This mean you
|
||
can create some pretty customized bookmarks that go way beyound your
|
||
simple inbox, outbox and all. Actually, four of my bookmarks have a
|
||
couple of filtering:
|
||
- anything in my inbox linked to my university
|
||
- the [[https://emacs-doctor.com/lists/listinfo][emacs-doctor mailing list]] (French Emacs mailing list)
|
||
- the conlang mailing lists
|
||
- and my inbox for any mail not caught by any of these filters
|
||
And all of them will have the requirement not to display any trashed
|
||
email. Actually, all of my bookmarks will have this requirement,
|
||
except for the bookmark dedicated to them as well as my sent emails.
|
||
I’ll add these latter requirements later.
|
||
|
||
Here are the requirements for my university bookmark. The regex
|
||
matches any email address which contains either ~up8.edu~ or
|
||
~univ-paris8~, which can be found in email addresses from the University
|
||
Paris 8 (my university).
|
||
|
||
#+name: mu4e-bookmarks-from-copy-to-gen
|
||
#+begin_src emacs-lisp :tangle no :exports none :var regex="test"
|
||
(mapconcat (lambda (x) (concat x ":" regex))
|
||
'("f" "c" "t")
|
||
" OR ")
|
||
#+end_src
|
||
|
||
#+name: mu4e-bookmarks-mailing-lists
|
||
#+header: :tangle no :exports none
|
||
#+begin_src emacs-lisp :var lists=mu4e-emacs-mailing-lists
|
||
(mapconcat (lambda (list)
|
||
(let ((address (string-replace (regexp-quote "~") "" (car list))))
|
||
(mapconcat (lambda (flag)
|
||
(concat flag ":" address))
|
||
'("list" "t" "f" "contact")
|
||
" OR ")))
|
||
lists
|
||
" OR ")
|
||
#+end_src
|
||
|
||
#+name: mu4e-bookmarks-filter-uni
|
||
#+header: :tangle no :cache yes
|
||
#+begin_src emacs-lisp
|
||
(let ((regex "/.*up8\\.edu|.*univ-paris8.*/"))
|
||
<<mu4e-bookmarks-from-copy-to-gen>>)
|
||
#+end_src
|
||
|
||
#+RESULTS[f1b50a07521c59a4ccd72f6f8ec11431ac618139]: mu4e-bookmarks-filter-uni
|
||
: f:/.*up8\.edu|.*univ-paris8.*/ OR c:/.*up8\.edu|.*univ-paris8.*/ OR t:/.*up8\.edu|.*univ-paris8.*/
|
||
|
||
Next I need an inbox dedicated to the association I’m part of.
|
||
#+name: mu4e-bookmarks-filter-asso
|
||
#+header: :tangle no :cache yes
|
||
#+begin_src emacs-lisp
|
||
(let ((regex "/.*supran\\.fr/"))
|
||
<<mu4e-bookmarks-from-copy-to-gen>>)
|
||
#+end_src
|
||
|
||
#+RESULTS[e04566a9d56624e063b3dd4e2c639e87cf9683aa]: mu4e-bookmarks-filter-asso
|
||
: f:/.*supran\.fr/ OR c:/.*supran\.fr/ OR t:/.*supran\.fr/
|
||
|
||
As for the Emacs-doctor list, I need to match both the current, modern
|
||
mailing list address but also its old address. The same applies for
|
||
the emacs-devel mailing list as well as Github emails related to my
|
||
package ~eshell-info-banner.el~ ([[#Packages-Configuration-Emacs-built-ins-Eshell-Visual-configuratione7c2fl6184j0][see here]]). Here are the addresses to
|
||
match:
|
||
|
||
#+name: mu4e-emacs-mailing-lists
|
||
- ~/ateliers.*emacs.*/~
|
||
- ~/emacs-.*@gnu.org/~
|
||
- ~/.*eshell-info-banner.*/~
|
||
- ~/.*emacsfr.*/~
|
||
|
||
#+name: mu4e-bookmarks-filter-emacs-list
|
||
#+header: :tangle no :cache yes
|
||
#+begin_src emacs-lisp
|
||
"<<mu4e-bookmarks-mailing-lists(lists=mu4e-emacs-mailing-lists)>>"
|
||
#+end_src
|
||
|
||
#+RESULTS[88c964fba64459a050055bc5614bbd65f8740bfb]: mu4e-bookmarks-filter-emacs-list
|
||
: list:/ateliers.*emacs.*/ OR t:/ateliers.*emacs.*/ OR f:/ateliers.*emacs.*/ OR contact:/ateliers.*emacs.*/ OR list:/emacs-.*@gnu.org/ OR t:/emacs-.*@gnu.org/ OR f:/emacs-.*@gnu.org/ OR contact:/emacs-.*@gnu.org/ OR list:/.*eshell-info-banner.*/ OR t:/.*eshell-info-banner.*/ OR f:/.*eshell-info-banner.*/ OR contact:/.*eshell-info-banner.*/ OR list:/.*emacsfr.*/ OR t:/.*emacsfr.*/ OR f:/.*emacsfr.*/ OR contact:/.*emacsfr.*/
|
||
|
||
Another bookmark I wish to have is one dedicated to emails related to
|
||
more general development topics, including issues and PRs from Github.
|
||
#+name: mu4e-github-mailing-lists
|
||
- ~/.*\\.github\\.com/~
|
||
- ~/.*\\.gitlab\\.com/~
|
||
- ~stumpwm-devel@nongnu.org~
|
||
- ~/.*sr\\.ht/~
|
||
|
||
#+name: mu4e-bookmarks-filter-github-list
|
||
#+header: :tangle no :cache yes
|
||
#+begin_src emacs-lisp
|
||
;; "<<mu4e-bookmarks-mailing-lists(lists=mu4e-github-mailing-lists)>> AND NOT ()"
|
||
(string-join '("<<mu4e-bookmarks-mailing-lists(lists=mu4e-github-mailing-lists)>>"
|
||
"AND NOT ("
|
||
<<mu4e-bookmarks-filter-emacs-list>>
|
||
")")
|
||
" ")
|
||
#+end_src
|
||
|
||
#+RESULTS[673f76e7a682ed64f98dbe6d4a06810436ba6799]: mu4e-bookmarks-filter-github-list
|
||
: list:/.*\.github\.com/ OR t:/.*\.github\.com/ OR f:/.*\.github\.com/ OR contact:/.*\.github\.com/ OR list:/.*\.gitlab\.com/ OR t:/.*\.gitlab\.com/ OR f:/.*\.gitlab\.com/ OR contact:/.*\.gitlab\.com/ OR list:stumpwm-devel@nongnu.org OR t:stumpwm-devel@nongnu.org OR f:stumpwm-devel@nongnu.org OR contact:stumpwm-devel@nongnu.org OR list:/.*sr\.ht/ OR t:/.*sr\.ht/ OR f:/.*sr\.ht/ OR contact:/.*sr\.ht/ AND NOT ( list:/ateliers.*emacs.*/ OR t:/ateliers.*emacs.*/ OR f:/ateliers.*emacs.*/ OR contact:/ateliers.*emacs.*/ OR list:/emacs-.*@gnu.org/ OR t:/emacs-.*@gnu.org/ OR f:/emacs-.*@gnu.org/ OR contact:/emacs-.*@gnu.org/ OR list:/.*eshell-info-banner.*/ OR t:/.*eshell-info-banner.*/ OR f:/.*eshell-info-banner.*/ OR contact:/.*eshell-info-banner.*/ OR list:/.*emacsfr.*/ OR t:/.*emacsfr.*/ OR f:/.*emacsfr.*/ OR contact:/.*emacsfr.*/ )
|
||
|
||
When it comes to the conlang mailing list, let’s not match anything
|
||
from or to them. I’ll also include the auxlang mailing list –I’m not
|
||
subscribed anymore, but it’ll keep my inbox clean.
|
||
#+name: mu4e-conlanging-mailing-lists
|
||
- ~/.*LANG@LISTSERV.BROWN.EDU/~
|
||
|
||
#+name: mu4e-bookmarks-filter-conlang-list
|
||
#+header: :tangle no :cache yes
|
||
#+begin_src emacs-lisp
|
||
"<<mu4e-bookmarks-mailing-lists(lists=mu4e-conlanging-mailing-lists)>>"
|
||
#+end_src
|
||
|
||
#+RESULTS[4be7eb7c9f0b85ceb1cbd30aa87b5f1d9612d849]: mu4e-bookmarks-filter-conlang-list
|
||
: list:/.*LANG@LISTSERV.BROWN.EDU/ OR t:/.*LANG@LISTSERV.BROWN.EDU/ OR f:/.*LANG@LISTSERV.BROWN.EDU/ OR contact:/.*LANG@LISTSERV.BROWN.EDU/
|
||
|
||
As I said earlier, something that will often come back in my bookmarks
|
||
is the emails must not be trashed to appear. I want also to display
|
||
junk emails, so I end up with the following rule:
|
||
#+name: mu4e-bookmarks-default-filter
|
||
#+header: :tangle no :cache yes
|
||
#+begin_src emacs-lisp
|
||
(mapconcat #'identity
|
||
`("NOT flag:trashed"
|
||
,(format "(%s)" (mapconcat (lambda (maildir) (concat "maildir:" maildir))
|
||
'("/Inbox" "/Junk")
|
||
" OR ")))
|
||
" AND ")
|
||
#+end_src
|
||
|
||
#+RESULTS[f3f96c07b8341c1b7b3d02688aa6faa2ceeca16f]: mu4e-bookmarks-default-filter
|
||
: NOT flag:trashed AND (maildir:/Inbox OR maildir:/Junk)
|
||
|
||
And for the last string-generating code, let’s describe my main inbox:
|
||
#+name: mu4e-bookmarks-inbox-filters
|
||
#+header: :tangle no :cache yes
|
||
#+begin_src emacs-lisp
|
||
(mapconcat #'identity
|
||
(cons "<<mu4e-bookmarks-default-filter()>>"
|
||
`(,(format "(%s)"
|
||
<<mu4e-bookmarks-filter-conlang-list>>)
|
||
,(format "(%s)" "<<mu4e-bookmarks-filter-asso()>>")
|
||
,(format "(%s)"
|
||
<<mu4e-bookmarks-filter-emacs-list>>)
|
||
,(format "(%s)"
|
||
<<mu4e-bookmarks-filter-github-list>>)
|
||
,(format "(%s)"
|
||
<<mu4e-bookmarks-filter-uni>>)))
|
||
" AND NOT ")
|
||
#+end_src
|
||
|
||
#+RESULTS[78b388eafb2fdb1c0a8140cc0566c2de227439de]: mu4e-bookmarks-inbox-filters
|
||
: NOT flag:trashed AND (maildir:/Inbox OR maildir:/Junk) AND NOT (list:/.*LANG@LISTSERV.BROWN.EDU/ OR t:/.*LANG@LISTSERV.BROWN.EDU/ OR f:/.*LANG@LISTSERV.BROWN.EDU/ OR contact:/.*LANG@LISTSERV.BROWN.EDU/) AND NOT (f:/.*supran.fr/ OR c:/.*supran.fr/ OR t:/.*supran.fr/) AND NOT (list:/ateliers.*emacs.*/ OR t:/ateliers.*emacs.*/ OR f:/ateliers.*emacs.*/ OR contact:/ateliers.*emacs.*/ OR list:/emacs-.*@gnu.org/ OR t:/emacs-.*@gnu.org/ OR f:/emacs-.*@gnu.org/ OR contact:/emacs-.*@gnu.org/ OR list:/.*eshell-info-banner.*/ OR t:/.*eshell-info-banner.*/ OR f:/.*eshell-info-banner.*/ OR contact:/.*eshell-info-banner.*/ OR list:/.*emacsfr.*/ OR t:/.*emacsfr.*/ OR f:/.*emacsfr.*/ OR contact:/.*emacsfr.*/) AND NOT (list:/.*\.github\.com/ OR t:/.*\.github\.com/ OR f:/.*\.github\.com/ OR contact:/.*\.github\.com/ OR list:/.*\.gitlab\.com/ OR t:/.*\.gitlab\.com/ OR f:/.*\.gitlab\.com/ OR contact:/.*\.gitlab\.com/ OR list:stumpwm-devel@nongnu.org OR t:stumpwm-devel@nongnu.org OR f:stumpwm-devel@nongnu.org OR contact:stumpwm-devel@nongnu.org OR list:/.*sr\.ht/ OR t:/.*sr\.ht/ OR f:/.*sr\.ht/ OR contact:/.*sr\.ht/ AND NOT ( list:/ateliers.*emacs.*/ OR t:/ateliers.*emacs.*/ OR f:/ateliers.*emacs.*/ OR contact:/ateliers.*emacs.*/ OR list:/emacs-.*@gnu.org/ OR t:/emacs-.*@gnu.org/ OR f:/emacs-.*@gnu.org/ OR contact:/emacs-.*@gnu.org/ OR list:/.*eshell-info-banner.*/ OR t:/.*eshell-info-banner.*/ OR f:/.*eshell-info-banner.*/ OR contact:/.*eshell-info-banner.*/ OR list:/.*emacsfr.*/ OR t:/.*emacsfr.*/ OR f:/.*emacsfr.*/ OR contact:/.*emacsfr.*/ )) AND NOT (f:/.*up8\.edu|.*univ-paris8.*/ OR c:/.*up8\.edu|.*univ-paris8.*/ OR t:/.*up8\.edu|.*univ-paris8.*/)
|
||
|
||
We can finally define our bookmarks! The code reads as follows:
|
||
#+name: mu4e-bookmarks
|
||
#+begin_src emacs-lisp :tangle no :cache yes :results none
|
||
(setq mu4e-bookmarks
|
||
`((:name "Inbox"
|
||
:key ?i
|
||
:query ,(format "%s"
|
||
<<mu4e-bookmarks-inbox-filters>>))
|
||
(:name "University"
|
||
:key ?u
|
||
:query ,(format "%s AND %s"
|
||
"<<mu4e-bookmarks-default-filter()>>"
|
||
"<<mu4e-bookmarks-filter-uni()>>"))
|
||
(:name "Dev"
|
||
:key ?d
|
||
:query ,(format "%s AND (%s)"
|
||
"<<mu4e-bookmarks-default-filter()>>"
|
||
"<<mu4e-bookmarks-filter-github-list()>>"))
|
||
(:name "Emacs"
|
||
:key ?e
|
||
:query ,(format "%s AND %s"
|
||
"<<mu4e-bookmarks-default-filter()>>"
|
||
<<mu4e-bookmarks-filter-emacs-list>>))
|
||
(:name "Linguistics"
|
||
:key ?l
|
||
:query ,(format "(%s) AND (%s)"
|
||
"<<mu4e-bookmarks-default-filter()>>"
|
||
<<mu4e-bookmarks-filter-conlang-list>>))
|
||
(:name "Supran"
|
||
:key ?s
|
||
:query ,(format "%s AND %s"
|
||
"<<mu4e-bookmarks-default-filter()>>"
|
||
"<<mu4e-bookmarks-filter-asso()>>"))
|
||
(:name "Sent" :key ?S :query "maildir:/Sent")
|
||
(:name "All Unread" :key ?U :query "flag:unread AND NOT flag:trashed")
|
||
(:name "Today" :key ?t :query "date:today..now AND NOT flag:trashed")
|
||
(:name "This Week" :key ?w :query "date:7d..now AND NOT flag:trashed")
|
||
(:name "This Month" :key ?m :query "date:1m..now AND NOT flag:trashed")
|
||
(:name "This Year" :key ?y :query "date:1y..now AND NOT flag:trashed")))
|
||
#+end_src
|
||
|
||
***** Maildirs
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Email-Mu4e-Maildirs-miy134713gj0
|
||
:END:
|
||
Sometimes, bookmarks are a bit too restrictive when I want to search
|
||
for stuff. Simply jumping to a mail directory, or maildir, is more
|
||
than enough. In mu4e, with my current setup, I can jump to any maildir
|
||
with the shortcut ~Jo~ in normal-mode, and I just need to chose in a
|
||
list the maildir I want to jump to. But it can be faster.
|
||
|
||
With this piece of code, I can simply jump to my inbox maildir with
|
||
~Ji~, to my sent messages with ~Js~, and so on.
|
||
#+name: mu4e-maildirs
|
||
#+begin_src emacs-lisp :tangle no
|
||
(setq mu4e-maildir-shortcuts
|
||
'((:maildir "/Inbox" :key ?i)
|
||
(:maildir "/Sent" :key ?s)
|
||
(:maildir "/Junk" :key ?j)
|
||
(:maildir "/Trash" :key ?t)))
|
||
#+end_src
|
||
|
||
***** Dealing with spammers
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Email-Mu4e-Dealing-with-spammers-tid4mw51l7j0
|
||
:END:
|
||
I’m sure you have received at least one email recently from a sketchy
|
||
email address asking you something that might be completely unrelated
|
||
to what you do, or at least somewhat related. Fortunately, [[https://twitter.com/Boris/status/1360208504544444417][we have a
|
||
hero]]! Now, let me write a function that will insert their pre-written
|
||
text at point so I don’t have to go back to their Twitter thread each
|
||
time I want to shut spammers up.
|
||
#+begin_src emacs-lisp
|
||
(defun reply-to-bill ()
|
||
(interactive)
|
||
(insert "Please forward this email to bill@noprocurement.com,
|
||
and delete my email, as I’ll be changing jobs soon, and this
|
||
email address will no longer be active.
|
||
|
||
Bill Whiskoney is a senior partner at Nordic Procurement
|
||
Services, and he handles our budget and will help you further or
|
||
introduce you to someone who can."))
|
||
#+end_src
|
||
|
||
If you want the full story, make sure to read the whole thread, I
|
||
guarantee it, it’s worth your time! And in case the Twitter thread
|
||
disappear in the future, [[https://threader.app/thread/1360208504544444417][here is a backup]].
|
||
|
||
***** Getting Fancy
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Email-Mu4e-Getting-Fancyg731fl6184j0
|
||
:END:
|
||
I’m not a huge fan of mu4e’s default icons marking my emails, so I’ll
|
||
redefine them as follows. Be aware the name of these icons are from
|
||
/faicon/ in the package ~all-the-icons~.
|
||
#+name: mu4e-fancy-marks-tbl
|
||
| Mark | Flag | Icon |
|
||
|-----------+------+-------------|
|
||
| draft | D | pencil |
|
||
| flagged | F | flag |
|
||
| new | N | rss |
|
||
| passed | P | check |
|
||
| replied | R | reply |
|
||
| seen | S | eye |
|
||
| unread | u | eye-slash |
|
||
| trashed | T | trash |
|
||
| attach | a | paperclip |
|
||
| encrypted | x | lock |
|
||
| signed | s | certificate |
|
||
|
||
#+name: mu4e-fancy-marks-gen
|
||
#+header: :tangle no :exports none :results value :cache yes
|
||
#+begin_src emacs-lisp :var table=mu4e-fancy-marks-tbl
|
||
(mapconcat (lambda (line)
|
||
(let ((mark (car line))
|
||
(flag (cadr line))
|
||
(icon (caddr line)))
|
||
(format "mu4e-headers-%s-mark `(\"%s\" . ,(all-the-icons-faicon \"%s\" :height 0.8))"
|
||
mark
|
||
flag
|
||
icon)))
|
||
table
|
||
"\n")
|
||
#+end_src
|
||
|
||
#+RESULTS[c6ed5d4bec4c10339a7de52a70822af74d782e62]: mu4e-fancy-marks-gen
|
||
#+begin_example
|
||
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))
|
||
#+end_example
|
||
|
||
Let’s enable them and set them:
|
||
#+name: mu4e-fancy-marks
|
||
#+begin_src emacs-lisp :tangle no
|
||
(setq mu4e-use-fancy-chars t
|
||
<<mu4e-fancy-marks-gen()>>)
|
||
#+end_src
|
||
|
||
#+name: mu4e-vertical-split
|
||
#+begin_src emacs-lisp :tangle no
|
||
(defun my/set-mu4e-headers-width ()
|
||
(let ((width (window-body-width))
|
||
(threshold (+ 120 80)))
|
||
(setq mu4e-split-view (if (> width threshold)
|
||
'vertical
|
||
'horizontal))))
|
||
|
||
(setq mu4e-headers-visible-columns 120
|
||
mu4e-headers-visible-lines 15)
|
||
(add-hook 'mu4e-headers-mode-hook #'my/set-mu4e-headers-width)
|
||
#+end_src
|
||
|
||
***** Headers mode
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Email-Mu4e-Headers-modeum41fl6184j0
|
||
:END:
|
||
#+name: mu4e-headers-mode
|
||
#+begin_src emacs-lisp :tangle no
|
||
(add-hook 'mu4e-headers-mode-hook (lambda () (visual-line-mode -1)))
|
||
(add-hook 'mu4e-headers-mode-hook (lambda () (toggle-truncate-lines -1)))
|
||
#+end_src
|
||
|
||
***** Keybindings
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Email-Mu4e-Keybindingsh161fl6184j0
|
||
:END:
|
||
By default, Evil has some pretty annoying keybindings for users of the
|
||
bépo layout: ~hjkl~ becomes ~ctsr~ for us. Let’s undefine some of these:
|
||
#+name: mu4e-keybindings-undef
|
||
#+begin_src emacs-lisp :tangle no
|
||
(phundrak/undefine
|
||
:keymaps 'mu4e-view-mode-map
|
||
:packages 'mu4e
|
||
"S" nil
|
||
"r" nil
|
||
"c" nil
|
||
"gu" nil)
|
||
|
||
(phundrak/undefine
|
||
:keymaps '(mu4e-view-mode-map mu4e-headers-mode-map)
|
||
:packages 'mu4e
|
||
"s" nil)
|
||
#+end_src
|
||
|
||
Now, let’s define some keybindings for mu4e’s view mode, that is when
|
||
we are viewing an email. All these keybindings will reside between the
|
||
major-mode specific leader key ~,~ and most of these keybindings can be
|
||
described with a simple function:
|
||
#+name: mu4e-keybindings-view-tbl
|
||
| Keybinding | Function | Description |
|
||
|------------+--------------------------------------+--------------------|
|
||
| & | mu4e-view-pipe | |
|
||
| . | mu4e-headers-split-adjust-width/body | mu4e-headers width |
|
||
| a | nil | attachments |
|
||
| a& | mu4e-view-pipe-attachment | |
|
||
| aa | mu4e-view-attachment-action | |
|
||
| ao | mu4e-view-open-attachment | |
|
||
| aO | mu4e-view-open-attachment-with | |
|
||
| c | nil | compose |
|
||
| cc | mu4e-compose-new | |
|
||
| ce | mu4e-compose-edit | |
|
||
| cf | mu4e-compose-forward | |
|
||
| cr | mu4e-compose-reply | |
|
||
| cR | mu4e-compose-resend | |
|
||
| l | mu4e-show-log | |
|
||
| m | nil | 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 | mu4e-view-mark-thread | mark thread |
|
||
| T | nil | 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 | |
|
||
| u | nil | url |
|
||
| uf | mu4e-view-go-to-url | |
|
||
| uF | mu4e-view-fetch-url | |
|
||
|
||
#+name: mu4e-keybindings-view
|
||
#+begin_src emacs-lisp :tangle no
|
||
(phundrak/major-leader-key
|
||
:keymaps 'mu4e-view-mode-map
|
||
:packages 'mu4e
|
||
<<general-keybindings-gen(table=mu4e-keybindings-view-tbl)>>)
|
||
#+end_src
|
||
|
||
Two other keybinds are added without a prefix, just for the sake of
|
||
convenience.
|
||
#+name: mu4e-keybindings-view-no-prefix
|
||
#+begin_src emacs-lisp
|
||
(phundrak/evil
|
||
:keymaps 'mu4e-view-mode-map
|
||
:packages 'mu4e
|
||
"«" #'mu4e-view-headers-prev
|
||
"»" #'mu4e-view-headers-next)
|
||
#+end_src
|
||
|
||
I’ll also declare two keybinds for mu4e’s headers mode.
|
||
#+name: mu4e-keybindings-header
|
||
#+begin_src emacs-lisp :tangle no
|
||
(phundrak/major-leader-key
|
||
:keymaps 'mu4e-headers-mode-map
|
||
:packages 'mu4e
|
||
"t" '(mu4e-view-mark-thread :which-key "mark thread")
|
||
"s" 'swiper)
|
||
#+end_src
|
||
|
||
I will also redefine without a leader key ~ctsr~ in order to be able to
|
||
move freely (remember, bépo layout for me).
|
||
#+name: mu4e-keybindings-header-no-leader-table
|
||
| Key | Function | Comment |
|
||
|-----+--------------------+---------|
|
||
| c | evil-backward-char | |
|
||
| t | evil-next-line | |
|
||
| s | evil-previous-line | |
|
||
| r | evil-forward-char | |
|
||
|
||
#+name: mu4e-keybindings-header-no-leader
|
||
#+begin_src emacs-lisp :tangle no
|
||
(phundrak/evil
|
||
:keymaps 'mu4e-headers-mode-map
|
||
:packages 'mu4e
|
||
<<general-keybindings-gen(table=mu4e-keybindings-header-no-leader-table)>>)
|
||
#+end_src
|
||
|
||
Finally, let’s declare a couple of keybindings for when we are
|
||
composing a message. This time, all my keybindings are prefixed with
|
||
the major-mode leader and call a simple function.
|
||
#+name: mu4e-keybindings-message-tbl
|
||
| Key | Function | Description |
|
||
|-----+-----------------------+-------------|
|
||
| , | 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 | |
|
||
|
||
#+name: mu4e-keybindings-message
|
||
#+begin_src emacs-lisp :tangle no
|
||
(phundrak/major-leader-key
|
||
:keymaps 'message-mode-map
|
||
:packages 'mu4e
|
||
<<general-keybindings-gen(table=mu4e-keybindings-message-tbl)>>)
|
||
#+end_src
|
||
|
||
**** Composing messages
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Email-Composing-messagesth71fl6184j0
|
||
:END:
|
||
Org mime is cool and all, you can write some org-mode and then export
|
||
it so you can send an HTML email. BUT, have you considered skipping
|
||
the export part and write your emails directly in org-mode?
|
||
#+begin_src emacs-lisp
|
||
(use-package org-msg
|
||
:after (mu4e)
|
||
:straight (:build t)
|
||
:hook ((mu4e-compose-pre . org-msg-mode))
|
||
:custom-face
|
||
(mu4e-replied-face ((t (:weight normal :foreground "#b48ead"))))
|
||
:config
|
||
(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)
|
||
(add-hook 'mu4e-headers-mode (lambda () (toggle-truncate-lines -1)))
|
||
(setq org-msg-startup "inlineimages"
|
||
org-msg-default-alternatives '((new . (text html))
|
||
(reply-to-html . (text html))
|
||
(reply-to-text . (text)))
|
||
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)
|
||
(while (re-search-forward "\n" nil t)
|
||
(replace-match "\n\n"))
|
||
(buffer-string))))
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'org-msg-edit-mode-map
|
||
:packages 'org-msg
|
||
<<general-keybindings-gen(table=org-msg-edit-mode-keybinds)>>))
|
||
#+end_src
|
||
|
||
The keybinds are relatively simple ~org-msg-edit-mode~:
|
||
#+name: org-msg-edit-mode-keybinds
|
||
| Key | Function | Description |
|
||
|-----+-----------------------+-------------|
|
||
| , | 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 | |
|
||
|
||
**** Email alerts
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Email-Email-alertsfx81fl6184j0
|
||
:END:
|
||
There is also a package for mu4e which generates desktop notifications
|
||
when new emails are received. By default, I want to be notified by all
|
||
messages in my inbox and junk folder. Also, I’ll use Emacs’ default
|
||
notification system, and I’ll activate the modeline notification.
|
||
#+begin_src emacs-lisp
|
||
(use-package mu4e-alert
|
||
:straight (:build t)
|
||
:after mu4e
|
||
:defer t
|
||
:init
|
||
(add-hook 'after-init-hook #'mu4e-alert-enable-notifications)
|
||
(add-hook 'after-init-hook #'mu4e-alert-enable-mode-line-display)
|
||
(mu4e-alert-set-default-style 'notifications)
|
||
:config
|
||
(setq mu4e-alert-interesting-mail-query "flag:unread"))
|
||
#+end_src
|
||
|
||
*** EMMS and Media
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-EMMS-ij71fr61v8j0
|
||
:END:
|
||
EMMS, also known as the /Emacs MultiMedia System/, allows the user to
|
||
interact through Emacs with multimedia elements such as music and
|
||
videos. My main use for it will be for music with MPD (see its
|
||
configuration [[file:mpd.org][here]]).
|
||
#+begin_src emacs-lisp
|
||
(use-package emms
|
||
:defer t
|
||
:after all-the-icons
|
||
:straight (:build t)
|
||
:init
|
||
(require 'emms-setup)
|
||
(require 'emms-mark)
|
||
(emms-all)
|
||
(add-to-list 'emms-info-functions 'emms-info-mpd)
|
||
(add-to-list 'emms-player-list 'emms-player-mpd)
|
||
(emms-player-mpd-sync-from-mpd)
|
||
(emms-player-mpd-connect)
|
||
|
||
<<emms-fd-name>>
|
||
<<emms-fd-function>>
|
||
<<emms-search-set-variable>>
|
||
|
||
<<emms-media-hydra>>
|
||
(defun emms-player-toggle-pause ()
|
||
(interactive)
|
||
(shell-command-and-echo "mpc toggle"))
|
||
:custom
|
||
((emms-source-file-default-directory (expand-file-name "~/Music"))
|
||
(emms-player-mpd-server-name "localhost")
|
||
(emms-player-mpd-server-port "6600")
|
||
(emms-player-mpd-music-directory (expand-file-name "~/Music"))
|
||
(emms-browser-thumbnail-small-size 64)
|
||
(emms-browser-thumbnail-medium-size 128)
|
||
(emms-browser-covers #'emms-browser-cache-thumbnail-async)
|
||
(emms-playlist-default-major-mode 'emms-mark-mode))
|
||
:general
|
||
(phundrak/undefine
|
||
:keymaps 'emms-browser-mode-map
|
||
:packages 'emms
|
||
"s" nil
|
||
"r" nil)
|
||
(phundrak/evil
|
||
:keymaps 'emms-browser-mode-map
|
||
:packages 'emms
|
||
"a" #'emms-browser-add-tracks
|
||
"A" #'emms-browser-add-tracks-and-play
|
||
"b" '(:ignore t :which-key "browse by")
|
||
"bA" #'emms-browse-by-album
|
||
"ba" #'emms-browse-by-artist
|
||
"bg" #'emms-browse-by-genre
|
||
"bs" #'emms-smart-browse
|
||
"by" #'emms-browse-by-year
|
||
"c" #'emms-browser-clear-playlist
|
||
"S" '(:ignore t :which-key "search")
|
||
"SA" '(emms-browser-search-by-album :which-key "by album")
|
||
"Sa" '(emms-browser-search-by-artist :which-key "by artist")
|
||
"Ss" '(emms-browser-search-by-names :which-key "by name")
|
||
"St" '(emms-browser-search-by-names :which-key "by title")
|
||
"q" #'kill-this-buffer)
|
||
(phundrak/evil
|
||
:keymaps 'emms-playlist-mode-map
|
||
:packages 'emms
|
||
"d" #'emms-playlist-mode-kill-track
|
||
"p" #'emms-playlist-mode-play-smart
|
||
"q" #'kill-this-buffer)
|
||
(phundrak/leader-key
|
||
:infix "m"
|
||
:packages 'emms
|
||
"" '(:ignore t :which-key "media")
|
||
"." #'hydra-media/body
|
||
"«" #'emms-player-mpd-previous
|
||
"»" #'emms-player-mpd-next
|
||
"c" #'emms-player-mpd-clear
|
||
"e" '(:ignore t :which-key "emms")
|
||
"eb" #'emms-browser
|
||
"ep" #'emms-playlist-mode-go
|
||
"es" #'emms-player-mpd-show
|
||
"p" '((lambda ()
|
||
(interactive)
|
||
(shell-command-and-echo "mpc toggle"))
|
||
:which-key "mpc toggle")
|
||
"s" #'emms-stop
|
||
"u" '(:ignore t :which-key "update")
|
||
"um" #'emms-player-mpd-update-all
|
||
"uc" #'emms-cache-set-from-mpd-all))
|
||
#+end_src
|
||
|
||
**** Finding files from EMMS
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-EMMS-and-Media-Finding-files-from-EMMS-as6fgpv0baj0
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
EMMS has two default ways of finding files: either a built-in function
|
||
relatively slow but portable, or with ~find~ which is arguably faster
|
||
but less portable. Honestly, I don’t care about portability, I’ll
|
||
always use this Emacs config on Linux, but I don’t want to use ~find~
|
||
either since there is something even faster: ~fd~.
|
||
|
||
First we’ll declare a variable that can hold the path to the ~fd~
|
||
executable:
|
||
#+name: emms-fd-name
|
||
#+begin_src emacs-lisp
|
||
(defvar emms-source-file-fd (executable-find "fd"))
|
||
#+end_src
|
||
|
||
Then, we need to declare a new function that will use ~fd~ to find
|
||
files. The function, as specified by the documentation of
|
||
~emms-source-file-directory-tree-function~, receives two arguments ~dir~
|
||
and ~regex~. We can work with that!
|
||
#+name: emms-fd-function
|
||
#+begin_src emacs-lisp
|
||
(defun emms-source-file-directory-tree-fd (dir regex)
|
||
"Return a list of all files under DIR that match REGEX.
|
||
This function uses the external fd utility. The name for fd may
|
||
be supplied using `emms-source-file-fd'."
|
||
(with-temp-buffer
|
||
(call-process emms-source-file-fd
|
||
nil t nil
|
||
"-L" ; follow symlinks
|
||
regex
|
||
"-t f"
|
||
(expand-file-name dir))
|
||
(delete ""
|
||
(split-string (buffer-substring (point-min)
|
||
(point-max))
|
||
"\n"))))
|
||
#+end_src
|
||
|
||
We can finally set this function as our search function.
|
||
#+name: emms-search-set-variable
|
||
#+begin_src emacs-lisp
|
||
(setq emms-source-file-directory-tree-function #'emms-source-file-directory-tree-fd)
|
||
#+end_src
|
||
|
||
**** Keybinds
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-EMMS-and-Media-Keybinds-ue071zv0baj0
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
I also want to create a small hydra for manipulating MPD:
|
||
#+name: emms-media-hydra
|
||
#+begin_src emacs-lisp
|
||
(defun shell-command-and-echo (command &optional echo prefix)
|
||
"Run COMMAND and display the result of ECHO prefixed by PREFIX.
|
||
|
||
Run COMMAND as a shell command.
|
||
|
||
If ECHO is non nil, display the result of its execution as a
|
||
shell command to the minibuffer through `MESSAGE'.
|
||
|
||
If PREFIX is non nil, it will prefix the output of ECHO in the
|
||
minibuffer, both separated by a single space."
|
||
(progn
|
||
(with-temp-buffer
|
||
(shell-command command
|
||
(current-buffer)
|
||
(current-buffer))
|
||
(when echo
|
||
(message "%s%s"
|
||
(if prefix
|
||
(concat prefix " ")
|
||
"")
|
||
(string-trim
|
||
(shell-command-to-string "mpc volume")))))))
|
||
|
||
(defhydra hydra-media (:hint nil)
|
||
"
|
||
%s(all-the-icons-material \"volume_up\" :height 1.0 :v-adjust -0.2)
|
||
[_s_]
|
||
« [_c_] _p_ [_r_] » [_S_] %s(all-the-icons-material \"stop\")
|
||
[_t_]
|
||
%s(all-the-icons-material \"volume_down\" :height 1.0 :v-adjust -0.2)
|
||
"
|
||
("c" emms-player-mpd-previous)
|
||
("r" emms-player-mpd-next)
|
||
("t" (shell-command-and-echo "mpc volume -2" "mpc volume" "mpc"))
|
||
("s" (shell-command-and-echo "mpc volume +2" "mpc volume" "mpc"))
|
||
("p" (shell-command-and-echo "mpc toggle"))
|
||
("S" emms-player-mpd-stop)
|
||
("q" nil :exit t))
|
||
#+end_src
|
||
|
||
*** Nov
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Nov0da1fl6184j0
|
||
:END:
|
||
Nov is a major-mode for reading EPUB files within Emacs. Since I have
|
||
it, I don’t need any other Epub reader on my computer! Plus this one
|
||
is customizable and programmable, why would I use any other EPUB
|
||
reader?
|
||
#+begin_src emacs-lisp
|
||
(use-package nov
|
||
:straight (:build t)
|
||
:defer t
|
||
:mode ("\\.epub\\'" . nov-mode)
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'nov-mode-map
|
||
:packages 'nov
|
||
"c" #'nov-previous-document
|
||
"t" #'nov-scroll-up
|
||
"C-d" #'nov-scroll-up
|
||
"s" #'nov-scroll-down
|
||
"C-u" #'nov-scroll-down
|
||
"r" #'nov-next-document
|
||
"gm" #'nov-display-metadata
|
||
"gn" #'nov-next-document
|
||
"gp" #'nov-previous-document
|
||
"gr" #'nov-render-document
|
||
"gt" #'nov-goto-toc
|
||
"gv" #'nov-view-source
|
||
"gV" #'nov-view-content-source)
|
||
:config
|
||
(setq nov-text-width 95))
|
||
#+end_src
|
||
|
||
*** PDF Tools
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-PDF-Toolsvqb1fl6184j0
|
||
:END:
|
||
~pdf-tools~ enables PDF support for Emacs, much better than its built-in
|
||
support with DocView. Aside from the classical settings such as
|
||
keybinds, I also enable the midnight colors by default; think of it as
|
||
an equivalent of Zathura’s recolor feature which kind of enables a
|
||
dark mode for PDFs.
|
||
#+begin_src emacs-lisp
|
||
(use-package pdf-tools
|
||
:defer t
|
||
:magic ("%PDF" . pdf-view-mode)
|
||
:straight (:build t)
|
||
:mode (("\\.pdf\\'" . pdf-view-mode))
|
||
:hook (pdf-tools-enabled . pdf-view-midnight-minor-mode)
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'pdf-view-mode-map
|
||
:packages 'pdf-tools
|
||
"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)
|
||
(phundrak/major-leader-key
|
||
:keymaps 'pdf-view-mode-map
|
||
:packages 'pdf-tools
|
||
"a" '(:ignore t :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" '(:ignore t :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" '(:ignore t :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)
|
||
:config
|
||
(with-eval-after-load 'pdf-view
|
||
(setq pdf-view-midnight-colors '("#d8dee9" . "#2e3440"))))
|
||
#+end_src
|
||
|
||
One thing ~pdf-tools~ doesn’t handle is restoring the PDF to the last
|
||
point it was visited --- in other words, open the PDF where I last
|
||
left it.
|
||
#+begin_src emacs-lisp
|
||
(use-package pdf-view-restore
|
||
:after pdf-tools
|
||
:defer t
|
||
:straight (:build t)
|
||
:hook (pdf-view-mode . pdf-view-restore-mode)
|
||
:config
|
||
(setq pdf-view-restore-filename (expand-file-name ".tmp/pdf-view-restore"
|
||
user-emacs-directory)))
|
||
#+end_src
|
||
|
||
*** Project Management
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Project-Managementi9n5fl6184j0
|
||
:END:
|
||
**** Magit
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Project-Management-Magitvso5fl6184j0
|
||
:END:
|
||
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!
|
||
#+begin_src emacs-lisp
|
||
(use-package magit
|
||
:straight (:build t)
|
||
:defer t
|
||
:init
|
||
(setq forge-add-default-bindings nil)
|
||
:config
|
||
(csetq magit-clone-default-directory "~/fromGIT/"
|
||
magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)
|
||
(with-eval-after-load 'evil-collection
|
||
(phundrak/evil
|
||
:packages '(evil-collection magit)
|
||
:keymaps '(magit-mode-map magit-log-mode-map magit-status-mode-map)
|
||
:states 'normal
|
||
"t" #'magit-tag
|
||
"s" #'magit-stage))
|
||
:general
|
||
(:keymaps '(git-rebase-mode-map)
|
||
:packages 'magit
|
||
"C-t" #'evil-next-line
|
||
"C-s" #'evil-previous-line)
|
||
(phundrak/major-leader-key
|
||
:keymaps 'git-rebase-mode-map
|
||
:packages 'magit
|
||
"," #'with-editor-finish
|
||
"k" #'with-editor-cancel
|
||
"a" #'with-editor-cancel)
|
||
(phundrak/leader-key
|
||
:infix "g"
|
||
:packages 'magit
|
||
"" '(:ignore t :wk "git")
|
||
"b" #'magit-blame
|
||
"c" #'magit-clone
|
||
"d" #'magit-dispatch
|
||
"i" #'magit-init
|
||
"s" #'magit-status
|
||
"y" #'my/yadm
|
||
"S" #'magit-stage-file
|
||
"U" #'magit-unstage-file
|
||
"f" '(:ignore t :wk "file")
|
||
"fd" #'magit-diff
|
||
"fc" #'magit-file-checkout
|
||
"fl" #'magit-file-dispatch
|
||
"fF" #'magit-find-file))
|
||
#+end_src
|
||
|
||
[[https://github.com/alphapapa][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.
|
||
|
||
First, let’s setup our todo keywords with ~hl-todo~. A good few todo
|
||
keywords are already defined in the ~hl-todo-keyword-faces~ variable.
|
||
Why not use them? ~hl-todo-mode~ enables fontlock highlight of these
|
||
keywords in a buffer. Let’s enable this mode globally.
|
||
#+begin_src emacs-lisp
|
||
(use-package hl-todo
|
||
:defer t
|
||
:straight (:build t)
|
||
:init (global-hl-todo-mode 1)
|
||
:general
|
||
(phundrak/leader-key
|
||
:packages '(hl-todo)
|
||
:infix "c"
|
||
"" '(:ignore t :which-key "todos")
|
||
"n" #'hl-todo-next
|
||
"p" #'hl-todo-previous))
|
||
#+end_src
|
||
|
||
We can now configure properly ~magit-todos~. Notice my custom function
|
||
hooked to ~magit-mode-hook~. This is because this package tries to find
|
||
TODOs in all files in the current project, and my yadm repository’s
|
||
root is my ~$HOME~. So, yeah, no ~magit-todos~ in yadm.
|
||
#+begin_src emacs-lisp
|
||
(use-package magit-todos
|
||
:straight (:build t)
|
||
:after (magit hl-todo)
|
||
:init
|
||
(with-eval-after-load 'magit
|
||
(defun my/magit-todos-if-not-yadm ()
|
||
"Deactivate magit-todos if in yadm Tramp connection.
|
||
If `magit--default-directory' points to a yadm Tramp directory,
|
||
deactivate `magit-todos-mode', otherwise enable it."
|
||
(if (string-prefix-p "/yadm:" magit--default-directory)
|
||
(magit-todos-mode -1)
|
||
(magit-todos-mode +1)))
|
||
(add-hook 'magit-mode-hook #'my/magit-todos-if-not-yadm))
|
||
:config
|
||
(csetq magit-todos-ignore-case t))
|
||
#+end_src
|
||
|
||
Finally, it is also possible to use Gitflow’s framework with Magit
|
||
with ~magit-gitflow~:
|
||
#+begin_src emacs-lisp
|
||
(use-package magit-gitflow
|
||
:defer t
|
||
:after magit
|
||
:straight (magit-gitflow :build t
|
||
:type git
|
||
:host github
|
||
:repo "jtatarik/magit-gitflow")
|
||
:hook (magit-mode . turn-on-magit-gitflow))
|
||
#+end_src
|
||
|
||
**** Forge
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Project-Management-Forgelcq5fl6184j0
|
||
:END:
|
||
Forge acts as an interface for Github, Gitlab, and Bitbucket inside
|
||
Magit. A lot of possibilities are present, you can read issues and
|
||
pull requests, create them, and fork projects among other things.
|
||
*NOTE*: Make sure to configure a GitHub token before using this
|
||
package!
|
||
- [[https://magit.vc/manual/forge/Token-Creation.html#Token-Creation][Token Creation]]
|
||
- [[https://magit.vc/manual/ghub/Getting-Started.html#Getting-Started][Getting started]]
|
||
#+begin_src emacs-lisp
|
||
(use-package forge
|
||
:after magit
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
**** Projectile
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Project-Management-Projectilesvr5fl6184j0
|
||
:END:
|
||
First, I need to install ~ripgrep~, a faster reimplementation of ~grep~,
|
||
which will be very useful when managing projects.
|
||
#+begin_src emacs-lisp
|
||
(use-package ripgrep
|
||
:if (executable-find "rg")
|
||
:straight (:build t)
|
||
:defer t)
|
||
#+end_src
|
||
|
||
Now, I can use projectile, which is sort of the /de facto/ standard
|
||
project manager in Emacs. I know there’s ~project.el~, but,… Eh…
|
||
#+begin_src emacs-lisp
|
||
(use-package projectile
|
||
:straight (:build t)
|
||
:diminish projectile-mode
|
||
:custom ((projectile-completion-system 'ivy))
|
||
:init
|
||
(setq projectile-switch-project-action #'projectile-dired)
|
||
:config
|
||
(projectile-mode)
|
||
(add-to-list 'projectile-ignored-projects "~/")
|
||
(add-to-list 'projectile-globally-ignored-directories "^node_modules$")
|
||
:general
|
||
(phundrak/leader-key
|
||
"p" '(:keymap projectile-command-map :which-key "projectile")))
|
||
#+end_src
|
||
|
||
And of course, there is a counsel package dedicated to projectile.
|
||
#+begin_src emacs-lisp
|
||
(use-package counsel-projectile
|
||
:straight (:build t)
|
||
:after (counsel projectile)
|
||
:config (counsel-projectile-mode))
|
||
#+end_src
|
||
|
||
**** Recentf
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Project-Management-Recentf-kndiupi04bj0
|
||
:END:
|
||
The built-in package ~recentf~ keeps track of recently opened files. But
|
||
by default, it only follows the twenty most recent files, that not
|
||
nearly enough for me, so I raise it to two hundred. I also don’t want
|
||
recentf to follow the Elfeed database, so I add it to the list of
|
||
excluded files.
|
||
#+begin_src emacs-lisp
|
||
(use-package recentf
|
||
:straight (:build t :type built-in)
|
||
:custom ((recentf-max-saved-items 2000))
|
||
:config
|
||
;; no Elfeed or native-comp files
|
||
(add-all-to-list 'recentf-exclude
|
||
`(,(rx (* any)
|
||
(or "elfeed-db"
|
||
"eln-cache"
|
||
"conlanging/content"
|
||
"org/config"
|
||
"/Mail/Sent"
|
||
".cache/")
|
||
(* any)
|
||
(? (or "html" "pdf" "tex" "epub")))
|
||
,(rx "/"
|
||
(or "rsync" "ssh" "tmp" "yadm" "sudoedit" "sudo")
|
||
(* any)))))
|
||
#+end_src
|
||
|
||
*** Screenshot
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Screenshot96d1fl6184j0
|
||
:END:
|
||
~screenshot.el~ is a nice utility package made by TEC. It allows the
|
||
user to take a screenshot of a specific area of a buffer and make it
|
||
look nice.
|
||
#+begin_src emacs-lisp
|
||
(use-package screenshot
|
||
:defer t
|
||
:straight (screenshot :build t
|
||
:type git
|
||
:host github
|
||
:repo "tecosaur/screenshot")
|
||
:config (load-file (locate-library "screenshot.el"))
|
||
:general
|
||
(phundrak/leader-key
|
||
:infix "a"
|
||
:packages '(screenshot)
|
||
"S" #'screenshot))
|
||
#+end_src
|
||
|
||
*** Shells
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Shellsxke1fl6184j0
|
||
:END:
|
||
**** Shell-pop
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Shells-Shell-popk0g1fl6184j0
|
||
:END:
|
||
Shell-pop allows the user to easily call for a new shell in a pop-up
|
||
buffer.
|
||
#+begin_src emacs-lisp
|
||
(use-package shell-pop
|
||
:defer t
|
||
:straight (:build t)
|
||
:custom
|
||
(shell-pop-default-directory "/home/phundrak")
|
||
(shell-pop-shell-type (quote ("eshell" "*eshell*" (lambda () (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))
|
||
#+end_src
|
||
|
||
**** VTerm
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Shells-VTermzfh1fl6184j0
|
||
:END:
|
||
VTerm gives Emacs access to regular shells with an almost regular
|
||
emulator. Be aware you will most likely need to hit ~C-c~ twice to send
|
||
an interrupt signal.
|
||
#+begin_src emacs-lisp
|
||
(use-package vterm
|
||
:defer t
|
||
:straight t
|
||
:config
|
||
(setq vterm-shell "/usr/bin/fish"))
|
||
#+end_src
|
||
|
||
One annoying think with vterm is it only can create one buffer, you
|
||
can’t have multiple vterm buffers by default. ~multi-vterm~ fixes this
|
||
issue.
|
||
#+begin_src emacs-lisp
|
||
(use-package multi-vterm
|
||
:after vterm
|
||
:defer t
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages '(vterm multi-vterm)
|
||
:keymap 'vterm-mode-map
|
||
"c" #'multi-vterm
|
||
"n" #'multi-vterm-next
|
||
"p" #'multi-vterm-prev))
|
||
#+end_src
|
||
|
||
*** XWidgets Webkit Browser
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-XWidgets-Webkit-Browsertui1fl6184j0
|
||
:END:
|
||
I used to use the xwidgets webkit browser in order to view or preview
|
||
HTML files from Emacs, but it seems the Cairo background transparency
|
||
patch breaks it. So while this isn’t patched, I will disable Xwidgets
|
||
in my Emacs build, and these keybinds *will not* be tangled.
|
||
#+begin_src emacs-lisp :tangle no
|
||
(phundrak/evil
|
||
:keymaps 'xwidget-webkit-mode-map
|
||
"<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
|
||
"C" #'xwidget-webkit-back
|
||
"R" #'xwidget-webkit-forward
|
||
"C-r" #'xwidget-webkit-reload
|
||
"j" nil
|
||
"k" nil
|
||
"l" nil
|
||
"H" nil
|
||
"L" nil
|
||
"C-d" #'xwidget-webkit-scroll-up
|
||
"C-u" #'xwidget-webkit-scroll-down)
|
||
#+end_src
|
||
|
||
*** Wttr.in
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Wttr-inpak1fl6184j0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package wttrin
|
||
:defer t
|
||
:straight (wttrin :build t
|
||
:local-repo "~/fromGIT/emacs-packages/emacs-wttrin"
|
||
:type git)
|
||
;; :host github
|
||
;; :repo "Phundrak/emacs-wttrin"
|
||
:config
|
||
(setq wttrin-default-cities '("Aubervilliers" "Paris" "Lyon" "Nonières" "Saint Agrève")
|
||
wttrin-use-metric t))
|
||
#+end_src
|
||
|
||
**** TODO Derive a major mode for wttrin :noexport:
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Applications-Wttr-in-Derive-a-major-mode-for-wttrinkrl1fl6184j0
|
||
:END:
|
||
To handle keybindings correctly, a major mode for wttrin could be
|
||
derived from ~fundamental-mode~ and get an associated keymap.
|
||
|
||
** Editing
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Editinggnu1fl6184j0
|
||
:END:
|
||
First, I’ll define some keybindings for easily inserting pairs when
|
||
editing text.
|
||
#+begin_src emacs-lisp
|
||
(general-define-key
|
||
:states 'visual
|
||
"M-[" #'insert-pair
|
||
"M-{" #'insert-pair
|
||
"M-<" #'insert-pair
|
||
"M-'" #'insert-pair
|
||
"M-`" #'insert-pair
|
||
"M-\"" #'insert-pair)
|
||
#+end_src
|
||
|
||
*** Atomic Chrome
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Editing-Atomic-Chrome-2w5bt8y029j0
|
||
:END:
|
||
Why write in your browser when you could write with Emacs? Despite its
|
||
name, this package isn’t only geared towards Chrome/Chromium-based
|
||
browsers but also towards Firefox since its 2.0 version. I find it a
|
||
bit unfortunate Chrome’s name stuck in the package’s name though.
|
||
#+begin_src emacs-lisp
|
||
(use-package atomic-chrome
|
||
:straight (:build t)
|
||
:init
|
||
(atomic-chrome-start-server)
|
||
:config
|
||
(setq atomic-chrome-default-major-mode 'markdown-mode
|
||
atomic-chrome-url-major-mode-alist `(("github\\.com" . gfm-mode)
|
||
("gitlab\\.com" . gfm-mode)
|
||
("labs\\.phundrak\\.com" . markdown-mode)
|
||
("reddit\\.com" . markdown-mode))))
|
||
#+end_src
|
||
|
||
*** Editorconfig
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Editing-Editorconfig-txn4dtx0rbj0
|
||
:END:
|
||
Editorconfig is a unified way of passing to your text editor settings
|
||
everyone working in a repo need to follow. ~.editorconfig~ files work
|
||
for VSCode users, vim users, Atom users, Sublime users, and of course
|
||
Emacs users.
|
||
#+begin_src emacs-lisp
|
||
(use-package editorconfig
|
||
:defer t
|
||
:straight (:build t)
|
||
:diminish editorconfig-mode
|
||
:config
|
||
(editorconfig-mode t))
|
||
#+end_src
|
||
|
||
*** Evil Nerd Commenter
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Editing-Evil-Nerd-Commenterd2w1fl6184j0
|
||
:END:
|
||
Emacs’ default commenting system is nice, but I don’t find it smart
|
||
enough for me.
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-nerd-commenter
|
||
:after evil
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
*** Iedit
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Editing-Iedit-eb98g8q0p8j0
|
||
:END:
|
||
Iedit is a powerful text editing tool that can be used to refactor
|
||
code through the edition of multiple regions at once, be it in a
|
||
region or in a whole buffer.
|
||
#+begin_src emacs-lisp
|
||
(use-package iedit
|
||
:defer t
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/leader-key
|
||
:infix "r"
|
||
:packages 'iedit
|
||
"" '(:ignore t :which-key "refactor")
|
||
"i" #'iedit-mode))
|
||
#+end_src
|
||
|
||
Since I’m using evil, I’ll also use a compatibility package that adds
|
||
states for iedit.
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-iedit-state
|
||
:after iedit
|
||
:defer t
|
||
:straight (:build t)
|
||
:commands (evil-iedit-state evil-iedit-state/iedit-mode)
|
||
:init
|
||
(setq iedit-curent-symbol-default t
|
||
iedit-only-at-symbol-boundaries t
|
||
iedit-toggle-key-default nil))
|
||
#+end_src
|
||
|
||
*** Parinfer
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Editing-Parinfermxy1fl6184j0
|
||
:END:
|
||
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
|
||
[[https://github.com/eraserhd/parinfer-rust][~parinfer-rust~]], obviously written in Rust, around which
|
||
~parinfer-rust-mode~ is built. Enabling ~parinfer-rust-mode~ should also
|
||
automatically disable ~smartparens-mode~ in order to avoid conflicting
|
||
behavior.
|
||
#+begin_src emacs-lisp
|
||
(use-package parinfer-rust-mode
|
||
:defer t
|
||
:straight (:build 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/"))
|
||
(add-hook 'parinfer-rust-mode-hook
|
||
(lambda () (smartparens-mode -1)))
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'parinfer-rust-mode-map
|
||
"m" #'parinfer-rust-switch-mode
|
||
"M" #'parinfer-rust-toggle-disable))
|
||
#+end_src
|
||
|
||
*** Smartparens
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Editing-Smartparens-zve93mk0k4j0
|
||
:END:
|
||
~smartparens~ is a package similar to ~parinfer~, but while the latter is
|
||
more specialized for Lisp dialects, ~smartparens~ works better with
|
||
other programming languages that still uses parenthesis, but not as
|
||
much as Lisp dialects; think for example C, C++, Rust, Javascript, and
|
||
so on.
|
||
#+begin_src emacs-lisp
|
||
(use-package smartparens
|
||
:defer t
|
||
:straight (:build t)
|
||
:hook (prog-mode . smartparens-mode))
|
||
#+end_src
|
||
|
||
*** ~string-edit~
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Editing-string-editae02fl6184j0
|
||
:END:
|
||
~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!
|
||
#+begin_src emacs-lisp
|
||
(use-package string-edit
|
||
:defer t
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
*** Writeroom
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Editing-Writeroomxt12fl6184j0
|
||
:END:
|
||
On the other hand, ~writeroom~ allows the user to enter a
|
||
distraction-free mode of Emacs, and I like that! But the default width
|
||
is a bit too small for me, and I prefer not to go fullscren.
|
||
#+begin_src emacs-lisp
|
||
(use-package writeroom-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:init (global-writeroom-mode 1)
|
||
:config
|
||
(setq writeroom-width 100
|
||
writeroom-fullscreen-effect nil
|
||
writeroom-maximize-window nil
|
||
writeroom-mode-line t
|
||
writeroom-major-modes '(text-mode org-mode markdown-mode nov-mode Info-mode)))
|
||
#+end_src
|
||
|
||
** Emacs built-ins
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-insr832fl6184j0
|
||
:END:
|
||
*** Dired
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Diredao42fl6184j0
|
||
:END:
|
||
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.
|
||
I used to have an extensive configuration for Dired with a couple of
|
||
additional packages to make it more usable. Dirvish rendered that
|
||
obsolete!
|
||
#+begin_src emacs-lisp
|
||
(use-package dirvish
|
||
:straight (:build t)
|
||
:defer t
|
||
:init (dirvish-override-dired-mode)
|
||
:custom
|
||
(dirvish-quick-access-entries
|
||
'(("h" "~/" "Home")
|
||
("d" "~/Downloads/" "Downloads")
|
||
("c" "~/org/config" "Config")
|
||
("C" "~/Documents/conlanging/content" "Conlanging")))
|
||
(dirvish-mode-line-format
|
||
'(:left (sort file-time "" file-size symlink) :right (omit yank index)))
|
||
(dirvish-attributes '(all-the-icons file-size collapse subtree-state vc-state git-msg))
|
||
:config
|
||
(dirvish-peek-mode)
|
||
<<dired-drag-and-drop>>
|
||
<<dired-listing-flags>>
|
||
<<dired-files-and-dirs>>
|
||
<<dirvish-exa-offload>>
|
||
(csetq dired-dwim-target t
|
||
dired-recursive-copies 'always
|
||
dired-recursive-deletes 'top
|
||
delete-by-moving-to-trash t)
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'dirvish-mode-map
|
||
:packages '(dired dirvish)
|
||
"q" #'dirvish-quit
|
||
"TAB" #'dirvish-subtree-toggle)
|
||
(phundrak/major-leader-key
|
||
:keymaps 'dirvish-mode-map
|
||
:packages '(dired dirvish)
|
||
"a" #'dirvish-quick-access
|
||
"e" #'dirvish-emerge-menu
|
||
"f" #'dirvish-fd-jump
|
||
"F" #'dirvish-file-info-menu
|
||
"h" '(:ignore t :which-key "history")
|
||
"hp" #'dirvish-history-go-backward
|
||
"hn" #'dirvish-history-go-forward
|
||
"hj" #'dirvish-history-jump
|
||
"hl" #'dirvish-history-last
|
||
"l" '(:ignore t :which-key "layout")
|
||
"ls" #'dirvish-layout-switch
|
||
"lt" #'dirvish-layout-toggle
|
||
"m" #'dirvish-mark-menu
|
||
"s" #'dirvish-quicksort
|
||
"S" #'dirvish-setup-menu
|
||
"y" #'dirvish-yank-menu
|
||
"n" #'dirvish-narrow))
|
||
#+end_src
|
||
|
||
Since Emacs 29, it is possible to enable drag-and-drop between Emacs
|
||
and other applications.
|
||
#+name: dired-drag-and-drop
|
||
#+begin_src emacs-lisp :tangle no
|
||
(csetq dired-mouse-drag-files t
|
||
mouse-drag-and-drop-region-cross-program t)
|
||
#+end_src
|
||
|
||
In Dirvish, it’s best to use the long name of flags whenever possible,
|
||
otherwise some commands won’t work.
|
||
#+name: dired-listing-flags
|
||
#+begin_src emacs-lisp :tangle no
|
||
(csetq dired-listing-switches (string-join '("--all"
|
||
"--human-readable"
|
||
"--time-style=long-iso"
|
||
"--group-directories-first"
|
||
"-lv1")
|
||
" "))
|
||
#+end_src
|
||
|
||
However, it is possible to instead use ~exa~ when it is available.
|
||
Instead of making Emacs’ main thread to the file listing in a
|
||
directory, we offload it to an external thread.
|
||
#+name: dirvish-exa-offload
|
||
#+begin_src emacs-lisp :tangle no
|
||
(dirvish-define-preview exa (file)
|
||
"Use `exa' to generate directory preview."
|
||
:require ("exa")
|
||
(when (file-directory-p file)
|
||
`(shell . ("exa" "--color=always" "-al" ,file))))
|
||
|
||
(add-to-list 'dirvish-preview-dispatchers 'exa)
|
||
#+end_src
|
||
|
||
Finally, some directories need to be set for Dired to store various
|
||
files and images.
|
||
#+name: dired-files-and-dirs
|
||
#+begin_src emacs-lisp :tangle no
|
||
(let ((my/file (lambda (path &optional dir)
|
||
(expand-file-name path (or dir user-emacs-directory))))
|
||
(my/dir (lambda (path &optional dir)
|
||
(expand-file-name (file-name-as-directory path)
|
||
(or dir user-emacs-directory)))))
|
||
(csetq image-dired-thumb-size 150
|
||
image-dired-dir (funcall my/dir "dired-img")
|
||
image-dired-db-file (funcall my/file "dired-db.el")
|
||
image-dired-gallery-dir (funcall my/dir "gallery")
|
||
image-dired-temp-image-file (funcall my/file "temp-image" image-dired-dir)
|
||
image-dired-temp-rotate-image-file (funcall my/file "temp-rotate-image" image-dired-dir)))
|
||
#+end_src
|
||
|
||
Copying files with Dired is a blocking process. It’s usually fine when
|
||
there’s not a lot to copy, but it becomes more annoying when moving
|
||
larger files. The package ~dired-rsync~ allows to copy files with ~rsync~
|
||
in the background so we can carry on with our tasks while the copy is
|
||
happening.
|
||
#+begin_src emacs-lisp
|
||
(use-package dired-rsync
|
||
:if (executable-find "rsync")
|
||
:defer t
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'dired-mode-map
|
||
:packages 'dired-rsync
|
||
"C-r" #'dired-rsync))
|
||
#+end_src
|
||
|
||
*** Compilation mode
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Compilation-mode-7nh817m0t8j0
|
||
:END:
|
||
After reading about a blog article, I found out it is possible to run
|
||
quite a few things through ~compilation-mode~, so why not? First, let’s
|
||
redefine some keybinds for this mode. I’ll also define a general
|
||
keybind in order to re-run my programs from other buffers than the
|
||
~compilation-mode~ buffer. I also want to follow the output of the
|
||
compilation buffer, as well as enable some syntax highlighting.
|
||
#+begin_src emacs-lisp
|
||
(use-package compile
|
||
:defer t
|
||
:straight (compile :type built-in)
|
||
:hook (compilation-mode . colorize-compilation-buffer)
|
||
:init
|
||
(require 'ansi-color)
|
||
(defun colorize-compilation-buffer ()
|
||
(let ((inhibit-read-only t))
|
||
(ansi-color-apply-on-region (point-min) (point-max))))
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'compilation-mode-map
|
||
"g" nil
|
||
"r" nil
|
||
"R" #'recompile
|
||
"h" nil)
|
||
(phundrak/leader-key
|
||
"R" #'recompile)
|
||
:config
|
||
(setq compilation-scroll-output t))
|
||
#+end_src
|
||
|
||
*** Eshell
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Eshell0662fl6184j0
|
||
:END:
|
||
#+include: img/emacs-eshell.svg export html
|
||
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(use-package eshell
|
||
:defer t
|
||
:straight (:type built-in :build t)
|
||
:config
|
||
(setq eshell-prompt-function
|
||
(lambda ()
|
||
(concat (abbreviate-file-name (eshell/pwd))
|
||
(if (= (user-uid) 0) " # " " λ ")))
|
||
eshell-prompt-regexp "^[^#λ\n]* [#λ] ")
|
||
<<eshell-alias-file>>
|
||
<<eshell-concat-shell-command>>
|
||
<<eshell-alias-open>>
|
||
<<eshell-alias-clear>>
|
||
<<eshell-alias-buffers>>
|
||
<<eshell-alias-emacs>>
|
||
<<eshell-alias-mkcd>>
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'eshell-mode-map
|
||
[remap evil-collection-eshell-evil-change] #'evil-backward-char
|
||
"t" #'evil-next-line
|
||
"s" #'evil-previous-line
|
||
"r" #'evil-forward-char
|
||
"h" #'evil-collection-eshell-evil-change)
|
||
(general-define-key
|
||
:keymaps 'eshell-mode-map
|
||
:states 'insert
|
||
"C-a" #'eshell-bol
|
||
"C-e" #'end-of-line))
|
||
#+end_src
|
||
|
||
**** Aliases
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Eshell-Aliasesom72fl6184j0
|
||
:END:
|
||
First, let’s declare our list of “dumb” aliases we’ll use in
|
||
Eshell. You can find them here.
|
||
#+name: eshell-alias-file
|
||
#+begin_src emacs-lisp :tangle no
|
||
(setq eshell-aliases-file (expand-file-name "eshell-alias" user-emacs-directory))
|
||
#+end_src
|
||
|
||
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:
|
||
#+name: eshell-concat-shell-command
|
||
#+begin_src emacs-lisp :tangle no
|
||
(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."
|
||
(mapconcat #'identity command " "))
|
||
#+end_src
|
||
|
||
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.
|
||
#+name: eshell-alias-open
|
||
#+begin_src emacs-lisp :tangle no
|
||
(defalias 'open #'find-file)
|
||
(defalias 'openo #'find-file-other-window)
|
||
#+end_src
|
||
|
||
The default behavior of ~eshell/clear~ is not great at all, although it
|
||
clears the screen it also scrolls all the way down. Therefore, let’s
|
||
alias it to ~eshell/clear-scrollback~ which has the correct behavior.
|
||
#+name: eshell-alias-clear
|
||
#+begin_src emacs-lisp :tangle no
|
||
(defalias 'eshell/clear #'eshell/clear-scrollback)
|
||
#+end_src
|
||
|
||
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.
|
||
#+name: eshell-alias-buffers
|
||
#+begin_src emacs-lisp :tangle no
|
||
(defalias 'list-buffers 'ibuffer)
|
||
#+end_src
|
||
|
||
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.
|
||
#+name: eshell-alias-emacs
|
||
#+begin_src emacs-lisp :tangle no
|
||
(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))))
|
||
#+end_src
|
||
|
||
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~.
|
||
#+name: eshell-alias-mkcd
|
||
#+begin_src emacs-lisp :tangle no
|
||
(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))
|
||
#+end_src
|
||
|
||
**** Autosuggestion
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Eshell-Autosuggestion-kf6ipm1195j0
|
||
:END:
|
||
I really like fish’s autosuggestion feature, so let’s reproduce it
|
||
here!
|
||
#+begin_src emacs-lisp
|
||
(use-package esh-autosuggest
|
||
:defer t
|
||
:after eshell
|
||
:straight (:build t)
|
||
:hook (eshell-mode . esh-autosuggest-mode)
|
||
:general
|
||
(:keymaps 'esh-autosuggest-active-map
|
||
"C-e" #'company-complete-selection))
|
||
#+end_src
|
||
|
||
**** Commands
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Eshell-Commands-n8w3fh2195j0
|
||
:END:
|
||
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:
|
||
#+BEGIN_SRC emacs-lisp
|
||
(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))
|
||
#+END_SRC
|
||
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(defun eshell-new ()
|
||
"Open a new instance of eshell."
|
||
(interactive)
|
||
(eshell 'N))
|
||
#+end_src
|
||
|
||
A very useful command I use often in fish is ~z~, a port from bash’s and
|
||
zsh’s command that allows to jump around directories based on how
|
||
often we go in various directories.
|
||
#+begin_src emacs-lisp
|
||
(use-package eshell-z
|
||
:defer t
|
||
:after eshell
|
||
:straight (:build t)
|
||
:hook (eshell-mode . (lambda () (require 'eshell-z))))
|
||
#+end_src
|
||
|
||
**** Environment Variables
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Eshell-Environment-Variablesmna2fl6184j0
|
||
:END:
|
||
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.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setenv "DART_SDK" "/opt/dart-sdk/bin")
|
||
(setenv "ANDROID_HOME" (concat (getenv "HOME") "/Android/Sdk/"))
|
||
#+END_SRC
|
||
|
||
The ~EDITOR~ variable also needs to be set for git commands, especially the
|
||
~yadm~ commands.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(setenv "EDITOR" "emacsclient -c -a emacs")
|
||
#+END_SRC
|
||
|
||
Finally, for some specific situations I need ~SHELL~ to be set to
|
||
something more standard than fish:
|
||
#+begin_src emacs-lisp
|
||
(setenv "SHELL" "/bin/sh")
|
||
#+end_src
|
||
|
||
**** Visual configuration
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Eshell-Visual-configuratione7c2fl6184j0
|
||
:END:
|
||
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 [[https://github.com/Phundrak/eshell-info-banner.el][I’ve written a package]]! It’s actually available on
|
||
MELPA, but since I’m the main dev of this package, I’ll keep track of
|
||
the git repository.
|
||
#+begin_src emacs-lisp
|
||
(use-package eshell-info-banner
|
||
:after (eshell)
|
||
:defer t
|
||
:straight (eshell-info-banner :build t
|
||
:type git
|
||
:host github
|
||
:protocol ssh
|
||
: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")))
|
||
#+end_src
|
||
|
||
Another feature I like is fish-like syntax highlight, which brings
|
||
some more colors to Eshell.
|
||
#+begin_src emacs-lisp
|
||
(use-package eshell-syntax-highlighting
|
||
:after (esh-mode eshell)
|
||
:defer t
|
||
:straight (:build t)
|
||
:config
|
||
(eshell-syntax-highlighting-global-mode +1))
|
||
#+end_src
|
||
|
||
Powerline prompts are nice, git-aware prompts are even better!
|
||
~eshell-git-prompt~ is nice, but I prefer to write my own package for
|
||
that.
|
||
#+begin_src emacs-lisp
|
||
(use-package powerline-eshell
|
||
:load-path "~/fromGIT/emacs-packages/powerline-eshell.el/"
|
||
:after eshell)
|
||
#+end_src
|
||
|
||
*** Eww
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Eww-m1343rs0t8j0
|
||
:END:
|
||
Since Emacs 29, it is possible to automatically rename ~eww~ buffers to
|
||
a more human-readable name, see [[https://protesilaos.com/codelog/2021-10-15-emacs-29-eww-rename-buffers/][Prot’s blog]] post on the matter.
|
||
#+begin_src emacs-lisp
|
||
(use-package eww
|
||
:defer t
|
||
:straight (:type built-in)
|
||
:config
|
||
(setq eww-auto-rename-buffer 'title))
|
||
#+end_src
|
||
|
||
*** Image-mode
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Image-mode-dchdl251jdj0
|
||
:END:
|
||
I won’t modify much for ~image-mode~ (the mode used to display images)
|
||
aside from Emacs’ ability to use external converters to display some
|
||
images it wouldn’t be able to handle otherwise.
|
||
#+begin_src emacs-lisp
|
||
(setq image-use-external-converter t)
|
||
#+end_src
|
||
|
||
*** Info
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Info-r7x90j20c5j0
|
||
:END:
|
||
Let’s define some more intuitive keybinds for ~info-mode~.
|
||
#+begin_src emacs-lisp
|
||
(use-package info
|
||
:defer t
|
||
:straight (info :type built-in :build t)
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'Info-mode-map
|
||
"c" #'Info-prev
|
||
"t" #'evil-scroll-down
|
||
"s" #'evil-scroll-up
|
||
"r" #'Info-next)
|
||
(phundrak/major-leader-key
|
||
:keymaps 'Info-mode-map
|
||
"?" #'Info-toc
|
||
"b" #'Info-history-back
|
||
"f" #'Info-history-forward
|
||
"m" #'Info-menu
|
||
"t" #'Info-top-node
|
||
"u" #'Info-up))
|
||
#+end_src
|
||
|
||
*** Tramp
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Tramplqd2fl6184j0
|
||
:END:
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(use-package tramp
|
||
:straight (tramp :type built-in :build t)
|
||
:init
|
||
<<tramp-add-yadm>>
|
||
:config
|
||
(csetq tramp-ssh-controlmaster-options nil
|
||
tramp-verbose 0
|
||
tramp-auto-save-directory (locate-user-emacs-file "tramp/")
|
||
tramp-chunksize 2000)
|
||
(add-to-list 'backup-directory-alist ; deactivate auto-save with TRAMP
|
||
(cons tramp-file-name-regexp nil)))
|
||
#+end_src
|
||
|
||
**** Yadm
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Emacs-built-ins-Tramp-Yadma8f2fl6184j0
|
||
:END:
|
||
[[https://yadm.io/][~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 [[https://yadm.io/docs/templates][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:
|
||
#+name: tramp-add-yadm
|
||
#+begin_src emacs-lisp :tangle no
|
||
(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"))))
|
||
#+end_src
|
||
|
||
I’ll also create a fuction for connecting to this new Tramp protocol:
|
||
#+begin_src emacs-lisp
|
||
(defun my/yadm ()
|
||
"Manage my dotfiles through TRAMP."
|
||
(interactive)
|
||
(magit-status "/yadm::"))
|
||
#+end_src
|
||
|
||
** EXWM
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-EXWM-pr14yxs09aj0
|
||
:END:
|
||
So, I’m finally slowly getting back to EXWM. I tried it a couple of
|
||
years ago, but that was with the SpacemacsOS layer on Spacemacs, on a
|
||
laptop which got accidentaly formatted before I could save my config
|
||
and all… So it got me some time to come back. I’m still a bit worried
|
||
about Emacs being single threaded, so if I get one blocking function
|
||
blocking Emacs, my whole desktop will hang, but for now I haven’t had
|
||
this issue.
|
||
|
||
All my EXWM config is enabled only if I launch Emacs with the argument
|
||
~--with-exwm~, otherwise none of the related packages get installed, let
|
||
alone activated and made available.
|
||
|
||
First, I need to install the /X protocol Emacs Lisp Bindings/. It
|
||
doesn’t seem to be available in any repo, so I’ll install it directly
|
||
from Git.
|
||
#+begin_src emacs-lisp
|
||
(use-package xelb
|
||
:if (seq-contains-p command-line-args "--with-exwm")
|
||
:straight (xelb :build t
|
||
:type git
|
||
:host github
|
||
:repo "emacs-straight/xelb"
|
||
:fork "ch11ng/xelb"))
|
||
#+end_src
|
||
|
||
Next is a function I’ve +stolen+ copied from Daviwil’s [[https://config.daviwil.com/desktop][desktop
|
||
configuration]]. This allows to launch software in the background
|
||
easily.
|
||
#+begin_src emacs-lisp
|
||
(defun exwm/run-in-background (command &optional once)
|
||
(let ((command-parts (split-string command " +")))
|
||
(apply #'call-process `(,(car command-parts) nil 0 nil ,@(cdr command-parts)))))
|
||
#+end_src
|
||
|
||
In order to launch Emacs with EXWM with ~startx~, I need a ~xinit~ file.
|
||
This one is exported to ~$HOME/.xinitrc.emacs~.
|
||
#+begin_src sh :tangle ~/.xinitrc.emacs :shebang "#!/bin/sh"
|
||
xhost +SI:localuser:$USER
|
||
|
||
# Set fallback cursor
|
||
xsetroot -cursor_name left_ptr
|
||
|
||
# If Emacs is started in server mode, `emacsclient` is a convenient
|
||
# way to edit files in place (used by e.g. `git commit`)
|
||
export VISUAL=emacsclient
|
||
export EDITOR="$VISUAL"
|
||
|
||
# in case Java applications display /nothing/
|
||
# wmname LG3D
|
||
# export _JAVA_AWT_WM_NONREPARENTING=1
|
||
|
||
autorandr -l home
|
||
|
||
exec emacs --with-exwm
|
||
#+end_src
|
||
|
||
*** EXWM itself
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-EXWM-EXWM-itself-hhgexz61aaj0
|
||
:END:
|
||
Now we come to the plat de résistance. Like with ~xelb~, I’m using its
|
||
Git source to install it to make sure I get the right version --- the
|
||
version available on the GNU ELPA is from the same source, true, but I
|
||
don’t know at which rate it is updated. And more packages down the
|
||
line will depend on this Git repository, so I might as well just clone
|
||
it right now.
|
||
|
||
As you can see, in the ~:config~ secion I added to two hooks functions
|
||
so buffers are accurately renamed. While the average X window will
|
||
simply get the name of the current X window, I want Firefox and
|
||
Qutebrowser to be prefixed with the name of the browser. Actually, all
|
||
these will be renamed this way:
|
||
#+name: exwm-renamed-buffers-list
|
||
- Kitty
|
||
- Qutebrowser
|
||
|
||
#+name: exwm-gen-buffers-rename
|
||
#+header: :exports none :tangle no
|
||
#+begin_src emacs-lisp :var buffers=exwm-renamed-buffers-list :cache yes
|
||
(format "%s\n%S"
|
||
(mapconcat (lambda (buffer)
|
||
(let ((buffer-name (car buffer)))
|
||
(format "(\"%s\" %S)"
|
||
(downcase buffer-name)
|
||
`(exwm-workspace-rename-buffer (concat ,buffer-name
|
||
" - "
|
||
exwm-title)))))
|
||
buffers
|
||
"\n")
|
||
'(_otherwise (exwm-workspace-rename-buffer exwm-title)))
|
||
#+end_src
|
||
|
||
#+RESULTS[65e7e5a72273bb2d3f820a85867dfdccb13ba550]: exwm-gen-buffers-rename
|
||
: ("kitty" (exwm-workspace-rename-buffer (concat "EXWM: " "Kitty" " - " exwm-title)))
|
||
: ("qutebrowser" (exwm-workspace-rename-buffer (concat "EXWM: " "Qutebrowser" " - " exwm-title)))
|
||
: (_otherwise (exwm-workspace-rename-buffer exwm-title))
|
||
|
||
#+name: exwm-buffers-name
|
||
#+begin_src emacs-lisp :tangle no
|
||
(add-hook 'exwm-update-class-hook
|
||
(lambda () (exwm-workspace-rename-buffer exwm-class-name)))
|
||
|
||
(add-hook 'exwm-update-title-hook
|
||
(lambda ()
|
||
(pcase exwm-class-name
|
||
<<exwm-gen-buffers-rename()>>)))
|
||
#+end_src
|
||
|
||
As you can see below, in the ~:config~ section I added two advices and one
|
||
hook in order to correctly integrate evil with EXWM. When I’m in an X
|
||
window, I want to be in insert-mode so I can type however I want.
|
||
However, when I exit one, I want to default back to normal-mode.
|
||
#+name: exwm-advices-evil
|
||
#+begin_src emacs-lisp :tangle no
|
||
(add-hook 'exwm-manage-finish-hook (lambda () (call-interactively #'exwm-input-release-keyboard)))
|
||
(advice-add #'exwm-input-grab-keyboard :after (lambda (&optional id) (evil-normal-state)))
|
||
(advice-add #'exwm-input-release-keyboard :after (lambda (&optional id) (evil-insert-state)))
|
||
#+end_src
|
||
|
||
Secondly, I add ~i~, ~C-SPC~, and ~M-m~ as exwm prefix keys so they aren’t
|
||
sent directly to the X windows but caught by Emacs (and EXWM). I’ll
|
||
use the ~i~ key in normal-mode to enter ~insert-mode~ and have Emacs
|
||
release the keyboard so the X window can grab it. Initially, I had
|
||
~s-<escape>~ as a keybind for grabbing back the keyboard from an X
|
||
window, as if I were in insert mode and wanted to go back to normal
|
||
mode, and I had ~s-I~ to toggle keyboard grabbing. But I found myself
|
||
more than once trying to use ~s-<escape>~ to toggle this state, ~s-I~
|
||
completely forgotten. So I removed ~s-I~ and made ~s-<escape>~ behave like
|
||
~s-I~ once did.
|
||
#+name: exwm-prefix-keys
|
||
#+begin_src emacs-lisp :tangle no
|
||
(general-define-key
|
||
:keymaps 'exwm-mode-map
|
||
:states 'normal
|
||
"i" #'exwm-input-release-keyboard)
|
||
|
||
(exwm-input-set-key (kbd "s-<escape>") #'exwm-input-toggle-keyboard)
|
||
|
||
(push ?\i exwm-input-prefix-keys)
|
||
(push (kbd "C-SPC") exwm-input-prefix-keys)
|
||
(push (kbd "M-m") exwm-input-prefix-keys)
|
||
#+end_src
|
||
|
||
As stated a couple of times in my different configuration files, I’m
|
||
using the bépo layout, which means the default keys in the number row
|
||
are laid as follow:
|
||
#+name: exwm-bepo-number-row
|
||
#+begin_src emacs-lisp :tangle no
|
||
(defconst exwm-workspace-keys '("\"" "«" "»" "(" ")" "@" "+" "-" "/" "*"))
|
||
#+end_src
|
||
|
||
With this, we can create keybinds for going or sending X windows to
|
||
workspaces 0 to 9.
|
||
#+name: exwm-workspace-keybinds
|
||
#+begin_src emacs-lisp :tangle no
|
||
(setq exwm-input-global-keys
|
||
`(,@exwm-input-global-keys
|
||
,@(mapcar (lambda (i)
|
||
`(,(kbd (format "s-%s" (nth i exwm-workspace-keys))) .
|
||
(lambda ()
|
||
(interactive)
|
||
(exwm-workspace-switch-create ,i))))
|
||
(number-sequence 0 9))
|
||
,@(mapcar (lambda (i)
|
||
`(,(kbd (format "s-%d" i)) .
|
||
(lambda ()
|
||
(interactive)
|
||
(exwm-workspace-move-window ,(let ((index (1- i)))
|
||
(if (< index 0)
|
||
(- 10 index)
|
||
;; FIXME: does not work with s-0
|
||
index))))))
|
||
(number-sequence 0 9))))
|
||
#+end_src
|
||
|
||
You can then see the list of the keybinds I have set for EXWM, which
|
||
are all prefixed with ~SPC x~ in normal mode (and ~C-SPC x~ in insert
|
||
mode), with the exception of ~s-RET~ which opens an eshell terminal.
|
||
#+name: exwm-keybinds
|
||
#+begin_src emacs-lisp :tangle no
|
||
(exwm-input-set-key (kbd "s-<return>") (lambda ()
|
||
(interactive)
|
||
(eshell)))
|
||
|
||
(phundrak/leader-key
|
||
:infix "x"
|
||
"" '(:ignore t :which-key "EXWM")
|
||
"d" #'exwm-debug
|
||
"k" #'exwm-input-send-next-key
|
||
"l" '((lambda ()
|
||
(interactive)
|
||
(start-process "" nil "plock"))
|
||
:which-key "lock")
|
||
"r" '(:ignore t :wk "rofi")
|
||
"rr" '((lambda () (interactive)
|
||
(shell-command "rofi -show drun" (current-buffer) (current-buffer)))
|
||
:wk "drun")
|
||
"rw" '((lambda () (interactive)
|
||
(shell-command "rofi -show window" (current-buffer) (current-buffer)))
|
||
:wk "windows")
|
||
"R" '(:ignore t :wk "restart")
|
||
"Rr" #'exwm-reset
|
||
"RR" #'exwm-restart
|
||
"t" '(:ignore t :which-key "toggle")
|
||
"tf" #'exwm-layout-toggle-fullscreen
|
||
"tF" #'exwm-floating-toggle-floating
|
||
"tm" #'exwm-layout-toggle-mode-line
|
||
"w" '(:ignore t :which-key "workspaces")
|
||
"wa" #'exwm-workspace-add
|
||
"wd" #'exwm-workspace-delete
|
||
"ws" #'exwm-workspace-switch
|
||
"x" '((lambda ()
|
||
(interactive)
|
||
(let ((command (string-trim (read-shell-command "RUN: "))))
|
||
(start-process command nil command)))
|
||
:which-key "run")
|
||
"RET" #'eshell-new)
|
||
#+end_src
|
||
|
||
A couple of commands are also automatically executed through my
|
||
~autostart~ script written [[file:bin.org::#Autostart-a99e99e7][here]].
|
||
#+name: exwm-autostart
|
||
#+begin_src emacs-lisp :tangle no
|
||
(exwm/run-in-background "autostart")
|
||
#+end_src
|
||
|
||
Finally, let’s only initialize and start EXWM once functions from
|
||
exwm-randr ran, because otherwise having multiple monitors don’t work.
|
||
#+name: exwm-init
|
||
#+begin_src emacs-lisp :tangle no
|
||
(with-eval-after-load 'exwm-randr
|
||
(exwm-init))
|
||
#+end_src
|
||
|
||
The complete configuration for the ~exwm~ package can be found below.
|
||
#+begin_src emacs-lisp :noweb yes
|
||
(use-package exwm
|
||
:if (seq-contains-p command-line-args "--with-exwm")
|
||
:straight (exwm :build t
|
||
:type git
|
||
:host github
|
||
:repo "ch11ng/exwm")
|
||
:custom
|
||
(use-dialog-box nil "Disable dialog boxes since they are unusable in EXWM")
|
||
(exwm-input-line-mode-passthrough t "Pass all keypresses to emacs in line mode.")
|
||
:init
|
||
(require 'exwm-config)
|
||
(setq exwm-workspace-number 6)
|
||
:config
|
||
(set-frame-parameter (selected-frame) 'alpha-background 0.7)
|
||
<<exwm-randr>>
|
||
|
||
<<exwm-buffers-name>>
|
||
|
||
<<exwm-advices-evil>>
|
||
<<exwm-prefix-keys>>
|
||
|
||
<<exwm-bepo-number-row>>
|
||
<<exwm-workspace-keybinds>>
|
||
|
||
<<exwm-keybinds>>
|
||
|
||
<<exwm-autostart>>
|
||
|
||
<<exwm-init>>)
|
||
#+end_src
|
||
|
||
*** EXWM-Evil integration
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-EXWM-EXWM-Evil-integration-kwlexz61aaj0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-exwm-state
|
||
:if (seq-contains-p command-line-args "--with-exwm")
|
||
:defer t
|
||
:after exwm
|
||
:straight (evil-exwm-state :build t
|
||
:type git
|
||
:host github
|
||
:repo "domenzain/evil-exwm-state"))
|
||
#+end_src
|
||
|
||
*** Multimonitor support
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-EXWM-Multimonitor-support-l5pexz61aaj0
|
||
:END:
|
||
#+name: exwm-randr
|
||
#+begin_src emacs-lisp :tangle no
|
||
(require 'exwm-randr)
|
||
(exwm/run-in-background "xwallpaper --zoom \"${cat $HOME/.cache/wallpaper}\"")
|
||
(start-process-shell-command
|
||
"xrandr" nil "xrandr --output eDP1 --mode 1920x1080 --pos 2560x0 --rotate normal --output HDMI1 --primary --mode 2560x1080 --pos 0x0 --rotate normal --output VIRTUAL1 --off --output DP-1-0 --off --output DP-1-1 --off")
|
||
(exwm-randr-enable)
|
||
(setq exwm-randr-workspace-monitor-plist '(3 "eDP1"))
|
||
#+end_src
|
||
|
||
*** Keybinds for a desktop environment
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-EXWM-Keybinds-for-a-desktop-environment-q2sexz61aaj0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package desktop-environment
|
||
:defer t
|
||
:straight (desktop-environment :build t
|
||
:type git
|
||
:host github
|
||
:repo "DamienCassou/desktop-environment")
|
||
:after exwm
|
||
:diminish t
|
||
:config
|
||
(add-hook 'exwm-init-hook #'desktop-environment-mode)
|
||
(setq desktop-environment-update-exwm-global-keys :prefix
|
||
exwm-layout-show-al-buffers t)
|
||
|
||
(setq desktop-environment-bluetooth-command "bluetoothctl"
|
||
|
||
desktop-environment-brightness-get-command "xbacklight -get"
|
||
desktop-environment-brightness-get-regexp (rx line-start (group (+ digit)))
|
||
desktop-environment-brightness-set-command "xbacklight %s"
|
||
desktop-environment-brightness-normal-increment "-inc 5"
|
||
desktop-environment-brightness-normal-decrement "-dec 5"
|
||
desktop-environment-brightness-small-increment "-inc 2"
|
||
desktop-environment-brightness-small-decrement "-dec 2"
|
||
|
||
desktop-environment-volume-normal-decrement "-d 5"
|
||
desktop-environment-volume-normal-increment "-i 5"
|
||
desktop-environment-volume-small-decrement "-d 2"
|
||
desktop-environment-volume-small-increment "-i 2"
|
||
desktop-environment-volume-set-command "pamixer -u %s"
|
||
|
||
desktop-environment-screenshot-directory "~/Pictures/Screenshots"
|
||
desktop-environment-screenlock-command "plock"
|
||
|
||
desktop-environment-music-toggle-command "mpc toggle"
|
||
desktop-environment-music-previous-command "mpc prev"
|
||
desktop-environment-music-next-command "mpc next"
|
||
desktop-environment-music-stop-command "mpc stop")
|
||
|
||
(general-define-key
|
||
"<XF86AudioPause>" (lambda () (interactive)
|
||
(with-temp-buffer
|
||
(shell-command "mpc pause" (current-buffer) (current-buffer)))))
|
||
|
||
(desktop-environment-mode))
|
||
#+end_src
|
||
|
||
*** Bluetooth
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-EXWM-Bluetooth-k0zhpda0aaj0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(defvar bluetooth-command "bluetoothctl")
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(defun bluetooth-turn-on ()
|
||
(interactive)
|
||
(let ((process-connection-type nil))
|
||
(start-process "" nil bluetooth-command "power" "on")))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(defun bluetooth-turn-off ()
|
||
(interactive)
|
||
(let ((process-connection-type nil))
|
||
(start-process "" nil bluetooth-command "power" "off")))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(defun create-bluetooth-device (raw-name)
|
||
"Create a bluetooth device cons from RAW NAME.
|
||
The cons will hold first the MAC address of the device, then its
|
||
human-friendly name."
|
||
(let ((split-name (split-string raw-name " " t)))
|
||
`(,(mapconcat #'identity (cddr split-name) " ") . ,(cadr split-name))))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(require 'dbus)
|
||
(defun bluetooth-get-devices ()
|
||
(let ((bus-list (dbus-introspect-get-node-names :system "org.bluez" "/org/bluez/hci0")))
|
||
(mapcar (lambda (device)
|
||
`(,(dbus-get-property :system
|
||
"org.bluez"
|
||
(concat "/org/bluez/hci0/" device)
|
||
"org.bluez.Device1"
|
||
"Alias")
|
||
. ,device))
|
||
bus-list)))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(defun bluetooth-connect-device ()
|
||
(interactive)
|
||
(progn
|
||
(bluetooth-turn-on)
|
||
(let* ((devices (bluetooth-get-devices))
|
||
(device (alist-get (completing-read "Device: " devices)
|
||
devices nil nil #'string=)))
|
||
(dbus-call-method-asynchronously
|
||
:system "org.bluez"
|
||
(concat "/org/bluez/hci0" device)
|
||
"org.bluez.Device1"
|
||
"Connect"
|
||
(lambda (&optional msg)
|
||
(when msg (message "%s" msg)))))))
|
||
#+end_src
|
||
|
||
** Making my life easier
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Making-my-life-easier2kz4fl6184j0
|
||
:END:
|
||
*** Bufler
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Making-my-life-easier-Buflerw215fl6184j0
|
||
:END:
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(use-package bufler
|
||
:straight (bufler :build t
|
||
:files (:defaults (:exclude "helm-bufler.el")))
|
||
:defer t
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'bufler-list-mode-map
|
||
:packages 'bufler
|
||
"?" #'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))
|
||
#+end_src
|
||
|
||
*** Helpful
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Making-my-life-easier-Helpfullh25fl6184j0
|
||
:END:
|
||
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~ | |
|
||
|
||
#+begin_src emacs-lisp
|
||
(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))
|
||
#+end_src
|
||
|
||
** LaTeX
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-LaTeX-qfu4g180gbj0
|
||
:END:
|
||
#+begin_src emacs-lisp :noweb yes
|
||
(use-package auctex
|
||
:defer t
|
||
:straight (:build t)
|
||
:hook (tex-mode . lsp-deferred)
|
||
:hook (latex-mode . lsp-deferred)
|
||
:init
|
||
(setq TeX-command-default (if (executable-find "latexmk") "LatexMk" "LaTeX")
|
||
TeX-engine (if (executable-find "xetex") 'xetex 'default)
|
||
TeX-auto-save t
|
||
TeX-parse-self t
|
||
TeX-syntactic-comment t
|
||
TeX-auto-local ".auctex-auto"
|
||
TeX-style-local ".auctex-style"
|
||
TeX-source-correlate-mode t
|
||
TeX-source-correlate-method 'synctex
|
||
TeX-source-correlate-start-server nil
|
||
TeX-electric-sub-and-superscript t
|
||
TeX-fill-break-at-separators nil
|
||
TeX-save-query t)
|
||
:config
|
||
<<latex-fontification>>
|
||
(setq TeX-master t)
|
||
(setcar (cdr (assoc "Check" TeX-command-list)) "chktex -v6 -H %s")
|
||
(add-hook 'TeX-mode-hook (lambda ()
|
||
(setq ispell-parser 'tex
|
||
fill-nobreak-predicate (cons #'texmathp fill-nobreak-predicate))))
|
||
(add-hook 'TeX-mode-hook #'visual-line-mode)
|
||
(add-hook 'TeX-update-style-hook #'rainbow-delimiters-mode)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages 'auctex
|
||
:keymaps '(latex-mode-map LaTeX-mode-map)
|
||
"v" '(TeX-view :which-key "View")
|
||
"c" '(TeX-command-run-all :which-key "Compile")
|
||
"m" '(TeX-command-master :which-key "Run a command")))
|
||
#+end_src
|
||
|
||
From Doom Emacs’ configuration:
|
||
#+name: latex-fontification
|
||
#+begin_src emacs-lisp :tangle no
|
||
(setq font-latex-match-reference-keywords
|
||
'(;; BibLaTeX.
|
||
("printbibliography" "[{") ("addbibresource" "[{")
|
||
;; Standard commands.
|
||
("cite" "[{") ("citep" "[{")
|
||
("citet" "[{") ("Cite" "[{")
|
||
("parencite" "[{") ("Parencite" "[{")
|
||
("footcite" "[{") ("footcitetext" "[{")
|
||
;; Style-specific commands.
|
||
("textcite" "[{") ("Textcite" "[{")
|
||
("smartcite" "[{") ("Smartcite" "[{")
|
||
("cite*" "[{") ("parencite*" "[{")
|
||
("supercite" "[{")
|
||
;; Qualified citation lists.
|
||
("cites" "[{") ("Cites" "[{")
|
||
("parencites" "[{") ("Parencites" "[{")
|
||
("footcites" "[{") ("footcitetexts" "[{")
|
||
("smartcites" "[{") ("Smartcites" "[{")
|
||
("textcites" "[{") ("Textcites" "[{")
|
||
("supercites" "[{")
|
||
;; Style-independent commands.
|
||
("autocite" "[{") ("Autocite" "[{")
|
||
("autocite*" "[{") ("Autocite*" "[{")
|
||
("autocites" "[{") ("Autocites" "[{")
|
||
;; Text commands.
|
||
("citeauthor" "[{") ("Citeauthor" "[{")
|
||
("citetitle" "[{") ("citetitle*" "[{")
|
||
("citeyear" "[{") ("citedate" "[{")
|
||
("citeurl" "[{")
|
||
;; Special commands.
|
||
("fullcite" "[{")
|
||
;; Cleveref.
|
||
("cref" "{") ("Cref" "{")
|
||
("cpageref" "{") ("Cpageref" "{")
|
||
("cpagerefrange" "{") ("Cpagerefrange" "{")
|
||
("crefrange" "{") ("Crefrange" "{")
|
||
("labelcref" "{")))
|
||
|
||
(setq font-latex-match-textual-keywords
|
||
'(;; BibLaTeX brackets.
|
||
("parentext" "{") ("brackettext" "{")
|
||
("hybridblockquote" "[{")
|
||
;; Auxiliary commands.
|
||
("textelp" "{") ("textelp*" "{")
|
||
("textins" "{") ("textins*" "{")
|
||
;; Subcaption.
|
||
("subcaption" "[{")))
|
||
|
||
(setq font-latex-match-variable-keywords
|
||
'(;; Amsmath.
|
||
("numberwithin" "{")
|
||
;; Enumitem.
|
||
("setlist" "[{") ("setlist*" "[{")
|
||
("newlist" "{") ("renewlist" "{")
|
||
("setlistdepth" "{") ("restartlist" "{")
|
||
("crefname" "{")))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package tex-mode
|
||
:defer t
|
||
:straight (:type built-in)
|
||
:config
|
||
(setq LaTeX-section-hook '(LaTeX-section-heading
|
||
LaTeX-section-title
|
||
LaTeX-section-toc
|
||
LaTeX-section-section
|
||
LaTeX-section-label)
|
||
LaTeX-fill-break-at-separators nil
|
||
LaTeX-item-indent 0))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package preview
|
||
:defer t
|
||
:straight (:type built-in)
|
||
:config
|
||
(add-hook 'LaTeX-mode-hook #'LaTeX-preview-setup)
|
||
(setq-default preview-scale 1.4
|
||
preview-scale-function
|
||
(lambda () (* (/ 10.0 (preview-document-pt)) preview-scale)))
|
||
(setq preview-auto-cache-preamble nil)
|
||
(phundrak/major-leader-key
|
||
:packages 'auctex
|
||
:keymaps '(latex-mode-map LaTeX-mode-map)
|
||
"p" #'preview-at-point
|
||
"P" #'preview-clearout-at-point))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package cdlatex
|
||
:defer t
|
||
:after auctex
|
||
:straight (:build t)
|
||
:hook (LaTeX-mode . cdlatex-mode)
|
||
:hook (org-mode . org-cdlatex-mode)
|
||
:config
|
||
(setq cdlatex-use-dollar-to-ensure-math nil)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages 'cdlatex
|
||
:keymaps 'cdlatex-mode-map
|
||
"$" nil
|
||
"(" nil
|
||
"{" nil
|
||
"[" nil
|
||
"|" nil
|
||
"<" nil
|
||
"^" nil
|
||
"_" nil
|
||
[(control return)] nil))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package adaptive-wrap
|
||
:defer t
|
||
:after auctex
|
||
:straight (:build t)
|
||
:hook (LaTeX-mode . adaptative-wrap-prefix-mode)
|
||
:init (setq-default adaptative-wrap-extra-indent 0))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package auctex-latexmk
|
||
:after auctex
|
||
:defer t
|
||
:straight (:build t)
|
||
:init
|
||
(setq auctex-latexmk-inherit-TeX-PDF-mode t)
|
||
(add-hook 'LaTeX-mode (lambda () (setq TeX-command-default "LatexMk")))
|
||
:config
|
||
(auctex-latexmk-setup))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package company-auctex
|
||
:defer t
|
||
:after (company auctex)
|
||
:straight (:build t)
|
||
:config
|
||
(company-auctex-init))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package company-math
|
||
:defer t
|
||
:straight (:build t)
|
||
:after (company auctex)
|
||
:config
|
||
(defun my-latex-mode-setup ()
|
||
(setq-local company-backends
|
||
(append '((company-math-symbols-latex company-latex-commands))
|
||
company-backends)))
|
||
(add-hook 'TeX-mode-hook #'my-latex-mode-setup))
|
||
#+end_src
|
||
|
||
** Org-mode
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-modedw35fl6184j0
|
||
:END:
|
||
Since recently, in order to make ~org-cite~ compile properly, we need
|
||
the ~citeproc~ package, a citation processor.
|
||
#+begin_src emacs-lisp
|
||
(use-package citeproc
|
||
:after (org)
|
||
:defer t
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
Org is the main reason I am using Emacs. It is an extremely powerfu
|
||
tool when you want to write anything that is not necessarily primarily
|
||
programming-related, though it absolutely can be! Org can be a
|
||
replacement for anything similar to LibreOffice Writer, LibreOffice
|
||
Calc, and LibreOffice Impress. It is a much more powerful (and older)
|
||
version of Markdown which can be exported to LaTeX and HTML at least,
|
||
rendering writing web pages and technical, scientific documents much
|
||
simpler than writing manually HTML and LaTeX code, especially when a
|
||
single document source is meant to be exported for both formats. And
|
||
since org is an Emacs package, that also means it can be greatly
|
||
extended however we like!
|
||
#+begin_src emacs-lisp
|
||
(use-package org
|
||
:straight t
|
||
:defer t
|
||
:commands (orgtbl-mode)
|
||
:hook ((org-mode . visual-line-mode)
|
||
(org-mode . org-num-mode))
|
||
:custom-face
|
||
(org-macro ((t (:foreground "#b48ead"))))
|
||
:init
|
||
(auto-fill-mode -1)
|
||
:config
|
||
<<org-hydra-babel>>
|
||
(require 'ox-beamer)
|
||
(require 'org-protocol)
|
||
(setq org-hide-leading-stars nil
|
||
org-hide-macro-markers t
|
||
org-ellipsis " ⤵"
|
||
org-image-actual-width 600
|
||
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-src-preserve-indentation t
|
||
org-log-done 'time
|
||
org-directory "~/org"
|
||
org-default-notes-file (expand-file-name "notes.org" org-directory))
|
||
(with-eval-after-load 'oc
|
||
(setq org-cite-global-bibliography '("~/org/bibliography/references.bib")))
|
||
<<org-agenda-files>>
|
||
<<org-behavior-electric>>
|
||
<<org-capture-target-files>>
|
||
<<org-capture-templates>>
|
||
<<org-create-emphasis-functions()>>
|
||
<<org-babel-load-languages>>
|
||
<<org-use-sub-superscripts>>
|
||
<<org-latex-compiler>>
|
||
<<org-latex-src-block-backend>>
|
||
<<org-latex-default-packages>>
|
||
<<org-export-latex-hyperref-format>>
|
||
<<org-latex-pdf-process>>
|
||
<<org-latex-logfiles-add-extensions>>
|
||
<<org-re-reveal>>
|
||
<<org-html-validation>>
|
||
<<org-latex-classes>>
|
||
<<org-publish-projects>>
|
||
<<org-mode-visual-prettify-symbols>>
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'org-mode-map
|
||
:packages 'org
|
||
"RET" 'org-open-at-point)
|
||
(phundrak/major-leader-key
|
||
:keymaps 'org-mode-map
|
||
:packages 'org
|
||
<<general-keybindings-gen(table=org-keybinds-various)>>
|
||
<<general-keybindings-gen(table=org-keybinds-babel)>>
|
||
<<general-keybindings-gen(table=org-keybinds-dates)>>
|
||
<<general-keybindings-gen(table=org-keybinds-insert)>>
|
||
<<general-keybindings-gen(table=org-keybinds-jump)>>
|
||
<<general-keybindings-gen(table=org-keybinds-tables)>>
|
||
<<general-keybindings-gen(table=org-keybinds-toggles)>>)
|
||
<<org-capture-keybinds>>
|
||
(phundrak/major-leader-key
|
||
:packages 'org
|
||
:keymaps 'org-src-mode-map
|
||
"'" #'org-edit-src-exit
|
||
"k" #'org-edit-src-abort))
|
||
#+end_src
|
||
|
||
The main feature from ~evil-org~ that I love is how easy it is to modify
|
||
some keybindings for keyboards layouts that do not have ~hjkl~, such as
|
||
the bépo layout (or Dvorak or Colemak if you are into that). But it
|
||
also adds a ton of default keybindings which are just much more
|
||
comfortable than the default ones you get with evil and org naked.
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-org
|
||
:straight (:build t)
|
||
:after (org)
|
||
:hook (org-mode . evil-org-mode)
|
||
:config
|
||
(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))
|
||
#+end_src
|
||
|
||
This package is a small package I’ve written that helps me when
|
||
writing conlanging documents, with features such as creating syntax
|
||
trees, converting translitterated text to its native script, etc…
|
||
#+begin_src emacs-lisp
|
||
(use-package conlanging
|
||
:straight (conlanging :build t
|
||
:type git
|
||
:repo "https://labs.phundrak.com/phundrak/conlanging.el")
|
||
:after org
|
||
:defer t)
|
||
#+end_src
|
||
|
||
Since very recently, the ~contrib/lisp/~ directory of org moved out of
|
||
the main repository to [[https://git.sr.ht/~bzg/org-contrib][this repository]]. On the other hand,
|
||
~contrib/scripts/~ moved to [[https://code.orgmode.org/bzg/worg/src/master/code][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.
|
||
#+begin_src emacs-lisp
|
||
(use-package org-contrib
|
||
:after (org)
|
||
:defer t
|
||
:straight (:build t)
|
||
:init
|
||
(require 'ox-extra)
|
||
(ox-extras-activate '(latex-header-blocks ignore-headlines)))
|
||
#+end_src
|
||
|
||
*** Agenda
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no :exports code :results silent
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Agenda8b55fl6184j0
|
||
:END:
|
||
#+name: org-agenda-files
|
||
#+begin_src emacs-lisp
|
||
(setq-default org-agenda-files (list "~/org/agenda" "~/org/notes.org"))
|
||
#+end_src
|
||
|
||
*** Babel
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Babel9585fl6184j0
|
||
:END:
|
||
One of the amazing features of org-mode is its literary programming capacities
|
||
by running code blocks from within Org-mode itself. But for that, only a couple
|
||
of languages are supported directly by Org-mode itself, and they need to be
|
||
activated. Here are the languages I activated in my Org-mode configuration:
|
||
#+NAME: org-babel-languages-table
|
||
| C |
|
||
| emacs-lisp |
|
||
| gnuplot |
|
||
| latex |
|
||
| makefile |
|
||
| python |
|
||
| sass |
|
||
| shell |
|
||
|
||
#+NAME: org-babel-languages-gen
|
||
#+header: :cache yes :results replace
|
||
#+header: :var languages=org-babel-languages-table[,0]
|
||
#+BEGIN_SRC emacs-lisp :exports none :tangle no
|
||
(format "'(%s)"
|
||
(mapconcat (lambda ($language)
|
||
(format "(%s . t)" $language))
|
||
languages
|
||
"\n "))
|
||
#+END_SRC
|
||
|
||
#+RESULTS[950a986eb2df1e9ebf0564f945fc997412fdf1f7]: org-babel-languages-gen
|
||
: '((C . t)
|
||
: (emacs-lisp . t)
|
||
: (gnuplot . t)
|
||
: (latex . t)
|
||
: (makefile . t)
|
||
: (python . t)
|
||
: (sass . t)
|
||
: (shell . t))
|
||
|
||
The corresponding code is as follows:
|
||
#+NAME: org-babel-load-languages
|
||
#+BEGIN_SRC emacs-lisp :noweb yes :tangle no
|
||
(org-babel-do-load-languages
|
||
'org-babel-load-languages
|
||
<<org-babel-languages-gen()>>)
|
||
#+END_SRC
|
||
|
||
Some languages can run asynchronously with the help of ~ob-async~.
|
||
#+begin_src emacs-lisp
|
||
(use-package ob-async
|
||
:straight (:build t)
|
||
:defer t
|
||
:after (org ob))
|
||
#+end_src
|
||
|
||
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:
|
||
#+begin_src emacs-lisp
|
||
(use-package ob-latex-as-png
|
||
:after org
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
A nice thing to have when working with REST APIs is to have a REST
|
||
client. Even better if it can work inside org-mode!
|
||
#+begin_src emacs-lisp
|
||
(use-package ob-restclient
|
||
:straight (:build t)
|
||
:defer t
|
||
:after (org ob)
|
||
:init
|
||
(add-to-list 'org-babel-load-languages '(restclient . t)))
|
||
#+end_src
|
||
|
||
*** Behavior
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Behaviorzp65fl6184j0
|
||
:END:
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(use-package toc-org
|
||
:after (org markdown-mode)
|
||
:straight (:build t)
|
||
:init
|
||
(add-to-list 'org-tag-alist '("TOC" . ?T))
|
||
:hook (org-mode . toc-org-enable)
|
||
:hook (markdown-mode . toc-org-enable))
|
||
#+end_src
|
||
~electric-mode~ also bothers me a lot when editing org files, so let’s deactivate it:
|
||
#+name: org-behavior-electric
|
||
#+begin_src emacs-lisp :tangle no
|
||
(add-hook 'org-mode-hook (lambda ()
|
||
(interactive)
|
||
(electric-indent-local-mode -1)))
|
||
#+end_src
|
||
|
||
As explained in my [[https://blog.phundrak.com/better-custom-ids-orgmode/][blog post]], org-mode is terrible with coming up with
|
||
meaningful IDs for its headings. I actually wrote a package for this!
|
||
#+begin_src emacs-lisp
|
||
(use-package org-unique-id
|
||
:straight (org-unique-id :build t
|
||
:type git
|
||
:host github
|
||
:repo "Phundrak/org-unique-id")
|
||
:defer t
|
||
:after org
|
||
:init
|
||
(add-hook 'org-mode-hook
|
||
(lambda ()
|
||
(add-hook 'before-save-hook
|
||
(lambda ()
|
||
(when (and (eq major-mode 'org-mode)
|
||
(eq buffer-read-only nil))
|
||
(org-unique-id)))))))
|
||
#+end_src
|
||
|
||
*** Capture
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Capture-2j6hpyh0u9j0
|
||
:END:
|
||
Org capture is an amazing tool for taking quick notes, be it simple
|
||
text, links, resources, or reminders. They are all organised is
|
||
specified org files which are described below.
|
||
#+name: org-capture-target-files
|
||
#+begin_src emacs-lisp :tangle no
|
||
(defvar org-conlanging-file "~/org/conlanging.org")
|
||
(defvar org-notes-file "~/org/notes.org")
|
||
(defvar org-journal-file "~/org/journal.org")
|
||
(defvar org-linguistics-file "~/org/linguistics.org")
|
||
(defvar org-novel-file "~/org/novel.org")
|
||
(defvar org-agenda-file "~/org/agenda/private.org")
|
||
(defvar org-school-file "~/org/agenda/school.org")
|
||
(defvar org-worldbuilding-file "~/org/worldbuilding.org")
|
||
#+end_src
|
||
|
||
Let me describe a keybind to invoke org-capture from anywhere within
|
||
Emacs.
|
||
#+name: org-capture-keybinds
|
||
#+begin_src emacs-lisp :tangle no
|
||
(phundrak/leader-key
|
||
:packages 'org
|
||
:infix "o"
|
||
"" '(:ignore t :which-key "org")
|
||
"c" #'org-capture)
|
||
#+end_src
|
||
|
||
When ~org-capture~ is invoked, it will ask which template we wish to
|
||
use. In the table [[org-capture-shortcuts-table]], the /key/ column
|
||
represents which keychord we need to hit, titled with /name/, we need to
|
||
hit in order to use the /template/, inserted in the designated /file/ in
|
||
the manner described by /insertion mode/.
|
||
#+name: org-capture-shortcuts-table
|
||
| Shortcut | Name | Title | Insertion mode | file | template |
|
||
|----------+---------------+-----------+----------------+-------------------------+--------------------------|
|
||
| e | Email | | | | |
|
||
| ew | Write Email | Emails | file+headline | org-default-notes-file | email.orgcaptmpl |
|
||
| j | Journal | | file+datetree | org-journal-file | journal.orgcaptmpl |
|
||
| l | Link | | | | |
|
||
| ll | General | | file+headline | org-default-notes-file | link.orgcaptmpl |
|
||
| ly | YouTube | | file+headline | org-default-notes-file | youtube.orgcaptmpl |
|
||
| L | Protocol Link | Link | file+headline | org-default-notes-file | protocol-link.orgcaptmpl |
|
||
| n | Notes | | | | |
|
||
| nc | Conlanging | Note | file+headline | org-conlanging-file | notes.orgcaptmpl |
|
||
| nn | General | | file+headline | org-default-notes-file | notes.orgcaptmpl |
|
||
| nN | Novel | Note | file+headline | org-novel-notes-file | notes.orgcaptmpl |
|
||
| nq | Quote | | file+headline | org-default-notes-file | notes-quote.orgcaptmpl |
|
||
| nw | Worldbuilding | Note | file+headline | org-wordbuilding-file | notes.orgcaptmpl |
|
||
| N | Novel | | | | |
|
||
| Ni | Ideas | | file+headline | org-novel-notes-file | notes.orgcaptmpl |
|
||
| p | Protocol | Link | file+headline | org-default-notes-file | protocol.orgcaptmpl |
|
||
| r | Resources | | | | |
|
||
| rc | Conlanging | Resources | file+headline | org-conlanging-file | resource.orgcaptmpl |
|
||
| re | Emacs | | file+headline | org-default-notes-file | resource.orgcaptmpl |
|
||
| ri | Informatique | | file+headline | org-default-notes-file | resource.orgcaptmpl |
|
||
| rl | Linguistics | | file+headline | org-default-notes-file | resource.orgcaptmpl |
|
||
| rL | Linux | | file+headline | org-default-notes-file | resource.orgcaptmpl |
|
||
| rw | Worldbuilding | Resources | file+headline | org-wordbuilding-file | resource.orgcaptmpl |
|
||
| t | Tasks | | | | |
|
||
| tb | Birthday | | file+headline | org-private-agenda-file | birthday.orgcaptmpl |
|
||
| te | Event | | file+headline | org-private-agenda-file | event.orgcaptmpl |
|
||
| th | Health | | file+headline | org-private-agenda-file | health.orgcaptmpl |
|
||
| ti | Informatique | | file+headline | org-private-agenda-file | informatique.orgcaptmpl |
|
||
|
||
All templates can be found [[https://labs.phundrak.com/phundrak/dotfiles/src/branch/master/org/capture][in my dotfiles’ repository]].
|
||
|
||
#+name: org-capture-shortcuts-gen
|
||
#+header: :exports none :cache yes :tangle no
|
||
#+begin_src emacs-lisp :var entries=org-capture-shortcuts-table
|
||
(mapconcat (lambda (entry)
|
||
(let ((key (nth 0 entry))
|
||
(name (nth 1 entry))
|
||
(title (nth 2 entry))
|
||
(ins-mode (nth 3 entry))
|
||
(file (nth 4 entry))
|
||
(template (nth 5 entry)))
|
||
(if (string= "" ins-mode)
|
||
(format "%S" `(,key ,name))
|
||
(format "(\"%s\" \"%s\" entry\n %S\n %S)"
|
||
key name
|
||
`(,(intern ins-mode) ,(intern file) ,(if (string= "file+datetree" ins-mode)
|
||
(intern "")
|
||
(if (string= title "")
|
||
name
|
||
title)))
|
||
`(file ,(concat "~/org/capture/" template))))))
|
||
entries
|
||
"\n")
|
||
#+end_src
|
||
|
||
#+RESULTS[9f565c3dd73becfad0a99c0cfaf4521762334b40]: org-capture-shortcuts-gen
|
||
#+begin_example
|
||
("e" "Email")
|
||
("ew" "Write Email" entry
|
||
(file+headline org-default-notes-file "Emails")
|
||
(file "~/org/capture/email.orgcaptmpl"))
|
||
("j" "Journal" entry
|
||
(file+datetree org-journal-file ##)
|
||
(file "~/org/capture/journal.orgcaptmpl"))
|
||
("l" "Link")
|
||
("ll" "General" entry
|
||
(file+headline org-default-notes-file "General")
|
||
(file "~/org/capture/link.orgcaptmpl"))
|
||
("ly" "YouTube" entry
|
||
(file+headline org-default-notes-file "YouTube")
|
||
(file "~/org/capture/youtube.orgcaptmpl"))
|
||
("L" "Protocol Link" entry
|
||
(file+headline org-default-notes-file "Link")
|
||
(file "~/org/capture/protocol-link.orgcaptmpl"))
|
||
("n" "Notes")
|
||
("nc" "Conlanging" entry
|
||
(file+headline org-conlanging-file "Note")
|
||
(file "~/org/capture/notes.orgcaptmpl"))
|
||
("nn" "General" entry
|
||
(file+headline org-default-notes-file "General")
|
||
(file "~/org/capture/notes.orgcaptmpl"))
|
||
("nN" "Novel" entry
|
||
(file+headline org-novel-notes-file "Note")
|
||
(file "~/org/capture/notes.orgcaptmpl"))
|
||
("nq" "Quote" entry
|
||
(file+headline org-default-notes-file "Quote")
|
||
(file "~/org/capture/notes-quote.orgcaptmpl"))
|
||
("nw" "Worldbuilding" entry
|
||
(file+headline org-wordbuilding-file "Note")
|
||
(file "~/org/capture/notes.orgcaptmpl"))
|
||
("N" "Novel")
|
||
("Ni" "Ideas" entry
|
||
(file+headline org-novel-notes-file "Ideas")
|
||
(file "~/org/capture/notes.orgcaptmpl"))
|
||
("p" "Protocol" entry
|
||
(file+headline org-default-notes-file "Link")
|
||
(file "~/org/capture/protocol.orgcaptmpl"))
|
||
("r" "Resources")
|
||
("rc" "Conlanging" entry
|
||
(file+headline org-conlanging-file "Resources")
|
||
(file "~/org/capture/resource.orgcaptmpl"))
|
||
("re" "Emacs" entry
|
||
(file+headline org-default-notes-file "Emacs")
|
||
(file "~/org/capture/resource.orgcaptmpl"))
|
||
("ri" "Informatique" entry
|
||
(file+headline org-default-notes-file "Informatique")
|
||
(file "~/org/capture/resource.orgcaptmpl"))
|
||
("rl" "Linguistics" entry
|
||
(file+headline org-default-notes-file "Linguistics")
|
||
(file "~/org/capture/resource.orgcaptmpl"))
|
||
("rL" "Linux" entry
|
||
(file+headline org-default-notes-file "Linux")
|
||
(file "~/org/capture/resource.orgcaptmpl"))
|
||
("rw" "Worldbuilding" entry
|
||
(file+headline org-wordbuilding-file "Resources")
|
||
(file "~/org/capture/resource.orgcaptmpl"))
|
||
("t" "Tasks")
|
||
("tb" "Birthday" entry
|
||
(file+headline org-private-agenda-file "Birthday")
|
||
(file "~/org/capture/birthday.orgcaptmpl"))
|
||
("te" "Event" entry
|
||
(file+headline org-private-agenda-file "Event")
|
||
(file "~/org/capture/event.orgcaptmpl"))
|
||
("th" "Health" entry
|
||
(file+headline org-private-agenda-file "Health")
|
||
(file "~/org/capture/health.orgcaptmpl"))
|
||
("ti" "Informatique" entry
|
||
(file+headline org-private-agenda-file "Informatique")
|
||
(file "~/org/capture/informatique.orgcaptmpl"))
|
||
#+end_example
|
||
|
||
The capture templates are set like so:
|
||
#+name: org-capture-templates
|
||
#+begin_src emacs-lisp :tangle no :results silent
|
||
(setq org-capture-templates
|
||
'(
|
||
<<org-capture-shortcuts-gen()>>))
|
||
#+end_src
|
||
|
||
*** Custom functions
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Custom-functions-h3v07sl02ej0
|
||
:END:
|
||
**** Emphasize text
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Custom-Elisp-Org-Functions-Emphasize-textkilkel6184j0
|
||
:END:
|
||
Sometimes, I want to emphasize some text in my org-mode documents.
|
||
It’s very possible to just go to the begining of the chosen text, add
|
||
the marker, then go to the end of the text than needs emphasis and add
|
||
another marker, and I’m sure most people are fine with that. But I
|
||
also like being able to select a region and hit a keybind to emphasize
|
||
it that way. The table [[org-emphasis-character]] lists the emphasis
|
||
characters in org-mode, their role, and the character code of each
|
||
emphasis character. From that, creating functions that emphasize a
|
||
selected text is quite easy.
|
||
|
||
#+name: org-emphasis-character
|
||
| Emphasis | Character | Character code |
|
||
|----------------+-----------+----------------|
|
||
| bold | ~*~ | 42 |
|
||
| italic | ~/~ | 47 |
|
||
| underline | ~_~ | 95 |
|
||
| verbatim | ~=~ | 61 |
|
||
| code | ~~~ | 126 |
|
||
| strike-through | ~+~ | 43 |
|
||
|
||
#+name: org-create-emphasis-functions
|
||
#+header: :tangle no :exports results :cache yes
|
||
#+header: :wrap "src emacs-lisp :tangle no :exports code"
|
||
#+begin_src emacs-lisp :var emphasis-list=org-emphasis-character
|
||
(mapconcat (lambda (emphasis)
|
||
(let ((type (car emphasis))
|
||
(code (nth 2 emphasis)))
|
||
(format "(defun org-emphasize-%s ()
|
||
\"Emphasize as %s the current region.\"
|
||
(interactive)
|
||
(org-emphasize %s))"
|
||
type
|
||
type
|
||
code)))
|
||
emphasis-list
|
||
"\n")
|
||
#+end_src
|
||
|
||
#+RESULTS[dbd10cce4ae05a046838214784f0f4c16765e728]: org-create-emphasis-functions
|
||
#+begin_src emacs-lisp :tangle no :exports code
|
||
(defun org-emphasize-bold ()
|
||
"Emphasize as bold the current region."
|
||
(interactive)
|
||
(org-emphasize 42))
|
||
(defun org-emphasize-italic ()
|
||
"Emphasize as italic the current region."
|
||
(interactive)
|
||
(org-emphasize 47))
|
||
(defun org-emphasize-underline ()
|
||
"Emphasize as underline the current region."
|
||
(interactive)
|
||
(org-emphasize 95))
|
||
(defun org-emphasize-verbatim ()
|
||
"Emphasize as verbatim the current region."
|
||
(interactive)
|
||
(org-emphasize 61))
|
||
(defun org-emphasize-code ()
|
||
"Emphasize as code the current region."
|
||
(interactive)
|
||
(org-emphasize 126))
|
||
(defun org-emphasize-strike-through ()
|
||
"Emphasize as strike-through the current region."
|
||
(interactive)
|
||
(org-emphasize 43))
|
||
#+end_src
|
||
|
||
You can find the keybinds for these functions in the chapter
|
||
§[[#Packages-Configuration-Org-mode-Keybindingsv0e5fl6184j0]].
|
||
|
||
**** ~phundrak/toggle-org-src-window-split~
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Custom-Elisp-Org-Functions-phundrak-toggle-org-src-window-splito2tkel6184j0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(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")))
|
||
#+end_src
|
||
|
||
*** Exporters
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-File-exportik95fl6184j0
|
||
:END:
|
||
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:
|
||
#+NAME: org-use-sub-superscripts
|
||
#+BEGIN_SRC emacs-lisp :tangle no
|
||
(setq org-use-sub-superscripts (quote {}))
|
||
#+END_SRC
|
||
|
||
**** ConlangDict
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Exporters-ConlangDict-wvi7vaa16fj0
|
||
:END:
|
||
I am currently working on new exporters as well as custom links for my
|
||
org websites. They are first thought for my [[https://conlang.phundrak.com][conlanging website]], hence
|
||
the name, but one can use the package for any org-generated website.
|
||
This package provides two exporters, ~ox-conlang-md~ and
|
||
~ox-conlang-json~, as well as ~ol-conlang~ to handle links to and inside
|
||
the dictionary. The markdown files are meant to be used with my custom
|
||
Vue front-end (not publicly released yet).
|
||
#+begin_src emacs-lisp
|
||
(use-package org-conlang
|
||
:defer t
|
||
:after '(org ol ox)
|
||
:straight (org-conlang :type git
|
||
:host nil
|
||
:repo "https://labs.phundrak.com/phundrak/org-conlang"
|
||
:build t))
|
||
#+end_src
|
||
|
||
**** Epub
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-File-export-Epub-w5ycfuz095j0
|
||
:END:
|
||
A backend for exporting files through org I like is ~ox-epub~ which, as
|
||
you can guess, exports org files to the [[https://www.w3.org/publishing/epub32/][Epub format]].
|
||
#+begin_src emacs-lisp
|
||
(use-package ox-epub
|
||
:after (org ox)
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
**** Gemini
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Exporters-Gemini-4td3dk21kej0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package ox-gemini
|
||
:defer t
|
||
:straight (:build t)
|
||
:after (ox org))
|
||
#+end_src
|
||
|
||
**** HTML
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-File-export-HTMLxjc5fl6184j0
|
||
:END:
|
||
On HTML exports, Org-mode tries to include a validation link for the
|
||
exported HTML. Let’s disable that since I never use it.
|
||
#+NAME: org-html-validation
|
||
#+BEGIN_SRC emacs-lisp :tangle no
|
||
(setq org-html-validation-link nil)
|
||
#+END_SRC
|
||
|
||
#+begin_src emacs-lisp
|
||
;; (use-package htmlize
|
||
;; :defer t
|
||
;; :straight (:build t))
|
||
#+end_src
|
||
|
||
This package allows for live-previewing the HTML export of an org
|
||
buffer in an XWidget Webkit browser window. But when testing it, it’s
|
||
not great for large org files, I should keep its usage for smaller org
|
||
files.
|
||
#+begin_src emacs-lisp
|
||
(use-package preview-org-html-mode
|
||
:defer t
|
||
:after (org)
|
||
:straight (preview-org-html-mode :build t
|
||
:type git
|
||
:host github
|
||
:repo "jakebox/preview-org-html-mode")
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'org-mode-map
|
||
:packages 'preview-org-html-mode
|
||
:infix "P"
|
||
"" '(:ignore t :which-key "preview")
|
||
"h" #'preview-org-html-mode
|
||
"r" #'preview-org-html-refresh
|
||
"p" #'preview-org-html-pop-window-to-frame)
|
||
:config
|
||
(setq preview-org-html-refresh-configuration 'save))
|
||
#+end_src
|
||
|
||
**** Hugo
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Exporters-Hugo-ca3473613aj0
|
||
:END:
|
||
I manage [[https://blog.phundrak.com][my blog]] with [[https://gohugo.io/][Hugo]]. Although it natively supports the org
|
||
format, it’s not great compared to its markdown support. So, instead,
|
||
let’s directly export our org files as markdown files and let Hugo do
|
||
the rest of the job for us!
|
||
#+begin_src emacs-lisp
|
||
(use-package ox-hugo
|
||
:defer t
|
||
:after ox
|
||
:straight t)
|
||
#+end_src
|
||
|
||
I also have a function for publishing my blog once I exported my
|
||
articles with ~ox-hugo~. It will compile blog into a ~public/~ directory
|
||
and copy its content over to my remote server.
|
||
#+begin_src emacs-lisp
|
||
(defun phundrak/blog-publish ()
|
||
"Publish my blog through Hugo to my remote server."
|
||
(interactive)
|
||
(let* ((default-directory (expand-file-name "~/org/blog"))
|
||
(public-path (concat default-directory "/public"))
|
||
(target-path "/rsync:Tilo:/home/phundrak/www/phundrak.com/blog"))
|
||
(compile "hugo")
|
||
(let ((files (mapcar (lambda (file)
|
||
(f-relative file public-path))
|
||
(f-files public-path nil t))))
|
||
(dolist (file files)
|
||
(copy-file (concat public-path "/" file)
|
||
(concat target-path "/" file)
|
||
t nil t)))))
|
||
#+end_src
|
||
|
||
**** LaTeX
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-File-export-LaTeXg2b5fl6184j0
|
||
:END:
|
||
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:
|
||
#+NAME: org-latex-compiler
|
||
#+BEGIN_SRC emacs-lisp :tangle no
|
||
(setq org-latex-compiler "xelatex")
|
||
#+END_SRC
|
||
|
||
A new backend that was introduced in org-mode for LaTeX source block
|
||
coloring is ~engraved~.
|
||
#+BEGIN_SRC emacs-lisp
|
||
(use-package engrave-faces
|
||
:defer t
|
||
:straight (:build t)
|
||
:after org)
|
||
#+END_SRC
|
||
|
||
#+name: org-latex-src-block-backend
|
||
#+begin_src emacs-lisp :tangle no
|
||
(require 'engrave-faces)
|
||
(csetq org-latex-src-block-backend 'engraved)
|
||
#+end_src
|
||
|
||
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; namely, I
|
||
need to remove ~inputenc~, ~fontenc~ and ~grffile~. I also added some default
|
||
packages:
|
||
- ~cleveref~ for better references to various elements.
|
||
- ~svg~ for inserting SVG files in PDF outputs
|
||
- ~booktabs~ for nicer tables
|
||
- and ~tabularx~ for tabulars with adjustable columns
|
||
#+NAME: org-latex-default-packages
|
||
#+BEGIN_SRC emacs-lisp :tangle no
|
||
(dolist (package '(("AUTO" "inputenc" t ("pdflatex"))
|
||
("T1" "fontenc" t ("pdflatex"))
|
||
("" "grffile" t)))
|
||
(delete package org-latex-default-packages-alist))
|
||
|
||
(dolist (package '(("capitalize" "cleveref")
|
||
("" "svg")
|
||
("" "booktabs")
|
||
("" "tabularx")))
|
||
(add-to-list 'org-latex-default-packages-alist package t))
|
||
|
||
(setq org-latex-reference-command "\\cref{%s}")
|
||
#+END_SRC
|
||
|
||
By the way, reference links in LaTeX should be written in this format,
|
||
since we are using ~cleveref~:
|
||
#+NAME: org-export-latex-hyperref-format
|
||
#+BEGIN_SRC emacs-lisp :tangle no
|
||
(setq org-export-latex-hyperref-format "\\ref{%s}")
|
||
#+END_SRC
|
||
|
||
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!
|
||
#+NAME: org-latex-pdf-process
|
||
#+BEGIN_SRC emacs-lisp :tangle no
|
||
(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"))
|
||
#+END_SRC
|
||
|
||
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:
|
||
#+name: org-latex-logfiles-add-extensions
|
||
#+begin_src emacs-lisp :tangle no
|
||
(dolist (ext '("bbl" "lot"))
|
||
(add-to-list 'org-latex-logfiles-extensions ext t))
|
||
#+end_src
|
||
|
||
**** Reveal.js
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-File-export-Reveal-js-mzijhel099j0
|
||
:END:
|
||
#+NAME: org-re-reveal
|
||
#+begin_src emacs-lisp
|
||
(use-package org-re-reveal
|
||
:defer t
|
||
:after org
|
||
:straight (:build t)
|
||
:init
|
||
(add-hook 'org-mode-hook (lambda () (require 'org-re-reveal)))
|
||
:config
|
||
(setq org-re-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js"
|
||
org-re-reveal-revealjs-version "4"))
|
||
#+end_src
|
||
|
||
**** SSH Config
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-File-export-SSH-Config-tatextz095j0
|
||
:END:
|
||
Yet another exporter I enjoy is [[https://github.com/dantecatalfamo/ox-ssh][~ox-ssh~]] with which I manage my
|
||
~$HOME/.ssh/config~ file. You won’t find my org file for managing my
|
||
servers on my repos though.
|
||
#+begin_src emacs-lisp
|
||
(use-package ox-ssh
|
||
:after (ox org)
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
*** Keybindings
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Keybindingsv0e5fl6184j0
|
||
:END:
|
||
Be prepared, I have a lot of keybindings for org-mode! They are all
|
||
prefixed with a comma ~,~ in normal mode.
|
||
#+name: org-keybinds-various
|
||
| Key chord | Function | Description |
|
||
|-----------+---------------------------+-------------|
|
||
| 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 | |
|
||
| e | org-export-dispatch | |
|
||
| l | org-store-link | |
|
||
| p | org-priority | |
|
||
| r | org-reload | |
|
||
| t | evil-next-visual-line | |
|
||
| s | evil-previous-visual-line | |
|
||
|
||
I then have a couple of babel-related functions.
|
||
#+name: org-keybinds-babel
|
||
| Key chord | Function | Description |
|
||
|-----------+-------------------------------------+-------------|
|
||
| b | nil | 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 | |
|
||
|
||
The ~org-babel-transient~ hydra allows me to quickly navigate between
|
||
code blocks and interact with them. This code block was inspired by
|
||
one you can find in Spacemacs.
|
||
#+name: org-hydra-babel
|
||
#+begin_src emacs-lisp :tangle no
|
||
(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))
|
||
#+end_src
|
||
|
||
We next have keybindings related to org-mode’s agenda capabilities. We
|
||
can schedule a todo header for some dates, or set a deadline.
|
||
#+name: org-keybinds-dates
|
||
| Key chord | Function | Description |
|
||
|-----------+-------------------------+-------------|
|
||
| d | nil | dates |
|
||
| dd | org-deadline | |
|
||
| ds | org-schedule | |
|
||
| dt | org-time-stamp | |
|
||
| dT | org-time-stamp-inactive | |
|
||
|
||
Let’s now define some keybinds for inserting stuff in our org buffer:
|
||
#+name: org-keybinds-insert
|
||
| Key chord | Function | Description |
|
||
|-----------+-------------------------------+-------------|
|
||
| i | nil | insert |
|
||
| ib | org-insert-structure-template | |
|
||
| id | org-insert-drawer | |
|
||
| ie | nil | emphasis |
|
||
| ieb | org-emphasize-bold | |
|
||
| iec | org-emphasize-code | |
|
||
| iei | org-emphasize-italic | |
|
||
| ies | org-emphasize-strike-through | |
|
||
| ieu | org-emphasize-underline | |
|
||
| iev | org-emphasize-verbatim | |
|
||
| iE | org-set-effort | |
|
||
| if | org-footnote-new | |
|
||
| ih | org-insert-heading | |
|
||
| iH | counsel-org-link | |
|
||
| 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 | |
|
||
|
||
There isn’t a lot of stuff I can jump to yet, but there’s still some:
|
||
#+name: org-keybinds-jump
|
||
| Key chord | Function | Description |
|
||
|-----------+----------------------+-------------|
|
||
| j | nil | jump |
|
||
| ja | counsel-org-goto-all | |
|
||
| jh | counsel-org-goto | |
|
||
|
||
Tables get a bit more love:
|
||
#+name: org-keybinds-tables
|
||
| Key chord | Function | Description |
|
||
|-----------+--------------------------------------+-------------|
|
||
| t | nil | tables |
|
||
| tc | org-table-move-column-left | |
|
||
| tt | org-table-move-row-down | |
|
||
| ts | org-table-move-row-up | |
|
||
| tr | org-table-move-column-right | |
|
||
| ta | org-table-align | |
|
||
| te | org-table-eval-formula | |
|
||
| tf | org-table-field-info | |
|
||
| tF | org-table-edit-formulas | |
|
||
| th | org-table-convert | |
|
||
| tl | org-table-recalculate | |
|
||
| tp | org-plot/gnuplot | |
|
||
| tS | org-table-sort-lines | |
|
||
| tw | org-table-wrap-region | |
|
||
| tx | org-table-shrink | |
|
||
| tN | org-table-create-with-table.el | |
|
||
| td | nil | delete |
|
||
| tdc | org-table-delete-column | |
|
||
| tdr | org-table-kill-row | |
|
||
| ti | nil | insert |
|
||
| tic | org-table-insert-column | |
|
||
| tih | org-table-insert-hline | |
|
||
| tir | org-table-insert-row | |
|
||
| tiH | org-table-hline-and-move | |
|
||
| tt | nil | toggle |
|
||
| ttf | org-table-toggle-formula-debugger | |
|
||
| tto | org-table-toggle-coordinate-overlays | |
|
||
|
||
Finally, let’s make enabling and disabling stuff accessible:
|
||
#+name: org-keybinds-toggles
|
||
| Key chord | Function | Description |
|
||
|-----------+--------------------------------------+-------------|
|
||
| T | nil | 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 | |
|
||
|
||
*** LaTeX formats
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no :exports code :results silent
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-LaTeX-formatszjf5fl6184j0
|
||
:END:
|
||
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:
|
||
#+NAME: org-latex-class-conlang
|
||
#+BEGIN_SRC emacs-lisp
|
||
'("conlang"
|
||
"\\documentclass{book}"
|
||
("\\chapter{%s}" . "\\chapter*{%s}")
|
||
("\\section{%s}" . "\\section*{%s}")
|
||
("\\subsection{%s}" . "\\subsection*{%s}")
|
||
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
|
||
#+END_SRC
|
||
|
||
And here is the declaration of the ~beamer~ class:
|
||
#+NAME: org-latex-class-beamer
|
||
#+BEGIN_SRC emacs-lisp
|
||
`("beamer"
|
||
,(concat "\\documentclass[presentation]{beamer}\n"
|
||
"[DEFAULT-PACKAGES]"
|
||
"[PACKAGES]"
|
||
"[EXTRA]\n")
|
||
("\\section{%s}" . "\\section*{%s}")
|
||
("\\subsection{%s}" . "\\subsection*{%s}")
|
||
("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
|
||
#+END_SRC
|
||
|
||
Both these classes have to be added to ~org-latex-classes~ like so:
|
||
#+NAME: org-latex-classes
|
||
#+BEGIN_SRC emacs-lisp :noweb yes
|
||
(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>>)))
|
||
#+END_SRC
|
||
|
||
*** Projects
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no :exports code :results silent
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Projectsf2h5fl6184j0
|
||
:END:
|
||
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:
|
||
#+NAME: org-publish-projects
|
||
#+BEGIN_SRC emacs-lisp :noweb yes
|
||
<<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>>))
|
||
#+END_SRC
|
||
|
||
**** Configuration website
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Projects-Configuration-websitelki5fl6184j0
|
||
:END:
|
||
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.
|
||
#+NAME: org-proj-config-setup
|
||
#+BEGIN_SRC emacs-lisp
|
||
(defvar phundrak//projects-config-target
|
||
"/ssh: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 the website config.phundrak.com.")
|
||
(defvar phundrak//projects-config-recursive
|
||
t
|
||
"Defines whether subdirectories should be parsed for config.phundrak.com.")
|
||
#+END_SRC
|
||
|
||
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.
|
||
#+NAME: org-proj-config-html
|
||
#+BEGIN_SRC emacs-lisp
|
||
("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)
|
||
#+END_SRC
|
||
|
||
We also have the component for all the static files needed to run the website
|
||
(mostly images tbh).
|
||
#+NAME: org-proj-config-static
|
||
#+BEGIN_SRC emacs-lisp
|
||
("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)
|
||
#+END_SRC
|
||
|
||
The project is then defined like so:
|
||
#+NAME: org-proj-config
|
||
#+BEGIN_SRC emacs-lisp
|
||
("config-website"
|
||
:components ("config-website-org"
|
||
"config-website-static"))
|
||
#+END_SRC
|
||
|
||
**** Conlanging website
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Projects-Linguistics-websitey4k5fl6184j0
|
||
:END:
|
||
#+begin_info
|
||
/Conlangs/, or /constructed languages/, are artificial languages created
|
||
by individuals or by groups of individuals, unlike natural languages
|
||
which evolve and appear naturally from other languages.
|
||
#+end_info
|
||
|
||
My conlanging website is made out of three projects. As for the
|
||
previous project, let’s declare the common values for these.
|
||
#+NAME: org-proj-lang-setup
|
||
#+BEGIN_SRC emacs-lisp
|
||
(defvar phundrak//projects-conlanging-target
|
||
"/rsync:Tilo:~/www/phundrak.com/conlang"
|
||
"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
|
||
"en"
|
||
"Language of langue.phundrak.com.")
|
||
(defvar phundrak//projects-conlanging-recursive
|
||
t
|
||
"Defines whether subdirectories should be parsed for langue.phundrak.com.")
|
||
#+END_SRC
|
||
|
||
The first component is the one generating the HTML files from the org files.
|
||
#+NAME: org-proj-lang-html
|
||
#+BEGIN_SRC emacs-lisp
|
||
("conlang-phundrak-com-org"
|
||
:base-directory ,phundrak//projects-conlanging-source
|
||
:base-extension "org"
|
||
:exclude ,(rx (* print)
|
||
(or "CONTRIB"
|
||
"README"
|
||
"header"
|
||
"temp"
|
||
"private"
|
||
"svg-ink")
|
||
(* print))
|
||
: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)
|
||
#+END_SRC
|
||
|
||
We also have the component for the LaTeX and PDF part of the website:
|
||
#+NAME: org-proj-lang-pdf
|
||
#+BEGIN_SRC emacs-lisp
|
||
("conlang-phundrak-com-pdf"
|
||
:base-directory ,phundrak//projects-conlanging-source
|
||
:base-extension "org"
|
||
:exclude ,(rx (* print)
|
||
(or "CONTRIB"
|
||
"README"
|
||
"header"
|
||
"temp"
|
||
"index"
|
||
"sitemap"
|
||
"private"
|
||
"svg-ink")
|
||
(* print))
|
||
: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)
|
||
#+END_SRC
|
||
|
||
And lastly, we have the component for all the static files needed to run the
|
||
website:
|
||
#+NAME: org-proj-lang-static
|
||
#+BEGIN_SRC emacs-lisp
|
||
("conlang-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)
|
||
#+END_SRC
|
||
|
||
The project is then defined like so:
|
||
#+NAME: org-proj-lang
|
||
#+BEGIN_SRC emacs-lisp
|
||
("conlang-phundrak-com"
|
||
:components ("conlang-phundrak-com-org"
|
||
"conlang-phundrak-com-static"
|
||
"conlang-phundrak-com-pdf"))
|
||
#+END_SRC
|
||
|
||
This project leaves a lot of junk files here and there. This is why I
|
||
have this command to remove them easily.
|
||
#+begin_src emacs-lisp
|
||
(defun phundrak/clean-conlanging-directory ()
|
||
"Clean my conlanging directory from junk files."
|
||
(interactive)
|
||
(let ((files (directory-files-recursively phundrak//projects-conlanging-source
|
||
(rx (* print)
|
||
"."
|
||
(or "glo"
|
||
"html"
|
||
"ist"
|
||
"tex"
|
||
"pdf")))))
|
||
(mapc (lambda (file) (delete-file file))
|
||
(cl-remove-if (lambda (str)
|
||
(string-match-p (regexp-quote "headers.tex") str))
|
||
files))))
|
||
#+end_src
|
||
|
||
*** Org-ref and Bibtex configuration
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-org-ref-1h586cd085j0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package reftex
|
||
:commands turn-on-reftex
|
||
:init (setq reftex-default-bibliography "~/org/bibliography/references.bib"
|
||
reftex-plug-into-AUCTeX t))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org-ref
|
||
;; :after (org ox-bibtex pdf-tools)
|
||
:after org
|
||
:defer t
|
||
:straight (:build t)
|
||
:custom-face
|
||
(org-ref-cite-face ((t (:weight bold))))
|
||
:init
|
||
(setq org-ref-completion-library 'org-ref-ivy-cite
|
||
org-latex-logfiles-extensions '("lof" "lot" "aux" "idx" "out" "log" "fbd_latexmk"
|
||
"toc" "nav" "snm" "vrb" "dvi" "blg" "brf" "bflsb"
|
||
"entoc" "ps" "spl" "bbl" "pygtex" "pygstyle"))
|
||
(add-hook 'org-mode-hook (lambda () (require 'org-ref)))
|
||
:config
|
||
(setq bibtex-completion-pdf-field "file"
|
||
bibtex-completion-notes-path "~/org/bibliography/notes/"
|
||
bibtex-completion-bibliography "~/org/bibliography/references.bib"
|
||
bibtex-completion-library-path "~/org/bibliography/bibtex-pdfs/"
|
||
bibtex-completion-pdf-symbol "⌘"
|
||
bibtex-completion-notes-symbol "✎")
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'bibtex-mode-map
|
||
:packages 'org-ref
|
||
"C-t" #'org-ref-bibtex-next-entry
|
||
"C-s" #'org-ref-bibtex-previous-entry
|
||
"gt" #'org-ref-bibtex-next-entry
|
||
"gs" #'org-ref-bibtex-previous-entry)
|
||
(phundrak/major-leader-key
|
||
:keymaps '(bibtex-mode-map)
|
||
:packages 'org-ref
|
||
;; Navigation
|
||
"t" #'org-ref-bibtex-next-entry
|
||
"s" #'org-ref-bibtex-previous-entry
|
||
|
||
;; Open
|
||
"b" #'org-ref-open-in-browser
|
||
"n" #'org-ref-open-bibtex-notes
|
||
"p" #'org-ref-open-bibtex-pdf
|
||
|
||
;; Misc
|
||
"h" #'org-ref-bibtex-hydra/body
|
||
"i" #'org-ref-bibtex-hydra/org-ref-bibtex-new-entry/body-and-exit
|
||
"s" #'org-ref-sort-bibtex-entry
|
||
|
||
"l" '(:ignore t :which-key "lookup")
|
||
"la" #'arxiv-add-bibtex-entry
|
||
"lA" #'arxiv-get-pdf-add-bibtex-entry
|
||
"ld" #'doi-utils-add-bibtex-entry-from-doi
|
||
"li" #'isbn-to-bibtex
|
||
"lp" #'pubmed-insert-bibtex-from-pmid)
|
||
(phundrak/major-leader-key
|
||
:keymaps 'org-mode-map
|
||
:pakages 'org-ref
|
||
"ic" #'org-ref-insert-link))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package ivy-bibtex
|
||
:defer t
|
||
:straight (:build t)
|
||
:config
|
||
(setq bibtex-completion-pdf-open-function #'find-file)
|
||
:general
|
||
(phundrak/leader-key
|
||
:keymaps '(bibtex-mode-map)
|
||
:packages 'ivy-bibtex
|
||
"m" #'ivy-bibtex))
|
||
#+end_src
|
||
|
||
*** Org-present
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Org-present-pw04s240w8j0
|
||
:END:
|
||
~org-present~ allows its user to create presentations through ~org-mode~,
|
||
which is really nice! However, most of my configuration will be stolen
|
||
[[https://config.daviwil.com/emacs#org-present][from Daviwil’s]] with minor changes.
|
||
#+begin_src emacs-lisp
|
||
(defun my/org-present-prepare-slide ()
|
||
(org-overview)
|
||
(org-show-entry)
|
||
(org-show-children)
|
||
(org-present-hide-cursor))
|
||
|
||
(defun my/org-present-init ()
|
||
(setq header-line-format " ")
|
||
(org-display-inline-images)
|
||
(my/org-present-prepare-slide))
|
||
|
||
(defun my/org-present-quit ()
|
||
(setq header-line-format nil)
|
||
(org-present-small)
|
||
(org-present-show-cursor))
|
||
|
||
(defun my/org-present-prev ()
|
||
(interactive)
|
||
(org-present-prev)
|
||
(my/org-present-prepare-slide))
|
||
|
||
(defun my/org-present-next ()
|
||
(interactive)
|
||
(org-present-next)
|
||
(my/org-present-prepare-slide))
|
||
|
||
(use-package org-present
|
||
:after org
|
||
:defer t
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages 'org-present
|
||
:keymaps 'org-mode-map
|
||
"P" #'org-present)
|
||
(phundrak/evil
|
||
:states 'normal
|
||
:packages 'org-present
|
||
:keymaps 'org-present-mode-keymap
|
||
"+" #'org-present-big
|
||
"-" #'org-present-small
|
||
"<" #'org-present-beginning
|
||
">" #'org-present-end
|
||
"«" #'org-present-beginning
|
||
"»" #'org-present-end
|
||
"c" #'org-present-hide-cursor
|
||
"C" #'org-present-show-cursor
|
||
"n" #'org-present-next
|
||
"p" #'org-present-prev
|
||
"r" #'org-present-read-only
|
||
"w" #'org-present-read-write
|
||
"q" #'org-present-quit)
|
||
:hook ((org-present-mode . my/org-present-init)
|
||
(org-present-mode-quit . my/org-present-quit)))
|
||
#+end_src
|
||
|
||
*** Visual Configuration
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Visual-Configurationrol5fl6184j0
|
||
:END:
|
||
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!
|
||
#+begin_src emacs-lisp
|
||
(use-package mixed-pitch
|
||
:after org
|
||
:straight (:build t)
|
||
:hook
|
||
(org-mode . mixed-pitch-mode)
|
||
(emms-browser-mode . mixed-pitch-mode)
|
||
(emms-playlist-mode . mixed-pitch-mode)
|
||
:config
|
||
(add-hook 'org-agenda-mode-hook (lambda () (mixed-pitch-mode -1))))
|
||
#+end_src
|
||
|
||
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!
|
||
#+begin_src emacs-lisp
|
||
(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))
|
||
#+end_src
|
||
|
||
Similarly, LaTeX fragments previews are nice and all, but if I have my
|
||
cursor on it, I want to see the LaTeX source code and modify it, not
|
||
just the generated image!
|
||
#+begin_src emacs-lisp
|
||
(use-package org-fragtog
|
||
:defer t
|
||
:after org
|
||
:straight (:build t)
|
||
:hook (org-mode . org-fragtog-mode))
|
||
#+end_src
|
||
|
||
Org-modern modernizes a bit the appearance of org buffers, including
|
||
tables, source blocks, and tags,, and it applies settings similar to
|
||
~org-superstar~ which I used to use.
|
||
#+begin_src emacs-lisp
|
||
(use-package org-modern
|
||
:straight (:build t)
|
||
:after org
|
||
:defer t
|
||
:hook (org-mode . org-modern-mode)
|
||
:hook (org-agenda-finalize . org-modern-agenda))
|
||
#+end_src
|
||
~org-fancy-priorities~ change the priority of an org element such as ~#A~
|
||
to anything user-defined. Let’s all-the-iconify this!
|
||
#+begin_src emacs-lisp
|
||
(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))))
|
||
#+end_src
|
||
/Org Outline Tree/ is a better way of managing my org files’ outline.
|
||
#+begin_src emacs-lisp
|
||
(use-package org-ol-tree
|
||
:after (org avy)
|
||
:defer t
|
||
:straight (org-ol-tree :build t
|
||
:host github
|
||
:type git
|
||
:repo "Townk/org-ol-tree")
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages 'org-ol-tree
|
||
:keymaps 'org-mode-map
|
||
"O" #'org-ol-tree))
|
||
#+end_src
|
||
|
||
#+name: org-mode-visual-prettify-symbols
|
||
#+begin_src emacs-lisp
|
||
(add-hook 'org-mode-hook
|
||
(lambda ()
|
||
(dolist (pair '(("[ ]" . ?☐)
|
||
("[X]" . ?☑)
|
||
("[-]" . ?❍)
|
||
("#+title:" . ?📕)
|
||
("#+TITLE:" . ?📕)
|
||
("#+author:" . ?✎)
|
||
("#+AUTHOR:" . ?✎)
|
||
("#+email:" . ?📧)
|
||
("#+EMAIL:" . ?📧)
|
||
("#+include" . ?⭳)
|
||
("#+INCLUDE" . ?⭳)
|
||
("#+begin_src" . ?λ)
|
||
("#+BEGIN_SRC" . ?λ)
|
||
("#+end_src" . ?λ)
|
||
("#+END_SRC" . ?λ)))
|
||
(add-to-list 'prettify-symbols-alist pair))
|
||
(prettify-symbols-mode)))
|
||
#+end_src
|
||
|
||
*** Misc
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Org-mode-Misc-l202k9z0l4j0
|
||
:END:
|
||
~org-tree-slide~ is a presentation tool for org-mode.
|
||
#+begin_src emacs-lisp
|
||
(use-package org-tree-slide
|
||
:defer t
|
||
:after org
|
||
:straight (:build t)
|
||
:config
|
||
(setq org-tree-slide-skip-done nil)
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'org-mode-map
|
||
:packages 'org-tree-slide
|
||
"<f8>" #'org-tree-slide-mode)
|
||
(phundrak/major-leader-key
|
||
:keymaps 'org-tree-slide-mode-map
|
||
:packages 'org-tree-slide
|
||
"d" (lambda () (interactive (setq org-tree-slide-skip-done (not org-tree-slide-skip-done))))
|
||
"p" #'org-tree-slide-move-next-tree
|
||
"n" #'org-tree-slide-move-previous-tree
|
||
"t" #'org-tree-slide-move-next-tree
|
||
"s" #'org-tree-slide-move-previous-tree
|
||
"u" #'org-tree-slide-content))
|
||
#+end_src
|
||
~org-roll~ is a simple package for tabletop RPGs for rolling dice.
|
||
#+begin_src emacs-lisp
|
||
(use-package org-roll
|
||
:defer t
|
||
:after org
|
||
:straight (:build t :type git :host github :repo "zaeph/org-roll"))
|
||
#+end_src
|
||
|
||
** Programming
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages6et5fl6184j0
|
||
:END:
|
||
*** Tools
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-Tools-w3q5rsg0k4j0
|
||
:END:
|
||
**** Appwrite
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-Tools-Appwrite-c1e4fh51mij0
|
||
:END:
|
||
[[https://appwrite.io][Appwrite]] is an open-source and self-hostable alternative to Firebase.
|
||
I am currently working on a server SDK for Appwrite in Emacs, so here
|
||
it is.
|
||
#+begin_src emacs-lisp
|
||
(use-package appwrite
|
||
:defer t
|
||
:straight (appwrite :build t
|
||
:type git
|
||
:host github
|
||
:repo "Phundrak/appwrite.el")
|
||
:config
|
||
(csetq appwrite-endpoint "https://appwrite.phundrak.com"
|
||
appwrite-devel t))
|
||
#+end_src
|
||
|
||
**** Copilot
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-Tools-Copilot-ik2ddc804fj0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package copilot
|
||
:defer t
|
||
:straight (copilot :type git
|
||
:host github
|
||
:repo "zerolfx/copilot.el"
|
||
:files ("dist" "copilot.el")
|
||
:build t)
|
||
:config
|
||
(csetq copilot-enable-predicates '(evil-insert-state-p)
|
||
copilot-node-executable
|
||
(expand-file-name ".local/share/nvm/versions/node/v17.9.1/bin/node"
|
||
(getenv "HOME")))
|
||
|
||
(defun my/copilot-tab ()
|
||
(interactive)
|
||
(or (copilot-accept-completion)
|
||
(company-indent-or-complete-common nil)))
|
||
|
||
(with-eval-after-load 'company
|
||
(delq 'company-preview-if-just-one-frontend company-frontends)
|
||
(phundrak/evil
|
||
:packages '(copilot company)
|
||
:keymaps '(company-mode-map company-active-map)
|
||
"TAB" #'my/copilot-tab))
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages 'copilot
|
||
"C" '(:ignore t :wk "Copilot")
|
||
"Cc" #'copilot-accept-completion
|
||
"CC" #'copilot-complete
|
||
"Cm" #'copilot-mode
|
||
"Cd" #'copilot-diagnose
|
||
"Cn" #'copilot-next-completion
|
||
"Cp" #'copilot-previous-completion))
|
||
#+end_src
|
||
|
||
**** Databases
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-Tools-Databases-73nhsqt09ij0
|
||
:END:
|
||
A really cool tool in Emacs for manipulating databases is ~emacsql~.
|
||
It’s able to manipulate Sqlite databases by default, but it’s also
|
||
possible to manipulate MariaDB and PostgreSQL databases by installing
|
||
additional packages. For now, I just need Sqlite and PostgreSQL
|
||
interfaces, so let’s install the relevant packages.
|
||
#+begin_src emacs-lisp
|
||
(use-package emacsql-psql
|
||
:defer t
|
||
:after (emacsql)
|
||
:straight (:build t))
|
||
|
||
(with-eval-after-load 'emacsql
|
||
(phundrak/major-leader-key
|
||
:keymaps 'emacs-lisp-mode-map
|
||
:packages '(emacsql)
|
||
"E" #'emacsql-fix-vector-indentation))
|
||
#+end_src
|
||
|
||
**** Flycheck
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-Flycheckb446fl6184j0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(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 evil)
|
||
:hook (flycheck-mode . flycheck-popup-tip-mode)
|
||
:config
|
||
(setq flycheck-popup-tip-error-prefix "X ")
|
||
(with-eval-after-load '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 "))
|
||
#+end_src
|
||
|
||
**** Langtool
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-Tools-Langtool-sjr1oox0y9j0
|
||
:END:
|
||
LanguageTool is a great tool for catching typos and grammatical errors
|
||
in quite a few languages.
|
||
#+begin_src emacs-lisp
|
||
(use-package langtool
|
||
:defer t
|
||
:straight (:build t)
|
||
:commands (langtool-check
|
||
langtool-check-done
|
||
langtool-show-message-at-point
|
||
langtool-correct-buffer)
|
||
:custom
|
||
(langtool-default-language "en-US")
|
||
(langtool-mother-tongue "fr")
|
||
:config
|
||
(setq langtool-java-classpath (string-join '("/usr/share/languagetool"
|
||
"/usr/share/java/languagetool/*")
|
||
":"))
|
||
:general
|
||
(phundrak/leader-key
|
||
:packages 'langtool
|
||
:infix "l"
|
||
"" '(:ignore t :which-key "LangTool")
|
||
"B" #'langtool-correct-buffer
|
||
"b" #'langtool-check-buffer
|
||
"c" #'langtool-check
|
||
"d" #'langtool-check-done
|
||
"l" #'langtool-switch-default-language
|
||
"p" #'langtool-show-message-at-point))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package writegood-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:hook org-mode latex-mode
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'writegood-mode-map
|
||
"g" #'writegood-grade-level
|
||
"r" #'writegood-reading-ease))
|
||
#+end_src
|
||
|
||
**** Spellcheck
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-Tools-Spellcheck-wos8d0y0y9j0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package ispell
|
||
:if (executable-find "aspell")
|
||
:defer t
|
||
:straight (:type built-in)
|
||
:config
|
||
(add-to-list 'ispell-skip-region-alist '(":\\(PROPERTIES\\|LOGBOOK\\):" . ":END:"))
|
||
(add-to-list 'ispell-skip-region-alist '("#\\+BEGIN_SRC" . "#\\+END_SRC"))
|
||
(add-to-list 'ispell-skip-region-alist '("#\\+BEGIN_EXAMPLE" . "#\\+END_EXAMPLE"))
|
||
(setq ispell-program-name "aspell"
|
||
ispell-extra-args '("--sug-mode=ultra" "--run-together")
|
||
ispell-aspell-dict-dir (ispell-get-aspell-config-value "dict-dir")
|
||
ispell-aspell-data-dir (ispell-get-aspell-config-value "data-dir")
|
||
ispell-personal-dictionary (expand-file-name (concat "ispell/" ispell-dictionary ".pws")
|
||
user-emacs-directory)))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package flyspell
|
||
:defer t
|
||
:straight (:type built-in)
|
||
:ghook 'org-mode 'markdown-mode 'TeX-mode
|
||
:init
|
||
(defhydra flyspell-hydra ()
|
||
"
|
||
Spell Commands^^ Add To Dictionary^^ Other
|
||
--------------^^---------- -----------------^^------------- -----^^---------------------------
|
||
[_b_] check whole buffer [_B_] add word to dict (buffer) [_t_] toggle spell check
|
||
[_r_] check region [_G_] add word to dict (global) [_q_] exit
|
||
[_d_] change dictionary [_S_] add word to dict (session) [_Q_] exit and disable spell check
|
||
[_n_] next error
|
||
[_c_] correct before point
|
||
[_s_] correct at point"
|
||
("B" nil)
|
||
("b" flyspell-buffer)
|
||
("r" flyspell-region)
|
||
("d" nil)
|
||
("G" nil)
|
||
("n" flyspell-goto-next-error)
|
||
("c" flyspell-correct-wrapper)
|
||
("Q" flyspell-mode :exit t)
|
||
("q" nil :exit t)
|
||
("S" nil)
|
||
("s" flyspell-correct-at-point)
|
||
("t" nil))
|
||
:config
|
||
(provide 'ispell) ;; force loading ispell
|
||
(setq flyspell-issue-welcome-flag nil
|
||
flyspell-issue-message-flag nil))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package flyspell-correct
|
||
:defer t
|
||
:straight (:build t)
|
||
:general ([remap ispell-word] #'flyspell-correct-at-point)
|
||
:config
|
||
(require 'flyspell-correct-ivy nil t))
|
||
|
||
(use-package flyspell-correct-ivy
|
||
:defer t
|
||
:straight (:build t)
|
||
:after flyspell-correct)
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package flyspell-lazy
|
||
:defer t
|
||
:straight (:build t)
|
||
:after flyspell
|
||
:config
|
||
(setq flyspell-lazy-idle-seconds 1
|
||
flyspell-lazy-window-idle-seconds 3)
|
||
(flyspell-lazy-mode +1))
|
||
#+end_src
|
||
|
||
**** LSP-Mode
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-Tools-LSP-Mode-g4v5rsg0k4j0
|
||
:END:
|
||
[[https://emacs-lsp.github.io/lsp-mode/][~lsp-mode~]] is a mode for Emacs which implements the [[https://github.com/Microsoft/language-server-protocol/][Language Server
|
||
Protocol]] and offers Emacs an IDE-like experience. In short, it’s
|
||
awesome!
|
||
#+begin_src emacs-lisp
|
||
(use-package lsp-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:init
|
||
(setq lsp-keymap-prefix "C-c l")
|
||
:hook ((c-mode . lsp-deferred)
|
||
(c++-mode . lsp-deferred)
|
||
(html-mode . lsp-deferred)
|
||
(sh-mode . lsp-deferred)
|
||
(lsp-mode . lsp-enable-which-key-integration)
|
||
(lsp-mode . lsp-ui-mode))
|
||
:commands (lsp lsp-deferred)
|
||
:custom
|
||
(lsp-rust-analyzer-cargo-watch-command "clippy")
|
||
(lsp-eldoc-render-all t)
|
||
(lsp-idle-delay 0.6)
|
||
(lsp-rust-analyzer-server-display-inlay-hints t)
|
||
(lsp-use-plist t)
|
||
:config
|
||
(lsp-register-client
|
||
(make-lsp-client :new-connection (lsp-tramp-connection "shellcheck")
|
||
:major-modes '(sh-mode)
|
||
:remote? t
|
||
:server-id 'shellcheck-remote)))
|
||
#+end_src
|
||
|
||
I also want all the visual enhancements LSP can provide.
|
||
#+begin_src emacs-lisp
|
||
(use-package lsp-ui
|
||
:after lsp
|
||
:defer t
|
||
:straight (:build t)
|
||
:commands lsp-ui-mode
|
||
:custom
|
||
(lsp-ui-peek-always-show t)
|
||
(lsp-ui-sideline-show-hover t)
|
||
(lsp-ui-doc-enable t)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'lsp-ui-peek-mode-map
|
||
:packages 'lsp-ui
|
||
"c" #'lsp-ui-pook--select-prev-file
|
||
"t" #'lsp-ui-pook--select-next
|
||
"s" #'lsp-ui-pook--select-prev
|
||
"r" #'lsp-ui-pook--select-next-file))
|
||
#+end_src
|
||
|
||
And let’s enable some intergration with ~ivy~.
|
||
#+begin_src emacs-lisp
|
||
(use-package lsp-ivy
|
||
:straight (:build t)
|
||
:defer t
|
||
:after lsp
|
||
:commands lsp-ivy-workspace-symbol)
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package lsp-treemacs
|
||
:defer t
|
||
:straight (:build t)
|
||
:requires treemacs
|
||
:config
|
||
(treemacs-resize-icons 15))
|
||
#+end_src
|
||
|
||
#+name: treemacs-keybinds
|
||
| Key | Function | Description |
|
||
|-----+----------------------------------------+-------------|
|
||
| | | treemacs |
|
||
| c | | create |
|
||
| cd | treemacs-create-dir | |
|
||
| cf | treemacs-create-file | |
|
||
| ci | treemacs-create-icon | |
|
||
| ct | treemacs-create-theme | |
|
||
| cw | treemacs-create-workspace | |
|
||
| d | treemacs-delete-file | |
|
||
| f | | files |
|
||
| ff | treemacs-find-file | |
|
||
| ft | treemacs-find-tag | |
|
||
| l | | lsp |
|
||
| ls | treemacs-expand-lsp-symbol | |
|
||
| ld | treemacs-expand-lsp-treemacs-deps | |
|
||
| lD | treemacs-collapse-lsp-treemacs-deps | |
|
||
| lS | treemacs-collapse-lsp-symbol | |
|
||
| p | | projects |
|
||
| pa | treemacs-add-project | |
|
||
| pf | treemacs-project-follow-mode | |
|
||
| pn | treemacs-project-of-node | |
|
||
| pp | treemacs-project-at-point | |
|
||
| pr | treemacs-remove-project-from-workspace | |
|
||
| pt | treemacs-move-project-down | |
|
||
| ps | treemacs-move-project-up | |
|
||
| r | | rename |
|
||
| rf | treemacs-rename-file | |
|
||
| rp | treemacs-rename-project | |
|
||
| rr | treemacs-rename | |
|
||
| rw | treemacs-rename-workspace | |
|
||
| t | treemacs | |
|
||
| T | | toggles |
|
||
| Td | treemacs-toggle-show-dotfiles | |
|
||
| Tn | treemacs-toggle-node | |
|
||
| v | | visit node |
|
||
| va | treemacs-visit-node-ace | |
|
||
| vc | treemacs-visit-node-close-treemacs | |
|
||
| vn | treemacs-visit-node-default | |
|
||
| y | | yank |
|
||
| ya | treemacs-copy-absolute-path-at-point | |
|
||
| yp | treemacs-copy-project-path-at-point | |
|
||
| yr | treemacs-copy-relative-path-at-point | |
|
||
| yf | treemacs-copy-file | |
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package exec-path-from-shell
|
||
:defer t
|
||
:straight (:build t)
|
||
:init (exec-path-from-shell-initialize))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package consult-lsp
|
||
:defer t
|
||
:after lsp
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'lsp-mode-map
|
||
[remap xref-find-apropos] #'consult-lsp-symbols))
|
||
#+end_src
|
||
~dap-mode~ is an advanced debugging mode that works through lsp.
|
||
#+begin_src emacs-lisp
|
||
(use-package dap-mode
|
||
:after lsp
|
||
:defer t
|
||
:straight (:build t)
|
||
:config
|
||
(dap-ui-mode)
|
||
(dap-ui-controls-mode 1)
|
||
|
||
(require 'dap-lldb)
|
||
(require 'dap-gdb-lldb)
|
||
|
||
(dap-gdb-lldb-setup)
|
||
(dap-register-debug-template
|
||
"Rust::LLDB Run Configuration"
|
||
(list :type "lldb"
|
||
:request "launch"
|
||
:name "LLDB::Run"
|
||
:gdbpath "rust-lldb"
|
||
:target nil
|
||
:cwd nil)))
|
||
#+end_src
|
||
|
||
*** DSLs
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLsbwu5fl6184j0
|
||
:END:
|
||
DSLs, or /Domain Specific Languages/, are languages dedicated to some
|
||
very tasks, such as configuration languages or non-general programming
|
||
such as SQL.
|
||
|
||
**** Makefiles
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-DSLs-Makefiles-2855jdn06jj0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(defun my/local-tab-indent ()
|
||
(setq-local indent-tabs-mode 1))
|
||
|
||
(add-hook 'makefile-mode-hook #'my/local-tab-indent)
|
||
#+end_src
|
||
|
||
**** Caddy
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-Caddy0fw5fl6184j0
|
||
:END:
|
||
[[https://caddyserver.com/][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~:
|
||
#+begin_src emacs-lisp
|
||
(use-package caddyfile-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:mode (("Caddyfile\\'" . caddyfile-mode)
|
||
("caddy\\.conf\\'" . caddyfile-mode)))
|
||
#+end_src
|
||
|
||
**** CMake
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-CMake-vpqkiyp0m5j0
|
||
:END:
|
||
CMake is one of the standard tools for indicating how a project should
|
||
be built. It is not as standard as some other tools such as automake,
|
||
autoconfig, and the likes, but still pretty standard. CMake however
|
||
doesn’t have a major mode available by default, so let’s provide one.
|
||
#+begin_src emacs-lisp
|
||
(use-package cmake-mode
|
||
:defer t
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
Let’s enable first some autocompletion for it.
|
||
#+begin_src emacs-lisp
|
||
(use-package company-cmake
|
||
:straight (company-cmake :build t
|
||
:type git
|
||
:host github
|
||
:repo "purcell/company-cmake")
|
||
:after cmake-mode
|
||
:defer t)
|
||
#+end_src
|
||
|
||
And let’s also enable a more advanced CMake fontlock.
|
||
#+begin_src emacs-lisp
|
||
(use-package cmake-font-lock
|
||
:defer t
|
||
:after cmake-mode
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
And finally, let’s enable some Eldoc integration for CMake.
|
||
#+begin_src emacs-lisp
|
||
(use-package eldoc-cmake
|
||
:straight (:build t)
|
||
:defer t
|
||
:after cmake-mode)
|
||
#+end_src
|
||
|
||
**** CSV
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-CSV-z1vh3rt0uaj0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package csv-mode
|
||
:straight (:build t)
|
||
:defer t
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'csv-mode-map
|
||
"a" #'csv-align-fields
|
||
"d" #'csv-kill-fields
|
||
"h" #'csv-header-line
|
||
"i" #'csv-toggle-invisibility
|
||
"n" #'csv-forward-field
|
||
"p" #'csv-backward-field
|
||
"r" #'csv-reverse-region
|
||
"s" '(:ignore t :wk "sort")
|
||
"sf" #'csv-sort-fields
|
||
"sn" #'csv-sort-numeric-fields
|
||
"so" #'csv-toggle-descending
|
||
"t" #'csv-transpose
|
||
"u" #'csv-unalign-fields
|
||
"y" '(:ignore t :wk yank)
|
||
"yf" #'csv-yank-fields
|
||
"yt" #'csv-yank-as-new-table))
|
||
#+end_src
|
||
|
||
**** Dotenv
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-DSLs-Dotenv-b7kb2gr0kij0
|
||
:END:
|
||
It is not rare to encounter a dotenv file, that is, a file with either
|
||
the ~.env~ extension or simply called ~.env~. They contain environment
|
||
variables for projects which might rely on values you do not want to
|
||
upload to a public git repository and such. While their syntax is
|
||
similar to shell files, they’re not exactly shell files either since
|
||
there’s rarely any script inside running. So, let’s install a simple
|
||
package which will dumb down a lot ~sh-mode~ for these dotenv files.
|
||
#+begin_src emacs-lisp
|
||
(use-package dotenv-mode
|
||
:defer t
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
**** Gnuplot
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-Gnuplot8zx5fl6184j0
|
||
:END:
|
||
This package is a front-end and major mode for the programming
|
||
language [[http://www.gnuplot.info/][Gnuplot]]. Let’s make some beautiful graphs, shall we?
|
||
#+begin_src emacs-lisp
|
||
(use-package gnuplot
|
||
:straight (:build t)
|
||
:defer t)
|
||
#+end_src
|
||
|
||
**** Graphviz
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-Graphviz-zifi28c1e8j0
|
||
:END:
|
||
[[https://graphviz.org/][Graphviz]], often known with ~dot~, allows to programatically create
|
||
visual graphs and networks.
|
||
#+begin_src emacs-lisp
|
||
(use-package graphviz-dot-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:after org
|
||
:mode (("\\.diag\\'" . graphviz-dot-mode)
|
||
("\\.blockdiag\\'" . graphviz-dot-mode)
|
||
("\\.nwdiag\\'" . graphviz-dot-mode)
|
||
("\\.rackdiag\\'" . graphviz-dot-mode)
|
||
("\\.dot\\'" . graphviz-dot-mode)
|
||
("\\.gv\\'" . graphviz-dot-mode))
|
||
:init
|
||
(setq graphviz-dot-indent-width tab-width)
|
||
(with-eval-after-load 'org
|
||
(defalias 'org-babel-execute:graphviz-dot #'org-babel-execute:dot)
|
||
(add-to-list 'org-babel-load-languages '(dot . t))
|
||
(require 'ob-dot)
|
||
(setq org-src-lang-modes
|
||
(append '(("dot" . graphviz-dot))
|
||
(delete '("dot" . fundamental) org-src-lang-modes))))
|
||
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'graphviz-dot-mode-map
|
||
"=" #'graphviz-dot-indent-graph
|
||
"c" #'compile)
|
||
:config
|
||
(setq graphviz-dot-indent-width 4))
|
||
#+end_src
|
||
|
||
**** Markdown
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-Markdown-7sljsrb1y8j0
|
||
:END:
|
||
Yes, I love org-mode and I largely prefer to use it instead of
|
||
Markdown due to its far superior power and abilities. But still,
|
||
sometimes I need to use Markdown because not everyone use org-mode,
|
||
unfortunately.
|
||
#+begin_src emacs-lisp
|
||
(use-package markdown-mode
|
||
:defer t
|
||
:straight t
|
||
:mode
|
||
(("\\.mkd\\'" . markdown-mode)
|
||
("\\.mdk\\'" . markdown-mode)
|
||
("\\.mdx\\'" . markdown-mode))
|
||
:hook (markdown-mode . orgtbl-mode)
|
||
:hook (markdown-mode . visual-line-mode)
|
||
:general
|
||
(phundrak/evil
|
||
:keymaps 'markdown-mode-map
|
||
:packages '(markdown-mode evil)
|
||
"M-RET" #'markdown-insert-list-item
|
||
"M-c" #'markdown-promote
|
||
"M-t" #'markdown-move-down
|
||
"M-s" #'markdown-move-up
|
||
"M-r" #'markdown-demote
|
||
"t" #'evil-next-visual-line
|
||
"s" #'evil-previous-visual-line)
|
||
(phundrak/major-leader-key
|
||
:keymaps 'markdown-mode-map
|
||
:packages 'markdown-mode
|
||
"{" #'markdown-backward-paragraph
|
||
"}" #'markdown-forward-paragraph
|
||
"]" #'markdown-complete
|
||
">" #'markdown-indent-region
|
||
"»" #'markdown-indent-region
|
||
"<" #'markdown-outdent-region
|
||
"«" #'markdown-outdent-region
|
||
"n" #'markdown-next-link
|
||
"p" #'markdown-previous-link
|
||
"f" #'markdown-follow-thing-at-point
|
||
"k" #'markdown-kill-thing-at-point
|
||
"c" '(:ignore t :which-key "command")
|
||
"c]" #'markdown-complete-buffer
|
||
"cc" #'markdown-check-refs
|
||
"ce" #'markdown-export
|
||
"cm" #'markdown-other-window
|
||
"cn" #'markdown-cleanup-list-numbers
|
||
"co" #'markdown-open
|
||
"cp" #'markdown-preview
|
||
"cv" #'markdown-export-and-preview
|
||
"cw" #'markdown-kill-ring-save
|
||
"h" '(:ignore t :which-key "headings")
|
||
"hi" #'markdown-insert-header-dwim
|
||
"hI" #'markdown-insert-header-setext-dwim
|
||
"h1" #'markdown-insert-header-atx-1
|
||
"h2" #'markdown-insert-header-atx-2
|
||
"h3" #'markdown-insert-header-atx-3
|
||
"h4" #'markdown-insert-header-atx-4
|
||
"h5" #'markdown-insert-header-atx-5
|
||
"h6" #'markdown-insert-header-atx-6
|
||
"h!" #'markdown-insert-header-setext-1
|
||
"h@" #'markdown-insert-header-setext-2
|
||
"i" '(:ignore t :which-key "insert")
|
||
"i-" #'markdown-insert-hr
|
||
"if" #'markdown-insert-footnote
|
||
"ii" #'markdown-insert-image
|
||
"il" #'markdown-insert-link
|
||
"it" #'markdown-insert-table
|
||
"iw" #'markdown-insert-wiki-link
|
||
"l" '(:ignore t :which-key "lists")
|
||
"li" #'markdown-insert-list-item
|
||
"T" '(:ignore t :which-key "toggle")
|
||
"Ti" #'markdown-toggle-inline-images
|
||
"Tu" #'markdown-toggle-url-hiding
|
||
"Tm" #'markdown-toggle-markup-hiding
|
||
"Tt" #'markdown-toggle-gfm-checkbox
|
||
"Tw" #'markdown-toggle-wiki-links
|
||
"t" '(:ignore t :which-key "table")
|
||
"tc" #'markdown-table-move-column-left
|
||
"tt" #'markdown-table-move-row-down
|
||
"ts" #'markdown-table-move-row-up
|
||
"tr" #'markdown-table-move-column-right
|
||
"ts" #'markdown-table-sort-lines
|
||
"tC" #'markdown-table-convert-region
|
||
"tt" #'markdown-table-transpose
|
||
"td" '(:ignore t :which-key "delete")
|
||
"tdc" #'markdown-table-delete-column
|
||
"tdr" #'markdown-table-delete-row
|
||
"ti" '(:ignore t :which-key "insert")
|
||
"tic" #'markdown-table-insert-column
|
||
"tir" #'markdown-table-insert-row
|
||
"x" '(:ignore t :which-key "text")
|
||
"xb" #'markdown-insert-bold
|
||
"xB" #'markdown-insert-gfm-checkbox
|
||
"xc" #'markdown-insert-code
|
||
"xC" #'markdown-insert-gfm-code-block
|
||
"xi" #'markdown-insert-italic
|
||
"xk" #'markdown-insert-kbd
|
||
"xp" #'markdown-insert-pre
|
||
"xP" #'markdown-pre-region
|
||
"xs" #'markdown-insert-strike-through
|
||
"xq" #'markdown-blockquote-region)
|
||
:config
|
||
(setq markdown-fontify-code-blocks-natively t))
|
||
#+end_src
|
||
|
||
Since most of my Markdown files are related to Github, I’d like to be
|
||
able to render Markdown through its API.
|
||
#+begin_src emacs-lisp
|
||
(use-package gh-md
|
||
:defer t
|
||
:after markdown-mode
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages 'gh-md
|
||
:keymaps 'markdown-mode-map
|
||
"cr" #'gh-md-render-buffer))
|
||
#+end_src
|
||
|
||
Sometimes, I have to work with Github’s markdown flavour, but I’m not
|
||
really a huge fan of writing it by hand. So instead, I’ll write it in
|
||
org-mode and then export it with ~ox-gfm~.
|
||
#+begin_src emacs-lisp
|
||
(use-package ox-gfm
|
||
:straight (:build t)
|
||
:defer t
|
||
:after (org ox))
|
||
#+end_src
|
||
|
||
Nuxt has its own flavour of Markdown, called [[https://content.nuxtjs.org/guide/writing/mdc/][MDC]] (/MarkDown Components/)
|
||
which is a godsend to write content for Nuxt websites! However, no
|
||
~mdc-mode~ existed when I began working with it, so I’m working on one.
|
||
#+begin_src emacs-lisp
|
||
(use-package mdc-mode
|
||
:defer t
|
||
:after markdown-mode
|
||
:straight (mdc-mode :type git
|
||
:host github
|
||
:repo "Phundrak/mdc-mode"
|
||
:build t))
|
||
#+end_src
|
||
|
||
Tables of content are always nice to have for large files, just like
|
||
with the ~toc-org~ package for org-mode.
|
||
#+begin_src emacs-lisp
|
||
(use-package markdown-toc
|
||
:defer t
|
||
:after markdown-mode
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages 'markdown-toc
|
||
:keymaps 'markdown-mode-map
|
||
"iT" #'markdown-toc-generate-toc))
|
||
#+end_src
|
||
~vmd-mode~ allows the user to live-preview their Github-flavored
|
||
Markdown file quickly. That’s neat!
|
||
#+begin_src emacs-lisp
|
||
(use-package vmd-mode
|
||
:defer t
|
||
:after markdown-mode
|
||
:straight (:build t)
|
||
:custom ((vmd-binary-path (executable-find "vmd")))
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages 'vmd-mode
|
||
:keymaps 'markdown-mode-map
|
||
"cP" #'vmd-mode))
|
||
#+end_src
|
||
|
||
Lastly, ~edit-indirect~ is a package that allows to edit code blocks as
|
||
in org-mode but with other major modes, such as code blocks in
|
||
Markdown.
|
||
#+begin_src emacs-lisp
|
||
(use-package edit-indirect
|
||
:straight (:build t)
|
||
:defer t)
|
||
#+end_src
|
||
|
||
**** Nginx
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-Nginxjiz5fl6184j0
|
||
:END:
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(use-package nginx-mode
|
||
:straight (:build t)
|
||
:defer t)
|
||
#+end_src
|
||
|
||
We then also have an autocompletion package that adds to ~company~ the
|
||
Nginx syntax.
|
||
#+begin_src emacs-lisp
|
||
(use-package company-nginx
|
||
:straight (company-nginx :build t
|
||
:type git
|
||
:host github
|
||
:repo "emacsmirror/company-nginx")
|
||
:defer t
|
||
:config
|
||
(add-hook 'nginx-mode-hook (lambda ()
|
||
(add-to-list 'company-backends #'company-nginx))))
|
||
#+end_src
|
||
|
||
**** PKGBUILD
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-PKGBUILD-ll37pjt0m9j0
|
||
:END:
|
||
As I am an ArchLinux user, I sometimes have to interact with PKGBUILD
|
||
files, both from the AUR when I want to install something from there
|
||
or some I write myself.
|
||
#+begin_src emacs-lisp
|
||
(use-package pkgbuild-mode
|
||
:straight (:build t)
|
||
:defer t
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'pkgbuild-mode-map
|
||
"c" #'pkgbuild-syntax-check
|
||
"i" #'pkgbuild-initialize
|
||
"I" #'pkgbuild-increase-release-tag
|
||
"m" #'pkgbuild-makepkg
|
||
"u" '(:ignore :wk "update")
|
||
"us" #'pkgbuild-update-sums-line
|
||
"uS" #'pkgbuild-update-srcinfo))
|
||
#+end_src
|
||
|
||
**** PlantUML
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-PlantUML-9zo88og099j0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package plantuml-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:mode ("\\.\\(pum\\|puml\\)\\'" . plantuml-mode)
|
||
:after ob
|
||
:init
|
||
(add-to-list 'org-babel-load-languages '(plantuml . t))
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'plantuml-mode-map
|
||
:packages 'plantuml-mode
|
||
"c" '(:ignore t :which-key "compile")
|
||
"cc" #'plantuml-preview
|
||
"co" #'plantuml-set-output-type)
|
||
:config
|
||
(setq plantuml-default-exec-mode 'jar
|
||
plantuml-jar-path "~/.local/bin/plantuml.jar"
|
||
org-plantuml-jar-path "~/.local/bin/plantuml.jar"))
|
||
#+end_src
|
||
|
||
**** Shells
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-Shellsn116fl6184j0
|
||
:END:
|
||
Aside from Eshell, my main shell on my machine is fish (see my [[file:fish.org][fish
|
||
config]]), therefore I need a mode for it.
|
||
#+begin_src emacs-lisp
|
||
(use-package fish-mode
|
||
:straight (:build t)
|
||
:defer t)
|
||
#+end_src
|
||
|
||
**** SSH Config files
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-SSH-Config-files-wt9j1c909cj0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package ssh-config-mode
|
||
:defer t
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
**** Systemd
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-Systemd-3a1dt090mdj0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package systemd
|
||
:defer t
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps '(systemd-mode-map)
|
||
"d" '(systemd-doc-directives :which-key "directives manpage")
|
||
"o" 'systemd-doc-open))
|
||
#+end_src
|
||
|
||
**** Toml
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-Toml-txu8xvk0k4j0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package toml-mode
|
||
:straight (:build t)
|
||
:defer t
|
||
:mode "/\\(Cargo.lock\\|\\.cargo/config\\)\\'")
|
||
#+end_src
|
||
|
||
**** Yaml
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-DSLs-Yamlsk26fl6184j0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package yaml-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:mode "\\.yml\\'"
|
||
:mode "\\.yaml\\'")
|
||
#+end_src
|
||
|
||
*** General Programming Languages
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-General-Programming-Languageszn56fl6184j0
|
||
:END:
|
||
**** C/C++
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-General-Programming-Languages-C-C-2nfcmoz0i5j0
|
||
:END:
|
||
I know, I know, C and C++ no longer are closely related languages,
|
||
each one of them went their own way and learning C won’t make you a
|
||
good C++ programmer, neither will the other way around. But, They are
|
||
still somewhat related, and Emacs thinks so too.
|
||
#+begin_src emacs-lisp
|
||
(use-package cc-mode
|
||
:straight (:type built-in)
|
||
:defer t
|
||
:init
|
||
(put 'c-c++-backend 'safe-local-variable 'symbolp)
|
||
:config
|
||
(require 'compile)
|
||
:general
|
||
(phundrak/undefine
|
||
:keymaps '(c-mode-map c++-mode-map)
|
||
";" nil)
|
||
(phundrak/major-leader-key
|
||
:keymaps '(c-mode-map c++-mode-map)
|
||
"l" '(:keymap lsp-command-map :which-key "lsp" :package lsp-mode))
|
||
(phundrak/evil
|
||
:keymaps '(c-mode-map c++-mode-map)
|
||
"ga" #'projectile-find-other-file
|
||
"gA" #'projectile-find-other-file-other-window))
|
||
#+end_src
|
||
|
||
Something that is also important when working with these languages is
|
||
respecting the ~.clang-format~ file that may be provided by a project.
|
||
#+begin_src emacs-lisp
|
||
(use-package clang-format+
|
||
:straight (:build t)
|
||
:defer t
|
||
:init
|
||
(add-hook 'c-mode-common-hook #'clang-format+-mode))
|
||
#+end_src
|
||
|
||
However, Emacs’ notion of C++ is somewhat outdated, so we need to
|
||
update its fontlock.
|
||
#+begin_src emacs-lisp
|
||
(use-package modern-cpp-font-lock
|
||
:straight (:build t)
|
||
:defer t
|
||
:hook (c++-mode . modern-c++-font-lock-mode))
|
||
#+end_src
|
||
|
||
**** CommonLisp
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-General-Programming-Languages-CommonLisp-gc2a7s31q5j0
|
||
:END:
|
||
In Lisp buffers, let’s enable ~parinfer-rust-mode~.
|
||
#+begin_src emacs-lisp
|
||
(use-package lisp-mode
|
||
:straight (:type built-in)
|
||
:defer t
|
||
:after parinfer-rust-mode
|
||
:hook (lisp-mode . parinfer-rust-mode)
|
||
:config
|
||
(put 'defcommand 'lisp-indent-function 'defun))
|
||
#+end_src
|
||
|
||
My current window manager is StumpWM, inspired by Emacs and written in
|
||
CommonLisp. ~stumpwm-mode~ offers some integration between Emacs and
|
||
StumpWM so we can evaluate CLisp code and see its effects in StumpWM
|
||
immediately. Since my only use for CommonLisp is for my StumpWM
|
||
configuration, it should be automatically enabled when entering
|
||
~lisp-mode~.
|
||
#+begin_src emacs-lisp
|
||
(use-package stumpwm-mode
|
||
:straight (:build t)
|
||
:defer t
|
||
:hook lisp-mode
|
||
:config
|
||
(phundrak/major-leader-key
|
||
:keymaps 'stumpwm-mode-map
|
||
:packages 'stumpwm-mode
|
||
"e" '(:ignore t :which-key "eval")
|
||
"ee" #'stumpwm-eval-last-sexp
|
||
"ed" #'stumpwm-eval-defun
|
||
"er" #'stumpwm-eval-region))
|
||
#+end_src
|
||
|
||
Sly enables some deep interactivity between Emacs and a CommonLisp
|
||
application running the Slynk backend. For an example, see [[file:stumpwm.org::#Utilities-Sly-kkok6oi0yaj0][my Sly
|
||
configuration for StumpWM]].
|
||
#+begin_src emacs-lisp
|
||
(use-package sly
|
||
:defer t
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
**** Dart
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-General-Programming-Languages-Dart-xkr3z8j0m6j0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package dart-mode
|
||
:straight (:build t)
|
||
:defer t
|
||
:hook (dart-mode . lsp-deferred)
|
||
:mode "\\.dart\\'")
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package lsp-dart
|
||
:straight (:build t)
|
||
:defer t)
|
||
#+end_src
|
||
|
||
**** EmacsLisp
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-General-Programming-Languages-EmacsLispo876fl6184j0
|
||
:END:
|
||
This package displays the function’s arglist or variable’s docstring
|
||
in the echo area at the bottom of the frame. Quite useful indeed.
|
||
#+begin_src emacs-lisp
|
||
(use-package eldoc
|
||
:defer t
|
||
:after company
|
||
:init
|
||
(eldoc-add-command 'company-complete-selection
|
||
'company-complete-common
|
||
'company-capf
|
||
'company-abort))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(add-hook 'emacs-lisp-mode-hook (lambda () (smartparens-mode -1)))
|
||
#+end_src
|
||
|
||
Still on the topic of documentation, I sometimes find it lacks
|
||
examples on how to use Elisp functions. ~elisp-demos~ got you covered!
|
||
#+begin_src emacs-lisp
|
||
(use-package elisp-demos
|
||
:defer t
|
||
:straight (:build t)
|
||
:config
|
||
(advice-add 'helpful-update :after #'elisp-demos-advice-helpful-update))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package epdh
|
||
:straight (epdh :type git
|
||
:host github
|
||
:repo "alphapapa/emacs-package-dev-handbook"
|
||
:build t)
|
||
:defer t)
|
||
#+end_src
|
||
|
||
Let’s also declare some Elisp-dedicated keybindings, prefixed by a
|
||
comma.
|
||
#+begin_src emacs-lisp
|
||
(phundrak/major-leader-key
|
||
:keymaps 'emacs-lisp-mode-map
|
||
"'" #'ielm
|
||
"c" '(emacs-lisp-byte-compile :which-key "Byte compile")
|
||
"C" '(:ignore t :which-key "checkdoc")
|
||
"Cc" #'checkdoc
|
||
"Cs" #'checkdoc-start
|
||
"e" '(:ignore t :which-key "eval")
|
||
"eb" #'eval-buffer
|
||
"ed" #'eval-defun
|
||
"ee" #'eval-last-sexp
|
||
"er" #'eval-region
|
||
|
||
"h" '(:ignore t :which-key "help")
|
||
"hh" #'helpful-at-point
|
||
|
||
"t" '(:ignore t :wk "toggle")
|
||
"tP" '(:ignore t :wk "parinfer")
|
||
"tPs" #'parinfer-rust-switch-mode
|
||
"tPd" #'parinfer-rust-mode-disable
|
||
"tPp" #'parinfer-rust-toggle-paren-mode)
|
||
#+end_src
|
||
|
||
Package linting is important when you want to publish your packages to
|
||
the world.
|
||
#+begin_src emacs-lisp
|
||
(use-package package-lint
|
||
:defer t
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'emacs-lisp-mode-map
|
||
:packages 'package-lint
|
||
"l" #'package-lint-current-buffer))
|
||
#+end_src
|
||
|
||
If I need to run CI on a package, [[https://github.com/cask/cask][Cask]] manages its dependencies.
|
||
#+begin_src emacs-lisp
|
||
(use-package cask-mode
|
||
:defer t
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
However, I recently began using [[https://github.com/emacs-eask/eask][Eask]] more and more, I find it nicer to
|
||
work with and it has a lot more features than Cask.
|
||
#+begin_src emacs-lisp
|
||
(use-package eask-mode
|
||
:defer t
|
||
:straight (eask-mode :type git
|
||
:host github
|
||
:repo "emacs-eask/eask-mode"))
|
||
#+end_src
|
||
|
||
**** Python
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-General-Programming-Languages-Python-7mwd2yq0z6j0
|
||
:END:
|
||
First, we need to set up the main Python mode. With this, we’ll also
|
||
add Python to the list of LSP languages and to the list of languages
|
||
org-babel supports.
|
||
#+begin_src emacs-lisp
|
||
(use-package python
|
||
:defer t
|
||
:straight (:build t)
|
||
:after ob
|
||
:mode (("SConstruct\\'" . python-mode)
|
||
("SConscript\\'" . python-mode)
|
||
("[./]flake8\\'" . conf-mode)
|
||
("/Pipfile\\'" . conf-mode))
|
||
:init
|
||
(setq python-indent-guess-indent-offset-verbose nil)
|
||
(add-hook 'python-mode-local-vars-hook #'lsp)
|
||
:config
|
||
(setq python-indent-guess-indent-offset-verbose nil)
|
||
(when (and (executable-find "python3")
|
||
(string= python-shell-interpreter "python"))
|
||
(setq python-shell-interpreter "python3")))
|
||
#+end_src
|
||
|
||
Now let’s add a package for [[https://docs.pytest.org/en/latest/][pytest]].
|
||
#+begin_src emacs-lisp
|
||
(use-package pytest
|
||
:defer t
|
||
:straight (:build t)
|
||
:commands (pytest-one
|
||
pytest-pdb-one
|
||
pytest-all
|
||
pytest-pdb-all
|
||
pytest-last-failed
|
||
pytest-pdb-last-failed
|
||
pytest-module
|
||
pytest-pdb-module)
|
||
:config
|
||
(add-to-list 'pytest-project-root-files "setup.cfg")
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'python-mode-map
|
||
:infix "t"
|
||
:packages 'pytest
|
||
"" '(:ignore t :which-key "test")
|
||
"a" #'python-pytest
|
||
"f" #'python-pytest-file-dwim
|
||
"F" #'python-pytest-file
|
||
"t" #'python-pytest-function-dwim
|
||
"T" #'python-pytest-function
|
||
"r" #'python-pytest-repeat
|
||
"p" #'python-pytest-dispatch))
|
||
#+end_src
|
||
|
||
Poetry is a nice tool with which we can manage our Python runtime
|
||
version as well as our dependencies.
|
||
#+begin_src emacs-lisp
|
||
(use-package poetry
|
||
:defer t
|
||
:straight (:build t)
|
||
:commands (poetry-venv-toggle
|
||
poetry-tracking-mode)
|
||
:config
|
||
(setq poetry-tracking-strategy 'switch-buffer)
|
||
(add-hook 'python-mode-hook #'poetry-tracking-mode))
|
||
#+end_src
|
||
|
||
This package will bring a new major mode for editing pip requirements.
|
||
#+begin_src emacs-lisp
|
||
(use-package pip-requirements
|
||
:defer t
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
Why use the command line to interact with pip when we can do it with
|
||
an Emacs frontend?
|
||
#+begin_src emacs-lisp
|
||
(use-package pippel
|
||
:defer t
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'python-mode-map
|
||
:packages 'pippel
|
||
"P" #'pippel-list-packages))
|
||
#+end_src
|
||
|
||
This is a [[https://github.com/pypa/pipenv][pipenv]] porcelain
|
||
#+begin_src emacs-lisp
|
||
(use-package pipenv
|
||
:defer t
|
||
:straight (:build t)
|
||
:commands (pipenv-activate
|
||
pipenv-deactivate
|
||
pipenv-shell
|
||
pipenv-open
|
||
pipenv-install
|
||
pipenv-uninstall)
|
||
:hook (python-mode . pipenv-mode)
|
||
:init (setq pipenv-with-projectile nil)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'python-mode-map
|
||
:packages 'pipenv
|
||
:infix "e"
|
||
"" '(:ignore t :which-key "pipenv")
|
||
"a" #'pipenv-activate
|
||
"d" #'pipenv-deactivate
|
||
"i" #'pipenv-install
|
||
"l" #'pipenv-lock
|
||
"o" #'pipenv-open
|
||
"r" #'pipenv-run
|
||
"s" #'pipenv-shell
|
||
"u" #'pipenv-uninstall))
|
||
#+end_src
|
||
|
||
This integrates ~pyenv~ into ~python-mode~.
|
||
#+begin_src emacs-lisp
|
||
(use-package pyenv
|
||
:defer t
|
||
:straight (:build t)
|
||
:config
|
||
(add-hook 'python-mode-hook #'pyenv-track-virtualenv)
|
||
(add-to-list 'global-mode-string
|
||
'(pyenv-virtual-env-name (" venv:" pyenv-virtual-env-name " "))
|
||
'append))
|
||
#+end_src
|
||
|
||
Let’s also add a mode for ~pyenv~:
|
||
#+begin_src emacs-lisp
|
||
(use-package pyenv-mode
|
||
:defer t
|
||
:after python
|
||
:straight (:build t)
|
||
:if (executable-find "pyenv")
|
||
:commands (pyenv-mode-versions)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages 'pyenv-mode
|
||
:keymaps 'python-mode-map
|
||
:infix "v"
|
||
"u" #'pyenv-mode-unset
|
||
"s" #'pyenv-mode-set))
|
||
#+end_src
|
||
|
||
This package automatically imports packages we forgot to import.
|
||
#+begin_src emacs-lisp
|
||
(use-package pyimport
|
||
:defer t
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages 'pyimport
|
||
:keymaps 'python-mode-map
|
||
:infix "i"
|
||
"" '(:ignore t :which-key "imports")
|
||
"i" #'pyimport-insert-missing
|
||
"r" #'pyimport-remove-unused))
|
||
#+end_src
|
||
|
||
On the other hand, this one sorts our imports to make them more readable.
|
||
#+begin_src emacs-lisp
|
||
(use-package py-isort
|
||
:defer t
|
||
:straight (:build t)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'python-mode-map
|
||
:packages 'py-isort
|
||
:infix "i"
|
||
"" '(:ignore t :which-key "imports")
|
||
"s" #'py-isort-buffer
|
||
"R" #'py-isort-region))
|
||
#+end_src
|
||
|
||
Access pydoc through counsel.
|
||
#+begin_src emacs-lisp
|
||
(use-package counsel-pydoc
|
||
:defer t
|
||
:straight (:build t))
|
||
#+end_src
|
||
|
||
This generates Python documentation that is meant to be compatible
|
||
with Sphinx, a documentation generaton for Python.
|
||
#+begin_src emacs-lisp
|
||
(use-package sphinx-doc
|
||
:defer t
|
||
:straight (:build t)
|
||
:init
|
||
(add-hook 'python-mode-hook #'sphinx-doc-mode)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'python-mode-map
|
||
:packages 'sphinx-doc
|
||
:infix "S"
|
||
"" '(:ignore t :which-key "sphinx-doc")
|
||
"e" #'sphinx-doc-mode
|
||
"d" #'sphinx-doc))
|
||
#+end_src
|
||
|
||
Cython is a Python to C compiler. It also introduces the extended
|
||
Cython programming language which makes writing C for Python easier.
|
||
This package is a major mode for the Cython programming language.
|
||
#+begin_src emacs-lisp
|
||
(use-package cython-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:mode "\\.p\\(yx\\|x[di]\\)\\'"
|
||
:config
|
||
(setq cython-default-compile-format "cython -a %s")
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'cython-mode-map
|
||
:packages 'cython-mode
|
||
:infix "c"
|
||
"" '(:ignore t :which-key "cython")
|
||
"c" #'cython-compile))
|
||
#+end_src
|
||
|
||
Flycheck can also be enabled for Cython:
|
||
#+begin_src emacs-lisp
|
||
(use-package flycheck-cython
|
||
:defer t
|
||
:straight (:build t)
|
||
:after cython-mode)
|
||
#+end_src
|
||
|
||
Blacken uses the ~black~ formatter backend to format Python buffers.
|
||
#+begin_src emacs-lisp
|
||
(use-package blacken
|
||
:defer t
|
||
:straight (:build t)
|
||
:init
|
||
(add-hook 'python-mode-hook #'blacken-mode))
|
||
#+end_src
|
||
|
||
Finally, I’m using [[https://github.com/microsoft/pyright][Pyright]] as my LSP backend for Python.
|
||
#+begin_src emacs-lisp
|
||
(use-package lsp-pyright
|
||
:after lsp-mode
|
||
:defer t
|
||
:straight (:buidl t))
|
||
#+end_src
|
||
|
||
**** Rust
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-General-Programming-Languages-Rust-n3jhh5h0k4j0
|
||
:END:
|
||
Rust is a general programming language, akin to C++ in some ways, but
|
||
much more oriented towards safe code, and much better suited for web
|
||
development. First, let’s install the most important package,
|
||
~rustic~.
|
||
#+begin_src emacs-lisp
|
||
(use-package rustic
|
||
:defer t
|
||
:straight (:build t)
|
||
:mode ("\\.rs\\'" . rustic-mode)
|
||
:hook (rustic-mode-local-vars . rustic-setup-lsp)
|
||
:hook (rustic-mode . lsp-deferred)
|
||
:init
|
||
(with-eval-after-load 'org
|
||
(defalias 'org-babel-execute:rust #'org-babel-execute:rustic)
|
||
(add-to-list 'org-src-lang-modes '("rust" . rustic)))
|
||
(setq rustic-lsp-client 'lsp-mode)
|
||
:general
|
||
(general-define-key
|
||
:keymaps 'rustic-mode-map
|
||
:packages 'lsp
|
||
"M-t" #'lsp-ui-imenu
|
||
"M-?" #'lsp-find-references)
|
||
(phundrak/major-leader-key
|
||
:keymaps 'rustic-mode-map
|
||
:packages 'rustic
|
||
"b" '(:ignore t :which-key "build")
|
||
"bb" #'rustic-cargo-build
|
||
"bB" #'rustic-cargo-bench
|
||
"bc" #'rustic-cargo-check
|
||
"bC" #'rustic-cargo-clippy
|
||
"bd" #'rustic-cargo-doc
|
||
"bf" #'rustic-cargo-fmt
|
||
"bn" #'rustic-cargo-new
|
||
"bo" #'rustic-cargo-outdated
|
||
"br" #'rustic-cargo-run
|
||
"l" '(:ignore t :which-key "lsp")
|
||
"la" #'lsp-execute-code-action
|
||
"lr" #'lsp-rename
|
||
"lq" #'lsp-workspace-restart
|
||
"lQ" #'lsp-workspace-shutdown
|
||
"ls" #'lsp-rust-analyzer-status
|
||
"t" '(:ignore t :which-key "cargo test")
|
||
"ta" #'rustic-cargo-test
|
||
"tt" #'rustic-cargo-current-test)
|
||
:config
|
||
(setq rustic-indent-method-chain t
|
||
rustic-babel-format-src-block nil
|
||
rustic-format-trigger nil)
|
||
(remove-hook 'rustic-mode-hook #'flycheck-mode)
|
||
(remove-hook 'rustic-mode-hook #'flymake-mode-off)
|
||
(remove-hook 'rustic-mode-hook #'rustic-setup-lsp))
|
||
#+end_src
|
||
|
||
**** Web programming
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-General-Programming-Languages-Web-programming-7ca40po085j0
|
||
:END:
|
||
[[https://emmet.io/][Emmet]] is a powerful templating engine that can generate through simple
|
||
CSS-like expression some HTML so you don’t have to write everything by
|
||
hand.
|
||
#+begin_src emacs-lisp
|
||
(use-package emmet-mode
|
||
:straight (:build t)
|
||
:defer t
|
||
:hook ((css-mode . emmet-mode)
|
||
(html-mode . emmet-mode)
|
||
(web-mode . emmet-mode)
|
||
(sass-mode . emmet-mode)
|
||
(scss-mode . emmet-mode)
|
||
(web-mode . emmet-mode))
|
||
:config
|
||
(general-define-key
|
||
:keymaps 'emmet-mode-keymap
|
||
"M-RET" #'emmet-expand-yas)
|
||
(phundrak/major-leader-key
|
||
:keymaps 'web-mode-map
|
||
:packages '(web-mode emmet-mode)
|
||
"e" '(:ignore t :which-key "emmet")
|
||
"ee" #'emmet-expand-line
|
||
"ep" #'emmet-preview
|
||
"eP" #'emmet-preview-mode
|
||
"ew" #'emmet-wrap-with-markup))
|
||
#+end_src
|
||
|
||
Impatient mode serves web buffers live over HTTP so you can see your
|
||
editions as you type them.
|
||
#+begin_src emacs-lisp
|
||
(use-package impatient-mode
|
||
:straight (:build t)
|
||
:defer t)
|
||
#+end_src
|
||
|
||
Web mode is a sort of hybrid major mode that allows editing several
|
||
languages in the same buffer, mainly HTML, CSS, and Javascript.
|
||
#+begin_src emacs-lisp
|
||
(use-package web-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:hook ((html-mode . web-mode))
|
||
:mode (("\\.phtml\\'" . web-mode)
|
||
("\\.tpl\\.php\\'" . web-mode)
|
||
("\\.twig\\'" . web-mode)
|
||
("\\.xml\\'" . web-mode)
|
||
("\\.html\\'" . web-mode)
|
||
("\\.htm\\'" . web-mode)
|
||
("\\.[gj]sp\\'" . web-mode)
|
||
("\\.as[cp]x?\\'" . web-mode)
|
||
("\\.eex\\'" . web-mode)
|
||
("\\.erb\\'" . web-mode)
|
||
("\\.mustache\\'" . web-mode)
|
||
("\\.handlebars\\'" . web-mode)
|
||
("\\.hbs\\'" . web-mode)
|
||
("\\.eco\\'" . web-mode)
|
||
("\\.ejs\\'" . web-mode)
|
||
("\\.svelte\\'" . web-mode)
|
||
("\\.ctp\\'" . web-mode)
|
||
("\\.djhtml\\'" . web-mode)
|
||
("\\.vue\\'" . web-mode))
|
||
:config
|
||
(csetq web-mode-markup-indent-offset 2
|
||
web-mode-code-indent-offset 2
|
||
web-mode-css-indent-offset 2
|
||
web-mode-style-padding 0
|
||
web-mode-script-padding 0)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'web-mode-map
|
||
:packages 'web-mode
|
||
"=" '(:ignore t :which-key "format")
|
||
"E" '(:ignore t :which-key "errors")
|
||
"El" #'web-mode-dom-errors-show
|
||
"gb" #'web-mode-element-beginning
|
||
"g" '(:ignore t :which-key "goto")
|
||
"gc" #'web-mode-element-child
|
||
"gp" #'web-mode-element-parent
|
||
"gs" #'web-mode-element-sibling-next
|
||
"h" '(:ignore t :which-key "dom")
|
||
"hp" #'web-mode-dom-xpath
|
||
"r" '(:ignore t :which-key "refactor")
|
||
"rc" #'web-mode-element-clone
|
||
"rd" #'web-mode-element-vanish
|
||
"rk" #'web-mode-element-kill
|
||
"rr" #'web-mode-element-rename
|
||
"rw" #'web-mode-element-wrap
|
||
"z" #'web-mode-fold-or-unfold)
|
||
(phundrak/major-leader-key
|
||
:keymaps 'web-mode-map
|
||
:packages '(lsp-mode web-mode)
|
||
"l" '(:keymap lsp-command-map :which-key "lsp")))
|
||
#+end_src
|
||
|
||
Auto-completion for ~emmet-mode~, ~html-mode~, and ~web-mode~.
|
||
#+begin_src emacs-lisp
|
||
(use-package company-web
|
||
:defer t
|
||
:straight (:build t)
|
||
:after (emmet-mode web-mode))
|
||
#+end_src
|
||
|
||
***** CSS
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-General-Programming-Languages-Web-programming-CSS-que40po085j0
|
||
:END:
|
||
Let’s customize a bit the built-in CSS mode.
|
||
#+begin_src emacs-lisp
|
||
(use-package css-mode
|
||
:defer t
|
||
:straight (:type built-in)
|
||
:hook (css-mode . smartparens-mode)
|
||
:hook (css-mode . lsp-deferred)
|
||
:init
|
||
(put 'css-indent-offset 'safe-local-variable #'integerp)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'css-mode-map
|
||
:packages 'css-mode
|
||
"=" '(:ignore :wk "format")
|
||
"g" '(:ignore :wk "goto")))
|
||
#+end_src
|
||
|
||
SCSS is much nicer to use than pure CSS in my opinion, so let’s add a
|
||
mode for that.
|
||
#+begin_src emacs-lisp
|
||
(use-package scss-mode
|
||
:straight (:build t)
|
||
:hook (scss-mode . smartparens-mode)
|
||
:hook (scss-mode . lsp-deferred)
|
||
:defer t
|
||
:mode "\\.scss\\'")
|
||
#+end_src
|
||
|
||
And let’s add some autocompletion for CSS.
|
||
#+begin_src emacs-lisp
|
||
(use-package counsel-css
|
||
:straight (:build t)
|
||
:defer t
|
||
:init
|
||
(cl-loop for (mode-map . mode-hook) in '((css-mode-map . css-mode-hook)
|
||
(scss-mode-map . scss-mode-hook))
|
||
do (add-hook mode-hook #'counsel-css-imenu-setup)
|
||
(phundrak/major-leader-key
|
||
:keymaps mode-map
|
||
"gh" #'counsel-css)))
|
||
#+end_src
|
||
|
||
***** Javascript
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-General-Programming-Languages-Web-programming-Javascript-8k5arup085j0
|
||
:END:
|
||
~javascript-mode~ is meh at best, while ~rjsx-mode~ (Real JSX) is much
|
||
better: it supports both Javascript and ~.jsx~ files for React and
|
||
Next.JS.
|
||
#+begin_src emacs-lisp
|
||
(use-package rjsx-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:after compile
|
||
:mode "\\.[mc]?jsx?\\'"
|
||
:mode "\\.es6\\'"
|
||
:mode "\\.pac\\'"
|
||
:interpreter "node"
|
||
:hook (rjsx-mode . rainbow-delimiters-mode)
|
||
:hook (rjsx-mode . lsp-deferred)
|
||
:init
|
||
(add-to-list 'compilation-error-regexp-alist 'node)
|
||
(add-to-list 'compilation-error-regexp-alist-alist
|
||
'(node "^[[:blank:]]*at \\(.*(\\|\\)\\(.+?\\):\\([[:digit:]]+\\):\\([[:digit:]]+\\)"
|
||
2 3 4))
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'rjsx-mode-map
|
||
:infix "a"
|
||
"" '(:keymap lsp-command-map :which-key "lsp")
|
||
"=" '(:ignore t :wk "format")
|
||
"a" '(:ignore t :which-key "actions"))
|
||
:config
|
||
(setq js-chain-indent t
|
||
js2-basic-offset 2
|
||
;; ignore shebangs
|
||
js2-skip-preprocessor-directives t
|
||
;; Flycheck handles this already
|
||
js2-mode-show-parse-errors nil
|
||
js2-mode-show-strict-warnings nil
|
||
;; conflicting with eslint, Flycheck already handles this
|
||
js2-strict-missing-semi-warning nil
|
||
js2-highlight-level 3
|
||
js2-idle-timer-delay 0.15))
|
||
#+end_src
|
||
~js2-refactor~ is an amazing tool for refactoring Javascript code. I
|
||
mean, [[https://www.youtube.com/watch?v=-7yMWD1wUu4][look at this]]! And the video is only from 2013 and it still
|
||
receives some commits!
|
||
#+begin_src emacs-lisp
|
||
(use-package js2-refactor
|
||
:defer t
|
||
:straight (:build t)
|
||
:after (js2-mode rjsx-mode)
|
||
:hook (js2-mode . js2-refactor-mode)
|
||
:hook (rjsx-mode . js2-refactor-mode))
|
||
#+end_src
|
||
|
||
Which Emacser prefers the command line over Emacs itself? I don’t.
|
||
Let’s interact with NPM through Emacs then.
|
||
#+begin_src emacs-lisp
|
||
(use-package npm-transient
|
||
:defer t
|
||
:straight (npm-transient :build t
|
||
:type git
|
||
:host github
|
||
:repo "Phundrak/npm-transient"))
|
||
;; :general
|
||
;; (phundrak/major-leader-key
|
||
;; :packages '(npm-transient rjsx-mode web-mode)
|
||
;; :keymaps '(rjsx-mode-map web-mode-map)
|
||
;; "n" #'npm-transient))
|
||
#+end_src
|
||
|
||
And finally, here is a formatter for Javascript.
|
||
#+begin_src emacs-lisp
|
||
(use-package prettier-js
|
||
:defer t
|
||
:straight (:build t)
|
||
:after (rjsx-mode web-mode typescript-mode)
|
||
:hook ((rjsx-mode web-mode typescript-mode) . prettier-js-mode)
|
||
:config
|
||
(csetq prettier-js-args '("--single-quote" "--jsx-single-quote")))
|
||
#+end_src
|
||
|
||
***** Typescript
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-General-Programming-Languages-Web-programming-Typescript-o0e8nh30z9j0
|
||
:END:
|
||
Typescript is a safer alternative to Javascript. Let’s install its major mode then.
|
||
#+begin_src emacs-lisp
|
||
(use-package typescript-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:hook (typescript-mode . rainbow-delimiters-mode)
|
||
:hook (typescript-mode . lsp-deferred)
|
||
:hook (typescript-tsx-mode . rainbow-delimiters-mode)
|
||
:hook (typescript-tsx-mode . lsp-deferred)
|
||
:commands typescript-tsx-mode
|
||
:after flycheck
|
||
:init
|
||
(add-to-list 'auto-mode-alist '("\\.tsx\\'" . typescript-tsx-mode))
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages 'lsp
|
||
:keymaps '(typescript-mode-map typescript-tsx-mode-map)
|
||
:infix "a"
|
||
"" '(:keymap lsp-command-map :which-key "lsp")
|
||
"=" '(:ignore t :wk "format")
|
||
"a" '(:ignore t :which-key "actions"))
|
||
(phundrak/major-leader-key
|
||
:packages 'typescript-mode
|
||
:keymaps '(typescript-mode-map typescript-tsx-mode-map)
|
||
"n" '(:keymap npm-mode-command-keymap :which-key "npm"))
|
||
:config
|
||
(with-eval-after-load 'flycheck
|
||
(flycheck-add-mode 'javascript-eslint 'web-mode)
|
||
(flycheck-add-mode 'javascript-eslint 'typescript-mode)
|
||
(flycheck-add-mode 'javascript-eslint 'typescript-tsx-mode)
|
||
(flycheck-add-mode 'typescript-tslint 'typescript-tsx-mode))
|
||
(when (fboundp 'web-mode)
|
||
(define-derived-mode typescript-tsx-mode web-mode "TypeScript-TSX"))
|
||
(autoload 'js2-line-break "js2-mode" nil t))
|
||
#+end_src
|
||
|
||
Tide enabled interactivity with Typescript.
|
||
#+begin_src emacs-lisp
|
||
(use-package tide
|
||
:defer t
|
||
:straight (:build t)
|
||
:hook (tide-mode . tide-hl-identifier-mode)
|
||
:config
|
||
(setq tide-completion-detailed t
|
||
tide-always-show-documentation t
|
||
tide-server-may-response-length 524288
|
||
tide-completion-setup-company-backend nil)
|
||
|
||
(advice-add #'tide-setup :after #'eldoc-mode)
|
||
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:keymaps 'tide-mode-map
|
||
"R" #'tide-restart-server
|
||
"f" #'tide-format
|
||
"rrs" #'tide-rename-symbol
|
||
"roi" #'tide-organize-imports))
|
||
#+end_src
|
||
|
||
**** Zig
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Programming-languages-General-Programming-Languages-Zig-ud8a6q504cj0
|
||
:END:
|
||
[[https://ziglang.org/][Zig]] is to C kind of what Rust is to C++: a modern replacement without
|
||
sacrificing performance. It is much safer than C while providing
|
||
interop with it. Plus, its [[https://ziglang.org/documentation/master/std/][standard library]] is pretty complete.
|
||
|
||
First, here is its major mode.
|
||
#+begin_src emacs-lisp
|
||
(use-package zig-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:after flycheck
|
||
:hook (zig-mode . lsp-deferred)
|
||
:config
|
||
;; This is from DoomEmacs
|
||
(flycheck-define-checker zig
|
||
"A zig syntax checker using the zig-fmt interpreter."
|
||
:command ("zig" "fmt" (eval (buffer-file-name)))
|
||
:error-patterns
|
||
((error line-start (file-name) ":" line ":" column ": error: " (mesage) line-end))
|
||
:modes zig-mode)
|
||
(add-to-list 'flycheck-checkers 'zig)
|
||
:general
|
||
(phundrak/major-leader-key
|
||
:packages 'zig-mode
|
||
:keymaps 'zig-mode-map
|
||
"c" #'zig-compile
|
||
"f" #'zig-format-buffer
|
||
"r" #'zig-run
|
||
"t" #'zig-test-buffer))
|
||
#+end_src
|
||
|
||
For LSP to work, we need ~zls~ to be installed.
|
||
#+begin_src fish :results raw :wrap "src text" :exports code
|
||
paru --skipreview --noconfirm -S zls-bin 2>&1
|
||
#+end_src
|
||
|
||
#+RESULTS:
|
||
#+begin_src text
|
||
:: Resolving dependencies...
|
||
:: Calculating conflicts...
|
||
:: Calculating inner conflicts...
|
||
|
||
Aur (1) zls-bin-0.9.0-2
|
||
|
||
:: Proceed with installation? [Y/n]:
|
||
|
||
:: Downloading PKGBUILDs...
|
||
PKGBUILDs up to date
|
||
fetching devel info...
|
||
==> Making package: zls-bin 0.9.0-2 (2022-01-17T00:43:50 CET)
|
||
==> Retrieving sources...
|
||
-> Found zls-LICENSE.txt
|
||
-> Found zls-bin-0.9.0.tar.gz
|
||
==> Validating source files with sha256sums...
|
||
zls-LICENSE.txt ... Passed
|
||
==> Validating source_x86_64 files with sha256sums...
|
||
zls-bin-0.9.0.tar.gz ... Passed
|
||
==> Making package: zls-bin 0.9.0-2 (2022-01-17T00:43:51 CET)
|
||
==> Checking runtime dependencies...
|
||
==> Checking buildtime dependencies...
|
||
==> Retrieving sources...
|
||
-> Found zls-LICENSE.txt
|
||
-> Found zls-bin-0.9.0.tar.gz
|
||
==> Validating source files with sha256sums...
|
||
zls-LICENSE.txt ... Passed
|
||
==> Validating source_x86_64 files with sha256sums...
|
||
zls-bin-0.9.0.tar.gz ... Passed
|
||
==> Removing existing $srcdir/ directory...
|
||
==> Extracting sources...
|
||
-> Extracting zls-bin-0.9.0.tar.gz with bsdtar
|
||
==> Sources are ready.
|
||
zls-bin-0.9.0-2: parsing pkg list...
|
||
:: zls-bin-0.9.0-2 is up to date -- skipping build
|
||
warning: zls-bin-0.9.0-2 is up to date -- reinstalling
|
||
loading packages...
|
||
resolving dependencies...
|
||
looking for conflicting packages...
|
||
|
||
Package (1) Old Version New Version Net Change
|
||
|
||
zls-bin 0.9.0-2 0.9.0-2 0,00 MiB
|
||
|
||
Total Installed Size: 1,16 MiB
|
||
Net Upgrade Size: 0,00 MiB
|
||
|
||
:: Proceed with installation? [Y/n]
|
||
checking keyring...
|
||
checking package integrity...
|
||
loading package files...
|
||
checking for file conflicts...
|
||
checking available disk space...
|
||
:: Processing package changes...
|
||
reinstalling zls-bin...
|
||
Consider re-running 'zls config' to reconfigure the language server
|
||
|
||
Some options may have changed when upgrading from 0.9.0-2 -> 0.9.0-2
|
||
|
||
Make sure to backup your old configuration!
|
||
:: Running post-transaction hooks...
|
||
(1/2) Arming ConditionNeedsUpdate...
|
||
(2/2) Refreshing PackageKit...
|
||
#+end_src
|
||
|
||
** Visual Configuration
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Visual-Configurationxr86fl6184j0
|
||
:END:
|
||
*** Dashboard
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Visual-Configuration-Dashboardnba6fl6184j0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(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)
|
||
(straight-rebuild-all)))))))
|
||
|
||
(setq dashboard-items '((recents . 15)
|
||
(projects . 10)))
|
||
(dashboard-setup-startup-hook)
|
||
:init
|
||
(add-hook 'after-init-hook 'dashboard-refresh-buffer))
|
||
#+end_src
|
||
|
||
*** Fringe
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Basic-configuration-Visual-Configuration-Fringe-glc9ch1195j0
|
||
:END:
|
||
It’s nice to know which lines were modified since the last commit in a
|
||
file.
|
||
#+begin_src emacs-lisp
|
||
(use-package git-gutter-fringe
|
||
:straight (:build t)
|
||
:hook ((prog-mode . git-gutter-mode)
|
||
(org-mode . git-gutter-mode)
|
||
(markdown-mode . git-gutter-mode)
|
||
(latex-mode . git-gutter-mode)))
|
||
#+end_src
|
||
|
||
*** Icons? Did someone say icons?
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Visual-Configuration-Icons-Did-someone-say-iconsfye6fl6184j0
|
||
:END:
|
||
/*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~ loads 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~.
|
||
#+begin_src emacs-lisp
|
||
(use-package all-the-icons
|
||
:defer t
|
||
:straight t)
|
||
#+end_src
|
||
~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! First, let’s declare the general symbols that
|
||
will be used everywhere.
|
||
#+begin_src emacs-lisp
|
||
|
||
|
||
(defun prog-mode-set-symbols-alist ()
|
||
(setq prettify-symbols-alist '(("lambda" . ?λ)
|
||
("null" . ?∅)
|
||
("NULL" . ?∅)))
|
||
(prettify-symbols-mode 1))
|
||
|
||
(add-hook 'prog-mode-hook #'prog-mode-set-symbols-alist)
|
||
#+end_src
|
||
|
||
We can now take care of the language-specific symbols. First, let’s
|
||
declare some symbols for the Lisp languages.
|
||
#+begin_src emacs-lisp
|
||
(setq-default lisp-prettify-symbols-alist '(("lambda" . ?λ)
|
||
("defun" . ?𝑓)
|
||
("defvar" . ?𝑣)
|
||
("defcustom" . ?𝑐)
|
||
("defconst" . ?𝐶)))
|
||
|
||
(defun lisp-mode-prettify ()
|
||
(setq prettify-symbols-alist lisp-prettify-symbols-alist)
|
||
(prettify-symbols-mode -1)
|
||
(prettify-symbols-mode 1))
|
||
|
||
(dolist (lang '(emacs-lisp lisp common-lisp scheme))
|
||
(add-hook (intern (format "%S-mode-hook" lang))
|
||
#'lisp-mode-prettify))
|
||
#+end_src
|
||
|
||
Finally, similar to how ~org-appear~ behaves, let’s show the real string
|
||
of our symbols when the cursor is on it.
|
||
#+begin_src emacs-lisp
|
||
(setq prettify-symbols-unprettify-at-point t)
|
||
#+end_src
|
||
|
||
*** Ligatures
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Visual-Configuration-Ligatures-2v50x451v8j0
|
||
:END:
|
||
The font I’m using (see
|
||
§[[#Basic-configuration-Visual-Configuration-Fontsxfkjel6184j0]]) supports
|
||
ligatures, but Emacs in GUI mode does not. And of course, there’s a
|
||
package for that.
|
||
#+begin_src emacs-lisp
|
||
(use-package ligature
|
||
:straight (ligature :type git
|
||
:host github
|
||
:repo "mickeynp/ligature.el"
|
||
:build t)
|
||
:config
|
||
(ligature-set-ligatures 't
|
||
'("www"))
|
||
;; Enable traditional ligature support in eww-mode, if the
|
||
;; `variable-pitch' face supports it
|
||
(ligature-set-ligatures '(eww-mode org-mode elfeed-show-mode)
|
||
'("ff" "fi" "ffi"))
|
||
;; Enable all Cascadia Code ligatures in programming modes
|
||
(ligature-set-ligatures 'prog-mode
|
||
'("|||>" "<|||" "<==>" "<!--" "####" "~~>" "***" "||=" "||>"
|
||
":::" "::=" "=:=" "===" "==>" "=!=" "=>>" "=<<" "=/=" "!=="
|
||
"!!." ">=>" ">>=" ">>>" ">>-" ">->" "->>" "-->" "---" "-<<"
|
||
"<~~" "<~>" "<*>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->"
|
||
"<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#_(" "..<"
|
||
"..." "+++" "/==" "///" "_|_" "www" "&&" "^=" "~~" "~@" "~="
|
||
"~>" "~-" "**" "*>" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|"
|
||
"[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:"
|
||
">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<*" "<|" "<:"
|
||
"<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!"
|
||
"##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:"
|
||
"?=" "?." "??" ";;" "/*" "/=" "/>" "//" "__" "~~" "(*" "*)"
|
||
"\\\\" "://"))
|
||
(global-ligature-mode t))
|
||
#+end_src
|
||
|
||
*** Modeline
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Visual-Configuration-Modelineavb6fl6184j0
|
||
:END:
|
||
The DoomEmacs modeline looks nice in my opinion, let’s use it.
|
||
#+begin_src emacs-lisp
|
||
(use-package doom-modeline
|
||
:straight (:build t)
|
||
:defer t
|
||
:init (doom-modeline-mode 1)
|
||
:config
|
||
(csetq doom-modeline-height 15
|
||
doom-modeline-enable-word-count t
|
||
doom-modeline-continuous-word-count-modes '(markdown-mode gfm-mode org-mode)))
|
||
#+end_src
|
||
|
||
*** Pixel-perfect alignment of Markdown and org-mode tables
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Visual-Configuration-Pixel-perfect-alignment-of-Markdown-and-org-mode-tables-u9rjl661bdj0
|
||
:END:
|
||
Usually, I have no issue with the alignment of the tables I write in
|
||
org-mode and (more rarely) Markdown. However, there are occurences
|
||
where I’ll use a character that does not exactly respect my monospace
|
||
font, which messes with the alignment of the table (often when I do
|
||
linguistics stuff). A solution to this is the package ~valign~. A little
|
||
caveat though, as its name implies ~valign~ helps with vertical
|
||
alignment. If some lines are too high, they won’t exactly fit. Unless?
|
||
Unless ~valign-fancy-bar~ is set to ~t~.
|
||
|
||
For now, I disabled the hook with org-mode and markdown-mode because
|
||
it slows down opening these files quite a lot. I’ll re-enable the hook
|
||
once it is fixed.
|
||
#+begin_src emacs-lisp
|
||
(use-package valign
|
||
:defer t
|
||
:straight (:build t)
|
||
:after (org markdown-mode)
|
||
;; :hook ((org-mode markdown-mode) . valign-mode)
|
||
:custom ((valign-fancy-bar t)))
|
||
#+end_src
|
||
|
||
*** Secret mode
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Visual-Configuration-Secret-mode-b2e9hp51v8j0
|
||
:END:
|
||
Sometimes, I want to hide the text displayed by Emacs but not lock
|
||
altogether my computer. In this case, ~secret-mode~ comes in handy.
|
||
#+begin_src emacs-lisp
|
||
(use-package secret-mode
|
||
:defer t
|
||
:straight (secret-mode :build t
|
||
:type git
|
||
:host github
|
||
:repo "bkaestner/secret-mode.el"))
|
||
#+end_src
|
||
|
||
*** Solaire: Incandescent Emacs
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Visual-Configuration-Solaire-Incandescent-Emacs-ah27z1q0obj0
|
||
:END:
|
||
A common issue when you have a lot of windows opened in Emacs is
|
||
sometimes there’s just too much. Is the first window source code? Is
|
||
the other one just an open email? Oh, let’s not forget the ~*Messages*~
|
||
buffer open next to another source buffer.
|
||
|
||
Solaire-mode applies a subtle but useful tweak to your current color
|
||
scheme: the background of programming buffers is slightly lighter than
|
||
the background of other buffers. (Or is it other buffers that have a
|
||
slightly darker background? I’m not sure.)
|
||
#+begin_src emacs-lisp
|
||
(use-package solaire-mode
|
||
:defer t
|
||
:straight (:build t)
|
||
:init (solaire-global-mode +1))
|
||
#+end_src
|
||
|
||
*** Theme
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Visual-Configuration-Themeded6fl6184j0
|
||
:END:
|
||
You may have noticed I use the Nord theme pretty much everywhere on my
|
||
computer, why not Emacs?
|
||
#+begin_src emacs-lisp
|
||
(use-package doom-themes
|
||
:straight (:build t)
|
||
:defer t
|
||
:init (load-theme 'doom-nord t))
|
||
#+end_src
|
||
|
||
*** Rainbow Delimiters
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Visual-Configuration-Rainbow-Delimiters3lg6fl6184j0
|
||
:END:
|
||
This makes Lisp especially more readable, but it’s also nice to have
|
||
for any language that has delimiters like brackets too.
|
||
#+begin_src emacs-lisp
|
||
(use-package rainbow-delimiters
|
||
:straight (:build t)
|
||
:defer t
|
||
:hook (prog-mode . rainbow-delimiters-mode))
|
||
#+end_src
|
||
|
||
*** Y’all want some more /COLORS/?
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Visual-Configuration-Y-all-want-some-more-COLORSs6i6fl6184j0
|
||
:END:
|
||
It is possible to make info buffers much more colorful (and imo easier
|
||
to read) with this simple package:
|
||
#+begin_src emacs-lisp
|
||
(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))
|
||
#+end_src
|
||
|
||
** Misc
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Misc0sj6fl6184j0
|
||
:END:
|
||
*** ArchWiki pages
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Misc-ArchWiki-pages-nha3jhq0r4j0
|
||
:END:
|
||
A small package I’ve written allows the user to view ArchLinux pages
|
||
either in Emacs or in an external web browser. I prefer the defaults.
|
||
#+begin_src emacs-lisp
|
||
(use-package archwiki
|
||
:defer t
|
||
:straight (archwiki :build t
|
||
:type git
|
||
:repo "https://labs.phundrak.com/phundrak/archwiki.el"))
|
||
#+end_src
|
||
|
||
*** ~avy~
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Misc-avyral6fl6184j0
|
||
:END:
|
||
~avy~ is a really convenient way of jumping around and performing
|
||
actions on these selections, but I’ll need some configuration to make
|
||
it bépo-compatible.
|
||
#+begin_src emacs-lisp
|
||
(use-package avy
|
||
:defer t
|
||
:straight t
|
||
:config
|
||
(csetq avy-keys '(?a ?u ?i ?e ?c ?t ?s ?r ?n)
|
||
avy-dispatch-alist '((?x . avy-action-kill-move)
|
||
(?X . avy-action-kill-stay)
|
||
(?T . avy-action-teleport)
|
||
(?m . avy-action-mark)
|
||
(?C . avy-action-copy)
|
||
(?y . avy-action-yank)
|
||
(?Y . avy-action-yank-line)
|
||
(?I . avy-action-ispell)
|
||
(?z . avy-action-zap-to-char)))
|
||
(defun my/avy-goto-url ()
|
||
"Jump to url with avy."
|
||
(interactive)
|
||
(avy-jump "https?://"))
|
||
(defun my/avy-open-url ()
|
||
"Open url selected with avy."
|
||
(interactive)
|
||
(my/avy-goto-url)
|
||
(browse-url-at-point))
|
||
:general
|
||
(phundrak/evil
|
||
:pakages 'avy
|
||
"gc" #'evil-avy-goto-char-timer
|
||
"gl" #'evil-avy-goto-line)
|
||
(phundrak/leader-key
|
||
:packages 'avy
|
||
:infix "j"
|
||
"b" #'avy-pop-mark
|
||
"c" #'evil-avy-goto-char-timer
|
||
"l" #'avy-goto-line)
|
||
(phundrak/leader-key
|
||
:packages 'avy
|
||
:infix "A"
|
||
"c" '(:ignore t :which-key "copy")
|
||
"cl" #'avy-copy-line
|
||
"cr" #'avy-copy-region
|
||
"k" '(:ignore t :which-key "kill")
|
||
"kl" #'avy-kill-whole-line
|
||
"kL" #'avy-kill-ring-save-whole-line
|
||
"kr" #'avy-kill-region
|
||
"kR" #'avy-kill-ring-save-region
|
||
"m" '(:ignore t :which-key "move")
|
||
"ml" #'avy-move-line
|
||
"mr" #'avy-move-region
|
||
"mt" #'avy-transpose-lines-in-region
|
||
"n" #'avy-next
|
||
"p" #'avy-prev
|
||
"u" #'my/avy-goto-url
|
||
"U" #'my/avy-open-url)
|
||
(phundrak/major-leader-key
|
||
:packages '(avy org)
|
||
:keymaps 'org-mode-map
|
||
"A" '(:ignore t :which-key "avy")
|
||
"Ar" #'avy-org-refile-as-child
|
||
"Ah" #'avy-org-goto-heading-timer))
|
||
#+end_src
|
||
|
||
*** Calc
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Misc-Calc3vm6fl6184j0
|
||
:END:
|
||
Let’s give ~calc-mode~ some better defaults.
|
||
#+begin_src emacs-lisp
|
||
(setq calc-angle-mode 'rad
|
||
calc-symbolic-mode t)
|
||
#+end_src
|
||
|
||
*** Elcord
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Misc-Elcord7eo6fl6184j0
|
||
:END:
|
||
What’s the point of using Emacs if you can’t tell everyone?
|
||
#+begin_src emacs-lisp
|
||
(use-package elcord
|
||
:straight (:built t)
|
||
:defer t
|
||
:config
|
||
(csetq elcord-use-major-mode-as-main-icon t
|
||
elcord-refresh-rate 5
|
||
elcord-boring-buffers-regexp-list `("^ "
|
||
,(rx "*" (+ any) "*")
|
||
,(rx bol (or "Re: "
|
||
"Fwd: ")))))
|
||
#+end_src
|
||
|
||
*** Enime
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Misc-Enime-avr3mbs0qcj0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(use-package enime
|
||
:defer t
|
||
:straight (enime :type git
|
||
:host github
|
||
:repo "xl666/enime"
|
||
:fork t
|
||
:build nil)
|
||
:config
|
||
(setq enime-storage-file "~/.cache/enime.db"))
|
||
#+end_src
|
||
|
||
*** ~ivy-quick-find-files.el~
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Misc-ivy-quick-find-files-el2yp6fl6184j0
|
||
:END:
|
||
This package is a small utility package I’ve written in order to
|
||
quickly find files across my filesystem.
|
||
#+begin_src emacs-lisp
|
||
(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"))))
|
||
#+end_src
|
||
|
||
*** Keycast
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Misc-Keycast-nsqgl431t4j0
|
||
:END:
|
||
In case I am sharing my screen with people and I want to show which
|
||
functions are called on my keystrokes since I don’t exactly use
|
||
standard keybindings.
|
||
#+begin_src emacs-lisp
|
||
(use-package keycast
|
||
:defer t
|
||
:straight (:build t)
|
||
:config
|
||
(define-minor-mode keycast-mode
|
||
"Show current command and its key binding in the mode line."
|
||
:global t
|
||
(if keycast-mode
|
||
(add-hook 'pre-command-hook 'keycast--update t)
|
||
(remove-hook 'pre-command-hook 'keycast--update)))
|
||
(add-to-list 'global-mode-string '("" mode-line-keycast " ")))
|
||
#+end_src
|
||
|
||
*** SICP
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Misc-SICP-96u6ukz0l4j0
|
||
:END:
|
||
Who would get interested in Emacs and not want to read the SICP?
|
||
Moreover, inside Emacs?
|
||
#+begin_src emacs-lisp
|
||
(use-package sicp
|
||
:straight (:build t)
|
||
:defer t)
|
||
#+end_src
|
||
|
||
*** Winum
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Misc-Winumvir6fl6184j0
|
||
:END:
|
||
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.
|
||
#+begin_src emacs-lisp
|
||
(use-package winum
|
||
:straight (:build t)
|
||
:init (winum-mode))
|
||
#+end_src
|
||
|
||
*** Ytplay
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Packages-Configuration-Misc-Ytplay-wxm9weq0r4j0
|
||
:END:
|
||
~ytplay~ is a small package I’ve written with which you can choose at
|
||
which resolution to play a YouTube video in an external video player.
|
||
#+begin_src emacs-lisp
|
||
(use-package ytplay
|
||
:defer t
|
||
:straight (ytplay :build t
|
||
:type git
|
||
:repo "https://labs.phundrak.com/phundrak/ytplay.el"))
|
||
#+end_src
|
||
|
||
* Keybindings
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybindings3ps6fl6184j0
|
||
:END:
|
||
Undefining some stuff to make keybind prefixes work correctly.
|
||
#+begin_src emacs-lisp
|
||
(general-define-key
|
||
:keymaps 'global-map
|
||
"<mouse-2>" nil
|
||
"<mouse-3>" nil)
|
||
|
||
(phundrak/evil
|
||
:packages '(counsel)
|
||
"U" #'evil-redo
|
||
"C-a" #'beginning-of-line
|
||
"C-e" #'end-of-line
|
||
"C-y" #'yank
|
||
"M-y" #'counsel-yank-pop)
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(phundrak/leader-key
|
||
"SPC" '(counsel-M-x :wk "M-x")
|
||
"'" #'shell-pop
|
||
|
||
<<general-keybindings-gen(table=keybinds-apps, prefix="a")>>
|
||
<<general-keybindings-gen(table=keybinds-apps-shell, prefix="as")>>
|
||
<<general-keybindings-gen(table=treemacs-keybinds, prefix="at")>>
|
||
|
||
<<general-keybindings-gen(table=keybinds-buffers, prefix="b")>>
|
||
|
||
"c" '(:ignore t :wk "code")
|
||
"cl" #'evilnc-comment-or-uncomment-lines
|
||
|
||
<<keybindings-flycheck>>
|
||
|
||
<<general-keybindings-gen(table=keybinds-files, prefix="f")>>
|
||
<<keybinds-specific-files>>
|
||
|
||
<<general-keybindings-gen(table=keybinds-help, prefix="h")>>
|
||
|
||
"i" '(:ignore t :wk "insert")
|
||
"iu" #'counsel-unicode-char
|
||
|
||
<<general-keybindings-gen(table=keybinds-jump, prefix="j")>>
|
||
|
||
<<general-keybindings-gen(table=keybinds-toggle, prefix="t")>>
|
||
|
||
<<general-keybindings-gen(table=keybinds-text, prefix="T")>>
|
||
|
||
<<general-keybindings-gen(table=keybinds-windows, prefix="w")>>
|
||
|
||
<<general-keybindings-gen(table=keybinds-quit, prefix="q")>>
|
||
|
||
"u" #'universal-argument
|
||
"U" #'undo-tree-visualize)
|
||
#+end_src
|
||
|
||
** Apps
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybindings-Apps-wz2ajup0baj0
|
||
:END:
|
||
Here are my apps keybinds. Each one of them is prefixed by ~a~.
|
||
#+name: keybinds-apps
|
||
| Key | Function | Description |
|
||
|-----+------------------+-------------|
|
||
| | | apps |
|
||
| c | calc | |
|
||
| d | docker | |
|
||
| E | elfeed | |
|
||
| e | | email |
|
||
| ec | mu4e-compose-new | |
|
||
| em | mu4e | |
|
||
| k | keycast-mode | |
|
||
| K | keycast-log-mode | |
|
||
| T | tetris | |
|
||
| w | wttrin | |
|
||
| C | calendar | |
|
||
|
||
I also have two main shell-related functions, prefixed with ~as~.
|
||
#+name: keybinds-apps-shell
|
||
| Key | Function | Description |
|
||
|-----+-------------+-------------|
|
||
| | | shells |
|
||
| e | eshell-new | |
|
||
| v | vterm | |
|
||
| V | multi-vterm | |
|
||
|
||
** Buffers
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybindings-Buffers-rj5ajup0baj0
|
||
:END:
|
||
My buffer-related keybinds are all prefixed by ~b~.
|
||
#+name: keybinds-buffers
|
||
| Key | Function | Description |
|
||
|-----+------------------------------------+-------------|
|
||
| | | buffers |
|
||
| b | bufler-switch-buffer | |
|
||
| B | bury-buffer | |
|
||
| c | clone-indirect-buffer | |
|
||
| C | clone-indirect-buffer-other-window | |
|
||
| l | bufler | |
|
||
| d | kill-this-buffer | |
|
||
| D | kill-buffer | |
|
||
| h | dashboard-refresh-buffer | |
|
||
| m | switch-to-messages-buffer | |
|
||
| n | next-buffer | |
|
||
| p | previous-buffer | |
|
||
| r | counsel-buffer-or-recentf | |
|
||
| s | switch-to-scratch-buffer | |
|
||
|
||
** Errors
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybindings-Email-gv0ltxp0baj0
|
||
:END:
|
||
#+begin_src emacs-lisp
|
||
(defhydra hydra-flycheck
|
||
(:pre (flycheck-list-errors)
|
||
:post (quit-windows-on "*Flycheck errors*")
|
||
:hint nil)
|
||
("f" flycheck-error-list-set-filter "Filter")
|
||
("t" flycheck-next-error "Next")
|
||
("s" flycheck-previous-error "Previous")
|
||
("gg" flycheck-first-error "First")
|
||
("G" (progn (goto-char (point-max)) (flycheck-previous-error)) "Last")
|
||
("q" nil))
|
||
#+end_src
|
||
|
||
#+name: keybindings-flycheck
|
||
#+begin_src emacs-lisp :tangle no
|
||
"e" '(:ignore t :which-key "errors")
|
||
"e." '(hydra-flycheck/body :wk "hydra")
|
||
"el" #'counsel-flycheck
|
||
"ee" '(:keymap flycheck-command-map :wk "flycheck")
|
||
"ef" '(:keymap flyspell-mode-map :wk "flyspell")
|
||
"eF" #'flyspell-hydra/body
|
||
#+end_src
|
||
|
||
** Files
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybindings-Files-9lj2j9q0baj0
|
||
:END:
|
||
My keybinds for file manipulation are prefixed by ~f~.
|
||
#+name: keybinds-files
|
||
| Key | Function | Description |
|
||
|-----+----------------------+-------------|
|
||
| | | files |
|
||
| f | counsel-find-file | |
|
||
| F | ivy-quick-find-files | |
|
||
| h | hexl-find-file | |
|
||
| r | counsel-recentf | |
|
||
| s | save-buffer | |
|
||
|
||
I also have some keybinds dedicated to opening specific files.
|
||
#+name: keybinds-specific-files
|
||
#+begin_src emacs-lisp :tangle no
|
||
"fc" '((lambda ()
|
||
(interactive)
|
||
(find-file "~/org/config/emacs.org"))
|
||
:wk "emacs.org")
|
||
"fi" '((lambda ()
|
||
(interactive)
|
||
(find-file (concat user-emacs-directory "init.el")))
|
||
:which-key "init.el")
|
||
"fR" '((lambda ()
|
||
(interactive)
|
||
(counsel-find-file ""
|
||
(concat user-emacs-directory
|
||
(file-name-as-directory "straight")
|
||
(file-name-as-directory "repos"))))
|
||
:which-key "straight package")
|
||
"fS" '((lambda ()
|
||
(interactive)
|
||
(find-file "~/org/config/stumpwm.org"))
|
||
:which-key "stumpwm.org")
|
||
#+end_src
|
||
|
||
** Help
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybindings-Help-ock4suq0baj0
|
||
:END:
|
||
My keybinds for help are prefixed by ~h~.
|
||
#+name: keybinds-help
|
||
| Key | Function | Description |
|
||
|-----+--------------------------+-------------|
|
||
| | | help |
|
||
| k | which-key-show-top-level | |
|
||
| i | info | |
|
||
| I | info-display-manual | |
|
||
| d | | describe |
|
||
| dc | describe-char | |
|
||
| dC | helpful-command | |
|
||
| df | helpful-callable | |
|
||
| di | describe-input-method | |
|
||
| dk | helpful-key | |
|
||
| dm | helpful-macro | |
|
||
| dM | helpful-mode | |
|
||
| dp | describe-package | |
|
||
| ds | helpful-symbol | |
|
||
| dv | helpful-variable | |
|
||
|
||
** Jump
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybindings-Jump-dt56n1r0baj0
|
||
:END:
|
||
My keybinds for jumping around are prefixed by ~j~.
|
||
#+name: keybinds-jump
|
||
| Key | Function | Description |
|
||
|-----+-------------------------+-------------|
|
||
| | | jump |
|
||
| f | counsel-file-jump | |
|
||
| d | dirvish-dwim | |
|
||
| D | dired-jump-other-window | |
|
||
|
||
** Project
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybindings-Project-yfo8z3r0baj0
|
||
:END:
|
||
My keybinds for my projects are prefixed by ~p~.
|
||
#+name: keybinds-project
|
||
| Key | Function | Description |
|
||
|-----+--------------------------------------------+-------------|
|
||
| | | project |
|
||
| ! | projectile-run-shell-command-in-root | |
|
||
| & | projectile-run-async-shell-command-in-root | |
|
||
| b | counsel-projectile-switch-to-buffer | |
|
||
| c | counsel-projectile | |
|
||
| d | counsel-projectile-find-dir | |
|
||
| e | projectile-edit-dir-locals | |
|
||
| f | counsel-projectile-find-file | |
|
||
| g | projectile-find-tag | |
|
||
| k | project-kill-buffers | |
|
||
| p | counsel-projectile-switch-project | |
|
||
| t | ivy-magit-todos | |
|
||
| v | projectile-vc | |
|
||
|
||
** Text
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybindings-Text-yf0cb4s0baj0
|
||
:END:
|
||
The prefix here is ~T~.
|
||
#+name: keybinds-text
|
||
| Key | Function | Description |
|
||
|-----+----------------------+-------------|
|
||
| | | text |
|
||
| e | string-edit-at-point | |
|
||
| u | downcase-region | |
|
||
| U | upcase-region | |
|
||
| z | hydra-zoom/body | |
|
||
|
||
** Toggles
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybindings-Toggles-9hoj2br0baj0
|
||
:END:
|
||
My toggle keybinds are prefixed by ~t~.
|
||
#+name: keybinds-toggle
|
||
| Key | Function | Description |
|
||
|-----+---------------------------------------+--------------|
|
||
| | | toggles |
|
||
| t | my/modify-frame-alpha-background/body | |
|
||
| T | counsel-load-theme | |
|
||
| d | | debug |
|
||
| de | toggle-debug-on-error | |
|
||
| dq | toggle-debug-on-quit | |
|
||
| i | | input method |
|
||
| it | toggle-input-method | |
|
||
| is | set-input-mode | |
|
||
|
||
** Windows
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybindings-Windows-fbgcper0baj0
|
||
:END:
|
||
A couple of keybinds are hidden from which-key, otherwise there’s not
|
||
much to say. The prefix here is ~w~.
|
||
#+name: keybinds-windows
|
||
| Key | Function | Description |
|
||
|-----+-------------------------------+-------------|
|
||
| | | windows |
|
||
| c | evil-window-left | |
|
||
| t | evil-window-down | |
|
||
| s | evil-window-up | |
|
||
| r | evil-window-right | |
|
||
| . | windows-adjust-size/body | |
|
||
| - | split-window-below-and-focus | |
|
||
| / | split-window-right-and-focus | |
|
||
| $ | winum-select-window-by-number | |
|
||
| 0 | winum-select-window-0-or-10 | none |
|
||
| 1 | winum-select-window-1 | none |
|
||
| 2 | winum-select-window-2 | none |
|
||
| 3 | winum-select-window-3 | none |
|
||
| 4 | winum-select-window-4 | none |
|
||
| 5 | winum-select-window-5 | none |
|
||
| 6 | winum-select-window-6 | none |
|
||
| 7 | winum-select-window-7 | none |
|
||
| 8 | winum-select-window-8 | none |
|
||
| 9 | winum-select-window-9 | none |
|
||
| b | kill-buffer-and-delete-window | |
|
||
| d | delete-window | |
|
||
| o | other-window | |
|
||
| D | delete-other-windows | |
|
||
| w | | writeroom |
|
||
| w. | writeroom-buffer-width/body | |
|
||
| ww | writeroom-mode | |
|
||
|
||
** Quit
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Keybindings-Quit-bs8kh6s0baj0
|
||
:END:
|
||
Why would I ever use any of these keybinds? They are prefixed with ~q~.
|
||
#+name: keybinds-quit
|
||
| Key | Function | Description |
|
||
|-----+----------------------------+-------------|
|
||
| | | quit |
|
||
| f | delete-frame | |
|
||
| q | save-buffers-kill-terminal | |
|
||
| Q | kill-emacs | |
|
||
|
||
* TODOs :noexport:
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Various-TODOsnwt6fl6184j0
|
||
:END:
|
||
** TODO advise ~evil-insert~ in eshell
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: Various-TODOs-advise-evil-insert-in-eshellc4v6fl6184j0
|
||
:END:
|
||
Advise ~evil-insert~ to go to the end of the buffer while in
|
||
~eshell-mode~.
|
||
|
||
** TODO Get started with org-roam
|
||
:PROPERTIES:
|
||
:CUSTOM_ID: TODOs-Get-started-with-org-roam-6w2jh671kij0
|
||
:END:
|