GNU Emacs Configuration
I’ve recently moved from a literate Emacs configuration based on Org
mode to a simpler init.el file, reproduced below:
Main configuration
;;; init.el --- Personal Emacs configuration -*- lexical-binding: t -*-
;; Copyright (C) 2021-2024 Eshel Yaron
;; Author: Eshel Yaron <[email protected]>
;;; Commentary:
;; My personal Emacs configuration
;;; Code:
;;; Temporarily increase GC threshold to expedite Emacs startup
(let ((normal-gc-cons-threshold (* 20 1024 1024))
(init-gc-cons-threshold (* 1024 1024 1024)))
(setq gc-cons-threshold init-gc-cons-threshold)
(add-hook 'after-init-hook
(lambda ()
(setq gc-cons-threshold normal-gc-cons-threshold))))
;;; OS-specific settings
(pcase system-type
('darwin
(add-to-list 'exec-path "/usr/local/bin")
(setq initial-frame-alist '((fullscreen . fullboth)
;; (minibuffer)
)
frame-title-format "Emacs")
(set-fontset-font t '(? . ?) "SF Pro Display"))
('android
(tool-bar-mode)
(modifier-bar-mode)
(visual-line-mode)
(setq initial-frame-alist '((tool-bar-position . bottom)))))
;;; Check for external programs
(unless (eq system-type 'android)
(dolist (program '("autoconf"
"automake"
"aws"
"bash"
"cmake"
"gcc"
"git"
"gpg"
"go"
"gopls"
"gtar"
"convert"
"ispell"
"jq"
"mutool"
"ninja"
"psql"
"rg"
"stow"
"makeinfo"
"tree-sitter"
"pandoc"
"mpv"))
(unless (executable-find program)
(display-warning 'programming
(format "Missing external program \"%s\"" program)
:error))))
;;; Set up Elpaca
(defvar elpaca-installer-version 0.9)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
:ref nil :depth 1 :inherit ignore
:files (:defaults "elpaca-test.el" (:exclude "extensions"))
:build (:not elpaca--activate-package)))
(let* ((repo (expand-file-name "elpaca/" elpaca-repos-directory))
(build (expand-file-name "elpaca/" elpaca-builds-directory))
(order (cdr elpaca-order))
(default-directory repo))
(add-to-list 'load-path (if (file-exists-p build) build repo))
(unless (file-exists-p repo)
(make-directory repo t)
(when (< emacs-major-version 28) (require 'subr-x))
(condition-case-unless-debug err
(if-let* ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
((zerop (apply #'call-process `("git" nil ,buffer t "clone"
,@(when-let* ((depth (plist-get order :depth)))
(list (format "--depth=%d" depth) "--no-single-branch"))
,(plist-get order :repo) ,repo))))
((zerop (call-process "git" nil buffer t "checkout"
(or (plist-get order :ref) "--"))))
(emacs (concat invocation-directory invocation-name))
((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
"--eval" "(byte-recompile-directory \".\" 0 'force)")))
((require 'elpaca))
((elpaca-generate-autoloads "elpaca" repo)))
(progn (message "%s" (buffer-string)) (kill-buffer buffer))
(error "%s" (with-current-buffer buffer (buffer-string))))
((error) (warn "%s" err) (delete-directory repo 'recursive))))
(unless (require 'elpaca-autoloads nil t)
(require 'elpaca)
(elpaca-generate-autoloads "elpaca" repo)
(load "./elpaca-autoloads")))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))
;;; Install packages
(elpaca org-transclusion)
(elpaca sweeprolog)
(elpaca (bbdb
:repo "https://git.savannah.nongnu.org/git/bbdb.git"
:files (:defaults "lisp/*.el.in")
:pre-build (("./autogen.sh")
("./configure")
("make"))))
(elpaca debbugs)
(elpaca diff-hl)
(elpaca (eat
:pre-build (("make" "terminfo")
("makeinfo" "--no-split" "eat.texi")
("touch" "dir")
("install-info" "--dir=dir" "eat.info"))))
(elpaca (elfeed
:pre-build (("pandoc" "-o" "elfeed.texi" "README.md"))))
(elpaca emms)
(elpaca htmlize)
;; (elpaca (oauth2
;; :repo "git://git.sv.gnu.org/emacs/elpa"
;; :local-repo "oauth2"
;; :branch "externals/oauth2"))
(elpaca transient)
(elpaca (magit :repo "[email protected]:~eshel/magit"))
(elpaca (el-search :repo "[email protected]:~eshel/el-search" :branch "master"))
(elpaca (markdown-mode
:pre-build (("pandoc" "-o" "markdown-mode.texi" "README.md"))))
(elpaca mastodon)
(elpaca (osm
:pre-build (("emacs" "--batch" "-l" "ox-texinfo" "README.org"
"--eval" "(setq org-babel-confirm-evaluate-answer-no t)"
"-f" "org-texinfo-export-to-texinfo"))))
(elpaca sqlformat)
(elpaca (auctex
:repo "[email protected]:~eshel/auctex"
:branch "main"
:files ("*.el" "*.info*")
:pre-build (("./autogen.sh")
("./configure" "--with-lispdir=." "--with-texmf-dir=/tmp")
("make")
("cp" "doc/auctex.info" "doc/auctex.info-1" "doc/auctex.info-2" "doc/preview-latex.info" "."))))
(elpaca (tablist :repo "[email protected]:~eshel/tablist"))
(elpaca (pdf-tools
:repo "[email protected]:~eshel/pdf-tools"
:pre-build (("emacs" "--batch" "-l" "ox-texinfo" "README.org"
"--eval" "(setq org-babel-confirm-evaluate-answer-no t)"
"--eval" "(setq org-export-with-broken-links t)"
"-f" "org-texinfo-export-to-texinfo")
("mv" "README.texi" "pdf-tools.texi"))))
(elpaca ob-prolog)
(elpaca (kubed :repo "~/checkouts/kubed"))
;; (elpaca (combobulate :repo "~/checkouts/combobulate"))
;; (elpaca (lean4-mode :repo "https://github.com/leanprover-community/lean4-mode"
;; :files ("*")))
(elpaca (ultra-scroll :repo "[email protected]:jdtsmith/ultra-scroll.git"))
;; (elpaca kubed)
(elpaca-wait)
(defvar-keymap esy/elpaca-prefix-map
:doc "Keymap for `elpaca' commands."
:prefix 'esy/elpaca-prefix-map
"r" #'elpaca-rebuild
"e" #'elpaca-recipe
"f" #'elpaca-fetch
"F" #'elpaca-fetch-all
"u" #'elpaca-merge
"U" #'elpaca-merge-all
"v" #'elpaca-visit
"d" #'elpaca-delete
"l" #'elpaca-log-updates
"i" #'elpaca-info
"t" #'elpaca-try
"m" #'elpaca-update-menus)
(keymap-unset elpaca-ui-mode-map "p" t)
(keymap-set elpaca-ui-mode-map "l" #'elpaca-ui-mark-pull)
(defun esy/log-diff (id ref)
(require 'diff-mode)
(let ((diff-default-read-only t))
(elpaca-log-diff id ref)))
(setq elpaca-log-diff-function #'esy/log-diff)
(add-hook 'elpaca-log-mode-hook #'elpaca-log-update-mode)
(add-hook 'elpaca-log-mode-hook
(lambda ()
(add-hook 'quit-window-hook
(lambda ()
(when (get-buffer-window "*elpaca-diff*")
(quit-windows-on "*elpaca-diff*")))
nil t)))
;;; Set some variables
(setq
;; my name
user-full-name "Eshel Yaron"
;; my email address
user-mail-address "[email protected]"
mail-user-agent 'gnus-user-agent
read-mail-command 'gnus
;; direct Custom definitions to some file and forget about it
custom-file (expand-file-name "custom.el" user-emacs-directory)
;; only display the warning buffer if something's really broken
warning-minimum-level :error
;; don't use stale .elc files
load-prefer-newer t
;; disable popup dialogs
use-dialog-box nil
;; disable splash screen
inhibit-startup-screen t
;; make the scratch message more concise
initial-scratch-message ";; Go.\n"
;; don't ring the bell
ring-bell-function #'ignore
;; make C-x b obey display-buffer-alist
switch-to-buffer-obey-display-actions t
;; disable new mail mode line indication
display-time-mail-function #'ignore
display-time-24hr-format t
;; enable recursive minibuffers
enable-recursive-minibuffers t
minibuffer-beginning-of-buffer-movement t
bug-reference-url-format "https://debbugs.gnu.org/%s"
;; save bookmarks immediately
bookmark-save-flag 1
;; show matches count in isearch prompt
isearch-lazy-count t
;; kill Eat terminal buffers when their process exits
eat-kill-buffer-on-exit t
;; don't spawn new frames in Ediff
ediff-window-setup-function #'ediff-setup-windows-plain
duplicate-line-final-position -1
;; tramp-kubernetes-namespace "argo"
;; configure Org capture templates
org-capture-templates '(("t" "Todo [inbox]" entry
(file+headline "~/org/inbox.org" "Tasks")
"** TODO [#B] %^{Task} %^g
:PROPERTIES:
:CreatedAt: %u
:CapturedAt: %a
:CapturedAs: Inbox Task
:END:"
:prepend t
:empty-lines 1
:immediate-finish t)
("w" "Work [inbox]" entry
(file+headline "~/org/inbox.org" "Tasks")
"** TODO [#B] %^{Task} :work:
:PROPERTIES:
:CreatedAt: %u
:CapturedAt: %a
:CapturedAs: Work Task
:END:"
:prepend t
:empty-lines 1
:immediate-finish t)
("c" "New Calendar Event" entry
(file+headline "~/org/inbox.org" "Calendar")
"** %^{Title} %^g
:PROPERTIES:
:CreatedAt: %u
:CapturedAt: %a
:CapturedAs: Calendar Event
:END:
%(format-time-string \"<%Y-%m-%d %H:%M\" (org-read-date t t))-%(format-time-string \"%H:%M>\" (org-read-date t t))
%i"
:prepend t
:empty-lines 1
:immediate-finish t)
("h" "Homework" entry
(file+headline "~/org/inbox.org" "Tasks")
"** TODO [#B] %^{Task} :studies:
DEADLINE: %(format-time-string \"<%Y-%m-%d %H:%M>\" (org-read-date t t))
:PROPERTIES:
:CreatedAt: %u
:CapturedAt: %a
:CapturedAs: Homework
:END:
%?"
:prepend t
:empty-lines 1))
;; point Org to file agenda file
org-agenda-files '("~/org/inbox.org")
org-default-notes-file "~/org/inbox.org"
;; weeks start on Sunday
org-agenda-start-on-weekday 0
;; use a nice unicode ellipsis
org-ellipsis "…"
;; open everything withing Emacs
org-file-apps '((t . emacs))
;; enable Org speed commands
org-use-speed-commands t
;; task states
org-todo-keywords '((sequence
"TODO(t)"
"BLOCKED(b@/!)"
"INPROGRESS(i!)"
"|"
"DONE(d!)"
"CANCELED(c@)"))
;; set a task to INPROGRESS when I clock into it
org-clock-in-switch-to-state "INPROGRESS"
;; log the finish time of tasks in Org
org-log-done 'time
org-log-into-drawer t
;; minimal prompt for task states
org-use-fast-todo-selection 'expert
;; set up a group of mutual exclusive tags
org-tag-alist '((:startgroup)
("adi" . ?a)
("holland" . ?h)
("work" . ?w)
("studies" . ?s)
("esols" . ?e)
("personal" . ?p)
("kubed" . ?k)
(:endgroup))
;; minimal prompt for tags
org-fast-tag-selection-single-key 'expert
;; allow evaluating some languages with Org Babel
org-babel-load-languages '((emacs-lisp . t)
(shell . t)
(sql . t)
(prolog . t))
;; do not ask for confirmation when evaluating Org source blocks
org-confirm-babel-evaluate nil
;; extra modules to load along with Org
org-modules '(ol-bbdb
ol-bibtex
ol-docview
ol-gnus
ol-info
ol-irc
ol-eww
ob-sql
org-tempo)
;; refile Org entries to my agenda file(s)
org-refile-targets '((org-agenda-files . (:maxlevel . 5))
(nil . (:maxlevel . 3)))
;; use full outline path when prompting for refile target
org-refile-use-outline-path t
;; archive for Org entries
org-archive-location "~/org/journal.org::datetree/* Finished Tasks :ARCHIVE:"
;; associate Org Babel languages with major modes
org-src-lang-modes '(("SWI-Prolog" . sweeprolog)
("Dockerfile" . dockerfile-ts)
("bash" . sh)
("shell" . sh)
("toml" . toml-ts)
("C" . c))
;; preview LaTeX fragments in Org buffers via SVG
org-preview-latex-default-process 'dvisvgm
;; don't auto-save remote files
remote-file-name-inhibit-auto-save t
;; don't ask me about it either
tramp-allow-unsafe-temporary-files t
remote-file-name-inhibit-locks t
;; increase maximum number of recent files Emacs remembers
recentf-max-saved-items 1024
;; increase maximum kill ring size
kill-ring-max 256
;; save text copied from another program to the kill ring
save-interprogram-paste-before-kill 2048
;; have C-u followed by repeated C-SPC keep popping
set-mark-command-repeat-pop t
osm-copyright nil
;; follow links to version-controlled files without confirming
vc-follow-symlinks t
vc-handled-backends '(Git)
;; jump from .c to .h files with find-sibling-file
find-sibling-rules '(("\\([^/]+\\)\\.c\\'" "\\1.h")
("nextstep/Emacs.app/Contents/Resources/\\(.*\\)\\'"
"\\1\\'"))
;; use ISO format for calendar dates
calendar-date-style 'iso
;; maintain legibility of rendered text in HTML mails
shr-color-visible-luminance-min 75
;; direct Magit to my Git checkouts directory
magit-repository-directories '(("~/checkouts/" . 1))
magit-status-sections-hook
'(magit-insert-status-headers
magit-insert-merge-log
magit-insert-rebase-sequence
magit-insert-am-sequence
magit-insert-sequencer-sequence
magit-insert-bisect-output
magit-insert-bisect-rest
magit-insert-bisect-log
magit-insert-untracked-files
magit-insert-unstaged-changes
magit-insert-staged-changes
magit-insert-stashes
magit-insert-unpushed-to-pushremote
magit-insert-recent-commits)
;; have Dired operations target another visible Dired buffer
dired-dwim-target t
;; use MPV with EMMS
emms-player-list '(emms-player-mpv)
eww-auto-rename-buffer 'title
browse-url-browser-function #'eww-browse-url
browse-url-generic-program "open"
global-auto-revert-non-file-buffers t
auto-revert-verbose nil
query-about-changed-file t
;; show flymake diagnostics as overlays at eol
;; flymake-show-diagnostics-at-end-of-line t
kill-do-not-save-duplicates t
show-trailing-whitespace t
read-extended-command-predicate #'command-completion-default-include-p
completions-format 'horizontal
completions-group t
completions-sort nil
completions-detailed t
completion-auto-select nil
completion-styles '(substring partial-completion)
completion-category-overrides
'((buffer (styles substring))
(file (styles basic partial-completion substring)
(sort-function . minibuffer-sort-by-history))
(cobra-command-line (styles basic substring partial-completion))
(project-file (styles basic partial-completion substring))
(recent-file (styles basic partial-completion substring))
(command (sort-function . minibuffer-sort-by-history)))
completion-auto-help 'visible
completions-max-height 16
completion-auto-wrap t
read-minibuffer-restore-windows nil
;; include CWD in shell command prompts
shell-command-prompt-show-cwd t
;; shell-kill-buffer-on-exit t
shell-dirtrack-verbose nil
compilation-scroll-output t
display-time-default-load-average nil
;; allow disabling confirming before compilation via local variables foo bar baz
safe-local-variable-values '((compilation-read-command . nil))
xref-search-program 'ripgrep
grep-use-headings t
sqlformat-command 'pgformatter
sql-input-ring-file-name (expand-file-name ".sqli-history" user-emacs-directory)
;; use relative line numbers
display-line-numbers-type 'relative
;; persist Git commit message history
savehist-additional-variables '(log-edit-comment-ring)
eldoc-minor-mode-string nil
flyspell-mode-line-string nil
eglot-confirm-server-edits 'diff
;; use my custom project-prompting function
project-prompter #'esy/read-project-by-name
;; have common bindings initially hidden in the output of C-h b
describe-bindings-outline-rules `((match-regexp
.
,(regexp-opt
'("Key translations"
"Global Bindings:"
"Function key map translations"
"pixel-scroll-precision-mode"
"context-menu-mode"))))
TeX-view-program-selection '((output-pdf "PDF Tools"))
TeX-source-correlate-start-server t
TeX-data-directory "~/.emacs.d/elpaca/builds/auctex/"
TeX-lisp-directory TeX-data-directory
TeX-auto-save t
TeX-parse-self t
TeX-source-correlate-mode t
TeX-electric-sub-and-superscript t
;; TeX-electric-escape t
LaTeX-electric-left-right-brace t
;; Read remote man pages
Man-support-remote-systems t
;; confirm-nonexistent-file-or-buffer nil
extended-command-dim-hyphens t
read-buffer-completion-ignore-case t
echo-keystrokes-help nil
checkdoc-verb-check-experimental-flag nil
prefix-help-command #'help-complete-keys
help-enable-variable-value-editing t
;; pcomplete-termination-string "" See `comint-completion-addsuffix'.
imenu-flatten t
imenu-auto-rescan t
scroll-conservatively 101 ; For `ultra-scroll'
rotate-windows-change-selected nil
use-system-tooltips nil
x-max-tooltip-size '(80 . 12))
(setq-default indent-tabs-mode nil
display-line-numbers-width 4)
(setq-default eglot-workspace-configuration
'(:yaml (:schemas (:kubernetes "*"))))
;;; Load my custom theme
(add-to-list 'custom-theme-load-path
(expand-file-name "theme/" user-emacs-directory))
(load-theme 'esy t)
;;; Add custom code directory to `load-path'
(add-to-list 'load-path (expand-file-name "lisp/" user-emacs-directory))
(unless (eq system-type 'android)
(add-to-list 'load-path "~/checkouts/esy-publish/")
(autoload 'esy-publish-setup "esy-publish" nil t)
(autoload 'esy-publish "esy-publish" nil t)
(autoload 'esy-publish-local-server "esy-publish" nil t)
(autoload 'esy-publish-create-post "esy-publish" nil t)
(with-eval-after-load 'recentf
(add-to-list 'recentf-exclude "~/checkouts/esy-publish/local/.*\.html")
(add-to-list 'recentf-exclude #'file-remote-p)))
(require 'esy-comm)
(add-to-list 'savehist-additional-variables
'esy-o365-token-refresh-last-time)
(with-eval-after-load 'gnus-sum
(keymap-unset gnus-summary-mode-map "M-#" t)
(keymap-unset gnus-summary-mode-map "M-&" t))
;;; Define custom commands
(defvar-keymap transpose-lines-repeat-map
:doc "Repeat map for \\[transpose-lines]"
"C-t" #'transpose-lines)
(put 'transpose-lines 'repeat-map 'transpose-lines-repeat-map)
;;; utility commands
(defun esy/emacs-patch (file)
(interactive "fPatch file: ")
(require 'emacsbug)
(delete-other-windows)
(let ((pbuf (get-buffer-create "*Patch*"))
(subject nil))
(with-current-buffer pbuf
(insert-file-contents file nil nil nil t)
(setq subject (mail-fetch-field "Subject"))
(diff-mode))
(compose-mail report-emacs-bug-address subject)
(message-goto-body)
(insert "Tags: patch\n\n\n\n")
(mml-attach-file file "text/patch" nil "attachment")
(message-goto-body)
(forward-line 2)
(display-buffer pbuf '(display-buffer-in-direction (direction . right)))
(message-add-action (lambda ()
(when-let (window (get-buffer-window pbuf))
(quit-window nil window)))
'send 'kill)))
;; (defun esy/kill-dwim ()
;; "When region is active, kill region, otherwise kill last word."
;; (interactive)
;; (if (region-active-p)
;; (let ((beg (region-beginning))
;; (end (region-end)))
;; (kill-region beg end))
;; (let ((end (point)))
;; (backward-word)
;; (kill-region (point) end))))
(defun duplicate-line-stay (arg)
(interactive "p")
(require 'misc)
(let ((duplicate-line-final-position 0))
(duplicate-line arg)))
(defun esy/ttyper ()
(interactive)
(require 'eat)
(let ((default-directory "~")
(eat-kill-buffer-on-exit t))
(eat "ttyper -c ~/.config/ttyper/config.toml" t)))
(defun esy/terminal (arg)
(interactive "P")
(if (file-remote-p default-directory)
(let ((process-environment (cons "TERM=xterm-256color" process-environment)))
(eat "/bin/bash" arg))
(eat nil arg)))
(defun esy/hut-builds ()
(interactive)
(require 'eat)
(let ((eat-kill-buffer-on-exit nil))
(eat "hut builds show -f" t)))
(defun esy/pulse-line (&optional _)
"Pulse current line."
(interactive)
(pulse-momentary-highlight-one-line))
(defun esy/seconds-to-date-string (seconds)
"Decode and display the date corresponding to SECONDS."
(interactive (list (if (use-region-p)
(string-to-number (buffer-substring (region-beginning)
(region-end)))
(read-number "Unix timestamp: " (round (float-time))))))
(message (format-time-string "%FT%T%z" (seconds-to-time seconds))))
(defun esy/find-init-file (init)
"Find the Emacs INIT file."
(interactive (list user-init-file))
(find-file init))
(defun esy/tmp-dired ()
"Start Dired in ~/tmp."
(interactive)
(dired "~/tmp"))
(with-eval-after-load 'shell
(keymap-set shell-mode-map "SPC" #'comint-magic-space))
(defvar-local esy/log-buffer-filename nil
"File in which the current buffer is logged.")
(defun esy/log-buffer ()
"Save the current buffer under the ~/logs/ directory."
(interactive)
(let ((filename (or esy/log-buffer-filename
(setq esy/log-buffer-filename
(expand-file-name
(concat mode-name
"_"
(format-time-string
"%Y%m%d%H%M%S"
(current-time))
".log")
"~/logs")))))
(save-restriction
(widen)
(write-region (point-min)
(point-max)
filename))))
(defface mode-face-lisp-interaction-mode '((t :background "#fff6d8")) "")
(with-eval-after-load 'mode-face
(add-to-list 'mode-face-modes 'lisp-interaction-mode))
(with-eval-after-load 'comint
(keymap-set comint-mode-map "C-c C-q" #'esy/log-buffer)
(setq comint-completion-addsuffix nil))
(defun esy/recent-log-summary ()
"Display a summary of my website's most recent access log."
(interactive)
(async-shell-command (string-join (list "ssh"
"[email protected]"
(shell-quote-argument
(string-join '("grep -E '\"GET.+\" 2' /var/log/apache2/access.log"
"tr -d '\"'"
"tr -d \"'\""
"cut -f 7,11,12 -d ' '"
"sort"
"uniq -c"
"sort -n")
" | ")))
" ")
"*Recent Log Summary*"))
(defun esy/access-log-summary ()
"Display a summary of my website's access log."
(interactive)
(async-shell-command (string-join (list "ssh"
"[email protected]"
(shell-quote-argument
(string-join '("zcat /var/log/apache2/access.log.*.gz"
"cat - /var/log/apache2/access.log /var/log/apache2/access.log.1"
"grep -E '\"GET.+\" 2'"
"tr -d '\"'"
"tr -d \"'\""
"cut -f 7,11,12 -d ' '"
"sort"
"uniq -c"
"sort -n")
" | ")))
" ")
"*Access Log Summary*"))
(defvar esy/clone-history nil)
(defun esy/clone (remote depth)
(interactive (list (let ((default (thing-at-point 'url)))
(read-string (format-prompt "Clone" default)
nil 'esy/clone-history default))
(and current-prefix-arg
(prefix-numeric-value current-prefix-arg))))
(let ((dir (expand-file-name
(file-name-base (car (url-path-and-query
(url-generic-parse-url remote))))
"~/checkouts")))
(apply #'call-process
(append (list "git" nil nil nil "clone")
(when depth
(list "--depth" (number-to-string depth)))
(list remote dir)))
(dired dir)))
(defun esy/json-path-to-position (pos)
"Return the JSON path from the document's root to the element at POS.
The path is represented as a list of strings and integers,
corresponding to the object keys and array indices that lead from
the root to the element at POS."
(named-let loop ((node (treesit-node-at pos)) (acc nil))
(if-let ((parent (treesit-parent-until
node
(lambda (n)
(member (treesit-node-type n)
'("pair" "array"))))))
(loop parent
(cons
(pcase (treesit-node-type parent)
("pair"
(treesit-node-text
(treesit-node-child (treesit-node-child parent 0) 1) t))
("array"
(named-let check ((i 1))
(if (< pos (treesit-node-end (treesit-node-child parent i)))
(/ (1- i) 2)
(check (+ i 2))))))
acc))
acc)))
(defun esy/json-path-at-point (point &optional kill)
"Display the JSON path at POINT. When KILL is non-nil, kill it too.
Interactively, POINT is point and KILL is the prefix argument."
(interactive "d\nP")
(let ((path (mapconcat (lambda (o) (format "%s" o))
(esy/json-path-to-position point)
".")))
(if kill
(progn (kill-new path) (message "Copied: %s" path))
(message path))
path))
(defun esy/dedicate-window (window flag)
(interactive (list (get-buffer-window) (not current-prefix-arg)))
(message "Window is %s dedicated to buffer %s."
(if flag (if (window-dedicated-p) "already" "now") "no longer")
(buffer-name))
(set-window-dedicated-p window flag))
;;; Ensure scripts ran with `executable-interpret' are executable
(with-eval-after-load 'executable
(define-advice executable-interpret (:before (&rest _) ensure-executable)
(unless (file-exists-p buffer-file-name)
(basic-save-buffer))
(executable-make-buffer-file-executable-if-script-p)))
;;; Pulse the line around point after switching windows
(defun esy/pulse-on-window-selection-change (&rest _)
(pulse-momentary-highlight-one-line))
(add-hook 'window-selection-change-functions
#'esy/pulse-on-window-selection-change)
;;; Override the default startup message
(define-advice startup-echo-area-message (:override () report-init-time)
(format "%s started in %s. Hack away."
(propertize "Emacs" 'face 'success)
(propertize (emacs-init-time) 'face 'error )))
;;; Extend standard programming mode hooks
(defun esy/prog-set-up-capf ()
(add-hook 'completion-at-point-functions
#'tags-completion-at-point-function 90 t))
(add-hook 'completion-at-point-functions #'dabbrev-capf)
(dolist (mode '(bug-reference-prog-mode
display-fill-column-indicator-mode
display-line-numbers-mode
flymake-mode
electric-indent-local-mode
;; esy/prog-set-up-capf
))
(add-hook 'prog-mode-hook mode))
;; (add-hook 'lisp-data-mode-hook #'electric-pair-mode)
(add-hook 'lisp-data-mode-hook #'rainbow-delimiters-mode)
;;; Extend standard text mode hooks
(add-hook 'text-mode-hook #'flyspell-mode)
;;; Bind some keys
(keymap-global-set "C-M-i" #'completion-at-point)
(keymap-global-set "C-c c" #'org-capture)
(keymap-global-set "C-c !" #'flymake-go-to-diagnostic)
(keymap-global-set "C-c f" #'recentf-open)
(keymap-global-set "C-c k" 'kubed-prefix-map)
(keymap-global-set "C-c l" #'org-store-link)
(keymap-global-set "C-c a" #'org-agenda)
(keymap-global-set "C-c v" #'esy/terminal)
(keymap-global-set "C-c p" 'sweeprolog-prefix-map)
(keymap-global-set "C-c S" #'scratch-buffer)
(keymap-global-set "C-c m" #'esy/emms-map)
(keymap-global-set "C-c C" #'esy-publish-create-post)
(keymap-global-set "C-c e" 'esy/elpaca-prefix-map)
(keymap-global-set "C-c E" #'elfeed)
(keymap-global-set "C-c T" #'esy/ttyper)
(keymap-global-set "C-c G" #'gnus)
(keymap-global-set "C-c M" #'mastodon)
(keymap-global-set "C-c t" #'esy/tmp-dired)
(keymap-global-set "C-c y" #'esy/search-mailing-list)
(keymap-global-set "C-c F" #'esy/find-init-file)
(keymap-global-set "C-c 1" #'delete-other-windows)
(keymap-global-set "C-c 2" #'split-window-below)
(keymap-global-set "C-c 3" #'split-window-right)
;; (keymap-global-set "<remap> <kill-region>" #'esy/kill-dwim)
(keymap-global-set "<remap> <suspend-frame>" #'zap-up-to-char)
(keymap-global-set "s-n" #'duplicate-line)
(keymap-global-set "s-p" #'duplicate-line-stay)
(keymap-global-set "s-u" #'universal-argument)
(keymap-global-set "s--" #'negative-argument)
(keymap-global-set "M-#" #'dictionary-search)
(keymap-global-set "M-o" #'previous-buffer)
(keymap-global-set "M-O" #'next-buffer)
(keymap-global-set "C-," #'delete-backward-char)
(keymap-global-set "C-;" #'avy-goto-char-timer)
(keymap-global-set "C-s-f" #'toggle-frame-fullscreen)
(keymap-global-set "C-s-l" #'esy/pulse-line)
(keymap-global-set "M-\"" #'insert-pair)
(keymap-global-set "M-[" #'insert-pair)
(keymap-global-set "M-'" #'insert-pair)
(keymap-global-set "M-]" #'up-list)
(keymap-set ctl-x-map "C-b" #'ibuffer)
(keymap-set ctl-x-map "k" #'esy/kill-buffers)
(find-function-setup-keys)
(keymap-set window-prefix-map "p" #'windmove-swap-states-up)
(keymap-set window-prefix-map "n" #'windmove-swap-states-down)
(keymap-set window-prefix-map "a" #'windmove-swap-states-left)
(keymap-set window-prefix-map "e" #'windmove-swap-states-right)
(keymap-set window-prefix-map "d" #'esy/dedicate-window)
(keymap-set window-prefix-map "r" #'rotate-window-layout-clockwise)
(keymap-set window-prefix-map "R" #'rotate-window-layout-counterclockwise)
(keymap-set window-prefix-map "|" #'flip-window-layout-horizontally)
(keymap-set window-prefix-map "-" #'flip-window-layout-vertically)
(keymap-set window-prefix-map "=" #'fit-window-to-buffer)
(dolist (command '(windmove-swap-states-up
windmove-swap-states-down
windmove-swap-states-left
windmove-swap-states-right
rotate-window-layout-clockwise
rotate-window-layout-counterclockwise
flip-window-layout-horizontally
flip-window-layout-vertically))
(put command 'repeat-map 'window-prefix-map))
(keymap-set isearch-mode-map "C-;" #'avy-isearch)
;;; digit arguments with the super modifier
(dotimes (i 10)
(keymap-global-set (concat "s-" (number-to-string i))
#'digit-argument))
;;; unbind some keys
(dolist (key '("s-a" "s-d" "s-e" "s-f" "s-g" "s-h" "s-j" "s-k" "s-l"
"s-m" "s-o" "s-q" "s-s" "s-t" "s-w" "s-x" "s-y" "s-z"))
(keymap-global-unset key))
;;; enable some commands
(dolist (command '(set-goal-column
narrow-to-region
narrow-to-page
downcase-region
upcase-region
list-timers))
(put command 'disabled nil))
;;; disable some commands
(put 'suspend-frame 'disabled t)
;;; Configure project management commands
(with-eval-after-load 'project
(alist-set ?\` project-switch-commands '("flymake" nil project-go-to-diagnostic))
(alist-set ?m project-switch-commands '("magit" nil magit-project-status))
(define-key project-prefix-map "w" #'project-copy-relative-file-name-as-kill)
(define-key project-prefix-map "m" #'magit-project-status)
(defvar esy/project-name-history nil)
(defvar esy/projects-directory "~/checkouts/")
(defun esy/read-project-by-name (&optional _)
"Read a project name and return its root directory.
If no known project matches the selected name, prompt for a
sub-directory of `esy/projects-directory' using the selected name
as the initial input for completion, and return that directory."
(let* ((name-dir-alist
(seq-keep (lambda (dir)
(when-let ((proj (project-current nil dir)))
(cons (project-name proj) dir)))
(project-known-project-roots)))
(current (project-current))
(default (and current (project-name current)))
(name (completing-read (format-prompt "Project" default)
name-dir-alist
nil nil nil
'esy/project-name-history
default)))
(or (alist-get name name-dir-alist nil nil #'string=)
(let* ((dir (read-directory-name "Project root directory: "
esy/projects-directory
nil t name))
(project (project-current nil dir)))
(when project (project-remember-project project))
dir))))
(defun project-copy-relative-file-name-as-kill ()
(interactive)
(if-let ((project (project-current))
(root (expand-file-name (project-root project)))
(directory-abbrev-alist (cons (cons root "")
directory-abbrev-alist))
(fn (abbreviate-file-name (or (buffer-file-name)
(expand-file-name default-directory)))))
(if (string-empty-p fn)
(user-error "At project root!")
(progn
(kill-new fn)
(message (concat (propertize "Copied file name " 'face 'shadow)
(propertize fn 'face 'bold)
(propertize " relative to " 'face 'shadow)
(propertize (project-name project) 'face 'italic)
(propertize " project root directory" 'face 'shadow)))))
(user-error "Not a project buffer!"))))
;;; Configure SQL connections
(with-eval-after-load 'sql
(defun esy/update-sql-connection-alist ()
(interactive)
(auth-source-forget-all-cached)
(setq sql-connection-alist (delete nil
(mapcar (lambda (source)
(pcase (split-string (plist-get source :host)
(rx "^"))
(`(,con ,db ,host)
(list (intern con)
(list 'sql-product ''postgres)
(list 'sql-user (plist-get source :user))
(list 'sql-port (string-to-number (plist-get source :port)))
(list 'sql-password (funcall (plist-get source :secret)))
(list 'sql-server host)
(list 'sql-database db)))))
(auth-source-search :port 5432 :max 100)))))
(esy/update-sql-connection-alist)
(add-hook 'sql-interactive-mode-hook #'toggle-truncate-lines)
(add-hook 'sql-interactive-mode-hook #'abbrev-mode)
(define-key sql-mode-map (kbd "C-c C-f") #'sqlformat)
(define-advice sql-comint-postgres (:around (fun &rest args) use-sql-password)
(let ((process-environment
(nconc
(list (format "PGPASSWORD=%s" sql-password))
process-environment)))
(apply fun args))))
;;; Configure Org mode
(with-eval-after-load 'org
(keymap-unset org-mode-map "C-," t)
(unless (eq system-type 'android)
(esy-publish-setup)))
(with-eval-after-load 'org-agenda
(add-to-list 'org-agenda-custom-commands
'("w" "Work TODOs" tags-todo "+work"))
(add-to-list 'org-agenda-custom-commands
'("h" "Holland TODOs" tags-todo "+holland")))
;;; Configure `dired'
(with-eval-after-load 'dired
(put 'dired-find-alternate-file 'disabled nil))
;;; Configure remote access via `tramp'
(with-eval-after-load 'tramp
(tramp-set-completion-function "ssh" '((tramp-parse-netrc "~/.authinfo.gpg")))
(add-to-list 'backup-directory-alist (cons tramp-file-name-regexp nil)))
;;; Configure `proced'
(with-eval-after-load 'proced
(add-hook 'proced-mode-hook (lambda ()
(setq proced-auto-update-flag t))))
;;; Configure `world-clock'
(with-eval-after-load 'time
(add-to-list 'zoneinfo-style-world-list '("Europe/Amsterdam" "Amsterdam"))
(add-to-list 'zoneinfo-style-world-list '("Asia/Tel_Aviv" "Tel Aviv")))
;;; Configure spelling errors correction via `flyspell'
(with-eval-after-load 'flyspell
(keymap-unset flyspell-mode-map "C-," t)
(keymap-unset flyspell-mode-map "C-." t)
(keymap-unset flyspell-mode-map "C-;" t)
(keymap-unset flyspell-mode-map "C-M-i" t))
;;; Configure minibuffer completions
(add-hook 'completion-list-mode-hook
(lambda ()
(setq-local cursor-in-non-selected-windows nil)))
;;; Configure highlighting of the current line via `lin'
(with-eval-after-load 'hl-line
(dolist (mode '(gnus-summary-mode gnus-group-mode gnus-server-mode))
(add-to-list 'global-hl-line-modes mode)))
;;; Enable some global minor modes
(column-number-mode)
(context-menu-mode)
(display-battery-mode)
(display-time-mode)
(global-auto-revert-mode)
(global-completion-preview-mode)
(global-diff-hl-mode)
(global-hl-line-mode)
(global-minibuffer-auto-completion-mode)
(kubed-menu-bar-mode)
(minibuffer-depth-indicate-mode)
;; (pixel-scroll-precision-mode
(ultra-scroll-mode)
(recentf-mode)
(repeat-mode)
(save-place-mode)
(savehist-mode)
(transient-mark-mode)
(winner-mode)
;;; Set up EMMS
(dolist (command '(emms-start
emms-stop
emms-next
emms-previous
emms-pause
emms-seek
emms-seek-to
emms-seek-forward
emms-seek-backward
emms-show))
(autoload command "emms"))
(defvar-keymap esy/emms-map
:doc "My keymap for EMMS commands."
:prefix 'esy/emms-map
:repeat (:hints ((emms-next . "Next")
(emms-previous . "Previous")
(emms-stop . "Stop")
(emms-seek-backward . "backward")
(emms-seek-forward . "forward")
(emms-seek . "seek")
(emms-show . "show")
(emms-pause . "pause")
(emms-start . "start")
(emms-seek-to . "to")))
"N" #'emms-next
"P" #'emms-previous
"S" #'emms-stop
"b" #'emms-seek-backward
"f" #'emms-seek-forward
"k" #'emms-seek
"m" #'emms-show
"p" #'emms-pause
"s" #'emms-start
"t" #'emms-seek-to)
(with-eval-after-load 'emms
(emms-minimalistic))
;;; Configure PDF handling
(pdf-loader-install)
(add-to-list 'revert-without-query "\\.pdf\\'")
;;; Configure Prolog integration via `sweeprolog'
(setq-default prolog-system 'swi)
(add-to-list 'major-mode-remap-alist '(prolog-mode . sweeprolog-mode))
;; (setq sweeprolog-swipl-path "/Applications/SWI-Prolog.app/Contents/MacOS/swipl")
(with-eval-after-load 'sweeprolog
(setq sweeprolog-top-level-persistent-history
(locate-user-emacs-file ".sweep_history"))
(add-hook 'sweeprolog-mode-hook #'sweeprolog-electric-layout-mode)
(add-hook 'sweeprolog-top-level-mode-hook #'compilation-shell-minor-mode))
;;; Configure recursive grepping via `rg'
(with-eval-after-load 'grep
(add-to-list 'grep-files-aliases '("pl" . "*.pl *.plt *.pro *.prolog")))
;;; Configure custom faces in `terraform-mode'
(with-eval-after-load 'terraform-mode
(setq terraform--resource-name-face font-lock-function-name-face
terraform--resource-type-face font-lock-type-face))
;;; Associate major modes with files based on their extensions
(dolist (cell '(("Dockerfile\\'" . dockerfile-ts-mode)
("\\.ya?ml\\'" . yaml-ts-mode)
("\\.toml\\'" . toml-ts-mode)
("\\.json\\'" . json-ts-mode)
("\\.tfstate\\'" . json-ts-mode)
("\\.ts\\'" . typescript-ts-mode)
("\\.rb\\'" . ruby-ts-mode)
("\\.rs\\'" . rust-ts-mode)
("\\.go\\'" . go-ts-mode)
("\\.plt?\\'" . prolog-mode)))
(push cell auto-mode-alist))
;;; Configure Help
(with-eval-after-load 'help-fns
(with-eval-after-load 'shortdoc
(add-hook 'help-fns-describe-function-functions
#'shortdoc-help-fns-examples-function)))
;;; Configure dictionary
(with-eval-after-load 'dictionary
(setopt dictionary-search-interface 'help
dictionary-default-dictionary "gcide"
dictionary-default-strategy "prefix"
dictionary-server "dict.org"))
;;; Configure news feeds
(with-eval-after-load 'elfeed
(keymap-set elfeed-show-mode-map "S-SPC" #'scroll-down-command)
(defvar esy/feeds-file (locate-user-emacs-file "feeds.eld"))
(defun esy/feeds ()
(with-temp-buffer
(insert-file-contents-literally esy/feeds-file)
(goto-char (point-min))
(read (current-buffer))))
(defun esy/read-feed-keywords ()
(mapcar #'intern
(completing-read-multiple
"Keywords: "
(let ((keywords nil))
(dolist
(feed-keywords
(mapcar
#'cdr
(esy/feeds)))
(mapc (lambda (keyword)
(add-to-list 'keywords keyword))
feed-keywords))
(mapcar #'symbol-name keywords)))))
(defun esy/add-feed (url keywords)
(interactive (let ((default (thing-at-point-url-at-point)))
(list (read-string (format-prompt "Feed URL"
default)
nil nil default)
(esy/read-feed-keywords))))
(let ((feeds (cons (cons url keywords) (esy/feeds))))
(with-temp-buffer
(pp (cons (cons url keywords) (esy/feeds))
(current-buffer))
(write-region (point-min)
(point-max)
esy/feeds-file))
(setq elfeed-feeds feeds)))
(defun esy/update-feeds ()
(interactive)
(setq elfeed-feeds (esy/feeds)))
(with-eval-after-load 'eww
(defun esy/eww-add-feed (url keywords)
(interactive (list (eww-read-alternate-url)
(esy/read-feed-keywords))
eww-mode)
(esy/add-feed url keywords)))
(setq
;; read feeds list from a separate file
elfeed-feeds (esy/feeds))
(with-eval-after-load 'completion-preview
(push '(not elfeed-search-mode elfeed-show-mode)
global-completion-preview-modes)))
;;; Disable some minor-mode mode-line lighters
(dolist (mm '((whitespace-cleanup-mode . whitespace-cleanup-mode)
(outline . outline-minor-mode)
(abbrev . abbrev-mode)
(iimage . iimage-mode)))
(with-eval-after-load (car mm)
(setf (alist-get (cdr mm) minor-mode-alist) '(""))))
;;; Configure Texinfo mode
(with-eval-after-load 'texinfo
(add-hook 'texinfo-mode-hook #'abbrev-mode))
;;; Configure TeX
(add-to-list 'major-mode-remap-alist '(latex-mode . Latex-Mode))
(with-eval-after-load 'tex-mode
(require 'tex))
(with-eval-after-load 'tex
(setopt TeX-modes '(tex-mode plain-tex-mode latex-mode doctex-mode))
(add-hook 'plain-TeX-mode-hook
(lambda () (set (make-local-variable 'TeX-electric-math)
(cons "$" "$"))))
(add-hook 'TeX-after-compilation-finished-functions #'TeX-revert-document-buffer))
(with-eval-after-load 'latex
(add-hook 'LaTeX-mode-hook
(lambda () (set (make-local-variable 'TeX-electric-math)
(cons "\\(" "\\)"))))
(add-hook 'LaTeX-mode-hook #'LaTeX-math-mode)
(defun LaTeX-mark-math ()
(interactive)
(when-let ((beg (search-backward "\\(" nil t))
(end (search-forward "\\)" nil t)))
(set-mark beg)))
(keymap-set LaTeX-mode-map "C-c C-SPC" #'LaTeX-mark-math))
(with-eval-after-load 'elisp-mode
(setq elisp-flymake-byte-compile-load-path (cons "./" load-path))
(keymap-set emacs-lisp-mode-map "C-c C-t" #'trace-function)
(keymap-set emacs-lisp-mode-map "C-c C-s" #'set-variable)
(add-hook 'emacs-lisp-mode-hook #'cursor-sensor-mode))
(dolist (mm '((go-ts-mode . go-ts-mode-hook)
(typescript-ts-mode . typescript-ts-mode-hook)
(python . python-base-mode-hook)
(cc-mode . c-mode-hook)))
(with-eval-after-load (car mm) (add-hook (cdr mm) #'eglot-ensure)))
(with-eval-after-load 'completion-preview
(add-to-list 'completion-preview-commands 'org-self-insert-command)
(add-to-list 'completion-preview-commands #'lisp-delete-backward)
(setq completion-preview-minimum-symbol-length 2)
(keymap-set completion-preview-active-mode-map "M-n" #'completion-preview-next-candidate)
(keymap-set completion-preview-active-mode-map "M-p" #'completion-preview-prev-candidate)
(keymap-set completion-preview-active-mode-map "M-f" #'completion-preview-insert-word))
(add-hook 'read-shell-command-minibuffer-setup-hook #'completion-preview-mode)
(add-hook 'eval-expression-minibuffer-setup-hook #'completion-preview-mode)
(add-hook 'eval-expression-minibuffer-setup-hook #'electric-pair-mode)
;; (load-file "/usr/local/opt/agda/libexec/ghc-9.10.1-inplace/Agd-2.6.4.3-7d670049/share/emacs-mode/agda2.el")
(defun read-buffer-to-switch-recentf (file)
(interactive
(minibuffer-with-setup-hook #'minibuffer-completion-help
(unless recentf-mode (recentf-mode 1))
(list (completing-read (format-prompt "Open recent file" nil)
recentf-list nil t (minibuffer-contents)))))
(let ((buf (find-file-noselect file)))
(delete-minibuffer-contents)
(insert (buffer-name buf))
(exit-minibuffer)))
(defun read-buffer-to-switch-bookmark (bookmark)
(interactive
(minibuffer-with-setup-hook #'minibuffer-completion-help
(list (bookmark-completing-read "Jump to bookmark"))))
(let ((buf (save-current-buffer
(bookmark-handle-bookmark bookmark)
(current-buffer))))
(delete-minibuffer-contents)
(insert (buffer-name buf))
(exit-minibuffer)))
;; (defun read-buffer-isearch (buffers string)
;; (interactive
;; (let ((s (minibuffer-contents-no-properties)))
;; (list (remove (current-buffer)
;; (seq-filter
;; (lambda (b)
;; (and (buffer-match-p '(not (or (major-mode . messages-buffer-mode)
;; (major-mode . fundamental-mode)))
;; b)
;; (with-current-buffer b
;; (save-excursion
;; (goto-char (point-min))
;; (search-forward s nil t)))))
;; (buffer-list)))
;; s))
;; minibuffer-mode)
;; (unless buffers
;; (user-error "No buffers contain `%s'" string))
;; (multi-isearch-buffers buffers)
;; (isearch-process-search-string string string))
(defvar-keymap read-buffer-to-switch-mode-map
:doc "Keymap for `read-buffer-to-switch-mode'."
"M-m" #'read-buffer-to-switch-bookmark
"M-r" #'read-buffer-to-switch-recentf
;; "C-s" #'read-buffer-isearch
)
(define-minor-mode read-buffer-to-switch-mode
"Minor mode for reading a buffer name in the minibuffer."
:group 'minibuffer)
(defun esy/read-buffer (prompt &optional def require-match predicate)
(minibuffer-with-setup-hook
(:append
(lambda ()
(when read-buffer-to-switch-current-buffer
(read-buffer-to-switch-mode))))
(let ((read-buffer-function nil))
(read-buffer prompt def require-match predicate))))
(defun esy/kill-buffers (buffers)
(interactive
(list (let ((def (buffer-name)))
(minibuffer-with-setup-hook
(lambda () (setq minibuffer-action
(cons (lambda (buf-name)
(kill-buffer buf-name)
(message "Killed %s" buf-name))
"kill")
minibuffer-alternative-action
(cons #'display-buffer "display")))
(completing-read-multiple
(format-prompt "Kill buffers" def)
(completion-buffer-name-table) nil t nil
'buffer-name-history def)))))
(when buffers
(let ((num (length buffers)))
(while (and buffers (kill-buffer (car buffers)))
(setq buffers (cdr buffers)))
(let* ((spared (length buffers))
(killed (- num spared)))
(message "Killed %d buffer%s%s."
killed (ngettext "" "s" killed)
(if buffers (format ", %s spared" spared) ""))))))
(setq read-buffer-function #'esy/read-buffer)
(load-file (expand-file-name "admin/cherry.el" source-directory))
(with-eval-after-load 'magit
(with-eval-after-load 'diff-hl
(add-hook 'magit-pre-refresh-hook 'diff-hl-magit-pre-refresh)
(add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh)))
(with-eval-after-load 'xref
(add-hook 'xref-after-update-hook #'outline-minor-mode))
(defun esy/search-mailing-list (q)
(interactive "sSearch: ")
(eww (url-encode-url (format "yhetil.org/emacs?q=%s" q))))
(with-eval-after-load 'vc-git
(add-hook 'vc-git-region-history-mode-hook #'bug-reference-mode))
(add-hook 'find-function-after-hook #'esy/pulse-line)
(defun esy/update-forks ()
(interactive)
(require 'with-editor)
(require 'git-commit)
(require 'server)
(unless (server-running-p) (server-start))
(with-editor
(compile (string-join
(mapcar (lambda (name)
(format
"echo Updating: %s && git -C %s pull && git -C %s push"
name name name))
(mapcar (lambda (base)
(expand-file-name base "~/checkouts"))
'("auctex" "pdf-tools" "magit" "tablist")))
" && "))))
(defvar esy/trusted-files `(,user-init-file ,early-init-file))
(setq trusted-content
(mapcar #'abbreviate-file-name
(append esy/trusted-files
(mapcar #'file-name-as-directory load-path))))
(provide 'init)
;;; init.el ends here
Mail and other communication settings
;;; esy-comm.el --- My communication settings -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Eshel Yaron
;; Author: Eshel Yaron <[email protected]>
;; Keywords: comm
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; 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
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(require 'esy-o365)
(defvar esy-comm-accounts
'(("me"
"[email protected]" "mail.eshelyaron.com" "mail.eshelyaron.com")
("gmail"
"[email protected]" "imap.gmail.com" "smtp.gmail.com")
;; ("swi"
;; "[email protected]"
;; ;; "mx.transip.email" "mx.transip.email"
;; ;; "mail.swi-prolog.com" "mail.swi-prolog.com"
;; )
("uva"
"[email protected]" "outlook.office365.com" "smtp.office365.com"
(nnimap-authenticator xoauth2))))
(defvar esy-comm-summary-line-format
(concat "%1{%U%R%z%}%B"
"%2{%~(form (replace-regexp-in-string \" via .*\" \"\" (gnus-summary-from-or-to-or-newsgroups gnus-tmp-header gnus-tmp-from)))@%} "
"%3{(%&user-date;, %k)%}:%* %s\n"))
(setq
;; Message
message-alternative-emails (regexp-opt (mapcar #'cadr esy-comm-accounts))
message-dont-reply-to-names message-alternative-emails
message-elide-ellipsis "\n[...%l lines elided...]\n"
message-generate-hashcash nil
message-server-alist (mapcar (pcase-lambda
(`(,_ ,cond ,_ ,server . ,_))
(cons cond
(format "smtp %s 587" server)))
esy-comm-accounts)
send-mail-function #'message-multi-smtp-send-mail
;; Gnus
gnus-expert-user t
gnus-always-read-dribble-file t
gnus-break-pages nil
gnus-inhibit-startup-message t
gnus-cite-parse-max-size nil
gnus-face-1 'bold
gnus-face-2 'gnus-cite-1
gnus-face-3 'shadow
gnus-summary-line-format esy-comm-summary-line-format
gnus-simplify-subject-functions '(gnus-simplify-subject-re)
gnus-treat-display-smileys nil
gnus-select-method '(nntp "news.gmane.io")
gnus-secondary-select-methods (cons
'(nntp "news.eternal-september.org")
(mapcar
(pcase-lambda
(`(,name ,_ ,address ,_ . ,tail))
`(nnimap ,name
(nnimap-address ,address)
(nnimap-server-port "imaps")
(nnimap-stream ssl)
.
,tail))
esy-comm-accounts))
gnus-no-groups-message "No new articles"
gnus-use-full-window nil
gnus-article-treat-types '("text/plain"
"text/x-verbatim"
"text/x-patch"
"text/html"
"text/calendar")
gnus-icalendar-org-capture-file "~/org/inbox.org"
gnus-icalendar-org-capture-headline '("Calendar")
gnus-large-newsgroup 2048
nntp-connection-timeout 15
;; Mastodon
mastodon-instance-url "https://social.eshelyaron.com"
mastodon-active-user "eshel"
;; BBDB
bbdb-phone-style nil
bbdb-complete-mail nil
bbdb-complete-mail-allow-cycling t
bbdb-completion-display-record nil
;; emacsbug.el
report-emacs-bug-no-explanations t
submit-emacs-patch-display-help nil
;; rcirc.el
rcirc-default-nick "esy"
rcirc-server-alist '(("irc.libera.chat"
:channels ("#emacs")
:port 6697
:encryption tls))
rcirc-log-flag t)
(with-eval-after-load 'gnus
(require 'gnus-icalendar)
(add-hook 'gnus-group-mode-hook #'gnus-topic-mode)
(unless (eq system-type 'android)
(esy-o365-setup 'gnus))
(gnus-icalendar-setup)
(gnus-icalendar-org-setup)
(bbdb-initialize 'gnus 'mail 'message))
(with-eval-after-load 'message
(add-hook 'message-send-hook 'ispell-message))
(provide 'esy-comm)
;;; esy-comm.el ends here
Custom libraries
Refresh OAuth 2.0 access token JIT for connecting to my university mail
;;; esy-o365.el --- Settings for outlook 365 -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Eshel Yaron
;; Author: Eshel Yaron <[email protected]>
;; Keywords: comm
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; 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
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(defvar esy-o365-token-directory "~/checkouts/M365-IMAP/")
(defvar esy-o365-token-refresh-last-time 0)
(defvar esy-o365-token-refresh-command '("python3" "refresh_token.py"))
(defun esy-o365-refresh-token ()
(interactive)
(files--ensure-directory esy-o365-token-directory)
(when-let ((default-directory esy-o365-token-directory)
(token (car (process-lines "python3" "refresh_token.py"))))
(with-temp-buffer
(insert "machine smtp.office365.com smtp-auth xoauth2 login [email protected] port 587 password \""
token
"\"\nmachine outlook.office365.com login [email protected] port imaps password \""
token
"\"\n")
(write-region (point-min) (point-max) ".authinfo"))
(auth-source-forget-all-cached)))
(defun esy-o365-maybe-refresh-token ()
(let ((now (float-time)))
(when (< (* 60 45) (- now esy-o365-token-refresh-last-time))
(esy-o365-refresh-token)
(setq esy-o365-token-refresh-last-time now))))
;;;###autoload
(defun esy-o365-setup (mua)
(require 'auth-source)
(if (not (file-exists-p esy-o365-token-directory))
(let ((m "`esy-o365-token-directory' does not exist"))
(if after-init-time (user-error m) (display-warning 'comm m)))
(require 'auth-source)
(add-to-list 'auth-sources
(expand-file-name ".authinfo" esy-o365-token-directory))
(pcase mua
('gnus
(dolist (hook '(gnus-before-startup-hook
gnus-before-resume-hook
gnus-select-group-hook
gnus-get-new-news-hook))
(add-hook hook #'esy-o365-maybe-refresh-token))))))
(provide 'esy-o365)
;;; esy-o365.el ends here
Automatically update the completions buffer
My custom theme
;;; esy-theme.el --- My custom theme -*- lexical-binding:t -*-
;; Copyright (C) 2023 Eshel Yaron
;; This file is NOT part of GNU Emacs.
;;; Commentary:
;;; Code:
(deftheme esy "My custom theme.")
(custom-theme-set-faces
'esy
`(default ((t . ,(append (when (eq system-type 'darwin) '(:height 130))
(unless (eq system-type 'android) '(:family "Iosevka"))))))
`(fixed-pitch ((t . ,(unless (eq system-type 'android) '(:family "Iosevka")))))
`(variable-pitch ((t . ,(unless (eq system-type 'android) '(:family "Iosevka Etoile")))))
'(mode-line ((t)))
'(mode-line-active ((t :overline "black" :background "lavender")))
'(mode-line-inactive ((t :underline "black")))
'(fringe ((t)))
'(line-number-current-line ((t :background "yellow" :inherit default)))
'(fill-column-indicator ((t :foreground "orange" :weight light)))
'(vertical-border ((t :foreground "grey")))
'(minibuffer-prompt ((t :weight bold :slant italic :foreground "red")))
'(italic ((t :slant italic)))
'(cursor ((t :background "salmon")))
'(font-lock-doc-face ((t :inherit font-lock-string-face :slant italic)))
;; '(font-lock-variable-name-face ((t :foreground "#8b0a50")))
;; '(font-lock-variable-use-face ((t :foreground "sienna")))
'(elisp-shorthand-font-lock-face ((t :inherit font-lock-keyword-face :slant italic)))
'(elfeed-search-title-face ((t :inherit shadow)))
'(tooltip ((t :background "#f0fff0")))
;; '(error ((t :foreground "Red1")))
)
(custom-theme-set-variables
'esy
'(mode-line-position-column-line-format
'("%5l" (4 "|%c")))
'(mode-line-position
'((line-number-mode
(column-number-mode
mode-line-position-column-line-format
mode-line-position-line-format)
(column-number-mode
mode-line-position-column-format))
" " (8 ("" mode-line-percent-position "/%I"))))
'(mode-line-format
'(" %+ "
(:eval (when-let (project
(and (not (file-remote-p default-directory))
(project-current)))
(list
(propertize (concat (project-name project) "/ ")
'face 'italic))))
(:propertize "%b" face mode-line-buffer-id)
" (%["
mode-name mode-line-process minor-mode-alist
(:eval (when (window-dedicated-p)
" Dedicated"))
"%n%])"
(vc-mode vc-mode)
(which-func-mode (" " which-func-format))
mode-line-format-right-align
(:eval (when (mode-line-window-selected-p)
(list "" 'display-time-string 'battery-mode-line-string)))
mode-line-position " "))
'(elfeed-search-face-alist '((unread default)))
'(mode-face-faces '(default fringe mode-line-inactive))
'(mode-face-global-mode t))
(provide-theme 'esy)
;;; esy-theme.el ends here