4718 lines
149 KiB
Org Mode
4718 lines
149 KiB
Org Mode
#+title: Emacs configuration
|
||
#+author: Aryadev Chavali
|
||
#+description: My Emacs configuration
|
||
#+property: header-args:emacs-lisp :tangle config.el :comments link :results none
|
||
#+startup: noindent
|
||
#+options: toc:nil num:t
|
||
#+latex_header:\usepackage[margin=1.0in]{geometry}
|
||
#+latex_class: article
|
||
#+latex_class_options: [a4paper,12pt]
|
||
|
||
* Introduction
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle config.el :results none
|
||
:END:
|
||
Welcome to my Emacs configuration. You may be confused by the fact
|
||
it's a readable document with prose; this file serves as both
|
||
documentation *and* code. Here's an example of some Emacs Lisp code:
|
||
|
||
#+begin_src emacs-lisp
|
||
;;; config.el --- Compiled configuration from config.org -*- lexical-binding: t; -*-
|
||
|
||
;; Copyright (C) 2024 Aryadev Chavali
|
||
|
||
;; Author: Aryadev Chavali <aryadev@aryadevchavali.com>
|
||
|
||
;; This program is distributed in the hope that it will be useful, but WITHOUT
|
||
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||
;; FOR A PARTICULAR PURPOSE. See the MIT License for details.
|
||
|
||
;; You may distribute and modify this code under the terms of the MIT License,
|
||
;; which you should have received a copy of along with this program. If not,
|
||
;; please go to <https://opensource.org/license/MIT>.
|
||
|
||
;;; Commentary:
|
||
;; Welcome to my Emacs configuration. This file is considered volatile i.e. any
|
||
;; edits made to this file will be overwritten if and when the configuration is
|
||
;; next compiled.
|
||
|
||
;; To propagate edits from this file back to the literate document, call
|
||
;; (org-babel-detangle).
|
||
;;; Code:
|
||
#+end_src
|
||
|
||
So how does this work? [[file:elisp/literate.el][Literate]] is a
|
||
small package I designed that essentially compiles the document you're
|
||
reading into working Emacs Lisp code for Emacs to load. The literate
|
||
package compiles this document by:
|
||
- concatenating all the Emacs Lisp blocks present in this document
|
||
- writing it to =config.el=,
|
||
Then, when starting Emacs, this =config.el= file is loaded.
|
||
|
||
The prose is completely ignored in the final product, so I can wax
|
||
poetically about anything to my hearts content. This allows the
|
||
document to act as both /source code/ and /documentation/ at once.
|
||
Pretty cool, right? This style of coding is called /literate
|
||
programming/. Donald Knuth
|
||
[[https://en.wikipedia.org/wiki/Literate_programming][really liked]]
|
||
the idea and I see why.
|
||
|
||
Some details about how my project while you'r p
|
||
- The ordering of sections is relevant: packages defined earlier can
|
||
be utilised by later packages
|
||
- Sections tagged with =WAIT= are not compiled into the final document
|
||
(using :PROPERTIES:), usually with some explanation.
|
||
- Some sections are essentially blog posts, so you may just want to
|
||
read the tangled output via ~(org-babel-tangle)~ and going to
|
||
[[file:config.el]].
|
||
* Basics
|
||
Let's setup a few absolute essentials:
|
||
- My name and mail address
|
||
- File encoding (no "\r" characters at the end of lines, please)
|
||
- Where to store backup files (~backup-directory-alist~)
|
||
- Auto refresh buffers when a change occurs (~auto-revert-mode~)
|
||
- Yes or no questions are less painful (~y-or-n-p~)
|
||
- Make the "kill ring" work seamlessly with the clipboard
|
||
- Deleting files or directories "trashes" them instead
|
||
- ... but when going on remote machines via [[info:tramp][tramp]],
|
||
don't try to trash the file to the local machine trash!
|
||
- Font size based on the machine
|
||
- Disable mouse usage where possible
|
||
- Ensure when compiling the Emacs configuration, we only get messages
|
||
for really bad stuff
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package emacs
|
||
:demand t
|
||
:init
|
||
(setq auth-sources '("~/.authinfo.gpg")
|
||
auto-revert-stop-on-user-input nil
|
||
auto-revert-use-notify nil
|
||
auto-revert-verbose nil
|
||
backup-directory-alist `(("." . ,(no-littering-expand-var-file-name "saves/")))
|
||
buffer-file-coding-system 'utf-8-unix
|
||
delete-by-moving-to-trash t
|
||
global-auto-revert-non-file-buffers t
|
||
read-answer-short t
|
||
read-extended-command-predicate #'command-completion-default-include-p
|
||
remote-file-name-inhibit-delete-by-moving-to-trash t
|
||
revert-without-query '(".")
|
||
save-buffer-coding-system 'utf-8-unix
|
||
select-enable-clipboard t
|
||
use-dialog-box nil
|
||
use-file-dialog nil
|
||
use-short-answers t
|
||
user-full-name "Aryadev Chavali"
|
||
user-mail-address "aryadev@aryadevchavali.com"
|
||
warning-minimum-level :error)
|
||
:config
|
||
(global-auto-revert-mode))
|
||
#+end_src
|
||
* Custom functionality and libraries
|
||
This is custom Lisp that I or someone else has written which I really
|
||
need to setup ASAP due to how necessary it is throughout the rest of
|
||
the configuration.
|
||
** dash
|
||
Dash is an external library that provides a ton of Emacs Lisp
|
||
functions that make the language a bit nicer to use.
|
||
#+begin_src emacs-lisp
|
||
(use-package dash
|
||
:straight t
|
||
:demand t)
|
||
#+end_src
|
||
** Procedure
|
||
An anonymous function (~lambda~) which takes no arguments is a
|
||
"procedure". This macro generates procedures, with the parameters of
|
||
the macro being the body of the procedure. The function is returned
|
||
quoted (as data rather than code), as that is the most common use of
|
||
this macro.
|
||
#+begin_src emacs-lisp
|
||
(defmacro proc (&rest BODY)
|
||
"For a given list of forms BODY, return a quoted 0 argument
|
||
lambda."
|
||
`(function (lambda nil ,@BODY)))
|
||
|
||
(defmacro proc-int (&rest BODY)
|
||
"For a given list of forms BODY, return a quoted 0 argument
|
||
lambda with the first form of the lambda being (INTERACTIVE)."
|
||
`(function (lambda nil (interactive) ,@BODY)))
|
||
#+end_src
|
||
** Clean buffer list
|
||
If you've got a particularly long running Emacs instance, as I usually
|
||
do, sometimes you want to clean it all up. Perhaps all of the buffers
|
||
or just the ones in your current project. Here I define a function
|
||
which allows you to do so - ~clean-buffers~.
|
||
|
||
By default, it will kill all buffers associated with the directory of
|
||
your current buffer. Using ~C-u~ will provide a list of directories
|
||
for buffers available, which you can select from. ~C-u C-u~ will kill
|
||
all buffers period.
|
||
|
||
Please note ~clean-buffers-keep~, which defines a list of buffers that
|
||
must be preserved in any form of the operation above.
|
||
|
||
#+begin_src emacs-lisp
|
||
(defconst clean-buffers-keep
|
||
(list "*scratch*" "*dashboard*"
|
||
"*Messages*" "*Warnings*"
|
||
"*eshell*" "*ChatGPT*")
|
||
"List of buffer names to preserve.")
|
||
|
||
(defun --get-dir-or-project-dir (buffer)
|
||
(with-current-buffer buffer
|
||
(expand-file-name
|
||
(if (project-current)
|
||
(project-root (project-current))
|
||
default-directory))))
|
||
|
||
(defun --make-clean-buffer-alist ()
|
||
(cl-loop with assoc-list = nil
|
||
for buffer in (buffer-list)
|
||
for dir = (--get-dir-or-project-dir buffer)
|
||
if (assoc dir assoc-list #'string=)
|
||
do (setf (cdr (assoc dir assoc-list #'string=))
|
||
(cons buffer (cdr (assoc dir assoc-list #'string=))))
|
||
else do (setf assoc-list (cons (list dir buffer) assoc-list))
|
||
finally (return assoc-list)))
|
||
|
||
(defun clean-buffers (&optional arg)
|
||
"Kill all buffers except any with names in CLEAN-BUFFERS-KEEP."
|
||
(interactive "P")
|
||
(let ((buffer-alist (--make-clean-buffer-alist))
|
||
(items nil)
|
||
(should-not-kill
|
||
#'(lambda (buf) (member (buffer-name buf) clean-buffers-keep))))
|
||
|
||
(cond
|
||
((null arg)
|
||
(let ((choice (completing-read "Choose directory to kill: "
|
||
(mapcar #'car buffer-alist)
|
||
nil t)))
|
||
(setq items (cdr (assoc choice buffer-alist #'string=)))))
|
||
((and (listp arg)
|
||
(eq 4 (car arg)))
|
||
(setq items
|
||
(thread-first (current-buffer)
|
||
(--get-dir-or-project-dir )
|
||
(assoc buffer-alist #'string=)
|
||
(cdr))))
|
||
((and (listp arg)
|
||
(eq 16 (car arg)))
|
||
(setq items (buffer-list))))
|
||
|
||
(message "[clean-buffers]: Cleaning %d buffers" (length items))
|
||
(if items
|
||
(thread-last items
|
||
(cl-remove-if should-not-kill)
|
||
(mapc #'kill-buffer)))))
|
||
#+end_src
|
||
** Custom window management
|
||
Emacs has a window management system unlike any other piece of
|
||
software I have ever used, with an ability to be incredibly precise on
|
||
how/where you want buffers to be presented in your Emacs instance.
|
||
Unfortunately, as a result, it is quite complex to use.
|
||
|
||
*** How does window management work?
|
||
The big idea is this table, ~display-buffer-alist~, which associates
|
||
regular expressions with "actions". The regular expressions are for
|
||
the name of buffers, and the actions are how the buffer should be
|
||
displayed.
|
||
|
||
Here's an example record:
|
||
#+begin_src lisp
|
||
'("config.org"
|
||
(display-buffer-in-side-window)
|
||
(side . bottom))
|
||
#+end_src
|
||
|
||
This states that for any buffer named =config.org=, display the buffer
|
||
in a side window (the bottom to be precise). And there are a *lot*
|
||
more ways to display buffers. We'd just need to ~add-to-list~ this to
|
||
~display-buffer-alist~ and that record will take first precedence.
|
||
*** ~:display~ keyword for use-package
|
||
What I want to do is make the process of adding records to
|
||
~display-buffer-alist~ a bit smooter by integrating it into Emacs'
|
||
use-package.
|
||
|
||
2024-04-23: Found this option ~switch-to-buffer-obey-display-actions~
|
||
which makes manual buffer switches obey the same constraints via
|
||
~display-buffer-alist~ as creating the buffer automatically.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package window
|
||
:demand t
|
||
:init
|
||
(setq switch-to-buffer-obey-display-actions nil)
|
||
(with-eval-after-load "use-package-core"
|
||
(add-to-list 'use-package-keywords ':display)
|
||
(defun use-package-normalize/:display (_name-symbol _keyword args)
|
||
"Normalise args for use in handler.
|
||
Don't do anything to the args here."
|
||
args)
|
||
|
||
(defun use-package-handler/:display (name _keyword args rest state)
|
||
(use-package-concat
|
||
(use-package-process-keywords name rest state)
|
||
(mapcar
|
||
#'(lambda (arg)
|
||
`(add-to-list 'display-buffer-alist
|
||
',arg))
|
||
args)))))
|
||
#+end_src
|
||
*** Some ~:display~ records
|
||
Here's some ~:display~ records for buffers that don't really have
|
||
configuration anywhere else in the file. These serve as good examples
|
||
on how to use the keyword.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package window
|
||
:defer t
|
||
:display
|
||
("\\*Process List\\*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25))
|
||
("\\*Async Shell Command\\*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25)))
|
||
#+end_src
|
||
** add-to-list multiple times
|
||
I want to be able to add multiple items to a list in a single
|
||
expression. Here's a macro to do that for me.
|
||
|
||
#+begin_src emacs-lisp
|
||
(defmacro add-multiple-to-list (listvar &rest elements)
|
||
(->> elements
|
||
(mapcar (lambda (el) (list 'cl-pushnew el listvar)))
|
||
(cons 'progn)))
|
||
#+end_src
|
||
** Setting number of native jobs
|
||
Emacs has a native compilation capability to make things /even
|
||
faster/. In [[file:early-init.el][early-init.el]] I set the number of
|
||
native-workers to 4, which isn't necessarily optimal when
|
||
loading/compiling the rest of this file depending on the machine I
|
||
use. On my machines, which have 8 process throughput (4 cores + hyper
|
||
threading), 6-7 workers makes much more sense. On a machine I've
|
||
never used before, 3 seems to be a reasonable default.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package comp
|
||
:init
|
||
(setq native-comp-async-jobs-number
|
||
(pcase (system-name)
|
||
("ravenmaiden" 6)
|
||
(_ 3))))
|
||
#+end_src
|
||
** Proper paths in Emacs
|
||
Imagine you adjust your path in ZSH. This change won't necessarily
|
||
affect the results of ~(getenv "PATH")~ - you'd need to ensure Emacs
|
||
was loaded from a recent ZSH instance. This allows you to synchronise
|
||
the PATH variable with the shell to avoid any silly issues.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package exec-path-from-shell
|
||
:straight t
|
||
:demand t
|
||
:config
|
||
(when (member window-system '(mac ns x))
|
||
(exec-path-from-shell-initialize)))
|
||
#+end_src
|
||
** Reset font size
|
||
|
||
Font size is best left unfixed: depending on the display size and the
|
||
machine, I will usually need to adjust it so it looks just right.
|
||
This function sets the font size using both those variables. It is
|
||
also added to `enable-theme-functions` such that loading a theme will
|
||
forcefully adjust the font size.
|
||
|
||
#+begin_src emacs-lisp
|
||
(defvar +oreo/font-size-alist
|
||
'((1920 140)
|
||
(2560 160)))
|
||
|
||
(defun +oreo/font-reset (&optional _)
|
||
(let ((font-size (or (car (alist-get (display-pixel-width) +oreo/font-size-alist))
|
||
(cadar +oreo/font-size-alist))))
|
||
(set-face-attribute 'default nil :height font-size)
|
||
(set-face-attribute 'mode-line nil :height font-size)))
|
||
|
||
(add-to-list 'enable-theme-functions #'+oreo/font-reset)
|
||
(add-to-list 'after-make-frame-functions #'+oreo/font-reset)
|
||
#+end_src
|
||
* Essential packages
|
||
External and internal packages absolutely necessary for the rest of
|
||
this configuration.
|
||
** General - Bindings package
|
||
What's the point of an editor with no keybindings? Vanilla Emacs has
|
||
the ~bind-key~ function (and the ~bind-key*~ macro) for this, but
|
||
[[*Evil - Vim Emulation][Evil]] has it's own ~evil-define-key~. I'd
|
||
like a unified interface for using both, which is why I use =general=.
|
||
General provides a set of very useful macros for defining keys in a
|
||
variety of different situations. One may redefine any key in any
|
||
keymap, bind over different Evil states, add =which-key=
|
||
documentation, create so-called "definers" which act as wrapper macros
|
||
over some pre-defined configuration, all through one interface.
|
||
|
||
Here I setup the rough outline of how bindings should be made in the
|
||
global scope, namely:
|
||
+ Use "SPC" as a "leader", the root of all general bindings
|
||
+ Use "\" as a local-leader, the root of all major mode specific
|
||
bindings
|
||
+ A few "definers" for the different sub bindings for the leader key
|
||
+ ~nmmap~ macro, for defining keys under both normal and motion
|
||
states.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package general
|
||
:straight t
|
||
:demand t
|
||
:config
|
||
;; General which key definitions for leaders
|
||
(general-def
|
||
:states '(normal motion)
|
||
"SPC" nil
|
||
"\\" nil
|
||
"SPC a" nil
|
||
"SPC b" nil
|
||
"SPC c" nil
|
||
"SPC d" nil
|
||
"SPC f" nil
|
||
"SPC i" nil
|
||
"SPC m" nil
|
||
"SPC r" nil
|
||
"SPC s" nil
|
||
"SPC t" nil
|
||
"SPC q" nil)
|
||
|
||
(general-create-definer leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC")
|
||
|
||
(general-create-definer local-leader
|
||
:states '(normal motion)
|
||
:prefix "\\")
|
||
|
||
(general-create-definer code-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC c")
|
||
|
||
(general-create-definer file-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC f")
|
||
|
||
(general-create-definer shell-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC t")
|
||
|
||
(general-create-definer tab-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC r")
|
||
|
||
(general-create-definer mode-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC m")
|
||
|
||
(general-create-definer app-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC a")
|
||
|
||
(general-create-definer search-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC s")
|
||
|
||
(general-create-definer buffer-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC b")
|
||
|
||
(general-create-definer quit-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC q")
|
||
|
||
(general-create-definer insert-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC i")
|
||
|
||
(general-create-definer dir-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC d")
|
||
|
||
(general-create-definer org-leader
|
||
:states '(normal motion)
|
||
:keymaps 'override
|
||
:prefix "SPC o")
|
||
|
||
(general-create-definer general-nmmap
|
||
:states '(normal motion))
|
||
|
||
(defalias 'nmmap #'general-nmmap)
|
||
|
||
(general-evil-setup t))
|
||
#+end_src
|
||
*** Some binds for Emacs
|
||
Here are some bindings for Emacs using general and the definers
|
||
created previously. Here I bind stuff I don't care to make a separate
|
||
heading for, so it serves as both a dumping ground and as a great
|
||
set of examples on how to use general.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package emacs
|
||
:init
|
||
(setq duplicate-line-final-position -1
|
||
async-shell-command-buffer 'new-buffer)
|
||
:config
|
||
(defmacro +oreo/then-recenter-top (&rest actions)
|
||
`(proc-int ,@actions (recenter 0)))
|
||
:general
|
||
(leader
|
||
"SPC" #'execute-extended-command
|
||
"R" #'revert-buffer
|
||
":" (proc-int (switch-to-buffer "*scratch*"))
|
||
"!" #'async-shell-command
|
||
"h" #'help-command)
|
||
|
||
(mode-leader
|
||
"t" (proc-int (+oreo/load-theme))
|
||
"T" (proc-int (+oreo/switch-theme)))
|
||
|
||
(code-leader
|
||
"F" (proc-int (find-file "~/Code/")))
|
||
|
||
(search-leader
|
||
"i" #'imenu)
|
||
|
||
(file-leader
|
||
"f" #'find-file
|
||
"P" (proc-int
|
||
(find-file (concat user-emacs-directory "config.org")))
|
||
"F" #'find-file-other-window
|
||
"t" #'find-file-other-tab
|
||
"v" #'add-file-local-variable
|
||
"s" #'save-buffer)
|
||
|
||
(insert-leader
|
||
"c" #'insert-char)
|
||
|
||
(dir-leader
|
||
"v" #'add-dir-local-variable)
|
||
|
||
(buffer-leader
|
||
"b" #'switch-to-buffer
|
||
"r" #'rename-buffer
|
||
"d" #'kill-current-buffer
|
||
"c" #'kill-buffer-and-window
|
||
"K" #'kill-buffer
|
||
"j" #'next-buffer
|
||
"k" #'previous-buffer
|
||
"D" #'clean-buffers)
|
||
|
||
(quit-leader
|
||
"d" #'toggle-debug-on-error
|
||
"p" #'straight-pull-package
|
||
"b" #'straight-rebuild-package
|
||
"q" #'save-buffers-kill-terminal
|
||
"c" #'+literate/compile-config
|
||
"C" #'+literate/clean-config
|
||
"l" #'+literate/load-config
|
||
"s" (proc-int (find-file (concat user-emacs-directory "straight/"))))
|
||
|
||
(leader
|
||
:prefix "SPC n"
|
||
"p" #'narrow-to-page
|
||
"f" #'narrow-to-defun
|
||
"r" #'narrow-to-region
|
||
"w" #'widen)
|
||
|
||
;; General normal/motion state maps
|
||
(nmmap
|
||
:keymaps 'override
|
||
"M-'" #'replace-regexp-as-diff
|
||
"M-%" #'query-replace-regexp
|
||
"M-o" #'duplicate-dwim
|
||
"M-;" #'comment-dwim
|
||
"gC" #'comment-dwim
|
||
"g=" #'align-regexp
|
||
"C--" #'text-scale-decrease
|
||
"C-=" #'text-scale-increase
|
||
"C-+" #'text-scale-adjust
|
||
"M-[" (+oreo/then-recenter-top (backward-paragraph))
|
||
"M-]" (+oreo/then-recenter-top (forward-paragraph))
|
||
"M-Y" (proc-int (let ((current (point)))
|
||
(mark-whole-buffer)
|
||
(call-interactively #'copy-region-as-kill)
|
||
(goto-char current)))
|
||
"M-D" (proc-int (mark-whole-buffer)
|
||
(call-interactively #'delete-region)))
|
||
|
||
(:keymaps 'override
|
||
"M-ESC" #'keyboard-quit)
|
||
|
||
(:keymaps 'help-map
|
||
"l" #'find-library))
|
||
#+end_src
|
||
** Evil - Vim emulation
|
||
My editor journey started off with Vim rather than Emacs, so my brain
|
||
has imprinted on its style. Emacs is super extensible so there exists
|
||
a package for porting Vim's modal editing style to Emacs, called Evil
|
||
(Extensible Vi Layer).
|
||
|
||
There are a lot of plugins in Vim that provide greater functionality,
|
||
for example tpope's "vim-surround". Emacs has some of these
|
||
capabilities out of the box, but there are further packages which
|
||
integrate them into Evil. These are setup later in [[*Evil
|
||
additions][Evil additions]].
|
||
|
||
Setup the evil package, with some opinionated settings:
|
||
+ Switch ~evil-upcase~ and ~evil-downcase~ because I use ~evil-upcase~
|
||
more
|
||
+ Use 'gt' prefix as an action for "transposing objects"
|
||
+ Swapping any two textual "objects" seems like a natural thing in
|
||
Vim considering the "verb-object" model most motions follow, but
|
||
by default Vim doesn't have the ability to do so. But Emacs can,
|
||
hence I can set these up.
|
||
+ Allow the Evil cursor to traverse EOLs like the Emacs cursor.
|
||
+ Do not move the cursor when exiting insert mode.
|
||
+ Respect visual lines, allowing movement through them.
|
||
#+begin_src emacs-lisp
|
||
(use-package evil
|
||
:straight t
|
||
:demand t
|
||
:init
|
||
(setq evil-split-window-below t
|
||
evil-vsplit-window-right t
|
||
evil-undo-system #'undo-tree
|
||
evil-move-beyond-eol t
|
||
evil-move-cursor-back nil
|
||
evil-want-abbrev-expand-on-insert-exit t
|
||
evil-want-minibuffer t
|
||
evil-want-keybinding nil
|
||
evil-want-Y-yank-to-eol t
|
||
evil-want-change-word-to-end t
|
||
evil-respect-visual-line-mode nil)
|
||
:config
|
||
(evil-mode)
|
||
(defun +evil/select-pasted ()
|
||
(interactive)
|
||
(evil-goto-mark 91)
|
||
(evil-visual-char)
|
||
(evil-goto-mark 93))
|
||
|
||
:general
|
||
(leader
|
||
"w" #'evil-window-map
|
||
"wt" #'window-swap-states
|
||
"wd" #'evil-window-delete
|
||
"w;" #'make-frame)
|
||
|
||
(nmmap
|
||
"K" #'man
|
||
"TAB" #'evil-jump-item
|
||
"C-p" #'evil-jump-forward
|
||
"#" #'evil-search-word-forward
|
||
"*" #'evil-search-word-backward
|
||
"r" #'evil-replace-state
|
||
"zC" #'hs-hide-level
|
||
"zO" #'hs-show-all
|
||
"M-," #'evil-jump-backward
|
||
"M-." #'evil-jump-forward)
|
||
|
||
(:states '(normal motion visual)
|
||
:keymaps 'override
|
||
"gu" #'evil-upcase
|
||
"gU" #'evil-downcase
|
||
"g C-v" #'+evil/select-pasted
|
||
"M-y" #'yank-pop
|
||
"T" 'nil)
|
||
|
||
(:states '(normal motion visual)
|
||
:keymaps 'override
|
||
:infix "T"
|
||
"w" #'transpose-words
|
||
"c" #'transpose-chars
|
||
"s" #'transpose-sentences
|
||
"p" #'transpose-paragraphs
|
||
"e" #'transpose-sexps
|
||
"l" #'transpose-lines))
|
||
#+end_src
|
||
** Text Completion
|
||
Emacs is a text based interface. Commands generally use textual
|
||
input, operate on text and produce text as output. A quintessential
|
||
command is ~execute-extended-command~, which takes a command name as
|
||
input then executes it. Input is taken from the /minibuffer/.
|
||
|
||
A critical component of this interaction is text completion: given a
|
||
list of options and some user input, try to find an option that best
|
||
fits it. Out of the box, Emacs provides the ~completions-list~ to
|
||
help with selecting an option given some initial input, which can be
|
||
activated in the minibuffer using ~TAB~. This is quite a handy
|
||
interface on its own, but we can do much better.
|
||
|
||
So called "text completion frameworks" remodel the interaction with
|
||
the minibuffer to improve certain aspects of it. Emacs provides two
|
||
such packages out of the box: ido and icomplete. They both eschew the
|
||
~completions-list~, instead providing an incrementally adjusted list
|
||
of results based on the current input within the minibuffer itself.
|
||
IDO only covers a few text based commands, such as ~find-file~, while
|
||
~IComplete~ covers essentially all of them.
|
||
|
||
There are also many, many external packages for this. I used Ivy for
|
||
a few years, partially from the inertia of Doom Emacs. I then moved
|
||
to ~icomplete~, then to ~vertico~. The move to these more minimal
|
||
frameworks come from a similar school of thought as the Unix
|
||
Philosophy, but for Emacs' packages: do one thing and do it well.
|
||
While Ivy is a very good piece of software, certain pieces of
|
||
functionality are done better by standalone packages built for that
|
||
purpose (such as [[*rg][rg]] for searching via ripgrep). ~vertico~
|
||
and ~icomplete~ are packages that only care about the minibuffer and
|
||
making interactions with it more pleasant, and they do a great job at
|
||
that.
|
||
*** Minibuffer
|
||
As described before, the minibuffer is the default text input/output
|
||
mechanism. Here are some basic binds that I need to work effectively
|
||
in it.
|
||
+ By default, the minibuffer is in insert state, with Escape going to
|
||
normal state. M-escape allows quick exits from the minibuffer while
|
||
in insert state
|
||
+ In normal state, escape exits the minibuffer
|
||
+ ~M-{j, k}~ for selecting elements
|
||
+ ~<backtab>~ (shift + TAB) to switch to the completions list
|
||
#+begin_src emacs-lisp
|
||
(use-package minibuffer
|
||
:defer t
|
||
:init
|
||
(setq enable-recursive-minibuffers t
|
||
completion-styles '(basic flex substring)
|
||
completion-category-defaults nil
|
||
completion-category-overrides
|
||
'((file (styles flex partial-completion substring)))
|
||
completion-ignore-case t
|
||
minibuffer-prompt-properties
|
||
'(read-only t intangible t cursor-intangible t face minibuffer-prompt)
|
||
read-file-name-completion-ignore-case t
|
||
read-buffer-completion-ignore-case t)
|
||
:general
|
||
(imap
|
||
:keymaps 'minibuffer-local-map
|
||
"M-<escape>" #'abort-minibuffers)
|
||
(nmap
|
||
:keymaps 'minibuffer-local-map
|
||
"<escape>" #'abort-minibuffers)
|
||
(:states '(normal insert)
|
||
:keymaps 'minibuffer-local-map
|
||
"<backtab>" #'switch-to-completions
|
||
"RET" #'exit-minibuffer
|
||
"M-j" #'next-line-or-history-element
|
||
"M-k" #'previous-line-or-history-element))
|
||
#+end_src
|
||
|
||
I can also save the history of the minibuffer to make it easier to
|
||
replicate previous inputs.
|
||
#+begin_src emacs-lisp
|
||
(use-package savehist
|
||
:defer t
|
||
:config
|
||
(savehist-mode t))
|
||
#+end_src
|
||
*** Completions list
|
||
The list of completions that comes by default with the minibuffer when
|
||
forcing it to complete some input is the completions list. Here I
|
||
just make some binds to make selection easier, if and when I need to
|
||
use it.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package simple
|
||
:defer t
|
||
:display
|
||
("\\*Completions\\*"
|
||
(display-buffer-in-side-window)
|
||
(window-height . 0.3)
|
||
(side . bottom))
|
||
:general
|
||
(nmmap
|
||
:keymaps 'completion-list-mode-map
|
||
"l" #'next-completion
|
||
"h" #'previous-completion
|
||
"q" #'quit-window
|
||
"RET" #'choose-completion
|
||
"<backtab>" #'switch-to-minibuffer)
|
||
:init
|
||
(with-eval-after-load "evil"
|
||
(evil-set-initial-state 'completion-list-mode 'normal)))
|
||
#+end_src
|
||
*** Vertico
|
||
Vertico is a minimalist text completion framework for the minibuffer.
|
||
It's configuration is /so/ similar to IComplete that I essentially
|
||
copy-pasted it, and it does a great job. It's quite fast as well,
|
||
outperforming ~icomplete~ consistently when displaying results.
|
||
#+begin_src emacs-lisp
|
||
(use-package vertico
|
||
:straight t
|
||
:demand t
|
||
:init
|
||
(setq vertico-count 8
|
||
vertico-cycle t
|
||
vertico-grid-min-columns 2
|
||
vertico-grid-max-columns 8)
|
||
:config
|
||
(vertico-mode)
|
||
:general
|
||
(:state '(normal insert)
|
||
:keymaps 'vertico-map
|
||
"M-j" #'vertico-next
|
||
"M-k" #'vertico-previous
|
||
"RET" #'vertico-exit
|
||
"TAB" #'minibuffer-complete
|
||
"M-TAB" #'minibuffer-force-complete
|
||
"SPC" #'self-insert-command
|
||
"DEL" #'vertico-directory-delete-char)
|
||
(:state '(normal insert)
|
||
:keymaps 'vertico-grid-map
|
||
"M-K" #'vertico-grid-scroll-down
|
||
"M-J" #'vertico-grid-scroll-up
|
||
"M-h" #'vertico-grid-left
|
||
"M-l" #'vertico-grid-right))
|
||
#+end_src
|
||
**** Vertico multiform
|
||
This extension to vertico allows one to specialise the behaviour of
|
||
vertico for specific forms.
|
||
#+begin_src emacs-lisp
|
||
(use-package vertico-multiform
|
||
:after vertico
|
||
:init
|
||
(setq vertico-multiform-categories '((t grid)))
|
||
:config
|
||
(vertico-multiform-mode))
|
||
#+end_src
|
||
*** Embark
|
||
I'm very late to the party here - mostly because I didn't see much
|
||
point in this. However, after seeing that [[*empv][empv]] had some
|
||
embark bindings for cool behaviours (such as moving tracks around on
|
||
the live playlist) I had to try it out - and I was not disappointed.
|
||
|
||
~embark-act~ is the entry point to using embark, and you can use it
|
||
basically anywhere to great effect. Searching a buffer via
|
||
~consult-line~? ~execute-extended-command~? Looking for files?
|
||
~embark-act~ will pop up a little buffer full of keybindings specific
|
||
to that context that lower the time it takes to get what you want
|
||
done.
|
||
|
||
A major reason for why this works so well is the actions buffer
|
||
generated by ~embark-act~. You can actually use this buffer
|
||
generation when asking for help after a prefix-key, which I've set
|
||
here. There are also many other little options you can tweak to make
|
||
embark act more like how you wish, which I've barely touch on here.
|
||
#+begin_src emacs-lisp
|
||
(use-package embark
|
||
:after vertico
|
||
:straight t
|
||
:general
|
||
(:keymaps 'override
|
||
"M-/" #'embark-act)
|
||
:display
|
||
("\\*Embark Collect \\(Live\\|Completions\\)\\*"
|
||
nil
|
||
(window-parameters (mode-line-format . none)))
|
||
:init
|
||
(setq embark-verbose-indicator-display-action
|
||
'((display-buffer-in-side-window)
|
||
(side . bottom)
|
||
(window-height . 0.25)
|
||
(window-parameters (mode-line-format . none)))
|
||
embark-prompter 'embark-keymap-prompter
|
||
embark-indicators '(embark-highlight-indicator)
|
||
embark-help-key "?"
|
||
embark-keymap-prompter-key "#"
|
||
prefix-help-command #'embark-prefix-help-command))
|
||
#+end_src
|
||
*** Consult
|
||
Consult provides some improved replacements for certain inbuilt
|
||
functions, and a few extensions as well. If we consider ivy/counsel
|
||
as two separate packages, ivy being the completion framework and
|
||
counsel the extension package using ivy, consult would be the latter.
|
||
Unlike counsel, however, it isn't dependent on any one completion
|
||
framework (it would work with icomplete or ivy) making it more
|
||
extensible and easier to use in different situations.
|
||
|
||
I also add the functionality when using consult-line to support Evil's
|
||
search system.
|
||
#+begin_src emacs-lisp
|
||
(use-package consult
|
||
:straight t
|
||
:init
|
||
(setq consult-preview-excluded-buffers nil
|
||
consult-preview-excluded-files '("\\`/[^/|:]+:")
|
||
consult-preview-key 'any
|
||
consult-ripgrep-args "rg --null --line-buffered --color=never \
|
||
--max-columns=1000 --path-separator / \
|
||
--smart-case --no-heading \
|
||
--with-filename --line-number \
|
||
--search-zip --hidden"
|
||
consult-fd-args "fd --full-path --color=never -H")
|
||
:general
|
||
([remap imenu] #'consult-imenu
|
||
[remap switch-to-buffer] #'consult-buffer
|
||
[remap info] #'consult-info)
|
||
(search-leader
|
||
"s" #'consult-line
|
||
"r" #'consult-ripgrep
|
||
"f" #'consult-fd
|
||
"o" #'consult-org-agenda
|
||
"e" #'consult-compile-error
|
||
"m" #'consult-register)
|
||
:config
|
||
(with-eval-after-load "vertico-multiform"
|
||
(add-multiple-to-list vertico-multiform-commands
|
||
'(consult-buffer grid)
|
||
'(consult-line list)))
|
||
|
||
(defun consult-line-isearch-history (&rest _)
|
||
"Add latest `consult-line' search pattern to the isearch history.
|
||
|
||
This allows n and N to continue the search after `consult-line' exits.
|
||
|
||
From https://jmthornton.net/blog/p/consult-line-isearch-history, taken
|
||
2024-10-10 03:58 BST."
|
||
(when (and (bound-and-true-p evil-mode)
|
||
(eq evil-search-module 'isearch)
|
||
consult--line-history)
|
||
(let* ((pattern (car consult--line-history))
|
||
(regexp (if (string-prefix-p "\\_" pattern)
|
||
(substring pattern 2)
|
||
pattern)))
|
||
(add-to-history 'regexp-search-ring regexp)
|
||
(setq evil-ex-search-pattern (evil-ex-make-pattern regexp t nil))
|
||
(setq evil-ex-search-direction 'forward))))
|
||
|
||
(advice-add #'consult-line :after #'consult-line-isearch-history))
|
||
#+end_src
|
||
*** Orderless
|
||
Orderless sorting method for completion, probably one of the best
|
||
things ever.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package orderless
|
||
:straight t
|
||
:after vertico
|
||
:config
|
||
(cl-pushnew 'orderless completion-styles))
|
||
#+end_src
|
||
*** Company
|
||
Company is the auto complete system I use. I don't like having heavy
|
||
setups for company as it only makes it slower. In this case, just
|
||
setup some evil binds for company.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package company
|
||
:straight t
|
||
:defer t
|
||
:hook
|
||
(prog-mode-hook . company-mode)
|
||
:init
|
||
(setq company-idle-delay nil
|
||
company-minimum-prefix-length 3
|
||
company-require-match nil)
|
||
:general
|
||
(imap
|
||
:keymaps 'company-mode-map
|
||
"C-SPC" #'company-complete
|
||
"C-@" #'company-complete
|
||
"M-j" #'company-select-next
|
||
"M-k" #'company-select-previous))
|
||
#+end_src
|
||
** Hydra
|
||
Hydra is a great package by =abo-abo= (yes the same guy who made ivy
|
||
and swiper). Though not absolutely essential it provides an easy
|
||
interface option for keybindings which enhances their discoverability
|
||
and ease of use. There are two use-package declarations here: one for
|
||
~hydra~ itself, and the other for ~use-package-hydra~ which provides
|
||
the keyword ~:hydra~ in use-package declarations.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package hydra
|
||
:straight t
|
||
:defer t
|
||
:autoload (defhydra))
|
||
|
||
(use-package use-package-hydra
|
||
:straight t
|
||
:demand t)
|
||
|
||
(use-package evil
|
||
:hydra
|
||
(evil-window-resize-hydra
|
||
nil "Resize windows easily."
|
||
("h" #'evil-window-decrease-width)
|
||
("j" #'evil-window-increase-height)
|
||
("k" #'evil-window-decrease-height)
|
||
("l" #'evil-window-increase-width))
|
||
:general
|
||
(leader "wr" #'evil-window-resize-hydra/body))
|
||
#+end_src
|
||
** Project.el
|
||
An out of the box system for managing projects. Where possible we
|
||
should try to use Emacs defaults, so when setting up on a new computer
|
||
it takes a bit less time.
|
||
|
||
Here I:
|
||
+ Bind ~project-prefix-map~ to "<leader>p"
|
||
+ Bind a tags generation command to "<leader>pr"
|
||
+ mimics projectile's one, so I can quickly generate them.
|
||
+ mimicking
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package project
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(:keymaps 'project-prefix-map
|
||
"r" #'+project/generate-tags)
|
||
(leader
|
||
"p" project-prefix-map)
|
||
:config
|
||
(setq project-vc-extra-root-markers '(".project"))
|
||
(defun +project/command (folder)
|
||
(format "ctags -Re -f %sTAGS %s*"
|
||
folder folder))
|
||
|
||
(defun +project/root ()
|
||
(if (project-current)
|
||
(project-root (project-current))
|
||
default-directory))
|
||
|
||
(defun +project/generate-tags ()
|
||
(interactive)
|
||
(set-process-sentinel
|
||
(start-process-shell-command
|
||
"PROJECT-GENERATE-TAGS"
|
||
"*tags*"
|
||
(+project/command (+project/root)))
|
||
(lambda (p event)
|
||
(when (string= event "finished\n")
|
||
(message "Finished generating tags!")
|
||
(visit-tags-table (format "%sTAGS" (+project/root))))))))
|
||
#+end_src
|
||
* Aesthetics
|
||
General look and feel of Emacs, perhaps the most important of all the
|
||
sections here.
|
||
** Themes
|
||
I have both a dark and light theme for differing situations. I wrote
|
||
my own themes by copying stuff I like from other themes then modifying
|
||
them. The dark theme is in
|
||
[[file:elisp/personal-solarized-theme.el][this file]] and the light
|
||
theme is in [[file:elisp/personal-light-theme.el][this file]].
|
||
|
||
By default load with the dark theme, but add bindings to switch to
|
||
other themes in a list.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package custom
|
||
:defer t
|
||
:commands (+oreo/load-theme)
|
||
:hook (after-init-hook . +oreo/load-theme)
|
||
:init
|
||
(setq custom-theme-directory (concat user-emacs-directory "elisp/"))
|
||
(defvar +oreo/theme-list `(personal-solarized leuven))
|
||
(defvar +oreo/theme 0)
|
||
:config
|
||
(defun +oreo/load-theme ()
|
||
"Load `+oreo/theme', disabling all other themes to reduce conflict."
|
||
(mapc #'disable-theme custom-enabled-themes)
|
||
(load-theme (nth +oreo/theme +oreo/theme-list) t))
|
||
|
||
(defun +oreo/switch-theme ()
|
||
"Flip between different themes set in `+oreo/theme-alist'."
|
||
(thread-last (length +oreo/theme-list)
|
||
(mod (+ 1 +oreo/theme))
|
||
(setq +oreo/theme))
|
||
(+oreo/load-theme)))
|
||
#+end_src
|
||
** Startup screen
|
||
The default startup screen is quite bad in all honesty. While for a
|
||
first time user it can be very helpful in running the tutorial and
|
||
finding more about Emacs, for someone who's already configured it
|
||
there isn't much point.
|
||
|
||
The scratch buffer is created at boot. When the splash screen isn't
|
||
enabled, it is the first buffer a user sees. By default, it is in
|
||
~lisp-interaction-mode~, which allows one to prototype Emacs Lisp
|
||
code.
|
||
|
||
I mostly use the scratch buffer to hold snippets of code and to write
|
||
text (usually then copy-pasted into other applications). So
|
||
~text-mode~ is a good fit for that.
|
||
|
||
2024-06-04: I use to load [[*Org mode][org-mode]] in the scratch
|
||
buffer and it added 2 seconds of load time, so let's just use
|
||
fundamental mode and call it a day.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package emacs
|
||
:defer t
|
||
:init
|
||
(setq inhibit-startup-screen t
|
||
inhibit-startup-echo-area-message user-login-name
|
||
initial-major-mode 'text-mode
|
||
initial-scratch-message ""
|
||
ring-bell-function 'ignore)
|
||
:config
|
||
(add-hook 'after-init-hook
|
||
(proc
|
||
(with-current-buffer "*scratch*"
|
||
(goto-char (point-max))
|
||
(thread-last
|
||
(straight-recipes-list)
|
||
length
|
||
(format "Emacs v%s - %s - %s packages\n" emacs-version (emacs-init-time))
|
||
(insert))))))
|
||
#+end_src
|
||
** Cursor and the highlighted line
|
||
Configure the blinking cursor.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package frame
|
||
:defer t
|
||
:init
|
||
(setq blink-cursor-delay 0.2)
|
||
:config
|
||
(blink-cursor-mode -1)
|
||
(global-hl-line-mode))
|
||
#+end_src
|
||
** Better Mode line
|
||
The mode line is the little bar at the bottom of the buffer, just
|
||
above the minibuffer. It can store essentially any text, but
|
||
generally details about the current buffer (such as name, major mode,
|
||
etc) is placed there.
|
||
|
||
The default mode-line is... disgusting. It displays information in an
|
||
unintelligible format and seems to smash together a bunch of
|
||
information without much care for ordering. Most heartbreaking is
|
||
that *anything* can seemingly append new information to it without any
|
||
purview, which is *REALLY* annoying. It can be very overstimulating
|
||
to look at, without even being that immediately informative.
|
||
|
||
I've got a custom Emacs lisp package
|
||
([[file:elisp/better-mode-line.el][here]]) which sets up the default
|
||
mode line as a set of 3 segments: left, centre and right. It pads out
|
||
the mode line with space strings to achieve this.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package better-mode-line
|
||
:load-path "elisp/"
|
||
:demand t
|
||
:init
|
||
(defun +mode-line/evil-state ()
|
||
"Returns either \"E\" if no evil-state is defined or the first character
|
||
of the evil state capitalised"
|
||
(if (bound-and-true-p evil-state)
|
||
(-->
|
||
(format "%s" evil-state)
|
||
(substring it 0 1)
|
||
(upcase it))
|
||
"E"))
|
||
|
||
(setq better-mode-line/left-segment
|
||
'(" " ;; Left padding
|
||
(:eval
|
||
(if (mode-line-window-selected-p)
|
||
'("%l:%c" ;; Line and column count
|
||
" "
|
||
"%p" ;; Percentage into buffer
|
||
"[" ;; Evil state
|
||
(:eval
|
||
(+mode-line/evil-state))
|
||
"]"))))
|
||
better-mode-line/centre-segment
|
||
'("%+" ;; Buffer state (changed or not)
|
||
"%b" ;; Buffer name
|
||
"(" ;; Major mode
|
||
(:eval (format "%s" major-mode))
|
||
")")
|
||
better-mode-line/right-segment
|
||
'((:eval
|
||
(when (mode-line-window-selected-p)
|
||
(if vc-mode ;; Project and Git branch
|
||
vc-mode
|
||
"")))
|
||
mode-line-misc-info ;; Any other information
|
||
(:eval
|
||
(when (and (eq major-mode 'dired-mode)
|
||
(bound-and-true-p dired-rsync-modeline-status)
|
||
(mode-line-window-selected-p))
|
||
(concat " "
|
||
dired-rsync-modeline-status)))
|
||
(:eval ;; Compilation mode errors
|
||
(if (eq major-mode 'compilation-mode)
|
||
compilation-mode-line-errors))
|
||
" " ;; Right padding
|
||
))
|
||
:config
|
||
(better-mode-line/setup-mode-line))
|
||
#+end_src
|
||
** Fringes
|
||
Turning off borders in my window manager was a good idea, so I should
|
||
adjust the borders for Emacs, so called fringes. However, some things
|
||
like [[info:emacs#Compilation Mode][Compilation Mode]] do require
|
||
fringes to provide arrows on the left side of the window. Hence I
|
||
provide a minimal fringe style with only 10 pixels on the left
|
||
provided.
|
||
|
||
#+begin_src emacs-lisp
|
||
(fringe-mode (cons 10 0))
|
||
#+end_src
|
||
** Scrolling
|
||
When scrolling, editors generally try to keep the cursor on screen.
|
||
Emacs has some variables which ensure the cursor is a certain number
|
||
of lines above the bottom of the screen and below the top of the
|
||
screen when scrolling. Here I set the margin to 8 (so it'll start
|
||
correcting at 8 lines) and scroll-conservatively to the same value so
|
||
it'll keep the cursor centred.
|
||
|
||
I also setup the ~pixel-scroll-mode~ to make scrolling nicer looking.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package emacs
|
||
:init
|
||
(setq scroll-conservatively 8
|
||
scroll-margin 8
|
||
scroll-preserve-screen-position t
|
||
pixel-dead-time nil
|
||
pixel-scroll-precision-use-momentum nil
|
||
pixel-resolution-fine-flag t
|
||
fast-but-imprecise-scrolling t)
|
||
:config
|
||
(pixel-scroll-mode t)
|
||
(pixel-scroll-precision-mode t))
|
||
#+end_src
|
||
** Display line numbers
|
||
Line numbers are nice - not for referencing specific lines by hand
|
||
(why not use [[*compile-mode][compile-mode]] or ~M-x goto-line~?) but
|
||
for relative vim motions: for example, d3j deletes 3 lines down and
|
||
having the number of lines directly in front of you can be invaluable.
|
||
|
||
2025-06-02: there's a specific option,
|
||
~display-line-numbers-width-start~, which when set to ~t~
|
||
automatically calculates the maximum width required to display all
|
||
line numbers. This solves all the weird artifacting issues I was
|
||
having with really large documents (such as this one).
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package display-line-numbers
|
||
:defer t
|
||
:hook ((prog-mode-hook text-mode-hook) . display-line-numbers-mode)
|
||
:commands display-line-numbers-mode
|
||
:general
|
||
(mode-leader
|
||
"l" #'display-line-numbers-mode)
|
||
:init
|
||
(setq-default display-line-numbers-type 'relative
|
||
display-line-numbers-width-start t))
|
||
#+end_src
|
||
** Pulsar
|
||
Similar to how [[*Evil goggles][Evil goggles]] highlights Evil
|
||
actions, pulsar provides more highlighting capabilities. Made by my
|
||
favourite Greek philosopher, Prot.
|
||
#+begin_src emacs-lisp
|
||
(use-package pulsar
|
||
:straight t
|
||
:defer t
|
||
:hook (after-init-hook . pulsar-global-mode)
|
||
:init
|
||
(setq pulsar-face 'pulsar-cyan
|
||
pulsar-pulse-functions
|
||
'(next-buffer
|
||
previous-buffer
|
||
fill-paragraph
|
||
drag-stuff-right
|
||
drag-stuff-left
|
||
drag-stuff-up
|
||
drag-stuff-down
|
||
evil-goto-first-line
|
||
evil-goto-line
|
||
evil-scroll-down
|
||
evil-scroll-up
|
||
evil-scroll-page-down
|
||
evil-scroll-page-up
|
||
evil-window-left
|
||
evil-window-right
|
||
evil-window-up
|
||
evil-window-down
|
||
evil-forward-paragraph
|
||
evil-backward-paragraph
|
||
evil-fill-and-move
|
||
evil-join
|
||
evil-avy-goto-char-timer
|
||
evil-avy-goto-line
|
||
org-forward-paragraph
|
||
org-backward-paragraph
|
||
org-fill-paragraph)))
|
||
#+end_src
|
||
** WAIT Zoom
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no :results none
|
||
:END:
|
||
2025-02-14: Though this is quite nice in concept, I find it quite
|
||
distracting with a lot of buffers. Will leave this configuration here
|
||
in case I realise later on I really like this - otherwise, it shall be
|
||
cleaned up.
|
||
|
||
Zoom provides a very useful capability: dynamic resizing of windows
|
||
based on which one is active. I prefer larger font sizes but make it
|
||
too large and it's difficult to have multiple buffers side by side.
|
||
This package allows larger font sizes and still have multiple buffers
|
||
side by side.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package zoom
|
||
:straight t
|
||
:defer t
|
||
:hook (after-init-hook . zoom-mode)
|
||
:init
|
||
(setq zoom-size '(90 . 20)))
|
||
#+end_src
|
||
** Hide mode line
|
||
Custom minor mode to toggle the mode line. Check it out at
|
||
[[file:elisp/hide-mode-line.el][elisp/hide-mode-line.el]].
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package hide-mode-line
|
||
:load-path "elisp/"
|
||
:defer t
|
||
:general
|
||
(mode-leader
|
||
"m" #'global-hide-mode-line-mode))
|
||
#+end_src
|
||
** Olivetti
|
||
Olivetti provides a focus mode for Emacs, which makes it look a bit
|
||
nicer. It uses margins by default and centres using fill-column. I
|
||
actually really like olivetti mode particularly with my [[*Mode
|
||
line][centred mode-line]], so I also define a global minor mode which
|
||
enables it in all but the minibuffer.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package olivetti
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(mode-leader
|
||
"o" #'olivetti-global-mode)
|
||
:init
|
||
(setq-default olivetti-body-width nil
|
||
olivetti-minimum-body-width 100
|
||
olivetti-style nil)
|
||
:config
|
||
(define-globalized-minor-mode olivetti-global-mode olivetti-mode
|
||
(lambda nil (unless (or (minibufferp)
|
||
(string= (buffer-name) "*which-key*"))
|
||
(olivetti-mode 1)))))
|
||
#+end_src
|
||
** All the Icons
|
||
Nice set of icons, for even more emojis.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package all-the-icons
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(insert-leader
|
||
"e" #'all-the-icons-insert))
|
||
#+end_src
|
||
** Pretty symbols
|
||
Prettify symbols mode allows users to declare "symbols" that replace
|
||
text within certain modes. It's eye candy in most cases, but can aid
|
||
comprehension for symbol heavy languages.
|
||
|
||
This configures a ~use-package~ keyword which makes declaring pretty
|
||
symbols for language modes incredibly easy. Checkout my [[*Emacs
|
||
lisp][Emacs lisp]] configuration for an example.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package prog-mode
|
||
:demand t
|
||
:init
|
||
(setq prettify-symbols-unprettify-at-point t)
|
||
:config
|
||
(with-eval-after-load "use-package-core"
|
||
(add-to-list 'use-package-keywords ':pretty)
|
||
(defun use-package-normalize/:pretty (_name-symbol _keyword args)
|
||
args)
|
||
|
||
(defun use-package-handler/:pretty (name _keyword args rest state)
|
||
(use-package-concat
|
||
(use-package-process-keywords name rest state)
|
||
(mapcar
|
||
#'(lambda (arg)
|
||
(let ((mode (car arg))
|
||
(rest (cdr arg)))
|
||
`(add-hook
|
||
',mode
|
||
#'(lambda nil
|
||
(setq prettify-symbols-alist ',rest)
|
||
(prettify-symbols-mode)))))
|
||
args)))))
|
||
#+end_src
|
||
|
||
Here's a collection of keywords and possible associated symbols for
|
||
any prog language of choice. Mostly for reference and copying.
|
||
|
||
#+begin_example
|
||
("null" . "Ø")
|
||
("list" . "ℓ")
|
||
("string" . "𝕊")
|
||
("char" . "ℂ")
|
||
("int" . "ℤ")
|
||
("float" . "ℝ")
|
||
("!" . "¬")
|
||
("for" . "Σ")
|
||
("return" . "≡")
|
||
("reduce" . "↓")
|
||
("map" . "→")
|
||
("some" . "∃")
|
||
("every" . "∃")
|
||
("lambda" . "λ")
|
||
("function" . "ƒ")
|
||
("<=" . "≤")
|
||
(">=" . "≥")
|
||
#+end_example
|
||
* Applications
|
||
Emacs is an operating system, now with a good text editor through
|
||
[[*Evil - Vim emulation][Evil]]. Let's configure some apps for it.
|
||
** Magit
|
||
Magit is *the* git porcelain for Emacs, which perfectly encapsulates
|
||
the git CLI. It's so good that some people use Emacs just for it.
|
||
It's another one of those "so indescribably good you have to try it"
|
||
things. I've hardly touched the Git CLI since getting Magit, and it
|
||
has actively taught me _new_ things about Git.
|
||
|
||
In this case I just need to setup the bindings for it.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package transient
|
||
:straight t)
|
||
|
||
(use-package magit
|
||
:straight t
|
||
:defer t
|
||
:display
|
||
("magit:.*"
|
||
(display-buffer-same-window)
|
||
(inhibit-duplicate-buffer . t))
|
||
("magit-diff:.*"
|
||
(display-buffer-below-selected))
|
||
("magit-log:.*"
|
||
(display-buffer-same-window))
|
||
("magit-revision:.*"
|
||
(display-buffer-below-selected)
|
||
(inhibit-duplicate-buffer . t))
|
||
:general
|
||
(leader
|
||
"g" #'magit-dispatch)
|
||
(code-leader
|
||
"b" #'magit-blame)
|
||
(nmap
|
||
:keymaps 'magit-status-mode-map
|
||
"M-j" #'magit-section-forward-sibling
|
||
"M-k" #'magit-section-backward-sibling)
|
||
:init
|
||
(setq vc-follow-symlinks t
|
||
magit-blame-echo-style 'lines
|
||
magit-copy-revision-abbreviated t
|
||
git-commit-major-mode #'org-mode)
|
||
:config
|
||
(with-eval-after-load "evil"
|
||
(evil-set-initial-state 'magit-status-mode 'motion))
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-magit-setup)))
|
||
#+end_src
|
||
*** Magit Forge
|
||
Imagine being able to do all the bureaucratic nonsense involved on
|
||
GitHub i.e. pull requests, issue handling, etc. all through Emacs! No
|
||
need to imagine any more, with Magit Forge.
|
||
#+begin_src emacs-lisp
|
||
(use-package forge
|
||
:straight t
|
||
:after magit
|
||
:init
|
||
(setq forge-add-default-bindings nil)
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-forge-setup)))
|
||
#+end_src
|
||
** EWW
|
||
Emacs Web Wowser is the inbuilt text based web browser for Emacs. It
|
||
can render images and basic CSS styles but doesn't have a JavaScript
|
||
engine, which makes sense as it's primarily a text interface.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package eww
|
||
:defer t
|
||
:general
|
||
(app-leader
|
||
"w" #'eww)
|
||
(nmmap
|
||
:keymaps 'eww-mode-map
|
||
"w" #'evil-forward-word-begin
|
||
"Y" #'eww-copy-page-url)
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-eww-setup)))
|
||
#+end_src
|
||
** Calendar
|
||
Calendar is a simple inbuilt application that helps with date
|
||
functionalities. I add functionality to copy dates from the calendar
|
||
to the kill ring and bind it to "Y".
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package calendar
|
||
:defer t
|
||
:commands (+calendar/copy-date +calendar/toggle-calendar)
|
||
:display
|
||
("\\*Calendar\\*"
|
||
(display-buffer-at-bottom)
|
||
(inhibit-duplicate-buffer . t)
|
||
(window-height . 0.17))
|
||
:general
|
||
(nmmap
|
||
:keymaps 'calendar-mode-map
|
||
"Y" #'+calendar/copy-date)
|
||
(app-leader
|
||
"d" #'calendar)
|
||
:config
|
||
(defun +calendar/copy-date ()
|
||
"Copy date under cursor into kill ring."
|
||
(interactive)
|
||
(if (use-region-p)
|
||
(call-interactively #'kill-ring-save)
|
||
(let ((date (calendar-cursor-to-date)))
|
||
(when date
|
||
(setq date (encode-time 0 0 0 (nth 1 date) (nth 0 date) (nth 2 date)))
|
||
(kill-new (format-time-string "%Y-%m-%d" date))))))
|
||
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-calendar-setup)))
|
||
#+end_src
|
||
** Mail
|
||
Mail is a funny thing; most people use it just for business or
|
||
advertising and it's come out of use in terms of personal
|
||
communication in the west for the most part (largely due to "social"
|
||
media applications). However, this isn't true for the open source and
|
||
free software movement who heavily use mail for communication.
|
||
|
||
Integrating mail into Emacs helps as I can send source code and
|
||
integrate it into my workflow just a bit better. There are a few
|
||
ways of doing this, both in built and via package.
|
||
*** Notmuch
|
||
Notmuch is an application for categorising some local mail system.
|
||
It's really fast, has tons of customisable functionality and has good
|
||
integration with Emacs. I use ~mbsync~ separately to pull my mail
|
||
from the remote server.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package notmuch
|
||
:straight t
|
||
:defer t
|
||
:commands (notmuch +mail/flag-thread)
|
||
:general
|
||
(app-leader "m" #'notmuch)
|
||
(nmap
|
||
:keymaps 'notmuch-search-mode-map
|
||
"f" #'+mail/flag-thread)
|
||
(nmmap
|
||
:keymaps 'notmuch-hello-mode-map
|
||
"t" #'notmuch-search-by-tag)
|
||
:init
|
||
(defconst +mail/local-dir (no-littering-expand-var-file-name "mail/"))
|
||
(setq notmuch-show-logo nil
|
||
notmuch-search-oldest-first nil
|
||
notmuch-hello-sections '(notmuch-hello-insert-saved-searches
|
||
notmuch-hello-insert-alltags
|
||
notmuch-hello-insert-recent-searches)
|
||
notmuch-archive-tags '("-inbox" "-unread" "+archive")
|
||
message-auto-save-directory +mail/local-dir
|
||
message-directory +mail/local-dir)
|
||
:config
|
||
(defun +mail/flag-thread (&optional unflag beg end)
|
||
(interactive (cons current-prefix-arg (notmuch-interactive-region)))
|
||
(notmuch-search-tag
|
||
(notmuch-tag-change-list '("-inbox" "+flagged") unflag) beg end)
|
||
(when (eq beg end)
|
||
(notmuch-search-next-thread)))
|
||
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-notmuch-setup)))
|
||
#+end_src
|
||
*** Smtpmail
|
||
Setup the smtpmail package, which is used when sending mail. Mostly
|
||
custom configuration for integration with other parts of Emacs' mail
|
||
system.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package smtpmail
|
||
:defer t
|
||
:commands mail-send
|
||
:init
|
||
(setq-default
|
||
smtpmail-smtp-server "mail.aryadevchavali.com"
|
||
smtpmail-smtp-user "aryadev"
|
||
smtpmail-servers-requiring-authorization "mail.aryadevchavali.com"
|
||
smtpmail-smtp-service 587
|
||
smtpmail-stream-type 'starttls
|
||
send-mail-function #'smtpmail-send-it
|
||
message-send-mail-function #'smtpmail-send-it))
|
||
#+end_src
|
||
*** Mail signature using fortune
|
||
Generate a mail signature using the ~fortune~ executable. Pretty
|
||
cool!
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package fortune
|
||
:after message
|
||
:init
|
||
(setq fortune-dir "/usr/share/fortune"
|
||
fortune-file "/usr/share/fortune/cookie")
|
||
:config
|
||
(defvar +mail/signature "---------------\nAryadev Chavali\n---------------\n%s")
|
||
(defun +mail/make-signature ()
|
||
(interactive)
|
||
(format +mail/signature
|
||
(with-temp-buffer
|
||
(let ((fortune-buffer-name (current-buffer)))
|
||
(fortune-in-buffer t)
|
||
(if (bolp) (delete-char -1))
|
||
(buffer-string)))))
|
||
;; (add-hook 'message-setup-hook
|
||
;; (lambda nil (setq message-signature (+mail/make-signature))))
|
||
)
|
||
#+end_src
|
||
** Dired
|
||
Dired: Directory editor for Emacs. An incredibly nifty piece of
|
||
software which deeply integrates with Emacs as a whole. I can't think
|
||
of a better file management tool than this.
|
||
*** Dired Core
|
||
Here I setup dired with a few niceties
|
||
+ Hide details by default (no extra stuff from ~ls~)
|
||
+ Omit dot files by default (using ~dired-omit-mode~)
|
||
+ If I have two dired windows open, moving or copying files in one
|
||
dired instance will automatically target the other dired window
|
||
(~dired-dwim~)
|
||
+ If opening an application on a PDF file, suggest ~zathura~
|
||
+ Examine all the subdirectories within the same buffer
|
||
(~+dired/insert-all-subdirectories~)
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package dired
|
||
:defer t
|
||
:commands (dired find-dired)
|
||
:hook
|
||
(dired-mode-hook . auto-revert-mode)
|
||
(dired-mode-hook . dired-hide-details-mode)
|
||
(dired-mode-hook . dired-omit-mode)
|
||
:init
|
||
(setq-default dired-listing-switches "-AFBlu --group-directories-first"
|
||
dired-omit-files "^\\." ; dotfiles
|
||
dired-omit-verbose nil
|
||
dired-dwim-target t
|
||
dired-recursive-copies 'always
|
||
dired-kill-when-opening-new-dired-buffer t
|
||
dired-deletion-confirmer 'y-or-n-p
|
||
dired-auto-revert-buffer t)
|
||
:general
|
||
(nmmap
|
||
:keymaps 'dired-mode-map
|
||
"SPC" #'nil
|
||
"SPC ," #'nil
|
||
"M-k" #'dired-prev-subdir
|
||
"M-j" #'dired-next-subdir
|
||
"q" #'quit-window
|
||
"j" #'dired-next-line
|
||
"k" #'dired-previous-line
|
||
"(" #'dired-hide-details-mode
|
||
")" #'dired-omit-mode
|
||
"T" #'dired-create-empty-file
|
||
"H" #'dired-up-directory
|
||
"L" #'dired-find-file
|
||
"#" #'dired-flag-auto-save-files
|
||
"." #'dired-clean-directory
|
||
"~" #'dired-flag-backup-files
|
||
"A" #'dired-do-find-regexp
|
||
"C" #'dired-do-copy
|
||
"B" #'dired-do-byte-compile
|
||
"D" #'dired-do-delete
|
||
"M" #'dired-do-chmod
|
||
"O" #'dired-do-chown
|
||
"P" #'dired-do-print
|
||
"Q" #'dired-do-find-regexp-and-replace
|
||
"R" #'dired-do-rename
|
||
"S" #'dired-do-symlink
|
||
"T" #'dired-do-touch
|
||
"X" #'dired-do-shell-command
|
||
"Z" #'dired-do-compress
|
||
"c" #'dired-do-compress-to
|
||
"!" #'dired-do-shell-command
|
||
"&" #'dired-do-async-shell-command
|
||
"{" #'dired-prev-marked-file
|
||
"}" #'dired-next-marked-file
|
||
"%" #'nil
|
||
"%u" #'dired-upcase
|
||
"%l" #'dired-downcase
|
||
"%d" #'dired-flag-files-regexp
|
||
"%g" #'dired-mark-files-containing-regexp
|
||
"%m" #'dired-mark-files-regexp
|
||
"%r" #'dired-do-rename-regexp
|
||
"%C" #'dired-do-copy-regexp
|
||
"%H" #'dired-do-hardlink-regexp
|
||
"%R" #'dired-do-rename-regexp
|
||
"%S" #'dired-do-symlink-regexp
|
||
"%&" #'dired-flag-garbage-files
|
||
"*" #'nil
|
||
"**" #'dired-mark-executables
|
||
"*/" #'dired-mark-directories
|
||
"*@" #'dired-mark-symlinks
|
||
"*%" #'dired-mark-files-regexp
|
||
"*c" #'dired-change-marks
|
||
"*s" #'dired-mark-subdir-files
|
||
"*m" #'dired-mark
|
||
"*t" #'dired-toggle-marks
|
||
"*?" #'dired-unmark-all-files
|
||
"*!" #'dired-unmark-all-marks
|
||
"U" #'dired-unmark-all-marks
|
||
"a" #'dired-find-alternate-file
|
||
"d" #'dired-flag-file-deletion
|
||
"gf" #'browse-url-of-dired-file
|
||
"gr" #'revert-buffer
|
||
"i" #'dired-toggle-read-only
|
||
"J" #'dired-goto-file
|
||
"K" #'dired-do-kill-lines
|
||
"r" #'revert-buffer
|
||
"m" #'dired-mark
|
||
"t" #'dired-toggle-marks
|
||
"u" #'dired-unmark
|
||
"x" #'dired-do-flagged-delete
|
||
"gt" #'dired-show-file-type
|
||
"Y" #'dired-copy-filename-as-kill
|
||
"+" #'dired-create-directory
|
||
"RET" #'dired-find-file
|
||
"C-<return>" #'dired-find-file-other-window
|
||
"o" #'dired-sort-toggle-or-edit
|
||
"[[" #'dired-prev-dirline
|
||
"]]" #'dired-next-dirline
|
||
[remap next-line] #'dired-next-line
|
||
[remap previous-line] #'dired-previous-line
|
||
"zt" #'dired-hide-subdir
|
||
"zC" #'dired-hide-all
|
||
[remap read-only-mode] #'dired-toggle-read-only
|
||
[remap toggle-read-only] #'dired-toggle-read-only
|
||
[remap undo] #'dired-undo
|
||
[remap advertised-undo] #'dired-undo)
|
||
(leader
|
||
"D" #'dired-jump)
|
||
(dir-leader
|
||
"f" #'find-dired
|
||
"d" #'dired
|
||
"D" #'dired-other-window
|
||
"i" #'image-dired
|
||
"b" (proc-int (find-file "~/Text/Books/")))
|
||
(local-leader
|
||
:keymaps 'dired-mode-map
|
||
"i" #'dired-maybe-insert-subdir
|
||
"d" #'dired-goto-subdir
|
||
"I" #'+dired/insert-all-subdirectories
|
||
"o" #'dired-omit-mode
|
||
"K" #'dired-kill-subdir
|
||
"m" #'dired-mark-files-regexp
|
||
"u" #'dired-undo)
|
||
:config
|
||
(add-multiple-to-list dired-guess-shell-alist-user
|
||
'("\\.pdf\\'" "zathura")
|
||
'("\\.epub\\'" "zathura")
|
||
'("\\.jpg\\'" "feh")
|
||
'("\\.png\\'" "feh")
|
||
'("\\.webm\\'" "mpv")
|
||
'("\\.mp[34]\\'" "mpv")
|
||
'("\\.mkv\\'" "mpv"))
|
||
|
||
(defun +dired/--subdirs-not-inserted ()
|
||
(dired-unmark-all-marks)
|
||
(dired-mark-directories nil)
|
||
(let* ((subdirs-inserted (mapcar #'car dired-subdir-alist))
|
||
(subdirs-available (mapcar #'(lambda (x) (concat x "/"))
|
||
(dired-get-marked-files))))
|
||
(dired-unmark-all-marks)
|
||
(cl-remove-if #'(lambda (f) (member f subdirs-inserted)) subdirs-available)))
|
||
|
||
(defun +dired/insert-all-subdirectories (&optional arg)
|
||
"Insert all subdirectories recursively."
|
||
(interactive "P")
|
||
(let ((subdirs-left (+dired/--subdirs-not-inserted)))
|
||
(if (null arg)
|
||
(mapc #'dired-insert-subdir subdirs-left)
|
||
(while subdirs-left
|
||
(mapc #'dired-insert-subdir subdirs-left)
|
||
(setq subdirs-left (+dired/--subdirs-not-inserted)))))))
|
||
#+end_src
|
||
*** image-dired
|
||
Image dired is a little cherry on top for Dired: the ability to look
|
||
through swathes of images in a centralised fashion while still being
|
||
able to do all the usual dired stuff as well is really cool.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package dired
|
||
:defer t
|
||
:init
|
||
(setq image-dired-external-viewer "nsxiv")
|
||
:general
|
||
(nmmap
|
||
:keymaps 'image-dired-thumbnail-mode-map
|
||
"h" #'image-dired-backward-image
|
||
"l" #'image-dired-forward-image
|
||
"j" #'image-dired-next-line
|
||
"k" #'image-dired-previous-line
|
||
"H" #'image-dired-display-previous
|
||
"L" #'image-dired-display-next
|
||
"RET" #'image-dired-display-this
|
||
"m" #'image-dired-mark-thumb-original-file
|
||
"q" #'quit-window))
|
||
#+end_src
|
||
*** fd-dired
|
||
Uses fd for finding file results in a directory: ~find-dired~ ->
|
||
~fd-dired~.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package fd-dired
|
||
:straight t
|
||
:after dired
|
||
:general
|
||
(dir-leader
|
||
"g" #'fd-dired))
|
||
#+end_src
|
||
*** wdired
|
||
Similar to [[*(Rip)grep][wgrep]] =wdired= provides
|
||
the ability to use Emacs motions and editing on file names. This
|
||
makes stuff like mass renaming and other file management tasks way
|
||
easier than even using the mark based system.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package wdired
|
||
:after dired
|
||
:hook (wdired-mode-hook . undo-tree-mode)
|
||
:general
|
||
(nmmap
|
||
:keymaps 'dired-mode-map
|
||
"W" #'wdired-change-to-wdired-mode)
|
||
(nmmap
|
||
:keymaps 'wdired-mode-map
|
||
"ZZ" #'wdired-finish-edit
|
||
"ZQ" #'wdired-abort-changes)
|
||
:config
|
||
(eval-after-load "evil"
|
||
;; 2024-09-07: Why does evil-set-initial-state returning a list of modes for
|
||
;; normal state make eval-after-load evaluate as if it were an actual
|
||
;; expression?
|
||
(progn (evil-set-initial-state 'wdired-mode 'normal)
|
||
nil)))
|
||
#+end_src
|
||
*** dired-rsync
|
||
Rsync is a great way of transferring files around *nix machines, and I
|
||
use dired for all my file management concerns. So I should be able to
|
||
rsync stuff around if I want.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package dired-rsync
|
||
:straight t
|
||
:after dired
|
||
:general
|
||
(nmmap
|
||
:keymaps 'dired-mode-map
|
||
"M-r" #'dired-rsync))
|
||
#+end_src
|
||
** EShell
|
||
*** Why EShell?
|
||
EShell is an integrated shell environment for Emacs, written in Emacs
|
||
Lisp. Henceforth I will argue that it is the best shell/command
|
||
interpreter to use in Emacs, so good that you should eschew any second
|
||
class terminal emulators (~term~, ~shell~, etc).
|
||
|
||
EShell is unlike the other alternatives in Emacs as it's a /shell/
|
||
first, not a terminal emulator (granted, with the ability to spoof
|
||
some aspects of a terminal emulator).
|
||
|
||
The killer benefits of EShell (which would appeal particularly to an
|
||
Emacs user) are a direct consequence of EShell being written in Emacs
|
||
Lisp:
|
||
- strong integration with Emacs utilities (such as ~dired~,
|
||
~find-file~, any read functions, etc)
|
||
- very extensible, easy to write new commands which leverage Emacs
|
||
commands as well as external utilities
|
||
- agnostic of platform: "eshell/cd" will call the underlying change
|
||
directory function for you, so commands will (usually) mean the same
|
||
thing regardless of platform
|
||
- this means as long as Emacs can run on an operating system, one
|
||
may run EShell
|
||
- mixing of Lisp and shell commands, with piping!
|
||
|
||
However, my favourite feature of EShell is the set of evaluators that
|
||
run on command input. Some of the benefits listed above come as a
|
||
consequence of this powerful feature.
|
||
|
||
The main evaluator for any expression for EShell evaluates an
|
||
expression by testing the first symbol against different namespaces.
|
||
The namespaces are ordered such that if a symbol is not found in one,
|
||
the next namespace is tested. These namespaces are:
|
||
- alias (defined in the [[file:.config/eshell/aliases][aliases
|
||
file]])
|
||
- "built-in" command i.e. in the ~eshell/~ namespace of functions
|
||
- external command
|
||
- Lisp function
|
||
|
||
You can direct EShell to use these latter two namespaces: any
|
||
expression delimited by parentheses is considered a Lisp expression,
|
||
and any expression delimited by curly braces is considered an external
|
||
command. You may even pipe the results of one into another, allowing
|
||
a deeper level of integration between Emacs Lisp and the shell!
|
||
*** EShell basics
|
||
Setup some niceties and basics you'd expect from any worthy shell
|
||
interpreter.
|
||
#+begin_src emacs-lisp
|
||
(use-package eshell
|
||
:defer t
|
||
:display
|
||
("\\*eshell\\*"
|
||
(display-buffer-same-window)
|
||
(reusable-frames . t))
|
||
:hook
|
||
(eshell-mode-hook . completion-preview-mode)
|
||
:init
|
||
(evil-set-initial-state 'eshell-mode 'normal)
|
||
|
||
(defun +eshell/banner-message ()
|
||
(concat (shell-command-to-string "fortune | cowsay -r") "\n"))
|
||
|
||
(setq eshell-cmpl-ignore-case t
|
||
eshell-cd-on-directory t
|
||
eshell-cd-shows-directory nil
|
||
eshell-highlight-prompt nil
|
||
eshell-banner-message '(+eshell/banner-message)))
|
||
#+end_src
|
||
*** EShell prompt
|
||
Here I use my external library
|
||
[[file:elisp/eshell-prompt.el][eshell-prompt]], which provides a
|
||
dynamic prompt for EShell. Current features include:
|
||
- Git repository details (with difference from remote and number of
|
||
modified files)
|
||
- A coloured prompt character which changes colour based on the exit
|
||
code of the previous command
|
||
|
||
NOTE: I don't defer this package because it doesn't use any EShell
|
||
internals without autoloading.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package eshell-prompt
|
||
:load-path "elisp/"
|
||
:config
|
||
(setq eshell-prompt-function #'eshell-prompt/make-prompt))
|
||
#+end_src
|
||
*** EShell additions
|
||
Using my external library
|
||
[[file:elisp/eshell-additions.el][eshell-additions]], I get a few new
|
||
internal EShell commands and a command to open EShell at the current
|
||
working directory.
|
||
|
||
I use these commands in my local bindings for EShell so I also setup
|
||
those here - they need to be hooked into the mode initialisation
|
||
because of how EShell works unfortunately.
|
||
|
||
NOTE: I don't defer this package because it must be loaded *before*
|
||
EShell is. This is because any ~eshell/*~ functions need to be loaded
|
||
before launching it.
|
||
#+begin_src emacs-lisp
|
||
(use-package eshell-additions
|
||
:demand t
|
||
:load-path "elisp/"
|
||
:config
|
||
;; FIXME: Why do I need to double load this? Otherwise +eshell/open doesn't
|
||
;; work as intended when using universal argument.
|
||
(load-file (concat user-emacs-directory "elisp/eshell-additions.el"))
|
||
|
||
(add-hook
|
||
'eshell-mode-hook
|
||
(defun +eshell/--setup-keymap nil
|
||
(interactive)
|
||
(general-def
|
||
:states '(normal insert visual)
|
||
:keymaps 'eshell-mode-map
|
||
"M-j" #'eshell-next-prompt
|
||
"M-k" #'eshell-previous-prompt
|
||
"C-j" #'eshell-next-matching-input-from-input
|
||
"C-k" #'eshell-previous-matching-input-from-input)
|
||
|
||
(local-leader
|
||
:keymaps 'eshell-mode-map
|
||
"g" (proc-int
|
||
(let ((buffer (current-buffer)))
|
||
(eshell/goto)
|
||
(with-current-buffer buffer
|
||
(eshell-send-input))))
|
||
"l" (proc-int (eshell-send-command "ls"))
|
||
"c" (proc-int (eshell-send-command "clear"))
|
||
"k" #'eshell-kill-process)))
|
||
|
||
:general
|
||
(shell-leader
|
||
"t" #'+eshell/open)
|
||
(leader
|
||
"T" #'+eshell/at-cwd
|
||
"E" #'eshell-command))
|
||
#+end_src
|
||
*** EShell syntax highlighting
|
||
This package external package adds syntax highlighting to EShell
|
||
(disabling it for remote work). Doesn't require a lot of config
|
||
thankfully.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package eshell-syntax-highlighting
|
||
:straight t
|
||
:after eshell
|
||
:hook (eshell-mode-hook . eshell-syntax-highlighting-mode))
|
||
#+end_src
|
||
** WAIT VTerm
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no :results none
|
||
:END:
|
||
2025-02-17: I haven't used this in at least 1.5 years. Why would I
|
||
use this when I can:
|
||
+ Use [[*EShell][EShell]]
|
||
+ Use ~async-shell-command~
|
||
+ Just spawn a terminal like a normie
|
||
|
||
There are a few times when EShell doesn't cut it, particularly in the
|
||
domain of TUI applications like ~cfdisk~. Emacs comes by default with
|
||
some terminal emulators that can run a system wide shell like SH or
|
||
ZSH (~shell~ and ~term~ for example), but they're pretty terrible.
|
||
~vterm~ is an external package using a shared library for terminal
|
||
emulation, and is much better than the default Emacs stuff.
|
||
|
||
Since my ZSH configuration enables vim emulation, using ~evil~ on top
|
||
of it would lead to some weird states. Instead, use the Emacs state
|
||
so vim emulation is completely controlled by the shell.
|
||
#+begin_src emacs-lisp
|
||
(use-package vterm
|
||
:straight t
|
||
:general
|
||
(shell-leader
|
||
"v" #'vterm)
|
||
:init
|
||
(with-eval-after-load "evil"
|
||
(evil-set-initial-state 'vterm-mode 'emacs)))
|
||
#+end_src
|
||
** (Rip)grep
|
||
Grep is a great piece of software, a necessary tool in any Linux
|
||
user's inventory. Out of the box Emacs has a family of functions
|
||
utilising grep which present results in a
|
||
[[*Compilation][compilation]] buffer: ~grep~ searches files, ~rgrep~
|
||
searches files in a directory using the ~find~ program and ~zgrep~
|
||
searches archives.
|
||
|
||
Ripgrep is a program that attempts to perform better than grep, and it
|
||
does. This is because of many optimisations, such as reading
|
||
=.gitignore= to exclude certain files from being searched. The
|
||
ripgrep package provides utilities to search projects and files. Of
|
||
course, this requires installing the rg binary which is available in
|
||
most distribution nowadays.
|
||
*** Grep
|
||
#+begin_src emacs-lisp
|
||
(use-package grep
|
||
:defer t
|
||
:display
|
||
("^\\*grep.*"
|
||
(display-buffer-reuse-window display-buffer-at-bottom)
|
||
(window-height . 0.35)
|
||
(reusable-frames . t))
|
||
:general
|
||
(search-leader
|
||
"g" #'grep-this-file
|
||
"d" #'rgrep)
|
||
(nmmap
|
||
:keymaps 'grep-mode-map
|
||
"0" #'evil-beginning-of-line
|
||
"q" #'quit-window
|
||
"i" #'wgrep-change-to-wgrep-mode
|
||
"c" #'recompile)
|
||
(nmmap
|
||
:keymaps 'wgrep-mode-map
|
||
"q" #'evil-record-macro
|
||
"ZZ" #'wgrep-finish-edit
|
||
"ZQ" #'wgrep-abort-changes)
|
||
:config
|
||
;; Without this wgrep doesn't work properly
|
||
(evil-set-initial-state 'grep-mode 'normal)
|
||
|
||
(defmacro grep-file (query filename)
|
||
`(grep (format "grep --color=auto -nIiHE --null -e \"%s\" %s"
|
||
,query ,filename)))
|
||
|
||
(defun grep-this-file ()
|
||
(interactive)
|
||
(let ((query (read-string "Search for: "))
|
||
(filename (or (buffer-file-name (current-buffer))
|
||
(let ((temp-file (make-temp-file "temp-grep")))
|
||
(write-region (point-min) (point-max) temp-file)
|
||
temp-file))))
|
||
(grep-file query filename))))
|
||
#+end_src
|
||
*** rg
|
||
#+begin_src emacs-lisp
|
||
(use-package rg
|
||
:straight t
|
||
:defer t
|
||
:commands (+rg/project-todo)
|
||
:display
|
||
("^\\*\\*ripgrep\\*\\*"
|
||
(display-buffer-reuse-window display-buffer-at-bottom)
|
||
(window-height . 0.35))
|
||
:general
|
||
(search-leader
|
||
"R" #'rg-menu)
|
||
(:keymaps 'project-prefix-map
|
||
"t" #'+rg/project-todo)
|
||
(nmmap
|
||
:keymaps 'rg-mode-map
|
||
"c" #'rg-recompile
|
||
"C" #'rg-rerun-toggle-case
|
||
"]]" #'rg-next-file
|
||
"[[" #'rg-prev-file
|
||
"q" #'quit-window
|
||
"i" #'wgrep-change-to-wgrep-mode)
|
||
:init
|
||
(setq rg-group-result t
|
||
rg-hide-command t
|
||
rg-show-columns nil
|
||
rg-show-header t
|
||
rg-custom-type-aliases nil
|
||
rg-default-alias-fallback "all"
|
||
rg-buffer-name "*ripgrep*")
|
||
:config
|
||
(defun +rg/project-todo ()
|
||
(interactive)
|
||
(rg "TODO|WIP|FIXME" "*"
|
||
(if (project-current)
|
||
(project-root (project-current))
|
||
default-directory)))
|
||
(evil-set-initial-state 'rg-mode 'normal))
|
||
#+end_src
|
||
** Elfeed
|
||
Elfeed is the perfect RSS feed reader, integrated into Emacs
|
||
perfectly. I've got a set of feeds that I use for a large variety of
|
||
stuff, mostly media and entertainment. I've also bound "<leader> ar"
|
||
to elfeed for loading the system.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package elfeed
|
||
:straight t
|
||
:general
|
||
(app-leader "r" #'elfeed)
|
||
(nmmap
|
||
:keymaps 'elfeed-search-mode-map
|
||
"gr" #'elfeed-update
|
||
"s" #'elfeed-search-live-filter
|
||
"<return>" #'elfeed-search-show-entry)
|
||
(nmmap
|
||
:keymaps '(elfeed-search-mode-map elfeed-show-mode-map)
|
||
"M-RET" #'elfeed-dispatch)
|
||
:init
|
||
(setq elfeed-db-directory (no-littering-expand-var-file-name "elfeed/"))
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-elfeed-setup))
|
||
|
||
(defvar +elfeed/dispatch-options
|
||
'(("Yank URL" .
|
||
(lambda (url)
|
||
(kill-new url)
|
||
(message "elfeed-dispatch: Yanked %s" url)))
|
||
("Open via EWW" . eww)
|
||
("Play via EMPV" .
|
||
(lambda (url)
|
||
(if (member 'empv features)
|
||
;; FIXME: Using internal macro
|
||
(empv--with-video-enabled
|
||
(empv-play-or-enqueue url))
|
||
(message "elfeed-dispatch: EMPV is not available")))))
|
||
"Options available on entering an elfeed post.")
|
||
|
||
(defun elfeed-dispatch ()
|
||
"Provide some extra options once you've clicked on an article."
|
||
(interactive)
|
||
(if (not (or elfeed-show-entry (eq major-mode 'elfeed-search-mode)))
|
||
(user-error "elfeed-dispatch: Not in an elfeed post."))
|
||
(let ((choice (completing-read "Choose action: " (mapcar #'car +elfeed/dispatch-options)))
|
||
(url (elfeed-entry-link (if elfeed-show-entry
|
||
elfeed-show-entry
|
||
(elfeed-search-selected :ignore-region)))))
|
||
(if-let ((option (cdr (assoc choice +elfeed/dispatch-options #'string=))))
|
||
(funcall option url)))))
|
||
#+end_src
|
||
*** Elfeed-org
|
||
A small self-written package to load an org file as a set of elfeed
|
||
feeds.
|
||
#+begin_src emacs-lisp
|
||
(use-package elfeed-org
|
||
:load-path "elisp/"
|
||
:after elfeed
|
||
:init
|
||
(setq elfeed-org/file (concat org-directory "feeds.org"))
|
||
:config
|
||
(elfeed-org))
|
||
#+end_src
|
||
** IBuffer
|
||
IBuffer is the dired of buffers. Nothing much else to be said.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package ibuffer
|
||
:defer t
|
||
:general
|
||
(buffer-leader
|
||
"i" #'ibuffer)
|
||
:init
|
||
(setq ibuffer-formats
|
||
'((mark modified read-only locked
|
||
" " (name 40 40 :left :elide)
|
||
" " (size 8 -1 :right)
|
||
" " (mode 18 18 :left :elide) " " filename-and-process)
|
||
(mark " " (name 16 -1) " " filename)))
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-ibuffer-setup)))
|
||
#+end_src
|
||
** Proced
|
||
Emacs has two systems for process management:
|
||
+ proced: a general 'top' like interface which allows general
|
||
management of linux processes
|
||
+ list-processes: a specific Emacs based system that lists processes
|
||
spawned by Emacs (similar to a top for Emacs specifically)
|
||
|
||
Core Proced config, just a few bindings and evil collection setup.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package proced
|
||
:defer t
|
||
:general
|
||
(app-leader
|
||
"p" #'proced)
|
||
(nmap
|
||
:keymaps 'proced-mode-map
|
||
"za" #'proced-toggle-auto-update)
|
||
:display
|
||
("\\*Proced\\*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25))
|
||
:init
|
||
(setq proced-auto-update-interval 5)
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-proced-setup)))
|
||
#+end_src
|
||
** Calculator
|
||
~calc-mode~ is a calculator system within Emacs that provides a
|
||
diverse array of mathematical operations. It uses reverse polish
|
||
notation, but there is a standard infix algebraic notation mode so
|
||
don't be too shocked. It can do a surprising amount of stuff, such
|
||
as:
|
||
+ finding derivatives/integrals of generic equations
|
||
+ matrix operations
|
||
+ finding solutions for equations, such as for finite degree multi
|
||
variable polynomials
|
||
|
||
Perhaps most powerful is ~embedded-mode~. This allows one to perform
|
||
computation within a non ~calc-mode~ buffer. Surround any equation
|
||
with dollar signs and call ~(calc-embedded)~ with your cursor on it to
|
||
compute it. It'll replace the equation with the result it computed.
|
||
This is obviously incredibly useful; I don't even need to leave the
|
||
current buffer to perform some quick mathematics in it.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package calc
|
||
:defer t
|
||
:display
|
||
("*Calculator*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.2))
|
||
:general
|
||
(app-leader
|
||
"c" #'calc-dispatch)
|
||
:init
|
||
(setq calc-algebraic-mode t)
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-calc-setup)))
|
||
#+end_src
|
||
** Zone
|
||
Emacs' out of the box screensaver software.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package zone
|
||
:defer t
|
||
:commands (zone)
|
||
:general
|
||
(leader
|
||
"z" #'zone)
|
||
:init
|
||
|
||
(setq zone-programs
|
||
[zone-pgm-drip
|
||
zone-pgm-drip-fretfully]))
|
||
#+end_src
|
||
** (Wo)man
|
||
Man pages are the user manuals for most software on Linux. Of course,
|
||
Emacs comes out of the box with a renderer for man pages and some
|
||
searching capabilities.
|
||
|
||
2023-08-17: `Man-notify-method' is the reason the `:display' record
|
||
doesn't work here. I think it's to do with how Man pages are rendered
|
||
or something, but very annoying as it's a break from standards!
|
||
|
||
2024-10-08: Man pages are rendered via a separate process, which is
|
||
why this is necessary.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package man
|
||
:defer t
|
||
:init
|
||
(setq Man-notify-method 'thrifty)
|
||
:display
|
||
("\\*Man.*"
|
||
(display-buffer-reuse-mode-window display-buffer-same-window)
|
||
(mode . Man-mode))
|
||
:general
|
||
(file-leader
|
||
"m" #'man) ;; kinda like "find man page"
|
||
(nmmap
|
||
:keymaps 'Man-mode-map
|
||
"RET" #'man-follow))
|
||
#+end_src
|
||
** Info
|
||
Info is GNU's attempt at better man pages. Most Emacs packages have
|
||
info pages so I'd like nice navigation options.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package info
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
:keymaps 'Info-mode-map
|
||
"h" #'evil-backward-char
|
||
"k" #'evil-previous-line
|
||
"l" #'evil-forward-char
|
||
"H" #'Info-history-back
|
||
"L" #'Info-history-forward
|
||
"C-j" #'Info-forward-node
|
||
"C-k" #'Info-backward-node
|
||
"RET" #'Info-follow-nearest-node
|
||
"m" #'Info-menu
|
||
"C-o" #'Info-history-back
|
||
"s" #'Info-search
|
||
"S" #'Info-search-case-sensitively
|
||
"i" #'Info-index
|
||
"a" #'info-apropos
|
||
"gj" #'Info-next
|
||
"gk" #'Info-prev
|
||
"g?" #'Info-summary
|
||
"q" #'quit-window)
|
||
:init
|
||
(with-eval-after-load "evil"
|
||
(evil-set-initial-state 'Info-mode 'normal)))
|
||
#+end_src
|
||
** Image-mode
|
||
Image mode, for viewing images. Supports tons of formats, easy to use
|
||
and integrates slickly into image-dired. Of course,
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package image-mode
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
:keymaps 'image-mode-map
|
||
"q" #'quit-window
|
||
;; motion
|
||
"gg" 'image-bob
|
||
"G" 'image-eob
|
||
[remap evil-forward-char] 'image-forward-hscroll
|
||
[remap evil-backward-char] 'image-backward-hscroll
|
||
[remap evil-next-line] 'image-next-line
|
||
[remap evil-previous-line] 'image-previous-line
|
||
"0" 'image-bol
|
||
"^" 'image-bol
|
||
"$" 'image-eol
|
||
(kbd "C-d") 'image-scroll-up
|
||
(kbd "SPC") 'image-scroll-up
|
||
(kbd "S-SPC") 'image-scroll-down
|
||
(kbd "<delete>") 'image-scroll-down
|
||
;; animation
|
||
(kbd "RET") 'image-toggle-animation
|
||
"F" 'image-goto-frame
|
||
"," 'image-previous-frame ; mplayer/mpv style
|
||
"." 'image-next-frame ; mplayer/mpv style
|
||
";" 'image-next-frame ; Evil style
|
||
"{" 'image-decrease-speed ; mplayer/mpv style
|
||
"}" 'image-increase-speed ; mplayer/mpv style
|
||
|
||
"H" 'image-transform-fit-to-height
|
||
"W" 'image-transform-fit-to-width
|
||
|
||
"+" 'image-increase-size
|
||
"=" 'image-increase-size
|
||
"-" 'image-decrease-size
|
||
|
||
"[[" 'image-previous-file
|
||
"]]" 'image-next-file
|
||
"gk" 'image-previous-file
|
||
"gj" 'image-next-file
|
||
(kbd "C-k") 'image-previous-file
|
||
(kbd "C-j") 'image-next-file
|
||
|
||
(kbd "C-c C-c") 'image-toggle-display
|
||
|
||
;; quit
|
||
"q" 'quit-window
|
||
"ZQ" 'evil-quit
|
||
"ZZ" 'quit-window))
|
||
#+end_src
|
||
** empv
|
||
Emacs MPV bindings, with very cool controls for queuing files for
|
||
playing.
|
||
#+begin_src emacs-lisp
|
||
(use-package empv
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(app-leader
|
||
"e" #'empv-hydra/body)
|
||
:init
|
||
(setq empv-audio-dir (list (expand-file-name "~/Media/music"))
|
||
empv-video-dir (list (expand-file-name "~/Media/videos")
|
||
(expand-file-name "~/Media/anime"))
|
||
empv-playlist-dir (expand-file-name "~/Media/playlists")
|
||
empv-audio-file-extensions (list "mp3" "ogg" "wav" "m4a" "flac" "aac" "opus")
|
||
empv-video-file-extensions (list "mkv" "mp4" "avi" "mov" "webm")
|
||
empv-radio-channels
|
||
'(("SomaFM - Groove Salad" . "http://www.somafm.com/groovesalad.pls")
|
||
("SomaFM - Drone Zone" . "http://www.somafm.com/dronezone.pls")
|
||
("SomaFM - Sonic Universe" . "http://www.somafm.com/sonicuniverse.pls")
|
||
("SomaFM - Metal" . "http://www.somafm.com/metal.pls")
|
||
("SomaFM - Vaporwaves" . "http://www.somafm.com/vaporwaves.pls")
|
||
("SomaFM - DEFCON" . "http://www.somafm.com/defcon.pls")
|
||
("SomaFM - The Trip" . "http://www.somafm.com/thetrip.pls"))))
|
||
#+end_src
|
||
** Grand Unified Debugger (GUD)
|
||
GUD is a system for debugging, hooking into processes and
|
||
providing an interface to the user all in Emacs. Here I define a
|
||
hydra which provides a ton of the useful =gud= keybindings that exist
|
||
in an Emacs-only map.
|
||
#+begin_src emacs-lisp
|
||
(use-package gud
|
||
:general
|
||
:after hydra
|
||
:hydra
|
||
(gud-hydra
|
||
(:hint nil) "Hydra for GUD"
|
||
("<" #'gud-up "Up"
|
||
:column "Stack")
|
||
(">" #'gud-down "Down"
|
||
:column "Stack")
|
||
("b" #'gud-break "Break"
|
||
:column "Breakpoints")
|
||
("d" #'gud-remove "Remove"
|
||
:column "Breakpoints")
|
||
("f" #'gud-finish "Finish"
|
||
:column "Control Flow")
|
||
("J" #'gud-jump "Jump"
|
||
:column "Control Flow")
|
||
("L" #'gud-refresh "Refresh"
|
||
:column "Misc")
|
||
("n" #'gud-next "Next"
|
||
:column "Control Flow")
|
||
("p" #'gud-print "Print"
|
||
:column "Misc")
|
||
("c" #'gud-cont "Cont"
|
||
:column "Breakpoints")
|
||
("s" #'gud-step "Step"
|
||
:column "Control Flow")
|
||
("t" #'gud-tbreak "Tbreak"
|
||
:column "Breakpoints")
|
||
("u" #'gud-until "Until"
|
||
:column "Control Flow")
|
||
("w" #'gud-watch "Watch"
|
||
:column "Breakpoints")
|
||
("TAB" #'gud-stepi "Stepi"
|
||
:column "Control Flow"))
|
||
:general
|
||
(code-leader "d" #'gud-hydra/body
|
||
"D" #'gud-gdb))
|
||
#+end_src
|
||
** Jira
|
||
#+begin_src emacs-lisp
|
||
(use-package jira
|
||
:straight (:host github :repo "unmonoqueteclea/jira.el")
|
||
:init
|
||
(setq jira-base-url "https://reframe.atlassian.net")
|
||
(with-eval-after-load "evil"
|
||
(evil-set-initial-state 'jira-detail-mode 'motion)
|
||
(evil-set-initial-state 'jira-issues-mode 'motion))
|
||
:general
|
||
(app-leader
|
||
"j" #'jira-issues)
|
||
(mmap
|
||
:keymaps 'jira-issues-mode-map
|
||
"@" #'jira-issues-actions-menu))
|
||
#+end_src
|
||
* Text packages
|
||
Standard packages and configurations for dealing with text, usually
|
||
prose.
|
||
** Flyspell
|
||
Flyspell allows me to spell check text documents. I use it primarily
|
||
in org mode, as that is my preferred prose writing software, but I
|
||
also need it in commit messages and so on, thus it should really hook
|
||
into text-mode.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package flyspell
|
||
:defer t
|
||
:hook ((org-mode-hook text-mode-hook) . flyspell-mode)
|
||
:init
|
||
(setq flyspell-issue-message-flag nil
|
||
flyspell-issue-welcome-flag nil)
|
||
:general
|
||
(nmmap
|
||
:keymaps 'text-mode-map
|
||
"M-C" #'flyspell-correct-word-before-point
|
||
"M-c" #'flyspell-auto-correct-word)
|
||
(mode-leader
|
||
"s" #'flyspell-mode))
|
||
#+end_src
|
||
** Whitespace
|
||
I hate inconsistencies in whitespace. If I'm using tabs, I better be
|
||
using them everywhere, and if I'm using whitespace, it better be well
|
||
formed. Furthermore, hard character limits are important (enforced by
|
||
[[*Filling and displaying fills][auto-fill-mode]]) which is why I like
|
||
to have some kind of highlighting option.
|
||
|
||
I don't want to highlight whitespace for general mode categories (Lisp
|
||
shouldn't really have an 80 character limit), so set it for specific
|
||
modes that need the help.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package whitespace
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
"M--" #'whitespace-cleanup)
|
||
(mode-leader
|
||
"w" #'whitespace-mode)
|
||
:hook
|
||
(before-save-hook . whitespace-cleanup)
|
||
((c-mode-hook
|
||
c++-mode-hook haskell-mode-hook python-mode-hook
|
||
org-mode-hook text-mode-hook js-mode-hook nasm-mode-hook)
|
||
. whitespace-mode)
|
||
:init
|
||
(setq whitespace-line-column nil
|
||
whitespace-style '(face empty spaces tabs newline trailing
|
||
lines-char tab-mark)))
|
||
#+end_src
|
||
** Filling and displaying fills
|
||
The fill-column is the number of characters that should be in a single
|
||
line of text before doing a hard wrap. The default case is 80
|
||
characters for that l33t Unix hard terminal character limit. I like
|
||
different fill-columns for different modes: text modes should really
|
||
use 70 fill columns while code should stick to 80.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package emacs
|
||
:hook
|
||
(text-mode-hook . auto-fill-mode)
|
||
((c-mode-hook
|
||
c++-mode-hook haskell-mode-hook python-mode-hook
|
||
text-mode-hook js-mode-hook)
|
||
. display-fill-column-indicator-mode)
|
||
:init
|
||
(setq-default fill-column 80)
|
||
(add-hook 'text-mode-hook (proc (setq-local fill-column 70))))
|
||
#+end_src
|
||
** Visual line mode
|
||
When dealing with really long lines I have a specific taste. I don't
|
||
want text to just go off the screen, such that I have to move the
|
||
cursor forward in the line to see later content - I want line
|
||
wrapping. Emacs provides ~truncate-lines~ for line wrapping but it
|
||
cuts words, which isn't very nice as that cut word spans two lines.
|
||
Instead I want Emacs to cut by word, which is where visual-line-mode
|
||
comes in. Since I may experience really long lines anywhere, it
|
||
should be enabled globally.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package emacs
|
||
:demand t
|
||
:config
|
||
(global-visual-line-mode t))
|
||
#+end_src
|
||
** Show-paren-mode
|
||
When the cursor is over a parenthesis, highlight the other member of
|
||
the pair.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package paren
|
||
:hook (prog-mode-hook . show-paren-mode))
|
||
#+end_src
|
||
** Smartparens
|
||
Smartparens is a smarter electric-parens, it's much more aware of
|
||
context and easier to use.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package smartparens
|
||
:straight t
|
||
:defer t
|
||
:hook
|
||
(prog-mode-hook . smartparens-mode)
|
||
(text-mode-hook . smartparens-mode)
|
||
:config
|
||
(setq sp-highlight-pair-overlay nil
|
||
sp-highlight-wrap-overlay t
|
||
sp-highlight-wrap-tag-overlay t)
|
||
|
||
(let ((unless-list '(sp-point-before-word-p
|
||
sp-point-after-word-p
|
||
sp-point-before-same-p)))
|
||
(sp-pair "'" nil :unless unless-list)
|
||
(sp-pair "\"" nil :unless unless-list))
|
||
(sp-local-pair sp-lisp-modes "(" ")" :unless '(:rem sp-point-before-same-p))
|
||
(require 'smartparens-config))
|
||
#+end_src
|
||
** Powerthesaurus
|
||
Modern package for thesaurus in Emacs with a transient + hydra.
|
||
#+begin_src emacs-lisp
|
||
(use-package powerthesaurus
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(search-leader
|
||
"w" #'powerthesaurus-transient))
|
||
#+end_src
|
||
** lorem ipsum
|
||
Sometimes you need placeholder text for some UI or document. Pretty
|
||
easy to guess what text I'd use.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package lorem-ipsum
|
||
:straight t
|
||
:general
|
||
(insert-leader
|
||
"p" #'lorem-ipsum-insert-paragraphs))
|
||
#+end_src
|
||
** Auto insert
|
||
Allows inserting text immediately upon creating a new buffer with a
|
||
given name, similar to template. Supports skeletons for inserting
|
||
text. To make it easier for later systems to define their own auto
|
||
inserts, I define a ~use-package~ keyword (~:auto-insert~) which
|
||
allows one to define an entry for ~auto-insert-alist~.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package autoinsert
|
||
:demand t
|
||
:hook (after-init-hook . auto-insert-mode)
|
||
:config
|
||
(with-eval-after-load "use-package-core"
|
||
(add-to-list 'use-package-keywords ':auto-insert)
|
||
(defun use-package-normalize/:auto-insert (_name-symbol _keyword args)
|
||
args)
|
||
(defun use-package-handler/:auto-insert (name _keyword args rest state)
|
||
(use-package-concat
|
||
(use-package-process-keywords name rest state)
|
||
(mapcar
|
||
#'(lambda (arg)
|
||
`(add-to-list
|
||
'auto-insert-alist
|
||
',arg))
|
||
args)))))
|
||
#+end_src
|
||
* Programming packages
|
||
Packages that help with programming.
|
||
** Eldoc
|
||
Eldoc presents documentation to the user upon placing ones cursor upon
|
||
any symbol. This is very useful when programming as it:
|
||
- presents the arguments of functions while writing calls for them
|
||
- presents typing and documentation of variables
|
||
|
||
Eldoc box makes the help buffer a hovering box instead of printing it
|
||
in the minibuffer. A lot cleaner.
|
||
|
||
2024-05-31: Eldoc box is a bit useless now that I'm not using frames.
|
||
I prefer the use of the minibuffer for printing documentation now.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package eldoc
|
||
:defer t
|
||
:hook (prog-mode-hook . eldoc-mode)
|
||
:init
|
||
(global-eldoc-mode 1)
|
||
:general
|
||
(leader
|
||
"h>" #'eldoc-doc-buffer))
|
||
#+end_src
|
||
** Flycheck
|
||
Flycheck is the checking system for Emacs. I don't necessarily like
|
||
having all my code checked all the time, so I haven't added a hook to
|
||
prog-mode as it would be better for me to decide when I want checking
|
||
and when I don't. Many times Flycheck is annoying when checking a
|
||
program, particularly one which isn't finished yet.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package flycheck
|
||
:straight t
|
||
:defer t
|
||
:commands (flycheck-mode flycheck-list-errors)
|
||
:general
|
||
(mode-leader
|
||
"f" #'flycheck-mode)
|
||
(code-leader
|
||
"x" #'flycheck-list-errors
|
||
"j" #'flycheck-next-error
|
||
"k" #'flycheck-previous-error)
|
||
:display
|
||
("\\*Flycheck.*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25))
|
||
:init
|
||
(setq-default flycheck-check-syntax-automatically
|
||
'(save idle-change mode-enabled)
|
||
flycheck-idle-change-delay 1.0
|
||
flycheck-buffer-switch-check-intermediate-buffers t
|
||
flycheck-display-errors-delay 0.25)
|
||
:config
|
||
(with-eval-after-load "evil-collection"
|
||
(evil-collection-flycheck-setup)))
|
||
#+end_src
|
||
** Eglot
|
||
Eglot is package to communicate with LSP servers for better
|
||
programming capabilities. Interactions with a server provide results
|
||
to the client, done through JSON.
|
||
|
||
NOTE: Emacs 28.1 comes with better JSON parsing, which makes Eglot
|
||
much faster.
|
||
|
||
2023-03-26: I've found Eglot to be useful sometimes, but many of the
|
||
projects I work on don't require a heavy server setup to efficiently
|
||
edit and check for errors; Emacs provides a lot of functionality. So
|
||
by default I've disabled it, using =M-x eglot= to startup the LSP
|
||
server when I need it.
|
||
|
||
2024-06-27: In projects where I do use eglot and I know I will need it
|
||
regardless of file choice, I prefer setting it at the dir-local level
|
||
via an eval form. So I add to the safe values for the eval variable
|
||
to be set.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package eglot
|
||
:defer t
|
||
:general
|
||
(code-leader
|
||
:keymaps 'eglot-mode-map
|
||
"f" #'eglot-format
|
||
"a" #'eglot-code-actions
|
||
"R" #'eglot-reconnect)
|
||
:init
|
||
(setq eglot-auto-shutdown t
|
||
eglot-stay-out-of '(flymake)
|
||
eglot-ignored-server-capabilities '(:documentHighlightProvider
|
||
:documentOnTypeFormattingProvider
|
||
:inlayHintProvider))
|
||
(add-to-list 'safe-local-variable-values '(eval eglot-ensure)))
|
||
#+end_src
|
||
** Indentation
|
||
By default, turn off tabs and set the tab width to two.
|
||
|
||
#+begin_src emacs-lisp
|
||
(setq-default indent-tabs-mode nil
|
||
tab-width 2)
|
||
#+end_src
|
||
|
||
However, if necessary later, define a function that may activate tabs locally.
|
||
#+begin_src emacs-lisp
|
||
(defun +oreo/use-tabs ()
|
||
(interactive)
|
||
(setq-local indent-tabs-mode t))
|
||
#+end_src
|
||
** Highlight todo items
|
||
TODO items are highlighted in org-mode, but not necessarily in every
|
||
mode. This minor mode highlights all TODO like items via a list of
|
||
strings to match. It also configures faces to use when highlighting.
|
||
I hook it to prog-mode.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package hl-todo
|
||
:straight t
|
||
:after prog-mode
|
||
:hook (prog-mode-hook . hl-todo-mode)
|
||
:init
|
||
(setq hl-todo-keyword-faces
|
||
'(("TODO" . "#E50000")
|
||
("WIP" . "#ffa500")
|
||
("NOTE" . "#00CC00")
|
||
("FIXME" . "#d02090"))))
|
||
#+end_src
|
||
** Hide-show mode
|
||
Turn on ~hs-minor-mode~ for all prog-mode. This provides folds for
|
||
free.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package hideshow
|
||
:defer t
|
||
:hook (prog-mode-hook . hs-minor-mode))
|
||
#+end_src
|
||
** Aggressive indenting
|
||
Essentially my dream editing experience: when I type stuff in, try and
|
||
indent it for me on the fly. Just checkout the
|
||
[[https://github.com/Malabarba/aggressive-indent-mode][page]], any
|
||
description I give won't do it justice.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package aggressive-indent
|
||
:straight t
|
||
:hook ((scheme-mode-hook lisp-mode-hook emacs-lisp-mode-hook)
|
||
. aggressive-indent-mode))
|
||
#+end_src
|
||
** Compilation
|
||
Compilation mode is an incredibly useful subsystem of Emacs which
|
||
allows one to run arbitrary commands. If those commands produce
|
||
errors (particularly errors that have a filename, column and line)
|
||
compilation-mode can colourise these errors and help you navigate to
|
||
them.
|
||
|
||
Here I add some bindings and a filter which colourises the output of
|
||
compilation mode for ANSI escape sequences; the eyecandy is certainly
|
||
nice but it's very useful when dealing with tools that use those codes
|
||
so you can actually read the text.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package compile
|
||
:defer t
|
||
:display
|
||
("\\*compilation\\*"
|
||
(display-buffer-reuse-window display-buffer-at-bottom)
|
||
(window-height . 0.3)
|
||
(reusable-frames . t))
|
||
:hydra
|
||
(move-error-hydra
|
||
(:hint nil) "Hydra for moving between errors"
|
||
("j" #'next-error)
|
||
("k" #'previous-error))
|
||
:general
|
||
(leader
|
||
"j" #'move-error-hydra/next-error
|
||
"k" #'move-error-hydra/previous-error)
|
||
(code-leader
|
||
"c" #'compile
|
||
"r" #'recompile)
|
||
(nmap
|
||
"M-r" #'recompile)
|
||
(:keymaps 'compilation-mode-map
|
||
"g" nil ;; by default this is recompile
|
||
"M-j" #'compilation-next-error
|
||
"M-k" #'compilation-previous-error)
|
||
(nmmap
|
||
:keymaps 'compilation-mode-map
|
||
"c" #'recompile)
|
||
:init
|
||
(setq compilation-scroll-output 'first-error
|
||
compilation-context-lines nil
|
||
compilation-always-kill t
|
||
compilation-ask-about-save nil
|
||
next-error-recenter '(4)
|
||
next-error-highlight 'fringe-arrow)
|
||
:config
|
||
(add-hook 'compilation-filter-hook #'ansi-color-compilation-filter))
|
||
#+end_src
|
||
** xref
|
||
Find definitions, references, and general objects using TAGS without
|
||
external packages. Provided out of the box with Emacs, but requires a
|
||
way of generating a =TAGS= file for your project (look at
|
||
[[*Project.el][Project.el]] for my way of doing so). The heaviest
|
||
lifter in a minimal setup for programming without more extensive
|
||
systems like [[*Eglot][Eglot]].
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package xref
|
||
:defer t
|
||
:display
|
||
("\\*xref\\*"
|
||
(display-buffer-at-bottom)
|
||
(inhibit-duplicate-buffer . t)
|
||
(window-height . 0.3))
|
||
:general
|
||
(code-leader
|
||
"t" #'nil)
|
||
(code-leader
|
||
:infix "t"
|
||
"t" #'xref-find-apropos
|
||
"d" #'xref-find-definitions
|
||
"r" #'xref-find-references)
|
||
(nmmap
|
||
:keymaps 'xref--xref-buffer-mode-map
|
||
"RET" #'xref-goto-xref
|
||
"J" #'xref-next-line
|
||
"K" #'xref-prev-line
|
||
"r" #'xref-query-replace-in-results
|
||
"gr" #'xref-revert-buffer
|
||
"q" #'quit-window))
|
||
#+end_src
|
||
** devdocs
|
||
When man pages aren't enough, you need some documentation lookup
|
||
system. [[https://devdocs.io][Devdocs]] is a great little website
|
||
that provides a ton of documentation sets. There's an Emacs package
|
||
for it which works well and downloads documentation sets to my
|
||
machine, which is nice.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package devdocs
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(file-leader
|
||
"d" #'devdocs-lookup))
|
||
#+end_src
|
||
** rainbow-delimiters
|
||
Makes colours delimiters (parentheses) based on their depth in an
|
||
expression. LGBTQIA+ flag in your Lisp source code.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package rainbow-delimiters
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(mode-leader "r" #'rainbow-delimiters-mode)
|
||
:hook
|
||
((lisp-mode-hook emacs-lisp-mode-hook racket-mode-hook)
|
||
. rainbow-delimiters-mode))
|
||
#+end_src
|
||
** Licensing
|
||
Loads [[file:elisp/license.el][license.el]] for inserting licenses.
|
||
Licenses are important for distribution and attribution to be defined
|
||
clearly.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package license
|
||
:demand t
|
||
:load-path "elisp/"
|
||
:general
|
||
(insert-leader
|
||
"l" #'+license/insert-copyright-notice
|
||
"L" #'+license/insert-complete-license))
|
||
#+end_src
|
||
** diff mode
|
||
Good diff management is essentially mandatory in development. Emacs
|
||
comes with functionality out of the box to generate, manipulate, and
|
||
apply diffs - here I configure a small subset.
|
||
#+begin_src emacs-lisp
|
||
(use-package diff-mode
|
||
:general
|
||
(nmmap
|
||
:keymaps 'diff-mode-map
|
||
"J" #'diff-hunk-next
|
||
"K" #'diff-hunk-prev
|
||
"M-RET" #'diff-apply-hunk
|
||
"RET" #'diff-goto-source))
|
||
#+end_src
|
||
* Languages
|
||
Emacs comes with support for many different types of
|
||
programming/markup languages. Here I configure the ones I use, as
|
||
well as some external packages to increase the level of support.
|
||
** Org mode
|
||
Org is, at its most basic, a markup language. But to call it /just/ a
|
||
markup language is a major understatement. org-mode, the major mode
|
||
for Org files in Emacs, provides a lot of capabilities, such as:
|
||
- Code blocks with proper syntax highlighting and the ability to edit
|
||
them in the language mode
|
||
- Code block evaluation
|
||
- Export of code blocks to a variety of formats
|
||
- Export of code blocks to a separate file (so called "tangling",
|
||
which is used extensively in
|
||
[[file:elisp/literate.el][literate.el]] with this very document to
|
||
make my Emacs configuration work)
|
||
- Task management
|
||
- TODO system to mark the progress on a task, with the nesting of
|
||
headings allowing for non-trivial dependency management
|
||
- Feature complete scheduling system with [[*Calendar][calendar]]
|
||
integration
|
||
- A clock-in system to time tasks
|
||
- Links to a variety of formats:
|
||
- Websites (via http or https)
|
||
- FTP
|
||
- SSH
|
||
- Files (even to a specific line)
|
||
- Info pages
|
||
- A complete table based spreadsheet system, with formulas (including
|
||
[[*Calculator][calc-mode]] integration)
|
||
- Export to a variety of formats or make your own export engine using
|
||
the org AST.
|
||
- Inline $\LaTeX$, with the ability to render them on demand
|
||
|
||
This is but a portion of what Org is capable of; like
|
||
[[*Magit][Magit]], some use Emacs just for this system.
|
||
*** Org Variables
|
||
Tons of variables for org-mode, including a ton of latex ones. Can't
|
||
really explain because it sets up quite a lot of local stuff. Look at
|
||
the [[info:org#Top][org Info]] document for information regarding
|
||
this.
|
||
|
||
Also I copy pasted the majority of this, tweaking it till it
|
||
felt good. Doom Emacs was very helpful here.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org
|
||
:defer t
|
||
:init
|
||
(setq org-adapt-indentation nil
|
||
org-bookmark-names-plist nil
|
||
org-directory "~/Text/"
|
||
org-edit-src-content-indentation 0
|
||
org-eldoc-breadcrumb-separator " → "
|
||
org-enforce-todo-dependencies t
|
||
org-export-backends '(ascii html latex odt icalendar)
|
||
org-fontify-quote-and-verse-blocks t
|
||
org-fontify-whole-heading-line t
|
||
org-footnote-auto-label t
|
||
org-hide-emphasis-markers nil
|
||
org-hide-leading-stars t
|
||
org-image-actual-width nil
|
||
org-imenu-depth 10
|
||
org-imenu-depth 10
|
||
org-indent-mode nil
|
||
org-indirect-buffer-display 'current-window
|
||
org-link-descriptive nil
|
||
org-link-frame-setup '((vm . vm-visit-folder-other-frame)
|
||
(vm-imap . vm-visit-imap-folder-other-frame)
|
||
(file . find-file))
|
||
org-priority-faces '((?A . error) (?B . warning) (?C . success))
|
||
org-refile-targets '((nil . (:maxlevel . 2)))
|
||
org-src-window-setup 'current-window
|
||
org-startup-folded 'showeverything
|
||
org-startup-indented nil
|
||
org-startup-with-latex-preview nil
|
||
org-tags-column 0
|
||
org-todo-keywords '((sequence "TODO" "WIP" "DONE")
|
||
(sequence "PROJ" "WAIT" "COMPLETE"))
|
||
org-use-sub-superscripts '{})
|
||
:config
|
||
(org-babel-do-load-languages
|
||
'org-babel-load-languages
|
||
'((emacs-lisp . t)
|
||
(lisp . t)
|
||
(shell . t)
|
||
(python . t)
|
||
(C . t))))
|
||
#+end_src
|
||
*** Org Functionality
|
||
Hooks, prettify-symbols and records for auto insertion.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org
|
||
:defer t
|
||
:hook
|
||
(org-mode-hook . prettify-symbols-mode)
|
||
:display
|
||
("\\*Org Src.*"
|
||
(display-buffer-same-window))
|
||
("\\*Org Links\\*"
|
||
(display-buffer-no-window)
|
||
(allow-no-window . t))
|
||
:auto-insert
|
||
(("\\.org\\'" . "Org skeleton")
|
||
"Enter title: "
|
||
"#+title: " str | (buffer-file-name) "\n"
|
||
"#+author: " (read-string "Enter author: ") | user-full-name "\n"
|
||
"#+description: " (read-string "Enter description: ") | "Description" "\n"
|
||
"#+date: " (format-time-string "%Y-%m-%d" (current-time)) "\n"
|
||
"* " _))
|
||
#+end_src
|
||
*** Org Bindings
|
||
A load of bindings for org-mode which binds together a lot of
|
||
functionality. It's best to read it yourself; to describe it is to
|
||
write the code.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org
|
||
:defer t
|
||
:init
|
||
(with-eval-after-load "consult"
|
||
(general-def
|
||
:keymaps 'org-mode-map
|
||
[remap imenu] #'consult-outline))
|
||
:general
|
||
(leader
|
||
";" #'org-agenda)
|
||
|
||
(org-leader
|
||
"l" #'org-store-link
|
||
"a" #'org-agenda
|
||
"d" #'org-babel-detangle
|
||
"i" #'org-insert-last-stored-link
|
||
"o" #'org-open-at-point-global)
|
||
|
||
(nmmap
|
||
:keymaps 'org-mode-map
|
||
"]]" #'org-next-visible-heading
|
||
"[[" #'org-previous-visible-heading
|
||
"TAB" #'org-cycle)
|
||
|
||
(local-leader
|
||
:keymaps 'org-mode-map
|
||
"d" #'org-deadline
|
||
"s" #'org-schedule
|
||
"t" #'org-todo
|
||
"r" #'org-refile
|
||
"," #'org-priority
|
||
"n" #'org-narrow-to-subtree
|
||
"w" #'widen
|
||
"i" #'org-insert-structure-template
|
||
"p" #'org-latex-preview
|
||
"e" #'org-babel-execute-src-block-maybe
|
||
"E" #'org-export-dispatch
|
||
"o" #'org-edit-special
|
||
"T" #'org-babel-tangle
|
||
"S" #'org-property-action
|
||
"R" #'org-list-repair
|
||
"O" #'org-open-at-point-global)
|
||
|
||
(local-leader
|
||
:keymaps 'org-mode-map
|
||
:infix "l"
|
||
"i" #'org-insert-link
|
||
"l" #'org-open-at-point
|
||
"f" #'org-footnote-action)
|
||
|
||
(local-leader
|
||
:keymaps 'org-mode-map
|
||
:infix "'"
|
||
"a" #'org-table-align
|
||
"c" #'org-table-create
|
||
"f" #'org-table-edit-formulas
|
||
"t" #'org-table-toggle-coordinate-overlays
|
||
"s" #'org-table-sum
|
||
"e" #'org-table-calc-current-TBLFM
|
||
"E" #'org-table-eval-formula)
|
||
|
||
(local-leader
|
||
:keymaps 'org-src-mode-map
|
||
"o" #'org-edit-src-exit))
|
||
#+end_src
|
||
*** Org Agenda
|
||
Org agenda provides a nice viewing for schedules. With org mode it's
|
||
a very tidy way to manage your time.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org-agenda
|
||
:defer t
|
||
:init
|
||
(setq org-agenda-window-setup 'current-window
|
||
org-agenda-skip-deadline-prewarning-if-scheduled t
|
||
org-agenda-skip-scheduled-if-done t
|
||
org-agenda-skip-deadline-if-done t
|
||
org-agenda-start-with-entry-text-mode nil
|
||
org-agenda-span 'week
|
||
org-agenda-custom-commands
|
||
'(("n" "Agenda (no reading)" ((agenda "-reading") (alltodo "")))
|
||
("r" "Reading" agenda ""
|
||
((org-agenda-skip-function
|
||
(org-agenda-skip-entry-if 'regexp "reading.*"))))))
|
||
:config
|
||
(evil-set-initial-state 'org-agenda-mode 'normal)
|
||
:general
|
||
(file-leader
|
||
"a" (proc-int
|
||
(--> org-agenda-files
|
||
(completing-read "Enter file: " it nil t)
|
||
(find-file it))))
|
||
(nmmap
|
||
:keymaps 'org-agenda-mode-map
|
||
"," #'org-agenda-goto-date
|
||
"." #'org-agenda-goto-today
|
||
"J" #'org-agenda-later
|
||
"K" #'org-agenda-earlier
|
||
"RET" #'org-agenda-switch-to
|
||
"d" #'org-agenda-deadline
|
||
"gd" #'org-agenda-goto-date
|
||
"q" #'org-agenda-quit
|
||
"r" #'org-agenda-redo
|
||
"s" #'org-agenda-schedule
|
||
"t" #'org-agenda-todo
|
||
"zd" #'org-agenda-day-view
|
||
"zm" #'org-agenda-month-view
|
||
"zw" #'org-agenda-week-view
|
||
"f" #'org-agenda-filter-by-tag))
|
||
#+end_src
|
||
*** Org Capture
|
||
Org capture provides a system for quickly "capturing" some information
|
||
into an org file. A classic example is creating a new TODO in a
|
||
todo file, where the bare minimum to record one is:
|
||
+ where was it recorded?
|
||
+ when was it recorded?
|
||
+ what is it?
|
||
Org capture provides a way to do that seamlessly without opening the
|
||
todo file directly.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org-capture
|
||
:defer t
|
||
:init
|
||
(setq
|
||
org-capture-templates
|
||
'(("t" "Todo" entry
|
||
(file "general.org")
|
||
"* TODO %?
|
||
%T
|
||
%a")
|
||
("q" "Quote" entry
|
||
(file "quotes.org")
|
||
"* %^{Title}
|
||
,#+caption: %^{Origin} %t
|
||
,#+begin_quote
|
||
%?
|
||
,#+end_quote")))
|
||
:general
|
||
(org-leader
|
||
"c" #'org-capture)
|
||
(nmmap
|
||
:keymaps 'org-capture-mode-map
|
||
"ZZ" #'org-capture-finalize
|
||
"ZR" #'org-capture-refile
|
||
"ZQ" #'org-capture-kill))
|
||
#+end_src
|
||
*** Org Latex
|
||
Org mode has deep integration with latex, can export to PDF and even
|
||
display latex fragments in the document directly. I setup the
|
||
pdf-process, code listing options via minted and the format options
|
||
for latex fragments.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org
|
||
:defer t
|
||
:init
|
||
(setq org-format-latex-options
|
||
'(:foreground default :background "Transparent" :scale 1.5
|
||
:html-foreground "Black" :html-background "Transparent"
|
||
:html-scale 1.0 :matchers ("begin" "$1" "$" "$$" "\\(" "\\["))
|
||
org-latex-src-block-backend 'minted
|
||
org-latex-minted-langs '((emacs-lisp "common-lisp")
|
||
(ledger "text")
|
||
(cc "c++")
|
||
(cperl "perl")
|
||
(shell-script "bash")
|
||
(caml "ocaml"))
|
||
org-latex-packages-alist '(("" "minted"))
|
||
org-latex-pdf-process
|
||
(list (concat "latexmk -f -bibtex -pdf "
|
||
"-shell-escape -%latex -interaction=nonstopmode "
|
||
"-output-directory=%o %f"))
|
||
org-latex-minted-options
|
||
'(("style" "colorful")
|
||
("linenos")
|
||
("frame" "single")
|
||
("mathescape")
|
||
("fontfamily" "courier")
|
||
("samepage" "false")
|
||
("breaklines" "true")
|
||
("breakanywhere" "true"))))
|
||
#+end_src
|
||
*** Org Message
|
||
Org message allows for the use of org mode when composing mails,
|
||
generating HTML multipart emails. This integrates the WYSIWYG
|
||
experience with mail in Emacs while also providing powerful text
|
||
features with basically no learning curve (as long as you've already
|
||
learnt the basics of org).
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org-msg
|
||
:straight t
|
||
:hook
|
||
(message-mode-hook . org-msg-mode)
|
||
(notmuch-message-mode-hook . org-msg-mode)
|
||
:config
|
||
(setq org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil \\n:t tex:dvipng"
|
||
org-msg-greeting-name-limit 3)
|
||
|
||
(add-to-list
|
||
'org-msg-enforce-css
|
||
'(img latex-fragment-inline
|
||
((transform . ,(format "translateY(-1px) scale(%.3f)"
|
||
(/ 1.0 (if (boundp 'preview-scale)
|
||
preview-scale 1.4))))
|
||
(margin . "0 -0.35em")))))
|
||
#+end_src
|
||
*** Org for Evil
|
||
Evil org for some nice bindings.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-org
|
||
:straight t
|
||
:defer t
|
||
:hook (org-mode-hook . evil-org-mode))
|
||
#+end_src
|
||
*** WAIT Org Clock-in
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no :results none
|
||
:END:
|
||
2025-02-15: I haven't found much use for this yet but the system is
|
||
quite expressive. If I needed time-keeping somewhere, I know where to
|
||
go.
|
||
|
||
Org provides a nice timekeeping system that allows for managing how
|
||
much time is taken per task. It even has an extensive reporting
|
||
system to see how much time you spend on specific tasks or overall.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org-clock
|
||
:after org
|
||
:general
|
||
(local-leader
|
||
:keymaps 'org-mode-map
|
||
:infix "c"
|
||
"d" #'org-clock-display
|
||
"c" #'org-clock-in
|
||
"o" #'org-clock-out
|
||
"r" #'org-clock-report))
|
||
#+end_src
|
||
*** WAIT Org Ref
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no :results none
|
||
:END:
|
||
For bibliographic stuff in $\LaTeX$ export.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package org-ref
|
||
:straight t
|
||
:defer t
|
||
:init
|
||
(setq bibtex-files '("~/Text/bibliography.bib")
|
||
bibtex-completion-bibliography '("~/Text/bibliography.bib")
|
||
bibtex-completion-additional-search-fields '(keywords)))
|
||
#+end_src
|
||
** Makefile
|
||
Defines an auto-insert for Makefiles. Assumes C but it's very easy to
|
||
change it for C++.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package make-mode
|
||
:defer t
|
||
:auto-insert
|
||
(("[mM]akefile\\'" . "Makefile skeleton")
|
||
""
|
||
"CC=gcc
|
||
OUT=main.out
|
||
LIBS=
|
||
ARGS=
|
||
|
||
RELEASE=0
|
||
GFLAGS=-Wall -Wextra -Werror -Wswitch-enum -std=c11
|
||
DFLAGS=-ggdb -fsanitize=address -fsanitize=undefined
|
||
RFLAGS=-O3
|
||
ifeq ($(RELEASE), 1)
|
||
CFLAGS=$(GFLAGS) $(RFLAGS)
|
||
else
|
||
CFLAGS=$(GFLAGS) $(DFLAGS)
|
||
endif
|
||
|
||
.PHONY: all
|
||
all: $(OUT)
|
||
|
||
$(OUT): main.c
|
||
$(CC) $(CFLAGS) $^ -o $@ $(LIBS)
|
||
|
||
.PHONY: run
|
||
run: $(OUT)
|
||
./$^ $(ARGS)
|
||
|
||
.PHONY:
|
||
clean:
|
||
rm -v $(OUT)
|
||
"
|
||
_))
|
||
#+end_src
|
||
** WAIT SQL
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no :results none
|
||
:END:
|
||
The default SQL package provides support for connecting to common
|
||
database types (sqlite, mysql, etc) for auto completion and query
|
||
execution. I don't use SQL currently but whenever I need it it's
|
||
there.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package sql
|
||
:defer t
|
||
:init
|
||
(setq sql-display-sqli-buffer-function nil))
|
||
#+end_src
|
||
** NHexl
|
||
Hexl-mode is the inbuilt package within Emacs to edit hex and binary
|
||
format buffers. There are a few problems with hexl-mode though,
|
||
including an annoying prompt on /revert-buffer/.
|
||
|
||
Thus, nhexl-mode! It comes with a few other improvements. Check out
|
||
the [[https://elpa.gnu.org/packages/nhexl-mode.html][page]] yourself.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package nhexl-mode
|
||
:straight t
|
||
:defer t
|
||
:mode ("\\.bin" "\\.out"))
|
||
#+end_src
|
||
** NASM
|
||
#+begin_src emacs-lisp
|
||
(use-package nasm-mode
|
||
:straight t
|
||
:defer t
|
||
:mode ("\\.asm" . nasm-mode))
|
||
#+end_src
|
||
** C/C++
|
||
Setup for C and C++ modes, using Emacs' default package: cc-mode.
|
||
*** cc-mode
|
||
Tons of stuff, namely:
|
||
+ ~auto-fill-mode~ for 80 char limit
|
||
+ Some keybindings to make evil statement movement easy
|
||
+ Lots of pretty symbols
|
||
+ Indenting options and a nice (for me) code style for C
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package cc-mode
|
||
:defer t
|
||
:hook
|
||
((c-mode-hook c++-mode-hook) . auto-fill-mode)
|
||
:general
|
||
(:keymaps '(c-mode-map c++-mode-map)
|
||
:states '(normal motion visual)
|
||
"(" #'c-beginning-of-statement
|
||
")" #'c-end-of-statement
|
||
"{" #'c-beginning-of-defun
|
||
"}" #'c-end-of-defun)
|
||
:init
|
||
(setq c-basic-offset 2
|
||
c-auto-newline nil
|
||
c-default-style '((other . "user")))
|
||
(add-hook 'c-mode-hook (proc (c-toggle-comment-style -1)))
|
||
(add-hook 'c++-mode-hook (proc (c-toggle-comment-style -1)))
|
||
:config
|
||
(c-add-style
|
||
"user"
|
||
'((c-basic-offset . 2)
|
||
(c-comment-only-line-offset . 0)
|
||
(c-hanging-braces-alist (brace-list-open)
|
||
(brace-entry-open)
|
||
(substatement-open after)
|
||
(block-close . c-snug-do-while)
|
||
(arglist-cont-nonempty))
|
||
(c-cleanup-list brace-else-brace)
|
||
(c-offsets-alist
|
||
(statement-block-intro . +)
|
||
(substatement-open . 0)
|
||
(access-label . -)
|
||
(inline-open . 0)
|
||
(label . 0)
|
||
(statement-cont . +)))))
|
||
#+end_src
|
||
*** cc auto insert
|
||
This is a generic auto-insert template for C/C++ files.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package cc-mode
|
||
:defer t
|
||
:init
|
||
(defun +cc/copyright-notice ()
|
||
(let* ((lines (split-string (+license/copyright-notice) "\n"))
|
||
(copyright-line (car lines))
|
||
(rest (cdr lines)))
|
||
(concat
|
||
"* "
|
||
copyright-line
|
||
"\n"
|
||
(mapconcat
|
||
#'(lambda (x)
|
||
(if (string= x "")
|
||
""
|
||
(concat " * " x)))
|
||
rest
|
||
"\n"))))
|
||
:auto-insert
|
||
(("\\.c\\'" . "C skeleton")
|
||
""
|
||
"/* " (file-name-nondirectory (buffer-file-name)) ": " _ "\n"
|
||
" * Created: " (format-time-string "%Y-%m-%d") "\n"
|
||
" * Author: " user-full-name "\n"
|
||
" * License: See end of file\n"
|
||
" * Commentary:\n"
|
||
" */\n"
|
||
"\n\n"
|
||
"/" (+cc/copyright-notice) "\n\n*/")
|
||
(("\\.cpp\\'" "C++ skeleton")
|
||
""
|
||
"/* " (file-name-nondirectory (buffer-file-name)) ": " _ "\n"
|
||
" * Created: " (format-time-string "%Y-%m-%d") "\n"
|
||
" * Author: " user-full-name "\n"
|
||
" * License: See end of file\n"
|
||
" * Commentary:\n"
|
||
" */\n"
|
||
"\n\n"
|
||
"/" (+cc/copyright-notice) "\n\n*/")
|
||
(("\\.\\([Hh]\\|hh\\|hpp\\|hxx\\|h\\+\\+\\)\\'" . "C / C++ header")
|
||
(replace-regexp-in-string "[^A-Z0-9]" "_"
|
||
(string-replace "+" "P"
|
||
(upcase
|
||
(file-name-nondirectory buffer-file-name))))
|
||
""
|
||
"/* " (file-name-nondirectory (buffer-file-name)) ": " _ "\n"
|
||
" * Created: " (format-time-string "%Y-%m-%d") "\n"
|
||
" * Author: " user-full-name "\n"
|
||
" * License: See end of file\n"
|
||
" * Commentary:\n"
|
||
" */\n"
|
||
"\n\n"
|
||
"#ifndef " str n "#define " str "\n\n" "\n\n#endif\n"
|
||
"/" (+cc/copyright-notice) "\n\n*/"))
|
||
#+end_src
|
||
*** Clang format
|
||
clang-format is a program that formats C/C++ files. It's highly
|
||
configurable and quite fast. I have a root configuration in my
|
||
Dotfiles (check it out
|
||
[[file:~/Dotfiles/ClangFormat/).clang-format][here]].
|
||
|
||
Clang format comes inbuilt with clang/LLVM, so it's quite likely to be
|
||
on your machine.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package clang-format
|
||
:load-path "/usr/share/clang/"
|
||
:defer t
|
||
:after cc-mode
|
||
:commands (+code/clang-format-region-or-buffer
|
||
clang-format-mode)
|
||
:general
|
||
(code-leader
|
||
:keymaps '(c-mode-map c++-mode-map)
|
||
"f" #'clang-format-buffer)
|
||
:config
|
||
(define-minor-mode clang-format-mode
|
||
"On save formats the current buffer via clang-format."
|
||
:lighter nil
|
||
(let ((save-func (proc-int (clang-format-buffer))))
|
||
(if clang-format-mode
|
||
(add-hook 'before-save-hook save-func nil t)
|
||
(remove-hook 'before-save-hook save-func t))))
|
||
(defun +code/clang-format-region-or-buffer ()
|
||
(interactive)
|
||
(if (mark)
|
||
(clang-format-region (region-beginning) (region-end))
|
||
(clang-format-buffer))))
|
||
#+end_src
|
||
*** cc compile fsan
|
||
Sanitisers are a blessing for C/C++. An additional runtime on top of
|
||
the executable which catches stuff like undefined behaviour or memory
|
||
leaks make it super easy to see where and how code is failing.
|
||
However, by default, Emacs' compilation-mode doesn't understand the
|
||
logs =fsanitize= makes so you usually have to manually deal with it
|
||
yourself.
|
||
|
||
Compilation mode uses regular expressions to figure out whether
|
||
something is an error and how to navigate to the file where that error
|
||
is located. So adding support for =-fsanitize= is as simple as making
|
||
a regular expression which captures file names and digits
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package compile
|
||
:after cc-mode
|
||
:config
|
||
(add-to-list 'compilation-error-regexp-alist-alist
|
||
`(fsan ,(rx (and
|
||
line-start " #" (1+ digit) " 0x" (1+ hex) " in "
|
||
(1+ (or word "_")) " "
|
||
(group (seq (* any) (or ".c" ".cpp" ".h" ".hpp"))) ":"
|
||
(group (+ digit))))
|
||
|
||
1 2))
|
||
(add-to-list 'compilation-error-regexp-alist
|
||
'fsan))
|
||
#+end_src
|
||
** Markdown
|
||
Why use Markdown when you have org-mode? Because LSP servers
|
||
sometimes format their documentation as markdown, which
|
||
[[*Eglot][Eglot]] can use to provide nicer views on docs!
|
||
#+begin_src emacs-lisp
|
||
(use-package markdown-mode
|
||
:straight t
|
||
:defer t)
|
||
#+end_src
|
||
** Rust
|
||
Rust is a language with many hats - primarily systems level
|
||
programming.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package rust-mode
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(code-leader
|
||
:keymaps 'rust-mode-map
|
||
"f" #'rust-format-buffer)
|
||
(local-leader
|
||
:keymaps 'rust-mode-map
|
||
"c" #'rust-run-clippy)
|
||
:init
|
||
(setq rust-format-on-save t)
|
||
(with-eval-after-load "eglot"
|
||
(add-to-list 'eglot-server-programs '(rust-mode "rust-analyzer"))))
|
||
#+end_src
|
||
** WAIT Racket
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no :results none
|
||
:END:
|
||
A scheme with lots of stuff inside it. Using it for a language design
|
||
book so it's useful to have some Emacs binds for it.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package racket-mode
|
||
:straight t
|
||
:defer t
|
||
:hook (racket-mode-hook . racket-xp-mode)
|
||
:display
|
||
("\\*Racket REPL*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.3))
|
||
:init
|
||
(setq racket-documentation-search-location 'local)
|
||
:general
|
||
(nmap
|
||
:keymaps 'racket-describe-mode-map
|
||
"q" #'quit-window)
|
||
(nmap
|
||
:keymaps 'racket-mode-map
|
||
"gr" #'racket-eval-last-sexp)
|
||
(local-leader
|
||
:keymaps '(racket-mode-map racket-repl-mode-map)
|
||
"d" #'racket-repl-describe)
|
||
(local-leader
|
||
:keymaps 'racket-mode-map
|
||
"r" #'racket-run
|
||
"i" #'racket-repl
|
||
"e" #'racket-send-definition
|
||
"sr" #'racket-send-region
|
||
"sd" #'racket-send-definition))
|
||
#+end_src
|
||
** WAIT Haskell
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no :results none
|
||
:END:
|
||
2025-02-15: Haskell is a fun language so I'll leave this configuration
|
||
for now.
|
||
|
||
Haskell is a static lazy functional programming language (what a
|
||
mouthful). It's quite a beautiful language and really learning it
|
||
will change the way you think about programming. However, my
|
||
preferred functional language is still unfortunately Lisp so no extra
|
||
brownie points there.
|
||
|
||
Here I configure the REPL for Haskell via the
|
||
~haskell-interactive-mode~. I also load my custom package
|
||
[[file:elisp/haskell-multiedit.el][haskell-multiedit]] which allows a
|
||
user to create temporary ~haskell-mode~ buffers that, upon completion,
|
||
will run in the REPL. Even easier than making your own buffer.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package haskell-mode
|
||
:straight t
|
||
:defer t
|
||
:hook
|
||
(haskell-mode-hook . haskell-indentation-mode)
|
||
(haskell-mode-hook . interactive-haskell-mode)
|
||
:display
|
||
("\\*haskell.**\\*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.3))
|
||
:general
|
||
(shell-leader
|
||
"h" #'haskell-interactive-bring)
|
||
(local-leader
|
||
:keymaps 'haskell-mode-map
|
||
"c" #'haskell-compile
|
||
"t" #'haskell-process-do-type)
|
||
(nmmap
|
||
:keymaps 'haskell-mode-map
|
||
"C-c C-c" #'haskell-process-load-file)
|
||
(local-leader
|
||
:keymaps 'haskell-interactive-mode-map
|
||
"c" #'haskell-interactive-mode-clear)
|
||
(imap
|
||
:keymaps 'haskell-interactive-mode-map
|
||
"M-k" #'haskell-interactive-mode-history-previous
|
||
"M-j" #'haskell-interactive-mode-history-next)
|
||
:init
|
||
(setq haskell-interactive-prompt "[λ] "
|
||
haskell-interactive-prompt-cont "{λ} "
|
||
haskell-interactive-popup-errors nil
|
||
haskell-stylish-on-save t
|
||
haskell-process-type 'auto)
|
||
:config
|
||
(load (concat user-emacs-directory "elisp/haskell-multiedit.el")))
|
||
#+end_src
|
||
** Python
|
||
Works well for python. If you have ~pyls~ it should be on your path, so
|
||
just run eglot if you need. But an LSP server is not necessary for a
|
||
lot of my time in python. Here I also setup org-babel for python
|
||
source code blocks.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package python
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
:keymaps 'python-mode-map
|
||
"C-M-x" #'python-shell-send-defun)
|
||
(local-leader
|
||
:keymaps 'python-mode-map
|
||
"c" #'python-check)
|
||
(local-leader
|
||
:keymaps 'python-mode-map
|
||
:infix "e"
|
||
"e" #'python-shell-send-statement
|
||
"r" #'python-shell-send-region
|
||
"f" #'python-shell-send-buffer)
|
||
:pretty
|
||
(python-mode-hook
|
||
("None" . "Ø")
|
||
("list" . "ℓ")
|
||
("List" . "ℓ")
|
||
("str" . "𝕊")
|
||
("!" . "¬")
|
||
("for" . "∀")
|
||
("print" . "φ")
|
||
("lambda" . "λ")
|
||
("reduce" . "↓")
|
||
("map" . "→")
|
||
("return" . "≡")
|
||
("yield" . "≈"))
|
||
:init
|
||
(setq python-indent-offset 4)
|
||
:config
|
||
(with-eval-after-load "org-mode"
|
||
(setf (alist-get 'python org-babel-load-languages) t)))
|
||
#+end_src
|
||
*** Python shell
|
||
Setup for python shell, including a toggle option
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package python
|
||
:defer t
|
||
:commands +python/toggle-repl
|
||
:general
|
||
(shell-leader
|
||
"p" #'run-python)
|
||
(local-leader
|
||
:keymaps 'inferior-python-mode-map
|
||
"c" #'comint-clear-buffer)
|
||
:hook
|
||
(inferior-python-mode-hook . company-mode)
|
||
:display
|
||
("\\*Python\\*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.3)))
|
||
#+end_src
|
||
** YAML
|
||
YAML is a data language which is useful for config files.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package yaml-mode
|
||
:straight t
|
||
:defer t)
|
||
#+end_src
|
||
** HTML/CSS/JS
|
||
Firstly, web mode for consistent colouring of syntax.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package web-mode
|
||
:straight t
|
||
:defer t
|
||
:mode ("\\.html" . web-mode)
|
||
:mode ("\\.css" . web-mode)
|
||
:custom
|
||
((web-mode-code-indent-offset 2)
|
||
(web-mode-markup-indent-offset 2)
|
||
(web-mode-css-indent-offset 2)))
|
||
#+end_src
|
||
*** Emmet
|
||
Emmet for super speed code writing.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package emmet-mode
|
||
:straight t
|
||
:defer t
|
||
:hook (web-mode-hook . emmet-mode)
|
||
:general
|
||
(imap
|
||
:keymaps 'emmet-mode-keymap
|
||
"TAB" #'emmet-expand-line
|
||
"M-j" #'emmet-next-edit-point
|
||
"M-k" #'emmet-prev-edit-point))
|
||
#+end_src
|
||
*** HTML auto insert
|
||
An auto-insert for HTML buffers, which just adds some nice stuff.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package web-mode
|
||
:defer t
|
||
:auto-insert
|
||
(("\\.html\\'" . "HTML Skeleton")
|
||
""
|
||
"<!doctype html>
|
||
<html lang=''>
|
||
<head>
|
||
<meta charset='utf-8'>
|
||
<title>"(read-string "Enter title: ") | """</title>
|
||
<meta name='description' content='" (read-string "Enter description: ") | "" "'>
|
||
<meta name='author' content='"user-full-name"'/>
|
||
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||
|
||
<link rel='apple-touch-icon' href='/apple-touch-icon.png'>
|
||
<link rel='shortcut icon' href='/favicon.ico'/>
|
||
</head>
|
||
<body>
|
||
"
|
||
_
|
||
" </body>
|
||
</html>"))
|
||
#+end_src
|
||
*** Javascript Mode
|
||
A better mode for JavaScript that also has automatic integration with
|
||
eglot.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package js
|
||
:defer t
|
||
:mode ("\\.js" . js-mode)
|
||
:hook (js-mode-hook . auto-fill-mode)
|
||
:init
|
||
(setq js-indent-level 2))
|
||
#+end_src
|
||
*** Typescript
|
||
A language that adds a build step to JavaScript projects for "static"
|
||
typing. It's nice because it adds good auto completion.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package typescript-mode
|
||
:straight t
|
||
:defer t
|
||
:init
|
||
(setq typescript-indent-level 2))
|
||
#+end_src
|
||
** Scheme
|
||
Another Lisp but simpler than the rest. A beauty of engineering and
|
||
fun to write programs in. Here I setup ~geiser~, which is the
|
||
premiere way to interact with scheme REPLs.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package geiser
|
||
:straight t
|
||
:defer t
|
||
:display
|
||
("\\*Geiser.*"
|
||
(display-buffer-reuse-mode-window display-buffer-at-bottom)
|
||
(window-height . 0.3))
|
||
:general
|
||
(shell-leader
|
||
"S" #'geiser)
|
||
(local-leader
|
||
:keymaps 'scheme-mode-map
|
||
"t" #'geiser
|
||
"m" #'geiser-doc-look-up-manual
|
||
"d" #'geiser-doc-symbol-at-point)
|
||
(local-leader
|
||
:keymaps 'scheme-mode-map
|
||
:infix "e"
|
||
"e" #'geiser-eval-last-sexp
|
||
"b" #'geiser-eval-buffer
|
||
"d" #'geiser-eval-definition
|
||
"r" #'geiser-eval-region)
|
||
:init
|
||
(with-eval-after-load "evil"
|
||
(evil-set-initial-state 'geiser-debug-mode-map 'emacs)))
|
||
|
||
(use-package geiser-guile
|
||
:straight t
|
||
:defer t)
|
||
#+end_src
|
||
** WAIT Ocaml
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no :results none
|
||
:END:
|
||
*** Ocaml Setup
|
||
Firstly, install ~opam~ and ~ocaml~. Then run the following script:
|
||
#+begin_src sh
|
||
opam install tuareg ocamlformat odoc utop merlin user-setup;
|
||
opam user-setup install;
|
||
mv ~/.emacs.d/opam-user-setup.el ~/.config/emacs/elisp;
|
||
rm -rf ~/.emacs.d ~/.emacs;
|
||
#+end_src
|
||
|
||
This sets up the necessary packages (particularly Emacs Lisp) and some
|
||
configuration that ensures Emacs is consistent with the user
|
||
installation. Notice the moving of =opam-user-setup.el= into
|
||
=~/.config/emacs/elisp=, which we'll use to setup the ocaml
|
||
experience.
|
||
*** Ocaml Configuration
|
||
Here I load the =opam-user-setup= package setup earlier, with some
|
||
neat tips from the default =~/.emacs= generated by ~opam user-setup
|
||
install~.
|
||
#+begin_src emacs-lisp
|
||
(use-package opam-user-setup
|
||
:defer t
|
||
:load-path "elisp/"
|
||
:mode ("\\.ml" . tuareg-mode)
|
||
:hook (tuareg-mode-hook . whitespace-mode)
|
||
:display
|
||
("\\*utop\\*"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.3))
|
||
:general
|
||
(code-leader
|
||
:keymaps 'tuareg-mode-map
|
||
"f" #'+ocaml/format-buffer)
|
||
:config
|
||
(defun +ocaml/format-buffer ()
|
||
(interactive)
|
||
(when (eq major-mode 'tuareg-mode)
|
||
(let ((name (buffer-file-name (current-buffer)))
|
||
(format-str "ocamlformat -i --enable-outside-detected-project %s"))
|
||
(save-buffer)
|
||
(set-process-sentinel (start-process-shell-command "ocamlformat" "*ocamlformat*"
|
||
(format format-str name))
|
||
(lambda (p event)
|
||
(when (string= event "finished\n")
|
||
(revert-buffer nil t)
|
||
(message "[ocamlformat] Finished.")))))))
|
||
(add-to-list 'compilation-error-regexp-alist-alist
|
||
`(ocaml
|
||
"[Ff]ile \\(\"\\(.*?\\)\", line \\(-?[0-9]+\\)\\(, characters \\(-?[0-9]+\\)-\\([0-9]+\\)\\)?\\)\\(:\n\\(\\(Warning .*?\\)\\|\\(Error\\)\\):\\)?"
|
||
2 3 (5 . 6) (9 . 11) 1 (8 compilation-message-face)))
|
||
(add-to-list 'compilation-error-regexp-alist
|
||
'ocaml)
|
||
:general
|
||
(local-leader
|
||
:keymaps 'tuareg-mode-map
|
||
"u" #'utop)
|
||
(local-leader
|
||
:keymaps 'tuareg-mode-map
|
||
:infix "e"
|
||
"r" #'utop-eval-region
|
||
"e" #'utop-eval-phrase
|
||
"b" #'utop-eval-buffer))
|
||
|
||
(use-package merlin-eldoc
|
||
:straight t
|
||
:after opam-user-setup
|
||
:hook
|
||
(tuareg-mode-hook . merlin-eldoc-setup)
|
||
:init
|
||
(setq merlin-eldoc-occurrences nil))
|
||
#+end_src
|
||
** Lisp
|
||
Emacs is the greatest Lisp editor around, there are no two ways about
|
||
it. Here I setup the configuration for Emacs Lisp and Common Lisp.
|
||
*** Lisp configuration
|
||
All the general stuff I do for any other language: pretty symbols and
|
||
key bindings.
|
||
#+begin_src emacs-lisp
|
||
(use-package lisp-mode
|
||
:pretty
|
||
(lisp-mode-hook
|
||
("lambda" . "λ")
|
||
("nil" . "Ø")
|
||
("<=" . "≤")
|
||
(">=" . "≥")
|
||
("defun" . "ƒ")
|
||
("mapcar" . "→")
|
||
("reduce" . "↓")
|
||
("some" . "∃")
|
||
("every" . "∀")
|
||
("LAMBDA" . "λ")
|
||
("NIL" . "Ø")
|
||
("<=" . "≤")
|
||
(">=" . "≥")
|
||
("DEFUN" . "ƒ")
|
||
("MAPCAR" . "→")
|
||
("REDUCE" . "↓")
|
||
("SOME" . "∃")
|
||
("EVERY" . "∀"))
|
||
(emacs-lisp-mode-hook
|
||
("lambda" . "λ")
|
||
("nil" . "Ø")
|
||
("defun" . "ƒ")
|
||
("mapcar" . "→")
|
||
("LAMBDA" . "λ")
|
||
("NIL" . "Ø")
|
||
("DEFUN" . "ƒ")
|
||
("MAPCAR" . "→"))
|
||
:general
|
||
(:states '(normal motion insert visual)
|
||
:keymaps 'lisp-mode-shared-map
|
||
"C-j" #'sp-forward-slurp-sexp
|
||
"C-k" #'sp-forward-barf-sexp
|
||
"C-S-j" #'sp-backward-barf-sexp
|
||
"C-S-k" #'sp-backward-slurp-sexp
|
||
"M-h" #'sp-previous-sexp
|
||
"M-j" #'sp-down-sexp
|
||
"M-k" #'sp-backward-up-sexp
|
||
"M-l" #'sp-next-sexp
|
||
"M-S-j" #'sp-up-sexp
|
||
"M-S-k" #'sp-backward-down-sexp
|
||
"M-K" #'sp-split-sexp))
|
||
#+end_src
|
||
*** Common Lisp auto insert
|
||
Like C/C++'s auto insert, but with Common Lisp comments.
|
||
#+begin_src emacs-lisp
|
||
(use-package lisp-mode
|
||
:init
|
||
(defun +lisp/copyright-notice ()
|
||
(let* ((lines (split-string (+license/copyright-notice) "\n"))
|
||
(copyright-line (car lines))
|
||
(rest (cdr lines)))
|
||
(-->
|
||
(lambda (x)
|
||
(if (string= x "")
|
||
""
|
||
(concat ";; " x)))
|
||
(mapconcat it rest "\n")
|
||
(format ";; %s\n%s\n"
|
||
copyright-line
|
||
it))))
|
||
:auto-insert
|
||
(("\\.lisp\\'" . "Common Lisp Skeleton")
|
||
""
|
||
";;; " (file-name-nondirectory (buffer-file-name)) " - "
|
||
(format-time-string "%Y-%m-%d") "\n\n"
|
||
(+lisp/copyright-notice) "\n"
|
||
";;; Commentary:\n\n;;\n\n;;; Code:\n"))
|
||
#+end_src
|
||
*** Sly
|
||
While Emacs does an okay job for editing Common Lisp it's not amazing
|
||
for actually developing large scale projects. Considering how good an
|
||
environment Emacs is for Emacs Lisp, and how similar the two languages
|
||
are, we shouldn't need an LSP.
|
||
|
||
Enter /SLY/. Sly is a fork of /SLIME/ and it provides the essential
|
||
components to elevate Emacs' ability to develop Common Lisp. I feel
|
||
calling the ability Sly gives you "IDE-like" a slight against it - no
|
||
IDE I have used is as capable in aiding development as Emacs + Sly.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package sly
|
||
:straight t
|
||
:defer t
|
||
:init
|
||
(setq inferior-lisp-program "sbcl"
|
||
sly-lisp-loop-body-forms-indentation 0)
|
||
:display
|
||
("\\*sly-db"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25))
|
||
("\\*sly-inspector"
|
||
(display-buffer-at-bottom)
|
||
(window-height . 0.25))
|
||
("\\*sly-mrepl"
|
||
(display-buffer-in-side-window)
|
||
(window-height . 0.25)
|
||
(side . bottom))
|
||
:config
|
||
(evil-set-initial-state 'sly-db-mode 'normal)
|
||
(with-eval-after-load "org"
|
||
(setq-default org-babel-lisp-eval-fn #'sly-eval))
|
||
(with-eval-after-load "company"
|
||
(add-hook 'sly-mrepl-hook #'company-mode))
|
||
:general
|
||
(shell-leader
|
||
"s" #'sly)
|
||
(nmap
|
||
:keymaps 'lisp-mode-map
|
||
"gr" #'sly-eval-buffer
|
||
"gd" #'sly-edit-definition
|
||
"gR" #'sly-who-calls)
|
||
(local-leader
|
||
:keymaps 'lisp-mode-map
|
||
"a" #'sly-apropos
|
||
"d" #'sly-describe-symbol
|
||
"s" #'sly-stickers-dwim
|
||
"l" #'sly-load-file
|
||
"t" #'sly-trace-dialog-toggle-trace
|
||
"i" #'sly-inspect
|
||
"T" #'sly-trace-dialog
|
||
"c" #'sly-compile-defun
|
||
"S" #'sly-mrepl-sync
|
||
"D" #'sly-documentation-lookup
|
||
"C" #'sly-compile-file)
|
||
(local-leader
|
||
:keymaps 'lisp-mode-map
|
||
:infix "e"
|
||
"b" #'sly-eval-buffer
|
||
"e" #'sly-eval-last-expression
|
||
"f" #'sly-eval-defun
|
||
"r" #'sly-eval-region)
|
||
(nmap
|
||
:keymaps 'sly-mrepl-mode-map
|
||
"M-j" #'sly-mrepl-next-input-or-button
|
||
"M-k" #'sly-mrepl-previous-input-or-button
|
||
"C-j" #'sly-mrepl-next-prompt
|
||
"C-k" #'sly-mrepl-previous-prompt)
|
||
(local-leader
|
||
:keymaps 'sly-mrepl-mode-map
|
||
"c" #'sly-mrepl-clear-repl
|
||
"s" #'sly-mrepl-shortcut
|
||
"l" #'sly-load-file
|
||
"g" #'sly-mrepl-set-directory)
|
||
(nmap
|
||
:keymaps 'sly-db-mode-map
|
||
"C-i" #'sly-db-cycle
|
||
"g?" #'describe-mode
|
||
"S" #'sly-db-show-frame-source
|
||
"e" #'sly-db-eval-in-frame
|
||
"d" #'sly-db-pprint-eval-in-frame
|
||
"D" #'sly-db-disassemble
|
||
"i" #'sly-db-inspect-in-frame
|
||
"gj" #'sly-db-down
|
||
"gk" #'sly-db-up
|
||
"C-j" #'sly-db-down
|
||
"C-k" #'sly-db-up
|
||
"]]" #'sly-db-details-down
|
||
"[[" #'sly-db-details-up
|
||
"M-j" #'sly-db-details-down
|
||
"M-k" #'sly-db-details-up
|
||
"G" #'sly-db-end-of-backtrace
|
||
"t" #'sly-db-toggle-details
|
||
"gr" #'sly-db-restart-frame
|
||
"I" #'sly-db-invoke-restart-by-name
|
||
"R" #'sly-db-return-from-frame
|
||
"c" #'sly-db-continue
|
||
"s" #'sly-db-step
|
||
"n" #'sly-db-next
|
||
"o" #'sly-db-out
|
||
"b" #'sly-db-break-on-return
|
||
"a" #'sly-db-abort
|
||
"q" #'sly-db-quit
|
||
"A" #'sly-db-break-with-system-debugger
|
||
"B" #'sly-db-break-with-default-debugger
|
||
"P" #'sly-db-print-condition
|
||
"C" #'sly-db-inspect-condition
|
||
"g:" #'sly-interactive-eval
|
||
"0" #'sly-db-invoke-restart-0
|
||
"1" #'sly-db-invoke-restart-1
|
||
"2" #'sly-db-invoke-restart-2
|
||
"3" #'sly-db-invoke-restart-3
|
||
"4" #'sly-db-invoke-restart-4
|
||
"5" #'sly-db-invoke-restart-5
|
||
"6" #'sly-db-invoke-restart-6
|
||
"7" #'sly-db-invoke-restart-7
|
||
"8" #'sly-db-invoke-restart-8
|
||
"9" #'sly-db-invoke-restart-9)
|
||
(nmap
|
||
:keymaps 'sly-inspector-mode-map
|
||
"q" #'sly-inspector-quit)
|
||
(nmap
|
||
:keymaps 'sly-trace-dialog-mode-map
|
||
"gr" #'sly-trace-dialog-fetch-traces)
|
||
(local-leader
|
||
:keymaps 'sly-trace-dialog-mode-map
|
||
"r" #'sly-trace-dialog-fetch-status
|
||
"c" #'sly-trace-dialog-clear-fetched-traces))
|
||
#+end_src
|
||
*** Lisp indent function
|
||
Add a new lisp indent function which indents newline lists more
|
||
appropriately.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package lisp-mode
|
||
:defer t
|
||
:config
|
||
(defun +oreo/lisp-indent-function (indent-point state)
|
||
(let ((normal-indent (current-column))
|
||
(orig-point (point)))
|
||
(goto-char (1+ (elt state 1)))
|
||
(parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
|
||
(cond
|
||
;; car of form doesn't seem to be a symbol, or is a keyword
|
||
((and (elt state 2)
|
||
(or (not (looking-at "\\sw\\|\\s_"))
|
||
(looking-at ":")))
|
||
(if (not (> (save-excursion (forward-line 1) (point))
|
||
calculate-lisp-indent-last-sexp))
|
||
(progn (goto-char calculate-lisp-indent-last-sexp)
|
||
(beginning-of-line)
|
||
(parse-partial-sexp (point)
|
||
calculate-lisp-indent-last-sexp 0 t)))
|
||
;; Indent under the list or under the first sexp on the same
|
||
;; line as calculate-lisp-indent-last-sexp. Note that first
|
||
;; thing on that line has to be complete sexp since we are
|
||
;; inside the innermost containing sexp.
|
||
(backward-prefix-chars)
|
||
(current-column))
|
||
((and (save-excursion
|
||
(goto-char indent-point)
|
||
(skip-syntax-forward " ")
|
||
(not (looking-at ":")))
|
||
(save-excursion
|
||
(goto-char orig-point)
|
||
(looking-at ":")))
|
||
(save-excursion
|
||
(goto-char (+ 2 (elt state 1)))
|
||
(current-column)))
|
||
(t
|
||
(let ((function (buffer-substring (point)
|
||
(progn (forward-sexp 1) (point))))
|
||
method)
|
||
(setq method (or (function-get (intern-soft function)
|
||
'lisp-indent-function)
|
||
(get (intern-soft function) 'lisp-indent-hook)))
|
||
(cond ((or (eq method 'defun)
|
||
(and (null method)
|
||
(> (length function) 3)
|
||
(string-match "\\`def" function)))
|
||
(lisp-indent-defform state indent-point))
|
||
((integerp method)
|
||
(lisp-indent-specform method state
|
||
indent-point normal-indent))
|
||
(method
|
||
(funcall method indent-point state))))))))
|
||
(setq-default lisp-indent-function #'+oreo/lisp-indent-function))
|
||
#+end_src
|
||
* Miscellaneous
|
||
** gptel
|
||
LLMs in my Emacs?? What kind of developer have I become!
|
||
|
||
I came kinda late to the party with AI and LLM usage for development -
|
||
I did try them out much earlier near 2022-2023 but found them
|
||
obtrusive to use. They didn't integrate into my workflow well and
|
||
providing the context necessary for some of the problems I was facing
|
||
was, in and of itself, a gargantuan task.
|
||
|
||
~gptel~ changes that in quite a dramatic way: incredibly smooth
|
||
integration of LLMs into my Emacs instance. Some things this package
|
||
does:
|
||
- Call an LLM from any buffer in Emacs: from code buffers, to a
|
||
dedicated chat buffer, even [[*EShell][EShell]]!
|
||
- Maintain large persistent conversations just by saving the
|
||
conversation to disc
|
||
- Use ~org-mode~ for conversations, so generated code is actually in
|
||
clean source code blocks
|
||
- Allows one to slickly use all options available via one transient
|
||
interface ~gptel-menu~
|
||
#+begin_src emacs-lisp
|
||
(use-package gptel
|
||
:straight t
|
||
:general
|
||
(app-leader
|
||
"g" #'gptel-menu)
|
||
(local-leader
|
||
:keymaps 'gptel-mode-map
|
||
"RET" #'gptel-send)
|
||
:init
|
||
(setq gptel-default-mode 'org-mode
|
||
gptel-prompt-prefix-alist '((markdown-mode . "# Prompt:")
|
||
(org-mode . "* Prompt:")
|
||
(text-mode . "# Prompt:"))
|
||
gptel-response-prefix-alist '((markdown-mode . "# Response:\n")
|
||
(org-mode . "* Response:\n")
|
||
(text-mode . "# Response:\n"))
|
||
gptel-directives
|
||
'((default . "You are a large language model living in Emacs and a helpful assistant. Respond concisely and with justification.")
|
||
(programming . "You are a large language model and a careful programmer. Provide code and only code as output without any additional text, prompt or note.")
|
||
(writing . "You are a large language model and a writing assistant. Respond concisely.")
|
||
(chat . "You are a large language model and a conversation partner. Respond concisely.")
|
||
(networking . "You are a large language model and an experienced networking technician talking to a colleague. You have the CCNA qualification. Respond concisely and with justification.")))
|
||
:config
|
||
(defun gptel-auto-fill-response (beg end)
|
||
"Auto-fill the text response inserted between BEG and END, skipping Org
|
||
source code blocks."
|
||
(save-excursion
|
||
(goto-char beg)
|
||
(let ((current-point beg))
|
||
(while (re-search-forward "^\s*#\\+begin_src\\|^\s*#\\+end_src" end t)
|
||
;; we've found a block indicator - what kind of block is it?
|
||
(let ((block-start (match-beginning 0))
|
||
(block-end (match-end 0)))
|
||
(cond
|
||
((and (string-match-p "^\s*#\\+begin_src" (match-string 0))
|
||
(< current-point block-start))
|
||
;; We should fill everything we can up to the source block
|
||
(fill-region current-point block-start)
|
||
;; make sure we move current-point to look for the "end of block"
|
||
(setq current-point block-end))
|
||
(t
|
||
;; we're at an end block indicator, so we should bump our
|
||
;; current-point just past it.
|
||
(setq current-point (1+ block-end)))))
|
||
(goto-char current-point))
|
||
(when (< current-point end)
|
||
;; Any stragglers, get them filled as well
|
||
(fill-region current-point end)))))
|
||
|
||
(add-hook 'gptel-post-response-functions #'gptel-auto-fill-response))
|
||
#+end_src
|
||
** Evil additions
|
||
Additional packages that add the functionality of plugins in Vim I
|
||
really liked, as well as some new stuff.
|
||
*** Evil surround
|
||
A port for vim-surround, providing the ability to mutate delimiters
|
||
around some text.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-surround
|
||
:straight t
|
||
:after evil
|
||
:config
|
||
(global-evil-surround-mode))
|
||
#+end_src
|
||
*** Evil commentary
|
||
A port of vim-commentary, providing generalised commenting of objects.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-commentary
|
||
:straight t
|
||
:after evil
|
||
:config
|
||
(evil-commentary-mode))
|
||
#+end_src
|
||
*** Evil multi edit
|
||
Evil-ME provides a simpler parallel editing experience within the same
|
||
buffer. I now use it exclusively over evil multi-cursor: this is
|
||
designed better, its less buggy, and doesn't try to over complicate
|
||
things. There are many things it can't do, but normal Vim motions
|
||
can. It's useful for trialing
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-multiedit
|
||
:straight t
|
||
:defer t
|
||
:init
|
||
(setq evil-multiedit-scope 'visible)
|
||
:general
|
||
(:states '(normal visual)
|
||
:keymaps 'override
|
||
"M-e" #'evil-multiedit-match-and-next
|
||
"M-E" #'evil-multiedit-match-and-prev))
|
||
#+end_src
|
||
*** Evil collection
|
||
Provides a community based set of keybindings for most modes in Emacs.
|
||
I don't necessarily like all my modes having these bindings though, as
|
||
I may disagree with some. So I use it in a mode to mode basis.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-collection
|
||
:straight t
|
||
:after evil)
|
||
#+end_src
|
||
*** Evil numbers
|
||
Increment/decrement a number at point like Vim does, but use bindings
|
||
that don't conflict with Emacs default.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-numbers
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
"+" #'evil-numbers/inc-at-pt
|
||
"-" #'evil-numbers/dec-at-pt
|
||
"g+" #'evil-numbers/inc-at-pt-incremental
|
||
"g-" #'evil-numbers/dec-at-pt-incremental))
|
||
#+end_src
|
||
*** Evil goggles
|
||
Make it easier to notice edits and changes using Vim motions!
|
||
#+begin_src emacs-lisp
|
||
(use-package evil-goggles
|
||
:straight t
|
||
:after evil
|
||
:init
|
||
(setq evil-goggles-duration 0.1
|
||
evil-goggles-blocking-duration 0.1
|
||
evil-goggles-async-duration 0.9
|
||
evil-goggles-default-face 'pulsar-cyan)
|
||
:config
|
||
(evil-goggles-mode)
|
||
(evil-goggles-use-diff-faces))
|
||
#+end_src
|
||
** Save place
|
||
Saves current place in a buffer permanently, so on revisiting the file
|
||
(even in a different Emacs instance) you go back to the place you were
|
||
at last.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package saveplace
|
||
:defer t
|
||
:hook (after-init-hook . save-place-mode))
|
||
#+end_src
|
||
** Tabs
|
||
Tabs in vscode are just like buffers in Emacs but way slower and
|
||
harder to use. Tabs in Emacs are essentially window layouts, similar
|
||
to instances in Tmux. With this setup I can use tabs quite
|
||
effectively.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package tab-bar
|
||
:defer t
|
||
:hook (after-init-hook . tab-bar-mode)
|
||
:init
|
||
(setq tab-bar-close-button-show nil
|
||
tab-bar-format '(tab-bar-format-history
|
||
tab-bar-format-tabs
|
||
tab-bar-separator)
|
||
tab-bar-show 1
|
||
tab-bar-auto-width t
|
||
tab-bar-auto-width-max '((150) 20)
|
||
tab-bar-auto-width-min '((20) 2)
|
||
tab-bar-tab-name-function #'+tab-bar/name-func)
|
||
|
||
(defvar +tab-bar/buffer-name-max-len 9)
|
||
(defvar +tab-bar/project-name-max-len 10)
|
||
|
||
(defun +tab-bar/--abbreviate-project-name ()
|
||
(if-let* ((project (project-current))
|
||
(name (project-name project)))
|
||
(if (> (length name) +tab-bar/project-name-max-len)
|
||
(concat (substring name 0 +tab-bar/project-name-max-len) "<...>")
|
||
name)
|
||
""))
|
||
|
||
(defun +tab-bar/--abbreviate-buffer-name ()
|
||
(let* ((buffer-name (buffer-name (window-buffer (or (minibuffer-selected-window)
|
||
(and (window-minibuffer-p)
|
||
(get-mru-window))))))
|
||
(ext (string-match "\\.[[:word:]]+$" buffer-name)))
|
||
(if (> (or ext (length buffer-name)) +tab-bar/buffer-name-max-len)
|
||
(thread-first
|
||
buffer-name
|
||
(substring 0 +tab-bar/buffer-name-max-len)
|
||
(concat "<...>"
|
||
(if ext
|
||
(substring buffer-name ext)
|
||
"")))
|
||
buffer-name)))
|
||
|
||
(defun +tab-bar/name-func ()
|
||
(if (null (project-current))
|
||
(+tab-bar/--abbreviate-buffer-name)
|
||
(format "%s[%s]"
|
||
(+tab-bar/--abbreviate-project-name)
|
||
(+tab-bar/--abbreviate-buffer-name))))
|
||
:general
|
||
(tab-leader
|
||
"R" #'tab-rename
|
||
"c" #'tab-close
|
||
"d" #'tab-close
|
||
"h" #'tab-move-to
|
||
"j" #'tab-next
|
||
"k" #'tab-previous
|
||
"l" #'tab-move
|
||
"n" #'tab-new
|
||
"r" #'tab-switch
|
||
"w" #'tab-window-detach
|
||
"b" (proc-int (switch-to-buffer-other-tab (current-buffer)))))
|
||
#+end_src
|
||
** Registers
|
||
Registers are essentially an alist of symbols mapped to some Lisp
|
||
object, which can be easily accessed and manipulated. Some common use
|
||
cases of registers are:
|
||
+ Marking locations in a file to quickly go to (using Emacs' in-built
|
||
notion of marks)
|
||
+ Copying and pasting text without the clipboard (essentially even
|
||
more clipboards)
|
||
+ Creating number counters (usually for macros)
|
||
|
||
Of course, Vim has its own notion of registers which are way less
|
||
capable than Emacs. Evil emulates this limited notion of registers,
|
||
but I prefer Emacs' hence the configuration here.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package register
|
||
:config
|
||
(nmmap
|
||
"m" #'point-to-register
|
||
"'" #'jump-to-register))
|
||
#+end_src
|
||
** Bookmarks
|
||
Bookmarks are like persistent registers. Like registers, they can
|
||
kinda work anywhere in Emacs: from remote files via =tramp= to
|
||
webpages with [[*EWW][EWW]]. Since they're persistent, they'll live
|
||
regardless of the current Emacs session - and because they're like
|
||
registers, they'll remember the exact context (position in buffer,
|
||
time since last updated, etc).
|
||
#+begin_src emacs-lisp
|
||
(use-package bookmark
|
||
:general
|
||
(leader "x" bookmark-map)
|
||
:init
|
||
(setq bookmark-watch-bookmark-file t
|
||
bookmark-save-flag 1))
|
||
#+end_src
|
||
** Recentf
|
||
Recentf provides a method of keeping track of recently opened files.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package recentf
|
||
:defer t
|
||
:init
|
||
(setq recentf-auto-cleanup 'never)
|
||
:hook (after-init-hook . recentf-mode)
|
||
:general
|
||
(file-leader
|
||
"r" #'recentf))
|
||
#+end_src
|
||
** Memory-report
|
||
New feature of Emacs-29, gives a rough report of memory usage with
|
||
some details. Useful to know on a long Emacs instance what could be
|
||
eating up memory.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package memory-report
|
||
:defer t
|
||
:general
|
||
(leader
|
||
"qm" #'memory-report))
|
||
#+end_src
|
||
** Helpful
|
||
Helpful provides a modern interface for some common help commands. I
|
||
replace ~describe-function~, ~describe-variable~ and ~describe-key~ by
|
||
their helpful counterparts.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package helpful
|
||
:straight t
|
||
:defer t
|
||
:commands (helpful-callable helpful-variable)
|
||
:general
|
||
([remap describe-function] #'helpful-callable
|
||
[remap describe-variable] #'helpful-variable
|
||
[remap describe-key] #'helpful-key
|
||
[remap describe-symbol] #'helpful-symbol)
|
||
:display
|
||
("\\*helpful.*"
|
||
(display-buffer-at-bottom)
|
||
(inhibit-duplicate-buffer . t)
|
||
(window-height . 0.35))
|
||
:config
|
||
(evil-define-key 'normal helpful-mode-map "q" #'quit-window)
|
||
;; Override amx-describe-function to use helpful instead
|
||
(with-eval-after-load "amx"
|
||
(define-advice amx-describe-function (:override (&rest r))
|
||
"Use helpful-function to describe current function in amx."
|
||
(interactive)
|
||
(amx-do-with-selected-item (lambda (chosen)
|
||
(helpful-function chosen))))))
|
||
#+end_src
|
||
** Avy and Ace
|
||
Avy is a package that provides "jump" functions. Given some input,
|
||
the current buffer is scanned and any matching candidates are given a
|
||
tag which the user can input to perform some action (usually moving
|
||
the cursor to that point).
|
||
*** Avy core
|
||
Setup avy with leader. As I use ~avy-goto-char-timer~ a lot, use the
|
||
~C-s~ bind which replaces isearch. Switch isearch to M-s in case I
|
||
need to use it.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package avy
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
:keymaps 'override
|
||
"C-s" #'avy-goto-char-timer
|
||
"M-s" #'isearch-forward
|
||
"gp" #'avy-copy-region
|
||
"gP" #'avy-move-region
|
||
"gl" #'avy-goto-line
|
||
"gw" #'avy-goto-word-0))
|
||
#+end_src
|
||
*** Ace window
|
||
Though evil provides a great many features in terms of window
|
||
management, ace window can provide some nicer chords for higher
|
||
management of windows (closing, switching, etc).
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package ace-window
|
||
:straight t
|
||
:defer t
|
||
:custom
|
||
(aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
|
||
:general
|
||
(nmmap
|
||
[remap evil-window-next] #'ace-window))
|
||
#+end_src
|
||
*** Ace link
|
||
Avy-style link following!
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package ace-link
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
:keymaps 'override
|
||
"gL" #'ace-link))
|
||
#+end_src
|
||
** Drag Stuff
|
||
Drag stuff around, like my favourite russian programmer (Tsoding). I
|
||
use it for moving around lines - for moving "words" or "symbols"
|
||
around, I use [[*Jagger][Jagger]].
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package drag-stuff
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
"C-M-j" #'drag-stuff-down
|
||
"C-M-k" #'drag-stuff-up))
|
||
#+end_src
|
||
** Jagger
|
||
Jagger is the only package I could find that allowed me to move
|
||
symbols around as I wanted.
|
||
#+begin_src emacs-lisp
|
||
(use-package jagger
|
||
:straight (:host github :repo "twlz0ne/jagger")
|
||
:defer t
|
||
:general
|
||
(nmmap
|
||
"C-M-h" #'jagger-move-sexp-backward
|
||
"C-M-l" #'jagger-move-sexp-forward))
|
||
#+end_src
|
||
** Separedit
|
||
Edit anything anywhere all at once!
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package separedit
|
||
:straight t
|
||
:defer t
|
||
:general
|
||
(leader "e" #'separedit)
|
||
:init
|
||
(setq separedit-default-mode 'org-mode
|
||
separedit-remove-trailing-spaces-in-comment t))
|
||
#+end_src
|
||
** Undo tree
|
||
Undo tree sits on top of Emacs' undo capabilities. It provides a nice
|
||
visual for the history of a buffer and is a great way to produce
|
||
branches of edits. This history may be saved to and loaded from the
|
||
disk, which makes Emacs a quasi version control system in and of
|
||
itself. The only feature left is describing changes...
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package undo-tree
|
||
:straight t
|
||
:demand t
|
||
:general
|
||
(leader
|
||
"u" #'undo-tree-visualize)
|
||
(mmap
|
||
:keymaps 'undo-tree-visualizer-mode-map
|
||
"t" #'undo-tree-visualizer-toggle-timestamps)
|
||
:init
|
||
(setq undo-tree-auto-save-history t
|
||
undo-tree-history-directory-alist backup-directory-alist)
|
||
:config
|
||
(global-undo-tree-mode))
|
||
#+end_src
|
||
** Searching common directories
|
||
Using [[file:elisp/search.el][search.el]] I can search a set of
|
||
directories particularly efficiently.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package search
|
||
:defer t
|
||
:load-path "elisp/"
|
||
:general
|
||
(search-leader
|
||
"a" #'+search/search-all)
|
||
(file-leader
|
||
"p" #'+search/find-file))
|
||
#+end_src
|
||
** Abbrevs
|
||
Just define a few abbrevs for various date-time operations. Also
|
||
define a macro that will assume a function for the expansion, helping
|
||
with abstracting a few things away.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package abbrev
|
||
:defer t
|
||
:hook
|
||
((prog-mode-hook text-mode-hook) . abbrev-mode)
|
||
:init
|
||
(defmacro +abbrev/define-abbrevs (abbrev-table &rest abbrevs)
|
||
`(progn
|
||
,@(mapcar #'(lambda (abbrev)
|
||
`(define-abbrev
|
||
,abbrev-table
|
||
,(car abbrev)
|
||
""
|
||
(proc (insert ,(cadr abbrev)))))
|
||
abbrevs)))
|
||
(setq save-abbrevs nil)
|
||
:config
|
||
(+abbrev/define-abbrevs
|
||
global-abbrev-table
|
||
("sdate"
|
||
(format-time-string "%Y-%m-%d" (current-time)))
|
||
("stime"
|
||
(format-time-string "%H:%M:%S" (current-time)))
|
||
("sday"
|
||
(format-time-string "%A" (current-time)))
|
||
("smon"
|
||
(format-time-string "%B" (current-time)))
|
||
("swho"
|
||
(format "%s <%s>" user-full-name user-mail-address))
|
||
("stodo"
|
||
(thread-last (current-time)
|
||
(format-time-string "%Y-%m-%d %H:%M")
|
||
(format "TODO(%s)[%s]:" user-login-name)))))
|
||
#+end_src
|
||
** Amx
|
||
Amx is a fork of Smex that works to enhance the
|
||
~execute-extended-command~ interface. It provides a lot of niceties
|
||
such as presenting the key bind when looking for a command.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package amx
|
||
:straight t
|
||
:demand t
|
||
:init
|
||
(setq amx-backend 'auto)
|
||
:config
|
||
(amx-mode))
|
||
#+end_src
|
||
** Yasnippet
|
||
Look at the snippets [[file:../.config/yasnippet/snippets/][folder]]
|
||
for all snippets I've got.
|
||
|
||
#+begin_src emacs-lisp
|
||
(use-package yasnippet
|
||
:straight t
|
||
:defer t
|
||
:hook
|
||
(prog-mode-hook . yas-minor-mode)
|
||
(text-mode-hook . yas-minor-mode)
|
||
:general
|
||
(insert-leader
|
||
"i" #'yas-insert-snippet)
|
||
:config
|
||
(yas-load-directory (no-littering-expand-etc-file-name
|
||
"yasnippet/snippets")))
|
||
#+end_src
|