emacs-dot-d/unravel-emacs.org

984 lines
37 KiB
Org Mode
Raw Normal View History

#+title: GNU Emacs configuration
#+author: Vedang Manerikar
#+email: vedang@unravel.tech
#+language: en
#+options: ':t toc:nil num:t author:t email:t
This configuration is inspired from the work of [[https://github.com/protesilaos/dotfiles/blob/master/emacs/.emacs.d/prot-emacs.org][my hero Prot]]. It is not as clean and well-written as his configuration, but I'll get there eventually (probably before announcing this to the world).
#+src emacs-lisp :tangle no :results none
(org-babel-tangle)
#+end_src
#+toc: headlines 1
Here is what the generated directory structure should look like:
#+begin_src sh :dir ~/src/prototypes/emacs-up :results raw
tree -F
#+end_src
#+RESULTS:
./
├── early-init.el
├── init.el
├── unravel-emacs.org
└── unravel-modules/
├── unravel-langs.el
└── unravel-theme.el
2 directories, 5 files
To make a change to this Emacs configuration, edit this file and then type =C-c C-v C-t= (=M-x org-babel-tangle=) to republish all the relevant files.
* The ~early-init.el~ file
This is the first file that Emacs loads. This file should only contain the absolute basic stuff Emacs needs to load first, before it loads ~init.el~.
** Basic frame settings
#+begin_src emacs-lisp :tangle "early-init.el"
;;; No GUI
(dolist (mode '(menu-bar-mode tool-bar-mode scroll-bar-mode))
(when (fboundp mode) (funcall mode -1)))
#+end_src
** Tweaking the Garbage collector for faster startup
#+begin_src emacs-lisp :tangle "early-init.el"
;; A big contributor to startup times is garbage collection.
;; We up the gc threshold to temporarily prevent it from running, then
;; reset it later by enabling `gcmh-mode'. Not resetting it will cause
;; stuttering/freezes.
(setq gc-cons-threshold most-positive-fixnum)
#+end_src
** If you have both .el and .elc files, load the newer one
#+begin_src emacs-lisp :tangle "early-init.el"
;; When both .el and .elc / .eln files are available,
;; load the latest one.
(setq load-prefer-newer t)
#+end_src
** Initialize the package cache
#+begin_src emacs-lisp :tangle "early-init.el"
;; Ensure that `describe-package' does not require a
;; `package-refresh-contents'.
(setq package-enable-at-startup t)
#+end_src
** Give the default frame a name
Naming frames allows you to select them using completion (=M-x select-frame-by-name=).
#+begin_src emacs-lisp :tangle "early-init.el"
;; Name the default frame
;; You can select a frame with M-x select-frame-by-name
(add-hook 'after-init-hook (lambda () (set-frame-name "unravel")))
#+end_src
* The ~init.el~ file
This is the main initialisation file of Emacs. Everything loads from here, even if it has been split into multiple files for convenience.
** Silence native-comp warnings
These are unnecessarily scary
#+begin_src emacs-lisp :tangle "init.el"
;; Make native compilation silent and prune its cache.
(when (native-comp-available-p)
(setq native-comp-async-report-warnings-errors 'silent) ; Emacs 28 with native compilation
(setq native-compile-prune-cache t)) ; Emacs 29
#+end_src
** Ignore the custom file, and just rely on this config
There is no need to use the ~M-x customize~ infrastructure. It's easier to just rely on the init file instead.
#+begin_src emacs-lisp :tangle "init.el"
;; Disable custom.el by making it disposable.
(setq custom-file (make-temp-file "emacs-custom-"))
#+end_src
** Enable commands disabled by default
These commands are actually useful, especially in org-mode.
#+begin_src emacs-lisp :tangle "init.el"
;; Enable these commands which have been disabled by default
(mapc
(lambda (command)
(put command 'disabled nil))
'(list-timers narrow-to-region narrow-to-page upcase-region downcase-region))
#+end_src
** Disable unnecessary commands enabled by default
These commands are "unsafe", in that we should be using the alternatives (like ~vterm~ and ~org~)
#+begin_src emacs-lisp :tangle "init.el"
;; Disable these commands which have been enabled by default
(mapc
(lambda (command)
(put command 'disabled t))
'(eshell project-eshell overwrite-mode iconify-frame diary))
#+end_src
** Add the modules folder to the load-path
This is where all the custom configuration sits for all the packages I use. We write configuration on a per-file basis instead of in a giant file, because these smaller files are more readable, approachable and shareable.
#+begin_src emacs-lisp :tangle "init.el"
(mapc
(lambda (string)
(add-to-list 'load-path (locate-user-emacs-file string)))
'("unravel-modules"))
#+end_src
** Setup ~package.el~ for Package Management
#+begin_src emacs-lisp :tangle "init.el"
;;;; Packages
(setq package-vc-register-as-project nil) ; Emacs 30
(add-hook 'package-menu-mode-hook #'hl-line-mode)
;; Also read: <https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/>
(setq package-archives
'(("gnu-elpa" . "https://elpa.gnu.org/packages/")
("gnu-elpa-devel" . "https://elpa.gnu.org/devel/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("melpa" . "https://melpa.org/packages/")))
;; Highest number gets priority (what is not mentioned has priority 0)
(setq package-archive-priorities
'(("gnu-elpa" . 3)
("melpa" . 2)
("nongnu" . 1)))
(setq package-install-upgrade-built-in t)
#+end_src
** Load individual modules
Now we are ready to load our per-module configuration files:
#+begin_src emacs-lisp :tangle "init.el"
(require 'unravel-theme)
(require 'unravel-essentials)
;; (require 'unravel-modeline)
;; (require 'unravel-completion)
;; (require 'unravel-search)
;; (require 'unravel-dired)
;; (require 'unravel-window)
;; (require 'unravel-git)
;; (require 'unravel-org)
(require 'unravel-langs)
#+end_src
* The ~unravel-theme.el~ module
This module defines everything related to the aesthetics of Emacs.
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el" :mkdirp yes
;;; Everything related to the look of Emacs
#+end_src
** ~ef-themes~ for cool, modern themes
I use themes from the ~ef-themes~ package exclusively.
Prot is the lead developer and maintainer of this package.
+ Package name (GNU ELPA): ~ef-themes~
+ Official manual: <https://protesilaos.com/emacs/ef-themes>
+ Change log: <https://protesilaos.com/emacs/ef-themes-changelog>
+ Git repositories:
- GitHub: <https://github.com/protesilaos/ef-themes>
- GitLab: <https://gitlab.com/protesilaos/ef-themes>
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el"
;;; The Ef (εὖ) themes
;; The themes are customisable. Read the manual:
;; <https://protesilaos.com/emacs/ef-themes>.
(use-package ef-themes
:ensure t
:demand t
:bind
(("<f5>" . ef-themes-rotate)
("C-<f5>" . ef-themes-select))
:config
(setq ef-themes-to-toggle '(ef-elea-light ef-elea-dark)
ef-themes-variable-pitch-ui t
ef-themes-mixed-fonts t
ef-themes-headings ; read the manual's entry of the doc string
'((0 . (variable-pitch light 1.9))
(1 . (variable-pitch light 1.8))
(2 . (variable-pitch regular 1.7))
(3 . (variable-pitch regular 1.6))
(4 . (variable-pitch regular 1.5))
(5 . (variable-pitch 1.4)) ; absence of weight means `bold'
(6 . (variable-pitch 1.3))
(7 . (variable-pitch 1.2))
(agenda-date . (semilight 1.5))
(agenda-structure . (variable-pitch light 1.9))
(t . (variable-pitch 1.1))))
(ef-themes-select 'ef-elea-light))
#+end_src
** ~lin~ for an improvement on ~hl-line-mode~
Prot is the lead developer and maintainer of this package.
+ Package name (GNU ELPA): ~lin~
+ Official manual: <https://protesilaos.com/emacs/lin>
+ Change log: <https://protesilaos.com/emacs/lin-changelog>
+ Git repositories:
- GitHub: <https://github.com/protesilaos/lin>
- GitLab: <https://gitlab.com/protesilaos/lin>
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el"
;;;; Lin
;; Read the lin manual: <https://protesilaos.com/emacs/lin>.
(use-package lin
:ensure t
:hook (after-init . lin-global-mode) ; applies to all `lin-mode-hooks'
:config
(setopt lin-face 'lin-cyan))
#+end_src
** ~spacious-padding~ for a comfortable reading experience
Prot is the lead developer and maintainer of this package.
Inspiration for this package comes from [[https://github.com/rougier][Nicolas Rougier's impressive designs]]
and [[https://github.com/minad/org-modern][Daniel Mendler's ~org-modern~ package]].
+ Package name (GNU ELPA): ~spacious-padding~
+ Official manual: <https://protesilaos.com/emacs/spacious-padding>
+ Git repositories:
- GitHub: <https://github.com/protesilaos/spacious-padding>
- GitLab: <https://gitlab.com/protesilaos/spacious-padding>
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el"
;;;; Increase padding of windows/frames
;; <https://protesilaos.com/codelog/2023-06-03-emacs-spacious-padding/>.
(use-package spacious-padding
:ensure t
:if (display-graphic-p)
:hook (after-init . spacious-padding-mode)
:init
;; These are the defaults, but I keep it here for visiibility.
(setq spacious-padding-widths
'(:internal-border-width 30
:header-line-width 4
:mode-line-width 6
:tab-width 4
:right-divider-width 30
:scroll-bar-width 8
:left-fringe-width 20
:right-fringe-width 20))
;; Read the doc string of `spacious-padding-subtle-mode-line' as
;; it is very flexible.
(setq spacious-padding-subtle-mode-line t))
#+end_src
** ~cursory~ for an improved Emacs cursor
The ~cursory~ package provides a thin wrapper around built-in variables that affect the style of the Emacs cursor on graphical terminals. The intent is to allow the user to define preset configurations such as "block with slow blinking" or "bar with fast blinking" and set them on demand.
Prot is the lead developer and maintainer.
+ Package name (GNU ELPA): ~cursory~
+ Official manual: <https://protesilaos.com/emacs/cursory>
+ Change log: <https://protesilaos.com/emacs/cursory-changelog>
+ Git repositories:
- GitHub: <https://github.com/protesilaos/cursory>
- GitLab: <https://gitlab.com/protesilaos/cursory>
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el"
;;; Cursor appearance (cursory)
;; Read the manual: <https://protesilaos.com/emacs/cursory>.
(use-package cursory
:ensure t
:demand t
:if (display-graphic-p)
:config
(setq cursory-presets
'((box
:blink-cursor-interval 1.2)
(box-no-blink
:blink-cursor-mode -1)
(bar
:cursor-type (bar . 2)
:blink-cursor-interval 0.8)
(bar-no-other-window
:inherit bar
:cursor-in-non-selected-windows nil)
(bar-no-blink
:cursor-type (bar . 2)
:blink-cursor-mode -1)
(underscore
:cursor-type (hbar . 3)
:blink-cursor-interval 0.3
:blink-cursor-blinks 50)
(underscore-no-other-window
:inherit underscore
:cursor-in-non-selected-windows nil)
(underscore-thick
:cursor-type (hbar . 8)
:blink-cursor-interval 0.3
:blink-cursor-blinks 50
:cursor-in-non-selected-windows (hbar . 3))
(underscore-thick-no-blink
:blink-cursor-mode -1
:cursor-type (hbar . 8)
:cursor-in-non-selected-windows (hbar . 3))
(t ; the default values
:cursor-type box
:cursor-in-non-selected-windows hollow
:blink-cursor-mode 1
:blink-cursor-blinks 10
:blink-cursor-interval 0.2
:blink-cursor-delay 0.2)))
;; I am using the default values of `cursory-latest-state-file'.
;; Set last preset or fall back to desired style from `cursory-presets'.
(cursory-set-preset (or (cursory-restore-latest-preset) 'box))
(cursory-mode 1))
#+end_src
** ~theme-buffet~ for automatically changing the theme based on time of day
Bruno Boal is the lead developer and Prot is a co-maintainer.
+ Package name (GNU ELPA): ~theme-buffet~
+ Git repo on SourceHut: <https://git.sr.ht/~bboal/theme-buffet>
- Mirrors:
+ GitHub: <https://github.com/BBoal/theme-buffet>
+ Codeberg: <https://codeberg.org/BBoal/theme-buffet>
+ Mailing list: <https://lists.sr.ht/~bboal/general-issues>
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el"
;;;; Theme buffet
;; <https://git.sr.ht/~bboal/theme-buffet>
(use-package theme-buffet
:ensure t
:after (:any modus-themes ef-themes)
:defer 1
:config
(let ((modus-themes-p (featurep 'modus-themes))
(ef-themes-p (featurep 'ef-themes)))
(setq theme-buffet-menu 'end-user)
(setq theme-buffet-time-offset 5)
(setq theme-buffet-end-user
'(:night (ef-dark ef-winter ef-autumn ef-night ef-duo-dark ef-symbiosis ef-owl)
:morning (ef-light ef-cyprus ef-spring ef-frost ef-duo-light ef-eagle)
:afternoon (ef-arbutus ef-day ef-kassio ef-summer ef-elea-light ef-maris-light ef-melissa-light ef-trio-light ef-reverie)
:evening (ef-rosa ef-elea-dark ef-maris-dark ef-melissa-dark ef-trio-dark ef-dream)))
(when (or modus-themes-p ef-themes-p)
(theme-buffet-timer-hours 2))))
#+end_src
** ~fontaine~ for beautiful font configuration
The ~fontaine~ package allows the user to define detailed font configurations and set them on demand. For example, one can have a =regular-editing= preset and another for =presentation-mode= (these are arbitrary, user-defined symbols): the former uses small fonts which are optimised for writing, while the latter applies typefaces that are pleasant to read at comfortable point sizes.
Prot is the lead developer and maintainer.
+ Package name (GNU ELPA): ~fontaine~
+ Official manual: <https://protesilaos.com/emacs/fontaine>
+ Change log: <https://protesilaos.com/emacs/fontaine-changelog>
+ Git repositories:
- GitHub: <https://github.com/protesilaos/fontaine>
- GitLab: <https://gitlab.com/protesilaos/fontaine>
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el"
;;;; Fontaine (font configurations)
;; Read the manual: <https://protesilaos.com/emacs/fontaine>
(use-package fontaine
:ensure t
:if (display-graphic-p)
:hook
;; Persist the latest font preset when closing/starting Emacs and
;; while switching between themes.
((after-init . fontaine-mode)
(after-init . (lambda ()
;; Set last preset or fall back to desired style from `fontaine-presets'.
(fontaine-set-preset (or (fontaine-restore-latest-preset) 'regular)))))
:config
;; This is defined in Emacs C code: it belongs to font settings.
(setq x-underline-at-descent-line nil)
;; And this is for Emacs28.
(setq-default text-scale-remap-header-line t)
;; This is the default value. Just including it here for
;; completeness.
(setq fontaine-latest-state-file (locate-user-emacs-file "fontaine-latest-state.eld"))
(setq fontaine-presets
'((small
:default-height 80)
(regular) ; like this it uses all the fallback values and is named `regular'
(medium
:default-weight semilight
:default-height 115
:bold-weight extrabold)
(large
:inherit medium
:default-height 150)
(live-stream
:default-family "Iosevka Comfy Wide Motion"
:default-height 150
:default-weight medium
:fixed-pitch-family "Iosevka Comfy Wide Motion"
:variable-pitch-family "Iosevka Comfy Wide Duo"
:bold-weight extrabold)
(presentation
:default-height 180)
(jumbo
:default-height 260)
(t
;; I keep all properties for didactic purposes, but most can be
;; omitted. See the fontaine manual for the technicalities:
;; <https://protesilaos.com/emacs/fontaine>.
:default-family "Iosevka Comfy"
:default-weight regular
:default-slant normal
:default-width normal
:default-height 100
:fixed-pitch-family "Iosevka Comfy"
:fixed-pitch-weight nil
:fixed-pitch-slant nil
:fixed-pitch-width nil
:fixed-pitch-height 1.0
:fixed-pitch-serif-family nil
:fixed-pitch-serif-weight nil
:fixed-pitch-serif-slant nil
:fixed-pitch-serif-width nil
:fixed-pitch-serif-height 1.0
:variable-pitch-family "Iosevka Comfy Motion Duo"
:variable-pitch-weight nil
:variable-pitch-slant nil
:variable-pitch-width nil
:variable-pitch-height 1.0
:mode-line-active-family nil
:mode-line-active-weight nil
:mode-line-active-slant nil
:mode-line-active-width nil
:mode-line-active-height 1.0
:mode-line-inactive-family nil
:mode-line-inactive-weight nil
:mode-line-inactive-slant nil
:mode-line-inactive-width nil
:mode-line-inactive-height 1.0
:header-line-family nil
:header-line-weight nil
:header-line-slant nil
:header-line-width nil
:header-line-height 1.0
:line-number-family nil
:line-number-weight nil
:line-number-slant nil
:line-number-width nil
:line-number-height 1.0
:tab-bar-family nil
:tab-bar-weight nil
:tab-bar-slant nil
:tab-bar-width nil
:tab-bar-height 1.0
:tab-line-family nil
:tab-line-weight nil
:tab-line-slant nil
:tab-line-width nil
:tab-line-height 1.0
:bold-family nil
:bold-slant nil
:bold-weight bold
:bold-width nil
:bold-height 1.0
:italic-family nil
:italic-weight nil
:italic-slant italic
:italic-width nil
:italic-height 1.0
:line-spacing nil))))
#+end_src
** Finally, we provide ~unravel-theme.el~ module
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-theme.el"
(provide 'unravel-theme)
#+end_src
* The ~unravel-langs.el~ module
** Controlling the behaviour of the TAB key
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el" :mkdirp yes
;;;; Tabs, indentation, and the TAB key
(use-package emacs
:ensure nil
:demand t
:config
(setq tab-always-indent 'complete)
(setq tab-first-completion 'word-or-paren-or-punct) ; Emacs 27
(setq-default tab-width 4
indent-tabs-mode nil))
#+end_src
** ~show-paren-mode~ for highlighting matching parens
The built-in ~show-paren-mode~ highlights the parenthesis on the opposite end of the current symbolic expression. It also highlights matching terms of control flow in programming languages that are not using parentheses like Lisp: for instance, in a ~bash~ shell script it highlights the ~if~ and ~fi~ keywords. This mode also works for prose and I use it globally. Simple and effective!
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el"
;;;; Parentheses (show-paren-mode)
(use-package paren
:ensure nil
:hook (prog-mode . show-paren-local-mode)
:config
(setq show-paren-style 'mixed)
(setq show-paren-when-point-in-periphery nil)
(setq show-paren-when-point-inside-paren nil)
(setq show-paren-context-when-offscreen 'overlay)) ; Emacs 29
#+end_src
** Settings for ~eldoc~: Emacs Live Documentation Feedback
The built-in ~eldoc~ feature is especially useful in programming modes. While we are in a function call, it produces an indicator in the echo area (where the minibuffer appears upon invocation) that shows the name of the function, the arguments it takes, if any, and highlights the current argument we are positioned at. This way, we do not have to go back to review the signature of the function just to remember its arity. Same principle for variables, where ~eldoc-mode~ puts the first line of their documentation string in the echo area.
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el"
;;;; Eldoc (Emacs live documentation feedback)
(use-package eldoc
:ensure nil
:hook (prog-mode . eldoc-mode)
:config
(setq eldoc-message-function #'message)) ; don't use mode line for M-x eval-expression, etc.
#+end_src
** Settings for ~eglot~ (LSP client)
:PROPERTIES:
:CUSTOM_ID: h:92258aa8-0d8c-4c12-91b4-5f44420435ce
:END:
The built-in ~eglot~ feature, developed and maintained by João Távora, is Emacs' own client for the Language Server Protocol (LSP). The LSP technology is all about enhancing the ability of a text editor to work with a given programming language. This works by installing a so-called "language server" on your computer, which the "LSP client" (i.e. ~eglot~) will plug into. A typical language server provides the following capabilities:
- Code completion :: This can be visualised for in-buffer automatic expansion of function calls, variables, and the like.
- Code linting :: To display suggestions, warnings, or errors. These are highlighted in the buffer, usually with an underline, and can also be displayed in a standalone buffer with the commands ~flymake-show-buffer-diagnostics~, ~flymake-show-project-diagnostics~
- Code navigation and cross-referencing :: While over a symbol, use a command to jump directly to its definition. The default key bindings for going forth and then back are =M-.= (~xref-find-definitions~) and =M-,= (~xref-go-back~).
Assuming the language server is installed, to start using the LSP client in a given file, do =M-x eglot=.
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el"
;;;; Eglot (built-in client for the language server protocol)
(use-package eglot
:ensure nil
:functions (eglot-ensure)
:commands (eglot)
:config
(setq eglot-sync-connect nil)
(setq eglot-autoshutdown t))
#+end_src
** Settings for ~markdown-mode~
The ~markdown-mode~ lets us edit Markdown files. We get syntax highlighting and several extras, such as the folding of headings and navigation between them. The mode actually provides lots of added functionality for GitHub-flavoured Markdown and to preview a Markdown file's HTML representation on a web page. Though I only use it for basic text editing.
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el"
;;; Markdown (markdown-mode)
(use-package markdown-mode
:ensure t
:defer t
:config
(setq markdown-fontify-code-blocks-natively t))
#+end_src
** Settings for ~csv-mode~
The package ~csv-mode~ provides support for =.csv= files. I do need this on occasion, even though my use-case is pretty basic. For me, the killer feature is the ability to create a virtual tabulated listing with the command ~csv-align-mode~: it hides the field delimiter (comma or space) and shows a tab stop in its stead.
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el"
;;; csv-mode
(use-package csv-mode
:ensure t
:commands (csv-align-mode))
#+end_src
** Settings for spell checking (~flyspell~)
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el"
;;; Flyspell
(use-package flyspell
:ensure nil
:bind
( :map flyspell-mode-map
("C-;" . nil)
:map flyspell-mouse-map
("<mouse-3>" . flyspell-correct-word))
:config
(setq flyspell-issue-message-flag nil)
(setq flyspell-issue-welcome-flag nil)
(setq ispell-program-name "aspell")
(setq ispell-dictionary "en_GB"))
#+end_src
** Settings for code linting (~flymake~)
The built-in ~flymake~ feature defines an interface for viewing the output of linter programs. A "linter" parses a file and reports possible notes/warnings/errors in it. With ~flymake~ we get these diagnostics in the form of a standalone buffer as well as inline highlights (typically underlines combined with fringe indicators) for the portion of text in question. The linter report is displayed with the command ~flymake-show-buffer-diagnostics~, or ~flymake-show-project-diagnostics~. Highlights are shown in the context of the file.
The built-in ~eglot~ feature uses ~flymake~ internally to handle the LSP linter output ([[#h:92258aa8-0d8c-4c12-91b4-5f44420435ce][Settings for ~eglot~]]).
As for what I have in this configuration block, the essentials for me are the user options ~flymake-start-on-save-buffer~ and ~flymake-start-on-flymake-mode~ as they make the linter update its report when the buffer is saved and when ~flymake-mode~ is started, respectively. Otherwise, we have to run it manually, which is cumbersome.
The ~package-lint-flymake~ package by Steve Purcell adds the glue code to make ~flymake~ report issues with Emacs Lisp files for the purposes of packaging.
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el"
;;; Flymake
(use-package flymake
:ensure nil
:bind
(:map flymake-mode-map
("C-c ! s" . flymake-start)
("C-c ! l" . flymake-show-buffer-diagnostics) ; Emacs28
("C-c ! L" . flymake-show-project-diagnostics) ; Emacs28
("C-c ! n" . flymake-goto-next-error)
("C-c ! p" . flymake-goto-prev-error))
:config
(setq flymake-fringe-indicator-position 'left-fringe)
(setq flymake-suppress-zero-counters t)
(setq flymake-no-changes-timeout nil)
(setq flymake-start-on-flymake-mode t)
(setq flymake-start-on-save-buffer t)
(setq flymake-proc-compilation-prevents-syntax-check t)
(setq flymake-wrap-around nil)
(setq flymake-mode-line-format
'("" flymake-mode-line-exception flymake-mode-line-counters))
;; NOTE 2023-07-03: `prot-modeline.el' actually defines the counters
;; itself and ignores this.
(setq flymake-mode-line-counter-format
'("" flymake-mode-line-error-counter
flymake-mode-line-warning-counter
flymake-mode-line-note-counter ""))
(setq flymake-show-diagnostics-at-end-of-line nil)) ; Emacs 30
;;; Elisp packaging requirements
(use-package package-lint-flymake
:ensure t
:after flymake
:config
(add-hook 'flymake-diagnostic-functions #'package-lint-flymake))
#+end_src
** Settings for ~outline-minor-mode~
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el"
;;; General configurations for prose/writing
;;;; `outline' (`outline-mode' and `outline-minor-mode')
(use-package outline
:ensure nil
:bind
("<f10>" . outline-minor-mode)
:config
(setq outline-minor-mode-highlight nil) ; emacs28
(setq outline-minor-mode-cycle t) ; emacs28
(setq outline-minor-mode-use-buttons nil) ; emacs29---bless you for the nil option!
(setq outline-minor-mode-use-margins nil)) ; as above
#+end_src
** Settings for ~dictionary~
Use the entry point ~M-x dictionary-search~
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el"
;;;; `dictionary'
(use-package dictionary
:ensure nil
:config
(setq dictionary-server "dict.org"
dictionary-default-popup-strategy "lev" ; read doc string
dictionary-create-buttons nil
dictionary-use-single-buffer t))
#+end_src
** Finally, we provide ~unravel-langs.el~ module
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-langs.el"
(provide 'unravel-langs)
#+end_src
* The ~unravel-essentials.el~ module
** Basic configuration in ~unravel-essentials.el~
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el" :mkdirp yes
;;; Essential configurations
(use-package emacs
:ensure nil
:demand t
:config
;;;; General settings and common custom functions
(setq help-window-select t)
(setq next-error-recenter '(4)) ; center of the window
(setq find-library-include-other-files nil) ; Emacs 29
(setq tramp-connection-timeout (* 60 10)) ; seconds
(setq save-interprogram-paste-before-kill t)
(setq mode-require-final-newline t)
(setq-default truncate-partial-width-windows nil)
(setq eval-expression-print-length nil)
(setq kill-do-not-save-duplicates t)
(setq scroll-error-top-bottom t)
(setq echo-keystrokes-help t) ; Emacs 30
(setq epa-keys-select-method 'minibuffer)) ; Emacs 30
#+end_src
** Settings for ~recentf~: keeping track of recent files
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el"
(use-package recentf
:ensure nil
:hook (after-init . recentf-mode)
:config
(setq recentf-max-saved-items 100)
(setq recentf-max-menu-items 25) ; I don't use the `menu-bar-mode', but this is good to know
(setq recentf-save-file-modes nil)
(setq recentf-keep nil)
(setq recentf-auto-cleanup nil)
(setq recentf-initialize-file-name-history nil)
(setq recentf-filename-handlers nil)
(setq recentf-show-file-shortcuts-flag nil))
#+end_src
** Settings for Bookmarks
:PROPERTIES:
:CUSTOM_ID: h:581aa0ff-b136-4099-a321-3b86edbfbccb
:END:
Bookmarks are compartments that store arbitrary information about a file or buffer. The records are used to recreate that file/buffer inside of Emacs. Put differently, we can easily jump back to a file or directory (or anything that has a bookmark recorder+handler, really). Use the ~bookmark-set~ command (=C-x r m= by default) to record a bookmark and then visit one of your bookmarks with ~bookmark-jump~ (=C-x r b= by default).
Also see [[#h:5685df62-4484-42ad-a062-d55ab19022e3][Settings for registers]].
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el"
;;;; Built-in bookmarking framework (bookmark.el)
(use-package bookmark
:ensure nil
:commands (bookmark-set bookmark-jump bookmark-bmenu-list)
:hook (bookmark-bmenu-mode . hl-line-mode)
:config
(setq bookmark-use-annotations nil)
(setq bookmark-automatically-show-annotations nil)
(setq bookmark-fringe-mark nil) ; Emacs 29 to hide bookmark fringe icon
;; Write changes to the bookmark file as soon as 1 modification is
;; made (addition or deletion). Otherwise Emacs will only save the
;; bookmarks when it closes, which may never happen properly
;; (e.g. power failure).
(setq bookmark-save-flag 1))
#+end_src
** Settings for registers
:PROPERTIES:
:CUSTOM_ID: h:5685df62-4484-42ad-a062-d55ab19022e3
:END:
Much like bookmarks, registers store data that we can reinstate quickly ([[#h:581aa0ff-b136-4099-a321-3b86edbfbccb][Settings for bookmarks]]). A common use-case is to write some text to a register and then insert that text by calling the given register. This is much better than relying on the ~kill-ring~, because registers are meant to be overwritten by the user, whereas the ~kill-ring~ accumulates lots of text that we do not necessarily need.
To me, registers are essential for keyboard macros. By default, registers do not persist between Emacs sessions, though I do need to re-use them from time to time, hence the arrangement to record them with ~savehist-mode~ ([[#h:25765797-27a5-431e-8aa4-cc890a6a913a][Settings for saving the history (~savehist-mode~)]]).
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el"
;;;; Registers (register.el)
(use-package register
:ensure nil
:defer t ; its commands are autoloaded, so this will be loaded then
:config
(setq register-preview-delay 0.8
register-preview-function #'register-preview-default)
(with-eval-after-load 'savehist
(add-to-list 'savehist-additional-variables 'register-alist)))
#+end_src
** Settings for ~delete-selection-mode~
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el"
;;;; Delete selection
(use-package delsel
:ensure nil
:hook (after-init . delete-selection-mode))
#+end_src
** Settings for tooltips
:PROPERTIES:
:CUSTOM_ID: h:26afeb95-7920-45ed-8ff6-3648256c280b
:END:
With these settings in place, Emacs will use its own faces and frame infrastructure to display tooltips. I prefer it this way because then we can benefit from the text properties that can be added to these messages (e.g. a different colour or a slant).
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el"
;;;; Tooltips (tooltip-mode)
(use-package tooltip
:ensure nil
:hook (after-init . tooltip-mode)
:config
(setq tooltip-delay 0.5
tooltip-short-delay 0.5
x-gtk-use-system-tooltips t
tooltip-frame-parameters
'((name . "tooltip")
(internal-border-width . 10)
(border-width . 0)
(no-special-glyphs . t))))
#+end_src
** Settings for the ~world-clock~
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el"
;;;; World clock (M-x world-clock)
(use-package time
:ensure nil
:commands (world-clock)
:config
(setq display-time-world-list t)
(setq zoneinfo-style-world-list ; M-x shell RET timedatectl list-timezones
'(("America/Los_Angeles" "Los Angeles")
("America/Vancouver" "Vancouver")
("Canada/Pacific" "Canada/Pacific")
("America/Chicago" "Chicago")
("Brazil/Acre" "Rio Branco")
("America/Toronto" "Toronto")
("America/New_York" "New York")
("Canada/Atlantic" "Canada/Atlantic")
("Brazil/East" "Brasília")
("UTC" "UTC")
("Europe/Lisbon" "Lisbon")
("Europe/Brussels" "Brussels")
("Europe/Athens" "Athens")
("Asia/Riyadh" "Riyadh")
("Asia/Tehran" "Tehran")
("Asia/Tbilisi" "Tbilisi")
("Asia/Yekaterinburg" "Yekaterinburg")
("Asia/Kolkata" "Kolkata")
("Asia/Singapore" "Singapore")
("Asia/Shanghai" "Shanghai")
("Asia/Seoul" "Seoul")
("Asia/Tokyo" "Tokyo")
("Asia/Vladivostok" "Vladivostok")
("Australia/Brisbane" "Brisbane")
("Australia/Sydney" "Sydney")
("Pacific/Auckland" "Auckland")))
;; All of the following variables are for Emacs 28
(setq world-clock-list t)
(setq world-clock-time-format "%R %z (%Z) %A %d %B")
(setq world-clock-buffer-name "*world-clock*") ; Placement handled by `display-buffer-alist'
(setq world-clock-timer-enable t)
(setq world-clock-timer-second 60))
#+end_src
** Run Emacs as a server
The "server" is functionally like the daemon, except it is run by the first Emacs frame we launch. With a running server, we can connect to it through a new ~emacsclient~ call. This is useful if we want to launch new frames that share resources with the existing running process.
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el"
;;;; Emacs server (allow emacsclient to connect to running session)
(use-package server
:ensure nil
:defer 1
:config
(setq server-client-instructions nil)
(unless (server-running-p)
(server-start)))
#+end_src
** ~expreg~ (tree-sitter mark syntactically)
:PROPERTIES:
:CUSTOM_ID: h:ceb193bf-0de3-4c43-8ab7-6daa50817754
:END:
The ~expreg~ package by Yuan Fu (aka casouri) uses the tree-sitter framework to incrementally expand the region from the smallest to the largest syntactic unit in the given context. This is a powerful feature, though it (i) requires Emacs to be built with tree-sitter support and (ii) for the user to be running a major mode that is designed for tree-sitter (Lisp seems to work regardless).
The package offers the ~expreg-expand~ and ~expreg-contract~ commands.
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el"
;;; Mark syntactic constructs efficiently if tree-sitter is available (expreg)
(when (treesit-available-p)
(use-package expreg
:ensure t
:functions (prot/expreg-expand prot/expreg-expand-dwim)
:bind ("C-M-SPC" . prot/expreg-expand-dwim) ; overrides `mark-sexp'
:config
(defun prot/expreg-expand (n)
"Expand to N syntactic units, defaulting to 1 if none is provided interactively."
(interactive "p")
(dotimes (_ n)
(expreg-expand)))
(defun prot/expreg-expand-dwim ()
"Do-What-I-Mean `expreg-expand' to start with symbol or word.
If over a real symbol, mark that directly, else start with a
word. Fall back to regular `expreg-expand'."
(interactive)
(let ((symbol (bounds-of-thing-at-point 'symbol)))
(cond
((equal (bounds-of-thing-at-point 'word) symbol)
(prot/expreg-expand 1))
(symbol (prot/expreg-expand 2))
(t (expreg-expand)))))))
#+end_src
** Settings for Battery display
:PROPERTIES:
:CUSTOM_ID: h:080aa291-95b4-4d54-8783-d156b13190e9
:END:
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el"
;;;; Show battery status on the mode line (battery.el)
(use-package battery
:ensure nil
:hook (after-init . display-battery-mode)
:config
(setq battery-mode-line-format
(cond
((eq battery-status-function #'battery-linux-proc-acpi)
"⏻%b%p%%,%d°C ")
(battery-status-function
"⏻%b%p%% "))))
#+end_src
** Finally, we provide ~unravel-essentials.el~ module
#+begin_src emacs-lisp :tangle "unravel-modules/unravel-essentials.el"
(provide 'unravel-essentials)
#+end_src