- About
- Setup
- Vars
- Package Manager
- Tent Pole
- Look and Feel
- Directory Management
- OS Specific
- Navigating
- Editing
- Buffer Encoding
- Newline and indent
- Truncate lines
- Highlight current line
- Highlight matching parens
- No tabs
- Set base indentation
- Save place in file
- Comment or Uncomment region
- Backups
- Disable Superfluous warnings
- Mark Manipulation
- end emacs specific config
- Parens Balancing
- Whitespace
- Code Folding
- Hungry Delete
- Multiple Cursors
- Syntax and Error checking
- Integrated Debugging
- Completion
- Languages
- Communication
- Misc
- Custom Functions
“Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do.” - Donald Knuth This Config file is inspired by:
- http://pages.sachachua.com/.emacs.d/Sacha.html
- http://thewanderingcoder.com/2015/02/literate-emacs-configuration/
I’ve forgotten this before so this seems like the perfect place to put it but C-c C-v d
or org-babel-demarcate-block
creates a code-block for the language of your choice
This sets the garbage collection threshold to 100mb Reset garbage collection to emacs default after 5s
(setq gc-cons-threshold 1000000000)
(run-with-idle-timer
5 nil
(lambda ()
(setq-default gc-cons-threshold (* 1024 1024 100))
(message "gc-cons-threshold restored to %S"
gc-cons-threshold)))
They are really noisy and annoying right now. And nothing I can deal with.
(setq native-comp-deferred-compilation-deny-list '())
(setq native-comp-async-report-warnings-errors nil)
Other dependencies need updated packages and Elpaca is not good about unloading them. So we have to do it for Elpaca.
(when (featurep 'jsonrpc)
(unload-feature 'jsonrpc))
(setq custom-file (expand-file-name "customs.el" user-emacs-directory))
(add-hook 'after-init-hook (lambda () (load custom-file 'noerror)))
(setq-default lexical-binding t
load-prefer-newer t)
Some recommendations by https://github.com/hlissner/doom-emacs/wiki/FAQ#how-is-dooms-startup-so-fast
(defvar doom--file-name-handler-alist file-name-handler-alist)
(setq file-name-handler-alist nil)
Define custom package repositories besides ELPA. If I am being honest with myself, Marmalade and Tromey are probably not necessary repositories.
(setq package-enable-at-startup nil)
(setq package-user-dir "~/.emacs.d/elpa")
(setq load-prefer-newer t)
(setq package-archives
'(("melpa" . "http://melpa.org/packages/")
("melpa-stable" . "http://stable.melpa.org/packages/")
("gnu" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")))
(setq-default user-full-name "Justin Barclay"
user-mail-address "[email protected]")
Tangle and Compile init file
(defun my/tangle-dotfiles ()
"If the current file is this file, the code blocks are tangled"
(interactive)
(when (equal (buffer-file-name)
(expand-file-name "~/.emacs.d/README.org"))
(org-babel-tangle '() (expand-file-name "~/.emacs.d/init.el"))))
;;(byte-compile-file "~/.emacs.d/init.el")
(add-hook 'after-save-hook #'my/tangle-dotfiles)
Let’s describe some variables to help determine how to configure Emacs
(defvar jb/os-linux-p (eq system-type 'gnu/linux))
(defvar jb/os-windows-p (eq system-type 'windows-nt))
(defvar jb/os-macos-p (eq system-type 'darwin))
I’ve decided to experiment with a completely new package manager. The main purpose of this is to better work with git repositories as the main source of packaging system, without relying on quelpa as that can inhibit load times.
(defvar elpaca-installer-version 0.8)
(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
: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 use-package support
(elpaca elpaca-use-package
;; Enable :elpaca use-package keyword.
(elpaca-use-package-mode)
;; Assume :elpaca t unless otherwise specified.
(setq elpaca-use-package-by-default t))
(when jb/os-windows-p (setq elpaca-queue-limit 20))
;; Block until current queue processed.
(elpaca-wait)
For big emacs packages, that help define the experience of Emacs itself
I use Jon Wiegley’s use-package for dependency management. Let’s bootstrap use-package so it can download everything else as we need it.
It’s a god send, UsePackage landed in Emacs master, no bootstrapping required, just up and go.
(require 'use-package)
(setq use-package-always-ensure t)
(setq use-package-verbose nil)
(setq use-package-always-defer t)
(setq use-package-enable-imenu-support t)
(setq use-package-compute-statistics t)
(setq use-package-minimum-reported-time 0.01)
The plan is to use a copious amount of deferral to speed up emacs boot time. Use the :init keyword to execute code before a package is loaded. It accepts one or more forms, up until the next keyword :config can be used to execute code after a package is loaded. The :ensure keyword causes the package(s) to be installed automatically if not already present on your system (set (setq use-package-always-ensure t)) You can override package deferral with the :demand keyword. Thus, even if you use :bind, using :demand will force loading to occur immediately and not establish an autoload for the bound key. In almost all cases you don’t need to manually specify :defer t. This is implied whenever :bind or :mode or :interpreter is used.
(use-package gnu-elpa-keyring-update)
As stolen from progfolio
(defmacro use-feature (name &rest args)
"Like `use-package' but accounting for asynchronous installation.
NAME and ARGS are in `use-package'."
(declare (indent defun))
`(use-package ,name
:ensure nil
,@args))
The :disabled keyword can turn off a module you’re having difficulties with, or stop loading something you’re not using at the present time:
(use-package ess-site
:disabled
:commands R)
When byte-compiling your .emacs file, disabled declarations are omitted from the output entirely, to accelerate startup times.
This is hidden here to load right after we have use-package to be able to benchmark startup
(use-package benchmark-init
:demand t
:init
(benchmark-init/activate)
:config
;; To disable collection of benchmark data after init is done.
(add-hook 'window-setup-hook 'benchmark-init/deactivate))
(defun +elpaca-unload-seq (e)
(and (featurep 'seq) (unload-feature 'seq t))
(elpaca--continue-build e))
;; You could embed this code directly in the reicpe, I just abstracted it into a function.
(defun +elpaca-seq-build-steps ()
(append (butlast (if (file-exists-p (expand-file-name "seq" elpaca-builds-directory))
elpaca--pre-built-steps elpaca-build-steps))
(list '+elpaca-unload-seq 'elpaca--activate-package)))
(use-package seq
:init
(require 'seq)
:ensure `(seq :build ,(+elpaca-seq-build-steps)))
(use-package org
:defer t
:bind
(("C-c a" . org-agenda)
("C-c c" . org-capture))
:init
(global-unset-key "\C-c\C-v\C-c")
:hook (org-mode . visual-line-mode)
:config
(setq org-src-tab-acts-natively nil)
(defun jb/org-narrow-to-parent ()
"Narrow buffer to the current subtree."
(interactive)
(widen)
(org-up-element)
(save-excursion
(save-match-data
(org-with-limited-levels
(narrow-to-region
(progn
(org-back-to-heading t) (point))
(progn (org-end-of-subtree t t)
(when (and (org-at-heading-p) (not (eobp))) (backward-char 1))
(point)))))))
(defun jb/org-clear-results ()
(interactive)
(org-babel-remove-result-one-or-many 't))
;; As liberally borrowed from:
;; https://github.com/Fuco1/.emacs.d/blob/76e80dd07320b079fa26db3af6096d8d8a4f3bb9/files/org-defs.el#L1863C1-L1922C57
(defun my-org-archive-file ()
"Get the archive file for the current org buffer."
(car (org-archive--compute-location org-archive-location)))
(defadvice org-archive-subtree (around fix-hierarchy activate)
(let* ((fix-archive-p (and (not current-prefix-arg)
(not (use-region-p))))
(afile (my-org-archive-file))
(buffer (or (find-buffer-visiting afile) (find-file-noselect afile)))
;; Get all the parents and their tags, we will try to
;; recreate the same situation in the archive buffer.
;; TODO: make this customizable.
(parents-and-tags (save-excursion
(let (parents)
(while (org-up-heading-safe)
(push (list :heading (org-get-heading t t t t)
:tags (org-get-tags nil :local))
parents))
parents))))
ad-do-it
(when fix-archive-p
(with-current-buffer buffer
(goto-char (point-max))
(while (org-up-heading-safe))
(let* ((olpath (org-entry-get (point) "ARCHIVE_OLPATH"))
;; TODO: Factor out dash.el
(path (and olpath
(--map
(replace-regexp-in-string "^/" "" it)
(s-slice-at "/\\sw" olpath))))
(level 1)
tree-text)
(when olpath
(org-mark-subtree)
(setq tree-text (buffer-substring (region-beginning) (region-end)))
(let (this-command) (org-cut-subtree))
(goto-char (point-min))
(save-restriction
(widen)
(-each path
(lambda (heading)
(if (re-search-forward
(rx-to-string
`(: bol (repeat ,level "*") (1+ " ") ,heading)) nil t)
(progn
(org-narrow-to-subtree)
(org-set-tags (plist-get (car parents-and-tags) :tags)))
(goto-char (point-max))
(unless (looking-at "^")
(insert "\n"))
(insert (make-string level ?*)
" "
heading)
(org-set-tags (plist-get (car parents-and-tags) :tags))
(end-of-line)
(insert "\n"))
(pop parents-and-tags)
(cl-incf level)))
(widen)
(org-end-of-subtree t t)
(org-paste-subtree level tree-text))))))))
(defun run-org-block ()
(interactive)
(save-excursion
(goto-char
(org-babel-find-named-block
(completing-read "Code Block: " (org-babel-src-block-names))))
(org-babel-execute-src-block-maybe)))
(setq org-directory "~/dev/diary")
(setq org-agenda-files (list (concat org-directory "/personal/calendar.org")
(concat org-directory "/work/calendar.org")
(concat org-directory "/personal/tasks.org")
(concat org-directory "/work/tasks.org"))
org-todo-keywords
'((sequence "TODO(t)" "INPROGRESS(i)" "|" "DONE(d)")
("WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(c@/!)" "PHONE" "MEETING"))
org-todo-keyword-faces
'(("TODO" :foreground "red" :weight regular)
("INPROGRESS" :foreground "blue" :weight regular)
("DONE" :foreground "forest green" :weight regular)
("WAITING" :foreground "orange" :weight regular)
("BLOCKED" :foreground "magenta" :weight regular)
("CANCELLED" :foreground "forest green" :weight regular))
org-log-into-drawer 't
org-startup-truncated nil
org-default-notes-file (concat org-directory "/notes.org")
org-export-html-postamble nil
org-hide-leading-stars 't
org-startup-folded 'overview
org-startup-indented 't)
;; Add ts language support
(add-to-list 'org-src-lang-modes '("tsx" . tsx-ts))
(add-to-list 'org-src-lang-modes '("typescript" . typescript-ts))
(add-to-list 'org-src-lang-modes '("jsx" . jsx-ts))
(add-to-list 'org-src-lang-modes '("javascript" . javascript-ts))
(add-to-list 'org-src-lang-modes '("ruby" . ruby-ts))
(add-to-list 'org-src-lang-modes '("dot" . graphviz-dot))
;; `org-babel-do-load-languages' significantly slows loading time,
;; so let's run this well after we've loaded
(run-at-time "1 min" nil (lambda ()
(org-babel-do-load-languages 'org-babel-load-languages
'((shell . t)
(dot . t)
(js . t)
(sql . t)
(python . t)
(ruby . t))))))
(use-package org-contrib
:after org)
(use-feature ox-md
:ensure nil
:after org)
Now that we have that installed we can pull in org-pdftools from github
(use-package org-pdftools
:hook (org-mode . org-pdftools-setup-link))
(use-package ob-restclient
:config
(org-babel-do-load-languages
'org-babel-load-languages
'((restclient . t))))
After the installation, every time you’ll be saving an org file, the first headline with a :TOC: tag will be updated with the current table of contents.
To add a TOC tag, you can use the command org-set-tags-command (C-c C-q).
In addition to the simple :TOC: tag, you can also use the following tag formats:
You can also use @ as separator, instead of _.
(use-package toc-org
:hook (org-mode . toc-org-mode))
(use-package org-modern
:hook (org-mode . org-modern-mode))
These functions expand on the abilities of org-babel and ob-restclient mode and as such need both of these modes loaded before they’ll work.
;; generated-curl-command is used to communicate state across several function calls
(setq generated-curl-command nil)
(defvar org-babel-default-header-args:restclient-curl
`((:results . "raw"))
"Default arguments for evaluating a restclient block.")
;; Lambda function reified to a named function, stolen from restclient
(defun gen-restclient-curl-command (method url headers entity)
(let ((header-args
(apply 'append
(mapcar (lambda (header)
(list "-H" (format "%s: %s" (car header) (cdr header))))
headers))))
(setq generated-curl-command
(concat
"#+BEGIN_SRC sh\n"
"curl "
(mapconcat 'shell-quote-argument
(append '("-i")
header-args
(list (concat "-X" method))
(list url)
(when (> (string-width entity) 0)
(list "-d" entity)))
" ")
"\n#+END_SRC"))))
(defun org-babel-execute:restclient-curl (body params)
"Execute a block of Restclient code to generate a curl command with org-babel.
This function is called by `org-babel-execute-src-block'"
(message "executing Restclient source code block")
(with-temp-buffer
(let ((results-buffer (current-buffer))
(restclient-same-buffer-response t)
(restclient-same-buffer-response-name (buffer-name))
(display-buffer-alist
(cons
'("\\*temp\\*" display-buffer-no-window (allow-no-window . t))
display-buffer-alist)))
(insert (buffer-name))
(with-temp-buffer
(dolist (p params)
(let ((key (car p))
(value (cdr p)))
(when (eql key :var)
(insert (format ":%s = %s\n" (car value) (cdr value))))))
(insert body)
(goto-char (point-min))
(delete-trailing-whitespace)
(goto-char (point-min))
(restclient-http-parse-current-and-do 'gen-restclient-curl-command))
generated-curl-command)))
;; Make it easy to interactively generate curl commands
(defun jb/gen-curl-command ()
(interactive)
(let ((info (org-babel-get-src-block-info)))
(if (equalp "restclient" (car info))
(org-babel-execute-src-block t (cons "restclient-curl"
(cdr info)))
(message "I'm sorry, I can only generate curl commands for a restclient block."))))
This section includes tooling for organizing ones work or personal life. Generally the tools and setup is pretty straight forward but should that not be the case I’ll add more details about the purpose and how to use.
(use-feature org-agenda
:config
(defun air-org-skip-subtree-if-priority (priority)
"Skip an agenda subtree if it has a priority of PRIORITY.
PRIORITY may be one of the characters ?A, ?B, or ?C."
(let ((subtree-end (save-excursion (org-end-of-subtree t)))
(pri-value (* 1000 (- org-lowest-priority priority)))
(pri-current (org-get-priority (thing-at-point 'line t))))
(if (= pri-value pri-current)
subtree-end
nil)))
;; (setq initial-buffer-choice (lambda () (org-agenda nil "d")
;; (buffer-find "*Org Agenda*")))
(setq org-agenda-window-setup 'only-window
org-agenda-custom-commands
'(("d" "Today"
((tags-todo "SCHEDULED<\"<+1d>\"&PRIORITY=\"A\"" ;Priority tasks available to do today
((org-agenda-skip-function
'(org-agenda-skip-entry-if 'todo 'done))
(org-agenda-overriding-header "High-priority unfinished tasks:")))
(agenda "" ((org-agenda-span 'day)
(org-scheduled-delay-days -14)
(org-agenda-overriding-header "Schedule")))
(tags-todo "SCHEDULED<\"<+1d>\"" ;All tasks available today
((org-agenda-skip-function
'(or (org-agenda-skip-entry-if 'done)
(air-org-skip-subtree-if-priority ?A)))
(org-agenda-overriding-header "Tasks:"))))))))
(use-package elegant-agenda-mode
:hook (org-agenda-mode . elegant-agenda-mode))
Have alerts pop up from your org agenda
(use-package org-alert)
Declarative Org Capture Templates
(use-package doct
:after org
:commands (doct)
:init (setq org-capture-templates
(doct `(("Personal" :keys "p" :children
(("Todo" :keys "t"
:template ("* TODO %^{Description}"
"SCHEDULED: %U")
:headline "Tasks" :file ,(concat org-directory "/personal/tasks.org"))
("Notes" :keys "n"
:template ("* %^{Description}"
":PROPERTIES:"
":Created: %U"
":END:")
:headline "Notes" :file ,(concat org-directory "/personal/tasks.org"))
("Appointment" :keys "a"
:template ("* %^{Description}"
"SCHEDULED: %T"
":PROPERTIES:"
":calendar-id: [email protected]"
":END:")
:file ,(concat org-directory "/personal/calendar.org"))
("Emails" :keys "e"
:template "* TODO [#A] Reply: %a :@home:"
:headline "Emails" :file "~/org/personal/tasks.org")))
("Work" :keys "w"
:children
(("Todo" :keys "t"
:template ("* TODO %^{Description}"
":PROPERTIES:"
":Scheduled: %U"
":END:")
:headline "Tasks" :file ,(concat org-directory "/work/tasks.org"))
("Notes" :keys "n"
:template ("* %^{Description}"
":PROPERTIES:"
":Created: %U"
":END:")
:headline "Notes" :file ,(concat org-directory "/work/tasks.org"))
("Emails" :keys "e"
:template "* TODO [#A] Reply: %a :@work:"
:headline "Emails" :file ,(concat org-directory "/work/tasks.org"))
("Trello" :keys "r"
:template ("* TODO [#B] %a " "SCHEDULED: %U")
:headline "Tasks" :file ,(concat org-directory "/work/tasks.org"))
("Appointment" :keys "a"
:template ("* %^{Description}"
"SCHEDULED: %T"
":PROPERTIES:"
":calendar-id: [email protected]"
":END:")
:file ,(concat org-directory "/work/calendar.org"))))))))
Change priority cookies from alphanumeric cookies into symbols and explicitly colours them
(use-package org-fancy-priorities
:hook
(org-mode . org-fancy-priorities-mode)
:config
'((?A :foreground "red")
(?B :foreground "orange")
(?C :foreground "blue"))
(setq org-fancy-priorities-list '("⚡" "⬆" "⬇" "☕")))
(use-package org-roam
:init
(setq org-roam-v2-ack t)
:custom
(org-roam-directory "~/dev/diary")
(org-roam-completion-everywhere t)
:bind (("C-c n l" . org-roam-buffer-toggle)
("C-c n f" . org-roam-node-find)
("C-c n i" . org-roam-node-insert))
:config
(org-roam-setup))
(use-package org-roam-ui
:after org-roam
:config
(setq org-roam-ui-sync-theme t
org-roam-ui-follow t
org-roam-ui-update-on-save t
org-roam-ui-open-on-start t))
(use-package org-noter
:custom (org-noter-supported-mode '(doc-view-mode pdf-mode-view)))
(use-package org-download
:after org
:hook (org-mode . org-download-enable))
(use-package org-transclusion
:after org)
(use-package org-transclusion-http
:after org-transclusion)
(use-package org-auto-clock
:after org
:commands org-auto-clock-mode
:ensure (:type git :host github :repo "justinbarclay/org-auto-clock")
:init
(org-auto-clock-mode)
:custom
(org-clock-idle-time 20)
(org-auto-clock-projects '("tidal-wave" "application-inventory"))
(org-auto-clock-project-name-function #'projectile-project-name))
(use-package lsp-ltex)
(use-feature eshell
:config
(progn
(eval-after-load 'esh-opt
'(progn
(require 'em-prompt)
(require 'em-term)
(require 'em-cmpl)
(setenv "PAGER" "cat")
(add-to-list 'eshell-visual-commands "ssh")
(add-to-list 'eshell-visual-commands "htop")
(add-to-list 'eshell-visual-commands "top")
(add-to-list 'eshell-visual-commands "tail")
(add-to-list 'eshell-visual-commands "vim")
(add-to-list 'eshell-visual-commands "npm")
(add-to-list 'eshell-command-completions-alist
'("gunzip" "gz\\'"))
(add-to-list 'eshell-command-completions-alist
'("tar" "\\(\\.tar|\\.tgz\\|\\.tar\\.gz\\)\\'"))))))
(use-package eat
:init
(add-hook 'eshell-load-hook #'eat-eshell-mode)
:hook (eat-mode . (lambda () (setq display-line-numbers nil))))
(use-package transient)
;; Magit is an Emacs interface to Git.
;; (It's awesome)
;; https://github.com/magit/magit
(use-package magit
:commands magit-get-top-dir
;; :ensure-system-package git
:bind (("C-c g" . magit-status))
:hook
(git-commit-mode . magit-commit-mode-init)
:bind (:map magit-mode-map
("c" . magit-maybe-commit))
:init
(progn
;; magit extensions
;; stops the invalid style showing up.
;; From: http://git.io/rPBE0Q
(defun magit-commit-mode-init ()
"Force a new line to be inserted into a commit window"
(when (looking-at "\n"))
(open-line 1))
(defun magit-maybe-commit (&optional show-options)
"Runs magit-commit unless prefix is passed"
(interactive "P")
(if show-options
(magit-key-mode-popup-committing)
(magit-commit)))
;; make magit status go full-screen but remember previous window
;; settings
;; from: http://whattheemacsd.com/setup-magit.el-01.html
(advice-add 'magit-status :around #'(lambda (orig-fun &rest args)
(window-configuration-to-register :m)
(apply orig-fun args)
(delete-other-windows)))
(advice-add 'git-commit-commit :after #'(lambda (&rest _)
(delete-window)))
(advice-add 'git-commit-abort :after #'(lambda (&rest _)
(delete-window))))
:config
(progn
;; restore previously hidden windows
(advice-add 'magit-quit-window
:around
#'(lambda (oldfun)
(let ((current-mode major-mode))
(funcall oldfun)
(when (eq 'magit-status-mode current-mode)
(jump-to-register :m)))))
;; magit settings
(setq
;; don't put "origin-" in front of new branch names by default
magit-default-tracking-name-function #'magit-default-tracking-name-branch-only
;; open magit status in same window as current buffer
magit-status-buffer-switch-function #'switch-to-buffer
;; highlight word/letter changes in hunk diffs
magit-diff-refine-hunk t
;; ask me if I want to include a revision when rewriting
magit-rewrite-inclusive 'ask
;; ask me to save buffers
magit-save-some-buffers nil
;; pop the process buffer if we're taking a while to complete
magit-process-popup-time 10
;; ask me if I want a tracking upstream
magit-set-upstream-on-push 'askifnotset)))
(use-package forge
:after magit
:init
(setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3"))
Due to how hl-todo specified versions, we have to do a full clone and specify the version, as mentioned here
(use-package hl-todo
:ensure (hl-todo :depth nil :version (lambda (&rest _args) "1.9.0")))
(use-package magit-todos
:hook (magit-mode . magit-todos-mode))
(use-package pos-tip)
(use-feature mu4e
:commands (mu4e mu4e-update-mail-and-index)
:bind (:map mu4e-headers-mode-map
("q" . kill-current-buffer))
:after org
:config
(setq
mu4e-headers-skip-duplicates t
mu4e-view-show-images t
mu4e-view-show-addresses t
mu4e-use-fancy-chars t
mu4e-compose-format-flowed nil
mu4e-date-format "%y/%m/%d"
mu4e-headers-date-format "%Y/%m/%d"
mu4e-change-filenames-when-moving t
mu4e-attachments-dir "~/Downloads"
mu4e-maildir "~/Maildir/" ;; top-level Maildir
;; note that these folders below must start with /
;; the paths are relative to maildir root
;; this setting allows to re-sync and re-index mail
;; by pressing U
mu4e-get-mail-command "mbsync -a"
mu4e-completing-read-function 'completing-read
mu4e-context-policy 'pick-first
mu4e-contexts (list
(make-mu4e-context
:name "fastmail"
:match-func
(lambda (msg)
(when msg
(string-prefix-p "/fastmail" (mu4e-message-field msg :maildir))))
:vars '((user-mail-address . "[email protected]")
(user-full-name . "Justin Barclay")
(mu4e-drafts-folder . "/fastmail/Drafts")
(mu4e-sent-folder . "/fastmail/Sent")
(mu4e-refile-folder . "/fastmail/Archive")
(sendmail-program . "msmtp")
(send-mail-function . smtpmail-send-it)
(message-sendmail-f-is-evil . t)
(message-sendmail-extra-arguments . ("--read-envelope-from"))
(message-send-mail-function . message-send-mail-with-sendmail)
(smtpmail-default-smtp-server . "smtp.fastmail.com")
(smtpmail-smtp-server . "smtp.fastmail.com")
(mu4e-trash-folder . "/fastmail/Trash")))))
(display-line-numbers-mode -1))
(push 'mu4e elpaca-ignored-dependencies)
(use-package mu4e-dashboard
:ensure (:type git :host github :repo "rougier/mu4e-dashboard")
:bind ("C-c d" . mu4e-dashboard)
:after mu4e
:hook
(mu4e-dashboard-mode . (lambda () (display-line-numbers-mode -1)))
:custom
(mu4e-dashboard-file "~/.emacs.d/dashboards/mu4e-dashboard.org")
:config
(defun mu4e-dashboard-edit ()
(interactive)
(let ((edit-buffer "*edit-mu4e-dashboard*"))
(when (get-buffer edit-buffer)
(kill-buffer (get-buffer edit-buffer)))
(make-indirect-buffer (current-buffer) edit-buffer)
(switch-to-buffer-other-window (get-buffer edit-buffer))
(org-mode 1)))
(display-line-numbers-mode -1)
(flyspell-mode -1))
(use-package mu4e-thread-folding
:ensure (:type git :host github :repo "rougier/mu4e-thread-folding")
:hook
((mu4e-headers-mode . mu4e-thread-folding-mode)
(mu4e-headers-mode . (lambda () (display-line-numbers-mode -1))))
:config
(add-to-list 'mu4e-header-info-custom
'(:empty . (:name "Empty"
:shortname ""
:function (lambda (msg) " "))))
:custom
(mu4e-headers-fields '((:empty . 2)
(:human-date . 12)
(:flags . 6)
(:mailing-list . 10)
(:from . 22)
(:subject . nil)))
:bind (:map mu4e-headers-mode-map
("<tab>" . mu4e-headers-toggle-at-point)
("<left>" . mu4e-headers-fold-at-point)
("<S-left>" . mu4e-headers-fold-all)
("<right>" . mu4e-headers-unfold-at-point)
("<S-right>" . mu4e-headers-unfold-all)))
(use-package elfeed
:custom
(elfeed-feeds
'(("http://nullprogram.com/feed/" emacs)
("https://sachachua.com/blog/feed/" emacs)
("https://macowners.club/posts/index.xml" emacs)
("https://fasterthanli.me/index.xml" tech rust)
("https://justinbarclay.ca/index.xml" mine)
("https://blog.1password.com/index.xml" security authentication)
("https://www.michaelgeist.ca/blog/feed/" canada law)
("https://popehat.substack.com/feed" law)
("https://www.joelonsoftware.com/feed/" tech)
("https://xeiaso.net/blog.rss" tech nix)
("https://byorgey.wordpress.com/feed/" functional-programming)
("https://mjg59.dreamwidth.org/" tech)
("https://oxide.computer/blog/feed" tech company))))
(when jb/os-macos-p
(setq default-frame-alist '((ns-appearance . dark) (ns-transparent-titlebar . t) (ns-appearance . 'nil))))
Remove the tool bar
(tool-bar-mode -1)
Remove the menu bar
(menu-bar-mode -1)
Remove scroll bars
(when (fboundp 'scroll-bar-mode)
(scroll-bar-mode -1))
(pixel-scroll-precision-mode 1)
(when (display-graphic-p) ; Start full screen
(add-to-list 'default-frame-alist '(fullscreen . t))
(x-focus-frame nil))
(setq-default frame-title-format "%b (%f)")
Emacs keeps a margin between itself and other windows, but if we enable pixelwise resizing it keeps everything nice and snug
(setq frame-resize-pixelwise 't)
I’m a big fan of the Cascadia font from Microsoft lately. It’s looks pretty good and has great ligature support.
(let ((font-name (if jb/os-windows-p
"CaskaydiaCove NFM"
"CaskaydiaMono Nerd Font Mono")))
(set-face-attribute 'default nil
:family font-name :height 170 :weight 'normal))
Ok we know our font supports ligatures, let’s set that up.
(use-package ligature
:defer t
:config
;; Enable the "www" ligature in every possible major mode
(ligature-set-ligatures 't '("www"))
;; Enable traditional ligature support in eww-mode, if the
;; `variable-pitch' face supports it
(ligature-set-ligatures 'eww-mode '("ff" "fi" "ffi"))
;; Enable all Cascadia Code ligatures in programming modes
(ligature-set-ligatures 'prog-mode '("|||>" "<|||" "<==>" "<!--" "####" "~~>" "***" "||=" "||>"
":::" "::=" "=:=" "===" "==>" "=!=" "=>>" "=<<" "=/=" "!=="
"!!." ">=>" ">>=" ">>>" ">>-" ">->" "->>" "-->" "---" "-<<"
"<~~" "<~>" "<*>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->"
"<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#_(" "..<"
"..." "+++" "/==" "///" "_|_" "www" "&&" "^=" "~~" "~@" "~="
"~>" "~-" "**" "*>" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|"
"[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:"
">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<*" "<|" "<:"
"<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!"
"##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:"
"?=" "?." "??" ";;" "/*" "/=" "/>" "//" "__" "~~" "(*" "*)"
"\\\\" "://"))
;; Enables ligature checks globally in all buffers. You can also do it
;; per mode with `ligature-mode'.
:init
(global-ligature-mode t))
Lets improve our mapping for unicode-fonts
(use-package unicode-fonts
:defer 't
:config
(unicode-fonts-setup))
We can not place this in early-init because it requires the GUI to be initialized
(cond
(jb/os-macos-p
(progn
(set-fontset-font "fontset-default" 'symbol "Apple Color Emoji" nil 'prepend)
(set-fontset-font "fontset-default" 'emoji "Apple Color Emoji" nil 'prepend)))
((or jb/os-linux-p
jb/os-windows-p)
(progn
(set-fontset-font "fontset-default" 'symbol "Segoe UI Emoji" nil 'prepend)
(set-fontset-font "fontset-default" 'emoji "Segoe UI Emoji" nil 'prepend)))
nil)
No cursor blinking, it’s distracting
(blink-cursor-mode 0)
;; These settings relate to how emacs interacts with your operating system
(setq-default ;; makes killing/yanking interact with the clipboard
select-enable-clipboard t
;; I'm actually not sure what this does but it's recommended?
select-enable-primary t
;; Save clipboard strings into kill ring before replacing them.
;; When one selects something in another program to paste it into Emacs,
;; but kills something in Emacs before actually pasting it,
;; this selection is gone unless this variable is non-nil
;;save-interprogram-paste-before-kill nil ;; This is disabled because it crashes emacs.
;; Shows all options when running apropos. For more info,
;; https://www.gnu.org/software/emacs/manual/html_node/emacs/Apropos.html
apropos-do-all t
;; Mouse yank commands yank at point instead of at click.
mouse-yank-at-point t)
My name isn’t “Tinker”, so I don’t need a bell.
(setq-default ring-bell-function 'ignore)
;; Changes all yes/no questions to y/n type
(fset 'yes-or-no-p 'y-or-n-p)
;; shell scripts
(setq-default sh-basic-offset 2)
(setq-default sh-indentation 2)
;; No need for ~ files when editing
(setq-default create-lockfiles nil)
;; Go straight to scratch buffer on startup
(setq-default inhibit-startup-message t)
Note: Disabling the BPA makes redisplay faster, but might produce incorrect display reordering of bidirectional text with embedded parentheses
(setq bidi-inhibit-bpa t)
We want to unbind C-l
so we can use it later
(keymap-global-unset "C-l")
Increase the size of read-process-output-max from default of 4k to 1Mb
(setq-default read-process-output-max (* 1024 1024)) ;; 1mb
As of Emacs 26.0 we have native, perfomant, support for line numbers
(global-display-line-numbers-mode)
(set-default 'display-line-numbers-type 't)
(set-default 'display-line-numbers-current-absolute 't)
(use-package nano-theme
:ensure (nano-theme :type git :host github :repo "rougier/nano-theme"))
(use-package lambda-themes
:ensure (:type git :host github :repo "lambda-emacs/lambda-themes")
:custom
(lambda-themes-set-italic-comments nil)
(lambda-themes-set-italic-keywords nil)
(lambda-themes-set-variable-pitch nil)
:config
;; load preferred theme
(load-theme 'lambda-light))
(use-package catppuccin-theme)
(use-package doom-everblush-theme
:ensure (doom-everblush-theme :type git :host github :repo "Everblush/doomemacs"))
Let’s use Doom’s version instead
(use-package doom-themes
:ensure (:type git :host github :repo "justinbarclay/themes" :ref "laserwave-hc")
:init
(load-theme 'doom-laserwave-high-contrast t)
(setq doom-themes-enable-bold t ; if nil, bold is universally disabled
doom-themes-enable-italic t) ; if nil, italics is universally disabled
;; Corrects (and improves) org-mode's native fontification.
(doom-themes-org-config))
(use-package nerd-icons)
(use-package nerd-icons-completion
:after (nerd-icons marginalia)
:hook (marginalia-mode . nerd-icons-completion-marginalia-setup))
(use-package nerd-icons-ibuffer
:hook (ibuffer-mode . nerd-icons-ibuffer-mode))
(use-package doom-modeline
:hook
(elpaca-after-init . doom-modeline-mode)
:custom
(doom-modeline-buffer-file-name-style 'relative-to-project))
(use-package lambda-line
:ensure (:type git :host github :repo "lambda-emacs/lambda-line")
:custom
(lambda-line-position 'top) ;; Set position of status-line
(lambda-line-abbrev t) ;; abbreviate major modes
(lambda-line-hspace " ") ;; add some cushion
(lambda-line-prefix t) ;; use a prefix symbol
(lambda-line-prefix-padding nil) ;; no extra space for prefix
(lambda-line-status-invert t) ;; no invert colors
(lambda-line-git-diff-mode-line nil)
(lambda-line-gui-ro-symbol " ⨂") ;; symbols
(lambda-line-gui-mod-symbol " ⬤")
(lambda-line-gui-rw-symbol " ◯")
(lambda-line-space-top +.25) ;; padding on top and bottom of line
(lambda-line-space-bottom -.25)
(lambda-line-symbol-position 0.1) ;; adjust the vertical placement of symbol
(lambda-line-syntax t)
:hook (elpaca-after-init . lambda-line-mode)
:config
(require 'nerd-icons)
(setq lambda-line-flycheck-label (format " %s" (nerd-icons-mdicon "nf-md-alarm_light")))
(setq lambda-line-vc-symbol (format " %s" (nerd-icons-mdicon "nf-md-git")))
;; activate lambda-line
;; set divider line in footer
(when (eq lambda-line-position 'top)
(setq-default mode-line-format (list "%_"))
(setq mode-line-format (list "%_"))))
(use-package rainbow-delimiters
:hook (prog-mode . rainbow-delimiters-mode)
:config
(custom-set-faces
'(rainbow-delimiters-depth-0-face ((t (:foreground "saddle brown"))))
'(rainbow-delimiters-depth-1-face ((t (:foreground "dark orange"))))
'(rainbow-delimiters-depth-2-face ((t (:foreground "deep pink"))))
'(rainbow-delimiters-depth-3-face ((t (:foreground "chartreuse"))))
'(rainbow-delimiters-depth-4-face ((t (:foreground "deep sky blue"))))
'(rainbow-delimiters-depth-5-face ((t (:foreground "yellow"))))
'(rainbow-delimiters-depth-6-face ((t (:foreground "orchid"))))
'(rainbow-delimiters-depth-7-face ((t (:foreground "spring green"))))
'(rainbow-delimiters-depth-8-face ((t (:foreground "sienna1"))))
'(rainbow-delimiters-unmatched-face ((t (:foreground "black"))))))
Sometimes you have to quickly jump through you undo-history
(use-package vundo
:custom
(vundo-glyph-alist vundo-unicode-symbols))
(use-package undo-fu)
And sometimes you want that undo-history to persist over a couple of days
(use-package undo-fu-session
:init
(undo-fu-session-global-mode)
:custom
(undo-fu-session-file-limit 10))
Keybindings We’re prettying up ibuffer after
This code is liberally stolen from https://github.com/seagle0128/.emacs.d/blob/master/lisp/init-ibuffer.el (April 12, 2019)
(use-feature ibuffer
:commands (ibuffer-current-buffer
ibuffer-find-file
ibuffer-do-sort-by-alphabetic)
:bind ("C-x C-b" . ibuffer)
:init
(setq ibuffer-filter-group-name-face '(:inherit (font-lock-string-face bold)))
(setq ibuffer-formats '((mark modified read-only locked
" " (icon 2 2 :left :elide) (name 18 18 :left :elide)
" " (size 9 -1 :right)
" " (mode 16 16 :left :elide) " " filename-and-process)
(mark " " (name 16 -1) " " filename)))
:config
(with-eval-after-load 'consult
(defalias 'ibuffer-find-file 'consult-find-file)))
(use-package ibuffer-projectile
:init
(add-hook 'ibuffer-hook
(lambda ()
(ibuffer-projectile-set-filter-groups)
(unless (eq ibuffer-sorting-mode 'alphabetic)
(ibuffer-do-sort-by-alphabetic))))
:config
(setq ibuffer-projectile-prefix (concat
(nerd-icons-octicon "nf-oct-file_directory"
:face ibuffer-filter-group-name-face
:v-adjust 0.1
:height 1.0)
" ")))
(use-feature dired
:bind (:map dired-mode-map
("RET" . dired-find-alternate-file)
("a" . dired-find-file)))
(use-package dirvish
:custom
;; Go back home? Just press `bh'
(dirvish-bookmark-entries
'(("h" "~/" "Home")
("m" "~/dev/tidal/application-inventory/" "MMP")
("t" "~/dev/tidal/tidal-wave" "Tidal Wave")))
(dirvish-header-line-format '(:left (path) :right (free-space)))
(dirvish-mode-line-format ; it's ok to place string inside
'(:left (sort file-time " " file-size symlink) :right (omit yank index)))
;; Don't worry, Dirvish is still performant even you enable all these attributes
(dirvish-attributes '(nerd-icons file-size collapse subtree-state vc-state git-msg))
;; Maybe the icons are too big to your eyes
(dirvish-nerd-icons-height 0.8)
;; In case you want the details at startup like `dired'
;; (dirvish-hide-details nil)
:config
(dirvish-peek-mode)
(dirvish-override-dired-mode)
;; Dired options are respected except a few exceptions, see *In relation to Dired* section above
(setq dired-dwim-target t)
(setq delete-by-moving-to-trash t)
;; Enable mouse drag-and-drop files to other applications
(setq dired-mouse-drag-files t) ; added in Emacs 29
(setq mouse-drag-and-drop-region-cross-program t) ; added in Emacs 29
;; Make sure to use the long name of flags when exists
;; eg. use "--almost-all" instead of "-A"
;; Otherwise some commands won't work properly
(setq dired-listing-switches
"-l --almost-all --human-readable --time-style=long-iso --group-directories-first --no-group")
:bind
;; Bind `dirvish|dirvish-side|dirvish-dwim' as you see fit
(("C-c f" . dirvish)
;; Dirvish has all the keybindings (except `dired-summary') in `dired-mode-map' already
:map dirvish-mode-map
("a" . dirvish-quick-access)
("f" . dirvish-file-info-menu)
("y" . dirvish-yank-menu)
("N" . dirvish-narrow)
("^" . dired-up-directory)
("h" . dirvish-history-jump) ; remapped `describe-mode'
("s" . dirvish-quicksort) ; remapped `dired-sort-toggle-or-edit'
("TAB" . dirvish-subtree-toggle)
("M-n" . dirvish-history-go-forward)
("M-p" . dirvish-history-go-backward)
("M-l" . dirvish-ls-switches-menu)
("M-m" . dirvish-mark-menu)
("M-f" . dirvish-toggle-fullscreen)
("M-s" . dirvish-setup-menu)
("M-e" . dirvish-emerge-menu)
("M-j" . dirvish-fd-jump)))
In OS X, when Emacs is started from the GUI it inherits a default set of environment variables. Let’s fix that. Currently turned off due to debugging issues
(use-package exec-path-from-shell
:if jb/os-macos-p
:defer 1
:init
(exec-path-from-shell-initialize)
:custom
(exec-path-from-shell-arguments '("-l")))
(when jb/os-windows-p
(setq package-check-signature nil)
(require 'gnutls)
(add-to-list 'gnutls-trustfiles (expand-file-name "~/.cert/cacert.pm"))
(add-hook 'comint-output-filter-functions 'comint-strip-ctrl-m))
(when (not jb/os-windows-p)
(use-package envrc
:defer 2
:config
(envrc-global-mode)))
Ensure that buffers have unique file names
(use-feature uniquify
:config
(setq uniquify-buffer-name-style 'forward))
Turn on recent file mode so that you can more easily switch to recently edited files when you first start emacs
(use-feature recentf
:init
(recentf-mode)
:custom ((recentf-save-file (concat user-emacs-directory ".recentf"))
(recentf-max-menu-items 40)))
(use-package projectile
:defer 1
:bind (("C-s p" . projectile-ripgrep))
:commands
(projectile-find-file projectile-switch-project projectile-ripgrep)
:config
(projectile-global-mode)
(setq projectile-completion-system 'auto)
(setq projectile-enable-caching t)
(add-to-list 'projectile-globally-ignored-directories "~")
(setq projectile-switch-project-action #'magit-status)
(define-key projectile-mode-map (kbd "C-c p") '("projectile" . projectile-command-map))
(defvar projectile-other-window-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "a") '("find-other-file-other-window" . projectile-find-other-file-other-window))
(define-key map (kbd "b") '("switch-to-buffer-other-window" . projectile-switch-to-buffer-other-window))
(define-key map (kbd "C-o") '("display-buffer" . projectile-display-buffer))
(define-key map (kbd "d") '("find-dir-other-window" . projectile-find-dir-other-window))
(define-key map (kbd "D") '("dired-other-window" . projectile-dired-other-window))
(define-key map (kbd "f") '("find-file-other-window" . projectile-find-file-other-window))
(define-key map (kbd "g") '("find-file-dwim-other-window" . projectile-find-file-dwim-other-window))
(define-key map (kbd "t") '("find-implementation-or-test-other-window" . projectile-find-implementation-or-test-other-window))
map))
(defvar projectile-other-frame-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "a") '("find-other-file-other-frame" . projectile-find-other-file-other-frame))
(define-key map (kbd "b") '("switch-to-buffer-other-frame" . projectile-switch-to-buffer-other-frame))
(define-key map (kbd "d") '("find-dir-other-frame" . projectile-find-dir-other-frame))
(define-key map (kbd "D") '("dired-other-frame" . projectile-dired-other-frame))
(define-key map (kbd "f") '("find-file-other-frame" . projectile-find-file-other-frame))
(define-key map (kbd "g") '("find-file-dwim-other-frame" . projectile-find-file-dwim-other-frame))
(define-key map (kbd "t") '("find-implementation-or-test-other-frame" . projectile-find-implementation-or-test-other-frame))
map))
(defvar projectile-search-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "g") '("grep" . projectile-grep))
(define-key map (kbd "r") '("ripgrep" . projectile-ripgrep))
(define-key map (kbd "s") '("ag" . projectile-ag))
(define-key map (kbd "x") '("find-references" . projectile-find-references))
map))
(which-key-add-keymap-based-replacements projectile-command-map
"4" (cons "other-window" projectile-other-window-map)
"5" (cons "other-frame" projectile-other-frame-map)
"s" (cons "search" projectile-search-map)))
(use-package treemacs
:config
(progn
(setq treemacs-follow-after-init t
treemacs-width 35
treemacs-indentation 2
treemacs-git-integration t
treemacs-collapse-dirs 3
treemacs-silent-refresh nil
treemacs-change-root-without-asking nil
treemacs-sorting 'alphabetic-desc
treemacs-show-hidden-files t
treemacs-never-persist nil
treemacs-is-never-other-window nil
treemacs-goto-tag-strategy 'prefetch-index)
(treemacs-follow-mode t)
(treemacs-filewatch-mode t)
(setq treemacs-icons-hash (make-hash-table :size 200 :test #'equal)
treemacs-icon-fallback (concat
" "
(nerd-icons-faicon "nf-fa-file_o"
:face 'nerd-icons-dsilver
:height 0.9
:v-adjust -0.05)
" ")
treemacs-icon-text treemacs-icon-fallback)
(dolist (item nerd-icons/octicon-alist)
(let* ((extension (car item))
(func (cadr item))
(args (append (list (caddr item))
'(:height 0.9 :v-adjust -0.05)
(cdddr item)))
(icon (apply func args))
(key (s-replace-all '(("^" . "") ("\\" . "") ("$" . "") ("." . "")) extension))
(value (concat " " icon " ")))
(ht-set! treemacs-icons-hash (s-replace-regexp "\\?" "" key) value)
(ht-set! treemacs-icons-hash (s-replace-regexp ".\\?" "" key) value))))
:bind
(:map global-map
([f8] . treemacs-toggle)
("M-0" . treemacs-select-window)))
(use-package treemacs-projectile
:config
(setq treemacs-header-function #'treemacs-projectile-create-header))
Allows one to switch to a buffer based by using numbers, instead of cycling with C-x o
. ace-window
only kicks in if there are more than two buffers open.
(use-package ace-window
:bind ("C-x o" . ace-window))
(use-feature windmove-mode
:ensure nil
:commands (windmove-left windmove-right windmove-up windmove-down)
:init
(defvar-keymap windmove-custom-mode-map
:repeat (:exit (ignore))
"<down>" #'windmove-down
"<up>" #'windmove-up
"<left>" #'windmove-left
"<right>" #'windmove-right)
(set-keymap-parent windmove-custom-mode-map window-prefix-map)
(keymap-global-set "C-x w" windmove-custom-mode-map))
(use-feature repeat-mode
:init (repeat-mode))
Unbind C-s from isearch and make it the universal search command
(unbind-key "C-s")
(use-package rg
:bind (("C-s r" . rg)))
(use-package avy
:bind (("C-s a" . #'avy-goto-char-timer))
:custom
(avy-enter-times-out 't)
(avy-timeout-seconds 1))
(use-feature isearch
:bind (("C-s i" . isearch-forward-regexp)
:map isearch-mode-map
("M-j" . avy-isearch)))
(use-feature occur
:bind ("C-s o" . occur))
(use-feature multi-occur
:init
(defun get-buffers-matching-mode (mode)
"Returns a list of buffers where their major-mode is equal to MODE"
(let ((buffer-mode-matches '()))
(dolist (buf (buffer-list))
(with-current-buffer buf
(when (eq mode major-mode)
(push buf buffer-mode-matches))))
buffer-mode-matches))
(defun multi-occur-in-this-mode ()
(interactive)
(multi-occur (get-buffers-matching-mode major-mode)
(car (occur-read-primary-args))))
:bind ("C-s m" . multi-occur-in-this-mode))
General config to make editing text feel nice
(use-feature emacs
:config
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(define-key global-map (kbd "RET") 'newline-and-indent)
(setq-default truncate-lines t)
(global-hl-line-mode 1)
(show-paren-mode 1)
I prefer spaces like some sort of monster
(setq-default indent-tabs-mode nil)
It’s all about space efficiency
(setq-default tab-width 2)
(setq-default c-basic-offset 2)
Remember where point was when I come back to a file
(save-place-mode 1)
;; keep track of saved places in ~/.emacs.d/places
(setq save-place-file (concat user-emacs-directory "places"))
(global-set-key (kbd "C-;") 'comment-or-uncomment-region)
Emacs can automatically create backup files. This tells Emacs to put all backups in ~/.emacs.d/backups. More info.
(setq backup-directory-alist `(("." . ,(concat user-emacs-directory
"backups"))))
(setq auto-save-default nil)
(setq backup-by-copying t)
(setq-default warning-suppress-log-types '((copilot copilot-no-mode-indent)))
Moving around with the mark should be really simple. It’s such a super power that I want to use it more and to do that, I need to bind it to something easier, something already reserved for popping the mark. So we can upgrade xref-go-back
to be a DWIM
function.
Here is an example of and another examp I guess it is corfu being slow due to too many candidates?
(unbind-key "M-,")
(defun pop-mark-dwim ()
"If xref history exist, use that to move around and if not pop off the global mark stack."
(interactive)
(condition-case nil
(xref-go-back)
(user-error
(pop-global-mark))))
(bind-key "M-," #'pop-mark-dwim)
)
(use-package smartparens
:hook (prog-mode . smartparens-mode)
:bind (:map smartparens-mode-map ("M-<backspace>" . 'backward-kill-word)))
Config
(use-feature smartparens-config
:after smartparens)
Emacs doesn’t handle trailing spaces or anything like that very well by default, it’s far too aggressive for my tastes, so we’ll use ws-butler to fix this.
(use-package ws-butler
:commands (ws-butler-mode)
:hook (prog-mode . ws-butler-mode))
Emacs by default doesn’t have a good story for folding text so we have to add one.
(use-package origami
:defer t
:bind ("C-<tab>" . origami-recursively-toggle-node)
:hook (prog-mode . origami-mode))
(use-package hungry-delete
:hook (prog-mode . global-hungry-delete-mode))
Thank you Magnar Sveen! I’ve put this at the top, because I use this almost everyday and wish it existed in more places.
(use-package multiple-cursors
:bind
(("C->" . mc/mark-next-like-this)
("C-<" . mc/mark-previous-like-this))
:commands (mc/mark-next-like-this mc/mark-previous-like-this))
Load this before we load Flycheck
(use-package flycheck-posframe
:hook ((flycheck-mode . flycheck-posframe-mode)
(lsp-mode . (lambda () (flycheck-posframe-mode 0)))
(post-command . flycheck-posframe-monitor-post-command))
:custom
(flycheck-posframe-warning-prefix "⚠ ")
(flycheck-posframe-error-prefix "❌ ")
(flycheck-posframe-info-prefix "ⓘ ")
:config
(defun flycheck-posframe-monitor-post-command ()
(when (not (flycheck-posframe-check-position))
(posframe-hide flycheck-posframe-buffer)))
(set-face-attribute 'flycheck-posframe-info-face nil :inherit 'font-lock-variable-name-face)
(set-face-attribute 'flycheck-posframe-warning-face nil :inherit 'warning)
(set-face-attribute 'flycheck-posframe-error-face nil :inherit 'error))
(use-package flycheck-package
:init
(use-package package-lint))
(use-package flycheck
:init
(defun flycheck-node-modules-executable-find (executable)
(or
(let* ((base (locate-dominating-file buffer-file-name "node_modules"))
(cmd (if base (expand-file-name (concat "node_modules/.bin/" executable) base))))
(if (and cmd (file-exists-p cmd))
cmd))
(flycheck-default-executable-find executable)))
(defun flycheck-node-modules-hook ()
"Look inside node modules for the specified checker"
(setq-local flycheck-executable-find #'flycheck-node-modules-executable-find))
(global-flycheck-mode)
:hook
((typescript-ts-base-mode
js-base-mode
web-mode
css-ts-mode
less-css-mode) . #'flycheck-node-modules-hook)
:custom
(checkdoc-force-docstrings-flag nil)
;; (flycheck-javascript-eslint-executable "eslint_d")
;; (flycheck-typescript-tslint-executable "eslint_d")
(flycheck-check-syntax-automatically '(save idle-change idle-buffer-switch mode-enabled))
(flycheck-standard-error-navigation nil)
(flycheck-stylelintrc ".stylelintrc.json"))
(use-feature flyspell
:hook ((prog-mode . flyspell-prog-mode)
(text-mode . flyspell-mode))
:config (setq flyspell-issue-message-flag nil))
(use-package pgmacs
:ensure (:type git :host github :repo "emarsden/pgmacs"))
(use-package dape
:ensure (:fetcher git
:url "https://github.com/svaante/dape"))
(use-package vertico
:init
(vertico-mode)
:bind (:map vertico-map
("<escape>" . #'keyboard-escape-quit))
:config
(vertico-multiform-mode)
;; Custom candidate transforms
(defun +completion-category-highlight-files (cand)
(let ((len (length cand)))
(when (and (> len 0)
(eq (aref cand (1- len)) ?/))
(add-face-text-property 0 len 'dired-directory 'append cand)))
cand)
(defun +completion-category-highlight-commands (cand)
(let ((len (length cand)))
(when (and (> len 0)
(with-current-buffer (nth 1 (buffer-list)) ; get buffer before minibuffer
(or (eq major-mode (intern cand)) ; check major mode
(seq-contains-p local-minor-modes (intern cand))
(seq-contains-p global-minor-modes (intern cand))))) ; check minor modes
(add-face-text-property 0 len '(:foreground "red") 'append cand))) ; choose any color or face you like
cand)
(defun +completion-category-truncate-files (cand)
(if-let ((type (get-text-property 0 'multi-category cand))
((eq (car-safe type) 'file))
(response (ivy-rich-switch-buffer-shorten-path cand 30)))
response
cand))
;; Custom sorters
(defun sort-directories-first (files)
(setq files (vertico-sort-history-length-alpha files))
(nconc (seq-filter (lambda (x) (string-suffix-p "/" x)) files)
(seq-remove (lambda (x) (string-suffix-p "/" x)) files)))
;; Extend vertico-multiform abilities
(defvar +vertico-transform-functions nil)
(defun +vertico-transform (args)
(dolist (fun (ensure-list +vertico-transform-functions) args)
(setcar args (funcall fun (car args)))))
(advice-add #'vertico--format-candidate :filter-args #'+vertico-transform)
(setq vertico-multiform-commands
'((describe-symbol (vertico-sort-function . vertico-sort-alpha))))
(setq vertico-multiform-categories
'((symbol (vertico-sort-function . vertico-sort-alpha))
(command (+vertico-transform-functions . +completion-category-highlight-commands))
(file (vertico-sort-function . sort-directories-first)
(+vertico-transform-functions . +completion-category-highlight-files))
(multi-category (+vertico-transform-functions . +completion-category-truncate-files)))))
Save minibuffer history for better integration with orderless
(use-feature savehist
:init
(savehist-mode))
Marginalia looks and acts great, however as an old grey(ing) beard, I got used to some of the aesthetics of `ivy-rich` so I would like to bring some of these back.
(defun ivy-rich-switch-buffer-user-buffer-p (buffer)
"Check whether BUFFER-NAME is a user buffer."
(let ((buffer-name
(if (stringp buffer)
buffer
(buffer-name buffer))))
(not (string-match "^\\*" buffer-name))))
(defun ivy-rich--local-values (buffer args)
(let ((buffer (get-buffer buffer)))
(if (listp args)
(mapcar #'(lambda (x) (buffer-local-value x buffer)) args)
(buffer-local-value args buffer))))
(defun ivy-rich-switch-buffer-indicators (candidate)
(let* ((buffer (get-buffer candidate))
(process-p (get-buffer-process buffer)))
(cl-destructuring-bind
(filename directory read-only)
(ivy-rich--local-values candidate '(buffer-file-name default-directory buffer-read-only))
(let ((modified (if (and (buffer-modified-p buffer)
(null process-p)
(ivy-rich-switch-buffer-user-buffer-p candidate))
"*"
""))
(readonly (if (and read-only (ivy-rich-switch-buffer-user-buffer-p candidate))
"!"
""))
(process (if process-p
"&"
""))
(remote (if (file-remote-p (or filename directory))
"@"
"")))
(format "%s%s%s%s" remote readonly modified process)))))
(defun ivy-rich-switch-buffer-shorten-path (file len)
"Shorten the path of FILE until the length of FILE <= LEN.
For example, a path /a/b/c/d/e/f.el will be shortened to
/a/…/c/d/e/f.el
or /a/…/d/e/f.el
or /a/…/e/f.el
or /a/…/f.el."
(if (> (length file) len)
(let ((new-file (replace-regexp-in-string "/?.+?/\\(\\(…/\\)?.+?\\)/.*" "…" file nil nil 1)))
(if (string= new-file file)
file
(ivy-rich-switch-buffer-shorten-path new-file len)))
file))
(defun +marginalia-buffer-get-directory-name (cand)
(let ((name (buffer-file-name cand)))
(if name
(file-name-directory name)
(buffer-local-value 'list-buffers-directory cand))))
(defun +marginalia-display-project-name (cand)
(if-let ((dir (+marginalia-buffer-get-directory-name cand))
(message dir))
(projectile-project-name
(projectile-project-root dir))
"-"))
(defun +marginalia-category-truncate-files (cand)
(if-let ((type (get-text-property 0 'multi-category cand))
((eq (car-safe type) 'file)))
(ivy-rich-switch-buffer-shorten-path cand 30)
cand))
(defun +marginalia-truncate-helper (cand)
(if-let ((func (alist-get (vertico--metadata-get 'category)
+marginalia-truncation-func-overrides))
(shortened-candidate (funcall func cand)))
shortened-candidate
cand))
(use-package marginalia
:config
(setq marginalia-max-relative-age 0)
(setq marginalia-align 'left)
(defvar +marginalia-truncation-func-overrides
`((file . ,#'+marginalia-category-truncate-files)
(multi-category . ,#'+marginalia-category-truncate-files))
"Alist mapping category to truncate functions.")
(defun marginalia--align (cands)
"Align annotations of CANDS according to `marginalia-align'."
(cl-loop for (cand . ann) in cands do
(when-let (align (text-property-any 0 (length ann) 'marginalia--align t ann))
(setq marginalia--cand-width-max
(max marginalia--cand-width-max
(+ (string-width (+marginalia-truncate-helper cand))
(compat-call string-width ann 0 align))))))
(setq marginalia--cand-width-max (* (ceiling marginalia--cand-width-max
marginalia--cand-width-step)
marginalia--cand-width-step))
(cl-loop for (cand . ann) in cands collect
(progn
(when-let (align (text-property-any 0 (length ann) 'marginalia--align t ann))
(put-text-property
align (1+ align) 'display
`(space :align-to
,(pcase-exhaustive marginalia-align
('center `(+ center ,marginalia-align-offset))
('left `(+ left ,(+ marginalia-align-offset marginalia--cand-width-max 2)))
('right `(+ right ,(+ marginalia-align-offset 1
(- (compat-call string-width ann 0 align)
(string-width ann)))))))
ann))
(list (+marginalia-truncate-helper cand) "" ann))))
(defun marginalia-annotate-buffer (cand)
"Annotate buffer CAND with modification status, file name and major mode."
(when-let (buffer (get-buffer cand))
(marginalia--fields
((file-size-human-readable (buffer-size buffer)) :face 'marginalia-number :width -10)
((ivy-rich-switch-buffer-indicators buffer) :face 'error :width 3)
((+marginalia-display-project-name buffer) :face 'success :width 15)
((ivy-rich-switch-buffer-shorten-path
(+marginalia-buffer-get-directory-name
buffer)
30)
:face 'marginalia-file-name))))
:bind
(("M-A" . marginalia-cycle))
:init
(marginalia-mode))
(use-package orderless
:config
(defun prot-orderless-literal-dispatcher (pattern _index _total)
"Literal style dispatcher using the equals sign as a suffix.
It matches PATTERN _INDEX and _TOTAL according to how Orderless
parses its input."
(when (string-suffix-p "=" pattern)
`(orderless-literal . ,(substring pattern 0 -1))))
:custom
(completion-styles '(orderless basic)) ; Use orderless
(completion-category-overrides
'((file (styles basic ; For `tramp' hostname completion with `vertico'
partial-completion
orderless))))
(orderless-component-separator 'orderless-escapable-split-on-space)
(orderless-matching-styles
'(orderless-literal
orderless-prefixes
orderless-initialism
orderless-regexp))
(orderless-style-dispatchers '(prot-orderless-literal-dispatcher)))
(use-package consult
;; Replace bindings. Lazily loaded due by `use-package'.
:bind ;; C-c bindings (mode-specific-map)
(("C-s b" . consult-line)
;; C-x bindings (ctl-x-map)
("C-x b" . consult-buffer) ;; orig. switch-to-buffer
("C-x r b" . consult-bookmark) ;; orig. bookmark-jump
;; Custom M-# bindings for fast register access
("M-#" . consult-register-load)
("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated)
("C-M-#" . consult-register)
;; Other custom bindings
("M-y" . consult-yank-pop) ;; orig. yank-pop
("<help> a" . consult-apropos) ;; orig. apropos-command
;; M-g bindings (goto-map)
("M-g g" . consult-goto-line) ;; orig. goto-line
("M-g M-g" . consult-goto-line) ;; orig. goto-line
("M-g o" . consult-outline) ;; Alternative: consult-org-heading
;; M-s bindings (search-map)
("M-s d" . consult-find)
("M-s D" . consult-locate)
("M-s r" . consult-ripgrep)
("M-s u" . consult-focus-lines))
;; Enable automatic preview at point in the *Completions* buffer. This is
;; relevant when you use the default completion UI.
:hook (completion-list-mode . consult-preview-at-point-mode)
:init
;; Use Consult to select xref locations with preview
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
;; Configure other variables and modes in the :config section,
;; after lazily loading the package.
:config
;; Optionally configure preview. The default value
;; is 'any, such that any key triggers the preview.
;; For some commands and buffer sources it is useful to configure the
;; :preview-key on a per-command basis using the `consult-customize' macro.
(consult-customize
consult-theme :preview-key '(:debounce 0.2 any)
consult-ripgrep consult-git-grep consult-grep
consult-bookmark consult-recent-file consult-xref
consult--source-bookmark consult--source-file-register
consult--source-recent-file consult--source-project-recent-file
;; :preview-key "M-."
:preview-key '(:debounce 0.4 any))
(autoload 'projectile-project-root "projectile")
(setq consult-project-function (lambda (_) (projectile-project-root))))
(use-package embark
:bind
(("C-." . embark-act) ;; pick some comfortable binding
("C-;" . embark-dwim) ;; good alternative: M-.
("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'
:init
;; Optionally replace the key help with a completing-read interface
(setq prefix-help-command #'embark-prefix-help-command)
:config
;; Hide the mode line of the Embark live/completions buffers
(add-to-list 'display-buffer-alist
'("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
nil
(window-parameters (mode-line-format . none)))))
Consult users will also want the embark-consult package.
(use-package embark-consult
:after (embark consult)
:demand t ; only necessary if you have the hook below
;; if you want to have consult previews as you move around an
;; auto-updating embark collect buffer
:hook
(embark-collect-mode . consult-preview-at-point-mode))
(use-package consult-omni
:ensure (:type git :host github :repo "armindarvish/consult-omni" :branch "main" :files (:defaults "sources/*.el"))
:commands (consult-omni-multi consult-omni-apps)
:custom
;; General settings that apply to all sources
(consult-omni-show-preview t) ;;; show previews
(consult-omni-preview-key "C-o") ;;; set the preview key to C-o
(consult-omni-group-by :source)
:config
;; Load Sources Core code
(require 'consult-omni-sources)
;; Load Embark Actions
(require 'consult-omni-embark)
(when jb/os-macos-p
(add-to-list 'consult-omni-apps-paths "/Applications/Nix Apps"))
;; Either load all source modules or a selected list
;;; Select a list of modules you want to aload, otherwise all sources all laoded
(setq consult-omni-sources-modules-to-load
(list 'consult-omni-apps
'consult-omni-buffer
'consult-omni-calc
'consult-omni-dict
'consult-omni-fd
'consult-omni-google
'consult-omni-google-autosuggest
'consult-omni-gptel
'consult-omni-line-multi
;;'consult-omni-org-agenda
'consult-omni-ripgrep
'consult-omni-ripgrep-all
'consult-omni-wikipedia
'consult-omni-youtube))
(consult-omni-sources-load-modules)
;;; set multiple sources for consult-omni-multi command. Change these lists as needed for different interactive commands. Keep in mind that each source has to be a key in `consult-omni-sources-alist'.
(setq consult-omni-multi-sources '("calc"
"File"
"Buffer"
;; "Bookmark"
"Apps"
"gptel"
"Dictionary"
"Google"
"Wikipedia"
;; "elfeed"
"mu4e"
"buffers text search"
"Org Agenda"
;; "GitHub"
"YouTube"))
;; "Invidious"))
;;; Pick you favorite autosuggest command.
(setq consult-omni-default-autosuggest-command #'consult-omni-dynamic-google-autosuggest) ;;or any other autosuggest source you define
;;; Set your shorthand favorite interactive command
(setq consult-omni-default-interactive-command #'consult-omni-multi))
As Stolen from http://cestlaz.github.io/posts/using-emacs-6-swiper/ (January 10, 2017) it looks like counsel is a requirement for swiper
Let’s pretty up ivy This is stolen wholesale from Centaur Emacs. https://github.com/seagle0128/.emacs.d/blob/master/lisp/init-ivy.el
(use-package ivy-rich
:defines (all-the-icons-icon-alist
all-the-icons-dir-icon-alist
bookmark-alist)
:functions (all-the-icons-icon-for-file
all-the-icons-icon-for-mode
all-the-icons-icon-family
all-the-icons-match-to-alist
all-the-icons-faicon
all-the-icons-octicon
all-the-icons-dir-is-submodule)
:preface
(defun ivy-rich-bookmark-name (candidate)
(car (assoc candidate bookmark-alist)))
(defun ivy-rich-buffer-icon (candidate)
"Display buffer icons in `ivy-rich'."
(when (display-graphic-p)
(let* ((buffer (get-buffer candidate))
(buffer-file-name (buffer-file-name buffer))
(major-mode (buffer-local-value 'major-mode buffer))
(icon (if (and buffer-file-name
(all-the-icons-match-to-alist buffer-file-name
all-the-icons-icon-alist))
(all-the-icons-icon-for-file (file-name-nondirectory buffer-file-name)
:height 0.9 :v-adjust -0.05)
(all-the-icons-icon-for-mode major-mode :height 0.9 :v-adjust -0.05))))
(if (symbolp icon)
(setq icon (all-the-icons-faicon "file-o" :face 'all-the-icons-dsilver :height 0.9 :v-adjust -0.05))
icon))))
(defun ivy-rich-file-icon (candidate)
"Display file icons in `ivy-rich'."
(when (display-graphic-p)
(let* ((path (concat ivy--directory candidate))
(file (file-name-nondirectory path))
(icon (cond ((file-directory-p path)
(cond
((and (fboundp 'tramp-tramp-file-p)
(tramp-tramp-file-p default-directory))
(all-the-icons-octicon "file-directory" :height 0.93 :v-adjust 0.01))
((file-symlink-p path)
(all-the-icons-octicon "file-symlink-directory" :height 0.93 :v-adjust 0.01))
((all-the-icons-dir-is-submodule path)
(all-the-icons-octicon "file-submodule" :height 0.93 :v-adjust 0.01))
((file-exists-p (format "%s/.git" path))
(all-the-icons-octicon "repo" :height 1.0 :v-adjust -0.01))
(t (let ((matcher (all-the-icons-match-to-alist candidate all-the-icons-dir-icon-alist)))
(apply (car matcher) (list (cadr matcher) :height 0.93 :v-adjust 0.01))))))
((string-match "^/.*:$" path)
(all-the-icons-material "settings_remote" :height 0.9 :v-adjust -0.2))
((not (string-empty-p file))
(all-the-icons-icon-for-file file :height 0.9 :v-adjust -0.05)))))
(if (symbolp icon)
(setq icon (all-the-icons-faicon "file-o" :face 'all-the-icons-dsilver :height 0.9 :v-adjust -0.05))
icon))))
:hook ((ivy-mode . ivy-rich-mode)
(ivy-rich-mode . (lambda ()
(setq ivy-virtual-abbreviate
(or (and ivy-rich-mode 'abbreviate) 'name)))))
:init
;; For better performance
(setq ivy-rich-parse-remote-buffer nil)
(setq ivy-rich-display-transformers-list
'(ivy-switch-buffer
(:columns
((ivy-rich-buffer-icon)
(ivy-rich-candidate (:width 30))
(ivy-rich-switch-buffer-size (:width 7))
(ivy-rich-switch-buffer-indicators (:width 4 :face error :align right))
(ivy-rich-switch-buffer-major-mode (:width 12 :face warning))
(ivy-rich-switch-buffer-project (:width 15 :face success))
(ivy-rich-switch-buffer-path (:width (lambda (x) (ivy-rich-switch-buffer-shorten-path x (ivy-rich-minibuffer-width 0.3))))))
:predicate
(lambda (cand) (get-buffer cand)))
ivy-switch-buffer-other-window
(:columns
((ivy-rich-buffer-icon)
(ivy-rich-candidate (:width 30))
(ivy-rich-switch-buffer-size (:width 7))
(ivy-rich-switch-buffer-indicators (:width 4 :face error :align right))
(ivy-rich-switch-buffer-major-mode (:width 12 :face warning))
(ivy-rich-switch-buffer-project (:width 15 :face success))
(ivy-rich-switch-buffer-path (:width (lambda (x) (ivy-rich-switch-buffer-shorten-path x (ivy-rich-minibuffer-width 0.3))))))
:predicate
(lambda (cand) (get-buffer cand)))
counsel-switch-buffer
(:columns
((ivy-rich-buffer-icon)
(ivy-rich-candidate (:width 30))
(ivy-rich-switch-buffer-size (:width 7))
(ivy-rich-switch-buffer-indicators (:width 4 :face error :align right))
(ivy-rich-switch-buffer-major-mode (:width 12 :face warning))
(ivy-rich-switch-buffer-project (:width 15 :face success))
(ivy-rich-switch-buffer-path (:width (lambda (x) (ivy-rich-switch-buffer-shorten-path x (ivy-rich-minibuffer-width 0.3))))))
:predicate
(lambda (cand) (get-buffer cand)))
persp-switch-to-buffer
(:columns
((ivy-rich-buffer-icon)
(ivy-rich-candidate (:width 30))
(ivy-rich-switch-buffer-size (:width 7))
(ivy-rich-switch-buffer-indicators (:width 4 :face error :align right))
(ivy-rich-switch-buffer-major-mode (:width 12 :face warning))
(ivy-rich-switch-buffer-project (:width 15 :face success))
(ivy-rich-switch-buffer-path (:width (lambda (x) (ivy-rich-switch-buffer-shorten-path x (ivy-rich-minibuffer-width 0.3))))))
:predicate
(lambda (cand) (get-buffer cand)))
counsel-M-x
(:columns
((counsel-M-x-transformer (:width 50))
(ivy-rich-counsel-function-docstring (:face font-lock-doc-face))))
counsel-describe-function
(:columns
((counsel-describe-function-transformer (:width 50))
(ivy-rich-counsel-function-docstring (:face font-lock-doc-face))))
counsel-describe-variable
(:columns
((counsel-describe-variable-transformer (:width 50))
(ivy-rich-counsel-variable-docstring (:face font-lock-doc-face))))
counsel-find-file
(:columns
((ivy-rich-file-icon)
(ivy-read-file-transformer)))
counsel-file-jump
(:columns
((ivy-rich-file-icon)
(ivy-rich-candidate)))
counsel-dired
(:columns
((ivy-rich-file-icon)
(ivy-read-file-transformer)))
counsel-dired-jump
(:columns
((ivy-rich-file-icon)
(ivy-rich-candidate)))
counsel-git
(:columns
((ivy-rich-file-icon)
(ivy-rich-candidate)))
counsel-recentf
(:columns
((ivy-rich-file-icon)
(ivy-rich-candidate (:width 0.8))
(ivy-rich-file-last-modified-time (:face font-lock-comment-face))))
counsel-bookmark
(:columns
((ivy-rich-bookmark-type)
(ivy-rich-bookmark-name (:width 40))
(ivy-rich-bookmark-info)))
counsel-projectile-switch-project
(:columns
((ivy-rich-file-icon)
(ivy-rich-candidate)))
counsel-projectile-find-file
(:columns
((ivy-rich-file-icon)
(counsel-projectile-find-file-transformer)))
counsel-projectile-find-dir
(:columns
((ivy-rich-file-icon)
(counsel-projectile-find-dir-transformer)))
treemacs-projectile
(:columns
((ivy-rich-file-icon)
(ivy-rich-candidate))))))
(use-package ivy
:hook (after-init . ivy-mode)
:config
(progn
(setq ivy-use-virtual-buffers t)
(setq ivy-initial-inputs-alist nil)
(counsel-mode)
(ivy-rich-mode)))
(use-package counsel
:after ivy
:config
(setq counsel-grep-base-command
"rg -i -M 120 --no-heading --line-number --color never '%s' %s")
(setq ivy-initial-inputs-alist nil)
:bind
(("M-x" . counsel-M-x)
("C-x C-f" . counsel-find-file)
("C-c p f" . counsel-projectile-find-file)
("C-c p d" . counsel-projectile-find-dir)
("C-c p p" . counsel-projectile-switch-project)
("<f1> f" . counsel-describe-function)
("<f1> v" . counsel-describe-variable)
("<f1> l" . counsel-load-library)
("<f2> i" . counsel-info-lookup-symbol)
("<f2> u" . counsel-unicode-char)
("C-c k" . counsel-rg)))
(use-package swiper :tangle no
:after ivy
:bind ("C-s" . swiper))
(use-package corfu
:init
(global-corfu-mode)
:hook (corfu-mode . corfu-popupinfo-mode)
:config
(setq corfu-auto-delay 0.1
corfu-auto 't
corfu-auto-prefix 1
corfu-min-width 40
corfu-min-height 20)
;; You can also enable Corfu more generally for every minibuffer, as
;; long as no other completion UI is active. If you use Mct or
;; Vertico as your main minibuffer completion UI, the following
;; snippet should yield the desired result.
(defun corfu-enable-always-in-minibuffer ()
"Enable Corfu in the minibuffer if Vertico/Mct are not active."
(unless (or (bound-and-true-p mct--active) ; Useful if I ever use MCT
(bound-and-true-p vertico--input))
(setq-local corfu-auto nil) ; Ensure auto completion is disabled
(corfu-mode 1)))
(custom-set-faces '(corfu-current ((t :inherit region :background "#2d2844"))))
(custom-set-faces '(corfu-popupinfo ((t :inherit corfu-default))))
(add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1))
Things are always better when they are prettier, let’s add icons to our in buffer completions and make them pretty. Nothing requires kind-icon, so nothing really brings it into scope. So, we can’t just stuff everything into config. We need a way to trigger calling, and loading, kind-icons. So, we add kind-icon to corfu’s formatters within the init block.
(use-package kind-icon
:init
(add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter)
:custom
(kind-icon-default-face 'corfu-default) ; Have background color be the same as `corfu' face background
(kind-icon-default-style '(:padding 0 :stroke 0 :margin 0 :radius 0 :height 0.8 :scale 1.0)))
Cape adds some more completion backends for us, allowing us to get file and dabbrev completions where appropriate. We also have the possibility of using company completions with some helper functions given from cape.
(use-package cape
:init
;; Add `completion-at-point-functions', used by `completion-at-point'.
(add-to-list 'completion-at-point-functions #'cape-dabbrev)
(add-to-list 'completion-at-point-functions #'cape-file)
(add-to-list 'completion-at-point-functions #'cape-dict))
(use-package company
:commands (company-complete-common
company-complete-common-or-cycle
company-manual-begin
company-grab-line)
:hook (elpaca-after-init . global-company-mode)
:bind (("C-<shift>-<tab>" . company-manual-begin)
:map company-active-map
("C->" . #'company-filter-candidates)
("C-/" . #'company-other-backend))
:config
(require 'company-files)
(setq company-minimum-prefix-length 1
company-tooltip-limit 14
company-idle-delay 0.1
company-selection-wrap-around t
company-tooltip-align-annotations t
company-require-match 'never
company-global-modes '(not erc-mode
circe-mode
message-mode
help-mode
gud-mode
vterm-mode)
company-frontends '(company-pseudo-tooltip-frontend ; always show candidates in overlay tooltip
company-echo-metadata-frontend) ; show selected candidate docs in echo area
;; Buffer-local backends will be computed when loading a major mode, so
;; only specify a global default here.
company-backends '((company-capf :separate company-dabbrev)
company-files
company-yasnippet)
;; These auto-complete the current selection when
;; `company-auto-commit-chars' is typed. This is too magical. We
;; already have the much more explicit RET and TAB.
company-auto-commit nil
;; Make `company-dabbrev' fully case-sensitive, to improve UX with
;; domain-specific words with particular casing.
company-dabbrev-ignore-case nil
company-dabbrev-downcase nil)
(add-to-list 'company-files--regexps "file:\\(\\(?:\\.\\{1,2\\}/\\|~/\\|/\\)[^\]\n]*\\)"))
(use-package company-box
:hook (company-mode . company-box-mode)
:init
(require 'nerd-icons)
(setq company-box-icons-alist 'company-box-icons-nerd-icons
company-box-icons-nerd-icons
`((Unknown . ,(nerd-icons-codicon "nf-cod-text_size" :face 'font-lock-warning-face))
(Text . ,(nerd-icons-codicon "nf-cod-text_size" :face 'font-lock-doc-face))
(Method . ,(nerd-icons-codicon "nf-cod-symbol_method" :face 'font-lock-function-name-face))
(Function . ,(nerd-icons-codicon "nf-cod-symbol_method" :face 'font-lock-function-name-face))
(Constructor . ,(nerd-icons-codicon "nf-cod-triangle_right" :face 'font-lock-function-name-face))
(Field . ,(nerd-icons-codicon "nf-cod-symbol_field" :face 'font-lock-type-face))
(Variable . ,(nerd-icons-codicon "nf-cod-symbol_variable" :face 'font-lock-type-face))
(Class . ,(nerd-icons-codicon "nf-cod-symbol_class" :face 'font-lock-type-face))
(Interface . ,(nerd-icons-codicon "nf-cod-symbol_interface" :face 'font-lock-type-face))
(Module . ,(nerd-icons-codicon "nf-cod-file_submodule" :face 'font-lock-preprocessor-face))
(Property . ,(nerd-icons-codicon "nf-cod-symbol_property" :face 'font-lock-variable-name-face))
(Unit . ,(nerd-icons-codicon "nf-cod-symbol_ruler" :face 'font-lock-constant-face))
(Value . ,(nerd-icons-codicon "nf-cod-symbol_field" :face 'font-lock-builtin-face))
(Enum . ,(nerd-icons-codicon "nf-cod-symbol_enum" :face 'font-lock-builtin-face))
(Keyword . ,(nerd-icons-codicon "nf-cod-symbol_keyword" :face 'font-lock-keyword-face))
(Snippet . ,(nerd-icons-codicon "nf-cod-notebook_template" :face 'font-lock-string-face))
(Color . ,(nerd-icons-codicon "nf-cod-symbol_color" :face 'success))
(File . ,(nerd-icons-codicon "nf-cod-symbol_file" :face 'font-lock-string-face))
(Reference . ,(nerd-icons-codicon "nf-cod-references" :face 'font-lock-variable-name-face))
(Folder . ,(nerd-icons-codicon "nf-cod-folder" :face 'font-lock-variable-name-face))
(EnumMember . ,(nerd-icons-codicon "nf-cod-symbol_enum_member" :face 'font-lock-builtin-face))
(Constant . ,(nerd-icons-codicon "nf-cod-symbol_constant" :face 'font-lock-constant-face))
(Struct . ,(nerd-icons-codicon "nf-cod-symbol_structure" :face 'font-lock-variable-name-face))
(Event . ,(nerd-icons-codicon "nf-cod-symbol_event" :face 'font-lock-warning-face))
(Operator . ,(nerd-icons-codicon "nf-cod-symbol_operator" :face 'font-lock-comment-delimiter-face))
(TypeParameter . ,(nerd-icons-codicon "nf-cod-list_unordered" :face 'font-lock-type-face))
(Template . ,(nerd-icons-codicon "nf-cod-notebook_template" :face 'font-lock-escape-face))
(ElispFunction . ,(nerd-icons-codicon "nf-cod-symbol_method" :face 'font-lock-function-name-face))
(ElispVariable . ,(nerd-icons-codicon "nf-cod-symbol_variable" :face 'font-lock-type-face))
(ElispFeature . ,(nerd-icons-codicon "nf-cod-globe" :face 'font-lock-builtin-face))
(ElispFace . ,(nerd-icons-codicon "nf-cod-symbol_color" :face 'success))))
:config
(setq company-box-show-single-candidate t
company-box-backends-colors nil
company-box-tooltip-limit 50
;; Move company-box-icons--elisp to the end, because it has a catch-all
;; clause that ruins icons from other backends in elisp buffers.
company-box-icons-functions
(cons #'+company-box-icons--elisp-fn
(delq 'company-box-icons--elisp
company-box-icons-functions)))
(setq company-box-scrollbar nil)
(defun +company-box-icons--elisp-fn (candidate)
(when (derived-mode-p 'emacs-lisp-mode)
(let ((sym (intern candidate)))
(cond ((fboundp sym) 'ElispFunction)
((boundp sym) 'ElispVariable)
((featurep sym) 'ElispFeature)
((facep sym) 'ElispFace))))))
(use-package yasnippet
:init (yas-global-mode))
(use-package yasnippet-snippets
:after yasnippet)
(use-package yasnippet-capf
:commands yas-capf-minor-mode
:ensure (:type git :host github :repo "justinbarclay/yasnippet-capf")
:init
(add-hook 'yas-minor-mode-hook #'yas-capf-minor-mode))
Major mode customizations
(use-package apheleia
:init
(apheleia-global-mode)
:config
;; Override ruby-ts-mode defaults
(map-put! apheleia-mode-alist 'ruby-ts-mode '(rubocop)))
(use-feature treesit
:config
(setq-default treesit-font-lock-level 4))
We want automatic fall back and to have manually manage `treesit-language-source-alist`.
(use-package treesit-auto
:if (and (require 'treesit)
(treesit-available-p))
:defer 1
:commands (make-treesit-auto-recipe)
:custom
(treesit-auto-install 'prompt)
:init
(setq my-jsdoc-tsauto-config
(make-treesit-auto-recipe
:lang 'jsdoc
:ts-mode 'js-ts-mode
:url "https://github.com/tree-sitter/tree-sitter-jsdoc"
:revision "master"
:source-dir "src"
:requires 'javascript))
(setq my-js-tsauto-config
(make-treesit-auto-recipe
:lang 'javascript
:ts-mode 'js-ts-mode
:remap '(js2-mode js-mode javascript-mode)
:url "https://github.com/tree-sitter/tree-sitter-javascript"
:revision "master"
:requires 'jsdoc
:source-dir "src"
:ext "\\.js\\'"))
(add-to-list 'treesit-auto-recipe-list my-js-tsauto-config)
(add-to-list 'treesit-auto-recipe-list my-jsdoc-tsauto-config)
(add-to-list 'treesit-auto-langs 'jsdoc)
:config
(global-treesit-auto-mode))
Better movement through treesitter
(use-package combobulate
:ensure (:type git :host github :repo "mickeynp/combobulate")
:hook (prog-mode . combobulate-mode)
:custom (combobulate-js-ts-enable-auto-close-tag . nil))
(use-package copilot
:after jsonrpc
:hook (prog-mode . copilot-mode)
:ensure (copilot
:files ("dist" "*.el")
:type git
:host github
:repo "copilot-emacs/copilot.el")
:bind (:map copilot-completion-map
("TAB" . 'copilot-accept-completion)
("C-TAB" . 'copilot-accept-completion-by-word)))
(use-package gptel
:config
(setq
gptel-default-mode 'org-mode
gptel-model 'gemini-1.5-pro-latest
gptel-backend (gptel-make-gemini "gemini"
:key (string-trim (aio-wait-for (1password--read "Gemini" "credential" "private")))
:stream t)))
(use-package lsp-mode
:commands lsp
:hook ((rustic-mode
rust-base-mode
web-mode
ruby-base-mode
c-mode
js-base-mode
json-ts-mode
typescript-ts-mode
lua-mode
jsx-ts-mode
tsx-ts-mode
less-css-mode
css-ts-mode
go-base-mode
LaTeX-mode
nix-mode
org-mode)
. lsp-deferred)
(lsp-mode . lsp-enable-which-key-integration)
:custom
(lsp-idle-delay 0.1)
(lsp-log-io nil)
(lsp-completion-provider :none)
(lsp-headerline-breadcrumb-enable nil)
(lsp-solargraph-use-bundler 't)
(lsp-keymap-prefix "C-l")
(lsp-diagnostic-clean-after-change 't)
(lsp-copilot-enabled 't)
:config
(defvar lsp-flycheck-mapping '(less-css-mode (less-stylelint less)
css-base-mode (css-stylelint)
js-base-mode (javascript-eslint)
typescript-ts-base-mode (javascript-eslint)
tsx-ts-mode (javascript-eslint)
jsx-ts-mode (javascript-eslint))
"a selections of major modes and the associated checkers to run after lsp
runs it's diagnostics.")
(defvar-local my/flycheck-local-cache nil)
(defun my/flycheck-checker-get (fn checker property)
(or (alist-get property (alist-get checker my/flycheck-local-cache))
(funcall fn checker property)))
(advice-add 'flycheck-checker-get :around 'my/flycheck-checker-get)
(add-hook 'lsp-managed-mode-hook
(lambda ()
(when-let ((checkers (plist-get lsp-flycheck-mapping major-mode)))
(setq my/flycheck-local-cache `((lsp . ((next-checkers . ,checkers))))))))
(setopt flycheck-error-list-minimum-level nil)
(setopt flycheck-relevant-error-other-file-show 't)
(setopt flycheck-relevant-error-other-file-minimum-level 'warning)
;; As stolen from https://github.com/emacs-lsp/lsp-mode/issues/3279
(defun lsp-diagnostics--flycheck-start (checker callback)
"Start an LSP syntax check with CHECKER.
CALLBACK is the status callback passed by Flycheck."
(remove-hook 'lsp-on-idle-hook #'lsp-diagnostics--flycheck-buffer t)
(->> (lsp--get-buffer-diagnostics)
(-mapcat
(-lambda ((&Diagnostic :message :severity? :tags? :code? :source? :related-information?
:range (&Range :start (&Position :line start-line
:character start-character)
:end (&Position :line end-line
:character end-character))))
(let ((group (gensym)))
(cons (flycheck-error-new
:buffer (current-buffer)
:checker checker
:filename buffer-file-name
:message message
:level (lsp-diagnostics--flycheck-calculate-level severity? tags?)
:id code?
:group group
:line (lsp-translate-line (1+ start-line))
:column (1+ (lsp-translate-column start-character))
:end-line (lsp-translate-line (1+ end-line))
:end-column (1+ (lsp-translate-column end-character)))
(-mapcat
(-lambda ((&DiagnosticRelatedInformation
:message
:location
(&Location :range (&Range :start (&Position :line start-line
:character start-character)
:end (&Position :line end-line
:character end-character))
:uri)))
`(,(flycheck-error-new
:buffer (current-buffer)
:checker checker
:filename (-> uri lsp--uri-to-path lsp--fix-path-casing)
:message message
:level (lsp-diagnostics--flycheck-calculate-level (1+ severity?) tags?)
:id code?
:group group
:line (lsp-translate-line (1+ start-line))
:column (1+ (lsp-translate-column start-character))
:end-line (lsp-translate-line (1+ end-line))
:end-column (1+ (lsp-translate-column end-character)))))
related-information?)))))
(funcall callback 'finished))))
(use-package lsp-ui
:commands lsp-ui-mode
:hook (lsp-mode . lsp-ui-mode))
(use-package eglot
:bind (("C-c r" . eglot-rename)
("C-c o" . eglot-code-action-organize-imports)
("C-c h" . eldoc))
:custom
(eglot-send-changes-idle-time 0.1))
(use-package devdocs)
(use-feature css-mode
:config
(setq css-indent-offset 2))
(use-feature less-css-mode
:config
(setq css-indent-offset 2))
(use-package rainbow-mode
:hook ((css-mode . rainbow-mode)
(less-mode . rainbow-mode)))
(use-package sass-mode
:mode "\\.sass\\'")
(use-feature c-ts-base-mode
:config
(progn ; C mode hook
(add-hook 'c-mode-hook 'flycheck-mode)))
c++
(use-feature c++-ts-mode)
(use-package c-eldoc)
(defun enable-paredit ()
(turn-off-smartparens-mode)
(paredit-mode))
(defun enable-parinfer ()
(global-hungry-delete-mode 0)
(turn-off-smartparens-mode)
(paredit-mode)
(parinfer-rust-mode))
(defun enable-lispy ()
(turn-off-smartparens-mode)
(lispy-mode))
(use-package paredit
:commands (paredit-mode)
:hook ((common-lisp-mode . (lambda () (enable-paredit)))
(scheme-mode . (lambda () (enable-paredit)))
(lisp-mode . (lambda () (enable-paredit)))))
We need lispy for some of the excellent bracket based navigation integrations with parinfer
(use-package lispy)
(use-package parinfer-rust-mode
:commands (parinfer-rust-mode)
:hook (emacs-lisp-mode)
:custom
(parinfer-rust-disable-troublesome-modes 't)
(parinfer-rust-check-before-enable 'defer)
(parinfer-rust-auto-download nil)
:config
(add-hook 'vundo-pre-enter-hook (lambda () (parinfer-rust-mode 0) nil t))
(add-hook 'vundo-post-exit-hook (lambda () (parinfer-rust-mode 1) nil t))
(add-hook 'parinfer-rust-mode-hook 'parinfer-rust--auto-apply-fast-mode))
(use-feature eldoc
:config
(global-eldoc-mode))
(use-feature elisp-mode
:hook (emacs-lisp . enable-paredit))
;; clojure refactor library
;; https://github.com/clojure-emacs/clj-refactor.el
(use-package clj-refactor
:after clojure-mode
:config (progn (setq cljr-suppress-middleware-warnings t)
(add-hook 'clojure-mode-hook (lambda ()
(clj-refactor-mode 1)
(cljr-add-keybindings-with-prefix "C-c C-m")))))
(use-package kibit-helper
:defer t)
(use-package clojure-mode
:mode (("\\.clj\\'" . clojure-mode)
( "\\.cljs\\'" . clojurescript-mode))
:init
(progn
(add-hook 'clojure-mode-hook (lambda () (enable-parinfer)))
(add-hook 'clojure-mode-hook 'flycheck-mode)
(add-hook 'clojure-mode-hook 'cider-mode)
(add-hook 'clojure-mode-hook 'eldoc-mode)
(add-hook 'clojure-mode-hook 'subword-mode))
:config
(progn
(add-to-list 'auto-mode-alist '("\\.edn$" . clojure-mode))
(add-to-list 'auto-mode-alist '("\\.boot$" . clojure-mode))
(font-lock-add-keywords
nil
'(("(\\(facts?\\)"
(2 font-lock-keyword-face))
("(\\(background?\\)"
(2 font-lock-keyword-face))))
(electric-pair-mode 1)
(setq define-clojure-indent 2)))
Clojure mode also supports extra font locking(for syntax highlighting), but I have noticed that this causes performance issues in large and complicated clojure files (which I have been playing a lot with lately), so I have turned this feature off.
(require 'clojure-mode-extra-font-locking)
A REPL for Clojure and nrepl for ClojureScript
(use-package cider
:hook ((clojure-mode . cider-mode)
(clojurescript-mode . cider-mode))
:commands (cider-jack-in cider-jack-in-clojurescript)
:config
(progn
;; REPL related stuff
;; REPL history file
(setq cider-repl-history-file "~/.emacs.d/cider-history")
;; nice pretty printing
(setq cider-repl-use-pretty-printing t)
;; nicer font lock in REPL
(setq cider-repl-use-clojure-font-lock t)
;; result prefix for the REPL
(setq cider-repl-result-prefix ";; => ")
;; never ending REPL history
(setq cider-repl-wrap-history t)
;; looong history
(setq cider-repl-history-size 3000)
;; eldoc for clojure
(add-hook 'cider-mode-hook #'eldoc-mode)
;; error buffer not popping up
(setq cider-show-error-buffer nil)
;; go right to the REPL buffer when it's finished connecting
(setq cider-repl-pop-to-buffer-on-connect nil)
;; key bindings
;; these help me out with the way I usually develop web apps
(defun cider-refresh ()
(interactive)
(cider-interactive-eval (format "(user/reset)")))))
This was suggested by @dpsutton on the slack channel and seems to be really interesting. It creates a little pop-up window by you cursor to show you the meaning of a thing
(defun cider--tooltip-show ()
(interactive)
(if-let ((info (cider-var-info (thing-at-point 'symbol))))
(nrepl-dbind-response info (doc arglists-str name ns)
(pos-tip-show (format "%s : %s\n%s\n%s" ns name arglists-str doc)
nil
nil
nil
-1))
(message "info not found")))
(use-package janet-mode)
(use-feature js-base-mode
:custom
(js-indent-level 2)
(lsp-eslint-enable 't))
(use-feature js-ts-mode
:mode ("\\.js\\'" "\\.cjs\\'" "\\.mjs\\'"))
(use-feature typescript-ts-base-mode
:custom
(typescript-indent-level 2)
(lsp-eslint-enable 't)
:config
(flycheck-add-mode 'javascript-eslint 'typescript-ts-base-mode))
(use-feature typescript-ts-mode
:mode ("\\.ts\\'" "\\.mts\\'" "\\.ts.snap\\'"))
(use-feature tsx-ts-mode
:mode "\\.tsx\\'")
Autoformatting
(use-package prettier-js
:hook ((typescript-ts-base-mode . prettier-js-mode)
(js-base-mode . prettier-js-mode)
(json-ts-mode . prettier-js-mode)
(less-css-mode . prettier-js-mode)
(web-mode . prettier-js-mode)))
(use-package deno-fmt)
(use-package eslint-disable-rule)
(use-package web-mode
:mode (("\\.html?\\'" . web-mode))
:config
(setq web-mode-markup-indent-offset 2
web-mode-css-indent-offset 2
web-mode-code-indent-offset 2
web-mode-block-padding 2
web-mode-comment-style 2
web-mode-enable-css-colorization t
web-mode-enable-auto-pairing t
web-mode-enable-comment-keywords t
web-mode-enable-current-element-highlight t)
(flycheck-add-mode 'javascript-eslint 'web-mode))
(use-package tagedit
:defer t)
(use-feature sgml-mode
:after tagedit
:config
(require 'tagedit)
(tagedit-add-paredit-like-keybindings)
(add-hook 'html-mode-hook (lambda () (tagedit-mode 1))))
(use-feature ruby-ts-mode
:mode "\\.rb\\'"
:mode "Rakefile\\'"
:mode "Gemfile\\'"
:mode "Vagrantfile\\'"
:interpreter "ruby"
:bind (:map ruby-ts-mode-map
("C-c r b" . 'treesit-beginning-of-defun)
("C-c r e" . 'treesit-end-of-defun))
:hook (ruby-base-mode . subword-mode)
:custom (ruby-indent-level 2)
(ruby-indent-tabs-mode nil))
(use-package rbenv
:config
(setq rbenv-installation-dir "/usr/local/bin/rbenv"))
(use-package inf-ruby
:defer t
:bind
(:map inf-ruby-minor-mode-map
(("C-c C-z" . run-ruby)
("C-c C-b" . ruby-send-buffer)))
:init
(add-hook 'inf-ruby-mode-hook (lambda () (corfu-mode -1)))
:config
(progn
(when (executable-find "pry")
(add-to-list 'inf-ruby-implementations '("pry" . "pry"))
(setq inf-ruby-default-implementation "pry"))))
(use-package rubocopfmt
:hook ((ruby-base-mode . rubocopfmt-mode)
(ruby-ts-mode . rubocopfmt-mode))
:custom (rubocopfmt-on-save-use-lsp-format-buffer 't))
(use-package rspec-mode
:hook (ruby-base-mode . rspec-enable-appropriate-mode)
:config
(rspec-install-snippets))
(use-package cargo)
We need to remove rust-mode from auto-mode-alist because either Cargo or Rust-playground packages are causing rust-mode to shadow rustic-mode.
(use-package rustic
:mode (("\\.rs\\'" . rustic-mode))
:config
(setq rustic-lsp-setup-p '())
(setq rustic-indent-offset 2)
(electric-pair-mode 1))
(use-package evcxr-mode
:defer t
:ensure nil
:config (add-hook 'rustic-mode-hook 'evcxr-minor-mode)
:quelpa ((evcxr
:fetcher git
:url "https://github.com/SerialDev/evcxr-mode.git"
:upgrade t)))
(use-package nix-mode)
(use-package nixos-options)
(use-package nixpkgs-fmt
:hook (nix-mode . nixpkgs-fmt-on-save-mode))
(use-feature go-ts-mode
:mode "\\.go\\'"
:custom
(go-ts-mode-indent-offset 2)
:config
(add-hook 'before-save-hook 'gofmt-before-save))
(use-package lua-mode)
(use-feature json-ts-mode
:mode "\\.json\\'"
:config
(setq js-indent-level 2))
(use-package dockerfile-mode
:mode "\\Dockerfile\\'"
:config
(setq-local tab-width 2))
(use-package docker)
(use-package markdown-mode
:commands (markdown-mode gfm-mode)
:mode (("README\\.md\\'" . gfm-mode)
("\\.md\\'" . markdown-mode)
("\\.markdown\\'" . markdown-mode))
:init (setq markdown-command "multimarkdown"))
Live preview of MarkDown
(use-package flymd
:commands (flymd-flyit))
(use-package powershell
:mode "\\.ps\\'")
(use-package nushell-ts-mode
:mode "\\.nu\\'")
(use-package terraform-mode
:mode "\\.tf\\'" )
(use-package yaml-mode
:defer t)
(use-package ssh-config-mode
:defer t)
;; (use-package sqlint)
Much like Mu4e we treat this as a feature because otherwise there is too much ceremony in setting up an Auctex. Instead, we rely on Nix to install this on Darwin and Linux. On windows though? shrug 🤷.
(use-feature auctex
:mode (("\\.tex\\'" . TeX-latex-mode)
("\\.tex\\.erb\\'" . TeX-latex-mode)
("\\.etx\\'" . TeX-latex-mode))
:hook
((LaTeX-mode . turn-on-auto-fill)
(LaTeX-mode . TeX-source-correlate-mode))
:config
(setq-default TeX-master nil)
(add-hook 'TeX-after-compilation-finished-functions #'TeX-revert-document-buffer)
:custom
(LaTeX-item-indent 0)
(TeX-auto-local ".auctex-auto")
(TeX-style-local ".auctex-style")
(TeX-auto-save t)
(TeX-parse-self t)
(TeX-save-query nil)
;; use pdflatex
(TeX-PDF-mode t)
(TeX-source-correlate-start-server nil)
(TeX-source-correlate-method 'synctex)
;; use evince for dvi and pdf viewer
;; evince-dvi backend should be installed
(TeX-view-program-selection
'((output-dvi "DVI Viewer")
(output-pdf "PDF Tools")
(output-html "Firefox")))
(TeX-view-program-list
'(("DVI Viewer" "evince %o")
("PDF Tools" TeX-pdf-tools-sync-view))))
Then we want org support for math latex
(use-package cdlatex)
(use-package pdf-tools
:hook (pdf-view-mode . (lambda () (display-line-numbers-mode -1)))
:init
(pdf-loader-install))
(use-feature text-mode
:custom
(text-mode-ispell-word-completion nil))
(use-feature rst
:mode (("\\.txt$" . rst-mode)
("\\.rst$" . rst-mode)
("\\.rest$" . rst-mode)))
(use-package kotlin-ts-mode)
(use-package android-env)
(use-feature tramp
:defer t
:hook (tramp-mode . (lambda () (projectile-mode 0)))
:config (setq debug-ignored-errors (cons 'remote-file-error debug-ignored-errors))
:custom
(tramp-terminal-type "tramp")
(tramp-use-ssh-controlmaster-options nil))
(use-package alert
:commands (alert alert-define-style)
:config
(defun alert-burnt-toast-notify (info)
(let ((args
(list
"-c" "New-BurntToastNotification"
"-Text" (if-let ((title (plist-get info :title)))
(format "'%s', '%s'" title (plist-get info :message))
(format "'%s'" (plist-get info :message)))
)))
(apply #'start-process (append '("burnt-toast" nil "powershell.exe") args))))
(alert-define-style 'burnt-toast :title "Notify Windows 10 using the PowerShell library BurntToast"
:notifier
#'alert-burnt-toast-notify)
(setq alert-default-style 'burnt-toast))
(use-package cognitive-complexity
:ensure (:type git :host github :repo "emacs-vs/cognitive-complexity"))
(use-package yequake
:config
(add-to-list 'yequake-frames '("consult-omni-demo"
(buffer-fns . #'consult-omni-apps)
(width . 0.8)
(height . 0.1)
(top . 0.5)
(frame-parameters . ((name . "yequake-demo")
(minibuffer . only)
(autoraise . t)
(window-system . ns)))))) ;;change accordingly
(use-package graphviz-dot-mode)
(use-package 1password
:ensure (1password :type git :host github :repo "justinbarclay/1password.el")
:demand t
:commands (1password-search-password 1password-search-id 1password-enable-auth-source)
:hook (elpaca-after-init-hook . (lambda () (1password-enable-auth-source)))
:custom
(1password-results-formatter '1password-colour-formatter)
(1password-executable (if (executable-find "op.exe")
"op.exe"
"op")))
A better help buffer experience
(use-package helpful
:defer t
:bind (("C-h f" . helpful-callable)
("C-h v" . helpful-variable)
("C-h k" . helpful-key)))
(use-feature which-key
:defer 't
:init (which-key-mode))
(use-feature woman
:config
(progn (setq woman-manpath
(split-string (shell-command-to-string "man --path") ":" t "\n"))
(autoload 'woman "woman"
"Decode and browse a UN*X man page." t)
(autoload 'woman-find-file "woman"
"Find, decode and browse a specific UN*X man-page file." t)))
This function displays how long Emacs took to start. It’s a rough way of knowing when/if I need to optimize my init file.
(add-hook 'elpaca-after-init-hook
(lambda ()
(message "Emacs loaded in %s with %d garbage collections."
(format "%.2f seconds"
(float-time
(time-subtract (current-time) before-init-time)))
gcs-done)))
(use-package esup
:commands (esup))
(use-feature profiler
:bind
(("s-l" . profiler-start)
("s-r" . profiler-report)))
(use-package restclient)
For when you want to link to the current file from github or gitlab
(use-package git-link)
(use-package git-sync-mode
:commands (git-sync-mode git-sync-global-mode)
:ensure (:type git :host github :repo "justinbarclay/git-sync-mode"))
(use-package leetcode-emacs
:commands (leetcode-show)
:ensure (leetcode :type git
:host github
:repo "ginqi7/leetcode-emacs"
:files ("leetcode.el"))
:config
(setq leetcode-language "rust"))
(defmacro comment (docstring &rest body)
"Ignores body and yields nil"
nil)
Increases the fonts size across all buffers
(defun font-name-replace-size (font-name new-size)
(let ((parts (split-string font-name "-")))
(setcar (nthcdr 7 parts) (format "%d" new-size))
(mapconcat 'identity parts "-")))
(defun increment-default-font-height (delta)
"Adjust the default font height by DELTA on every frame.
The pixel size of the frame is kept (approximately) the same.
DELTA should be a multiple of 10, in the units used by the
:height face attribute."
(let* ((new-height (+ (face-attribute 'default :height) delta))
(new-point-height (/ new-height 10)))
(dolist (f (frame-list))
(with-selected-frame f
;; Latest 'set-frame-font supports a "frames" arg, but
;; we cater to Emacs 23 by looping instead.
(set-frame-font (font-name-replace-size (face-font 'default)
new-point-height)
t)))
(set-face-attribute 'default nil :height new-height)
(message "default font size is now %d" new-point-height)))
(defun increase-default-font-height ()
(interactive)
(increment-default-font-height 10))
(defun decrease-default-font-height ()
(interactive)
(increment-default-font-height -10))
(global-set-key (kbd "C-M-=") 'increase-default-font-height)
(global-set-key (kbd "C-M--") 'decrease-default-font-height)
(defun open-config-file ()
(interactive)
(find-file "~/.emacs.d/README.org"))
(global-set-key (kbd "C-c i") 'open-config-file)
For saving root protected files
(defun sudo-save ()
(interactive)
(if (not buffer-file-name)
(write-file (concat "/sudo:root@localhost:" (ido-read-file-name "File:")))
(write-file (concat "/sudo:root@localhost:" buffer-file-name))))
;; source: http://steve.yegge.googlepages.com/my-dot-emacs-file
(defun rename-file-and-buffer (new-name)
"Renames both current buffer and file it's visiting to NEW-NAME."
(interactive "sNew name: ")
(let ((name (buffer-name))
(filename (buffer-file-name)))
(if (not filename)
(message "Buffer '%s' is not visiting a file!" name)
(if (get-buffer new-name)
(message "A buffer named '%s' already exists!" new-name)
(progn
(rename-file filename new-name 1)
(rename-buffer new-name)
(set-visited-file-name new-name)
(set-buffer-modified-p nil))))))
(defun jb/mean (a)
(/ (apply '+ a)
(length a)))
(defun jb/square (a)
(* a a))
(defun jb/stdev (a)
(sqrt
(/
(apply '+ (mapcar 'square (mapcar (lambda (subtract)
(- subtract (mean a)))
a)))
(- (length a) 1 ))))
(defun take-screenshot ()
(interactive)
(let ((frame-height (read-number "Enter frame height: " 40))
(frame-width (read-number "Enter frame width: " 55))
(filename (read-file-name "Where would you like to save the svg? ")))
(set-frame-width (selected-frame) frame-width)
(set-frame-height (selected-frame) frame-height)
(with-temp-file filename
(insert (x-export-frames nil 'svg)))
(kill-new filename)
(message filename)))
(setq file-name-handler-alist doom--file-name-handler-alist)
Pomodoro timer
(require 'alert)
(require 'seq)
(require 'zone)
(defvar pomodoro--current-buffer nil "Buffer to return to after the break")
(defvar pomodoro--round 0 "The current iteration pomodoro")
;; Todo make this a defcustom
(defvar pomodoro-activity-list '("go for a walk" "stretch your back" "core exercises") "A list of activity you wish to be reminded to do during your breaks")
(defvar pomodoro--completed-activities '() "Activities completed during the current session")
(defvar pomodoro--timer nil "Current timer for pomodoro. It could be the break timer or the pomodoro timer itself.")
(defvar pomodoro--last-buffer nil "Return to this buffer after the pomodoro break is over.")
;; This is easy to do inline, but I like providing names to ideas like this rather than
;; relying on implicit knowledge
(defun pomodoro--minutes (minutes)
"Converts the given minutes to seconds"
(* minutes 60))
(defun pomodoro--suggest-activity (activity-list completed-activities)
(let ((suggested-list (seq-filter (lambda (item)
(not
(member item (or completed-activities
'()))))
activity-list)))
(when suggested-list
(nth
(random (length suggested-list))
suggested-list))))
(defun pomodoro-start (&optional message)
(interactive)
(when (yes-or-no-p (or message
"Would you like to start a pomodoro session?"))
(message "Round %s" pomodoro--round)
(setq pomodoro--timer
(run-at-time (pomodoro--minutes 25)
nil
(lambda ()
(let* ((suggested-activity-maybe (pomodoro--suggest-activity pomodoro-activity-list pomodoro--completed-activities))
;; Reset the completed activities list if we've run out of activities to suggest
(suggested-activity (if suggested-activity-maybe
suggested-activity-maybe
(pomodoro--suggest-activity pomodoro-activity-list
(setq pomodoro--completed-activities nil)))))
(if pomodoro--completed-activities
(push suggested-activity pomodoro--completed-activities)
(setq pomodoro--completed-activities (list suggested-activity)))
(message "Time to take a break!")
(alert (format "Get up and %s" 'suggested-activity) :title "Pomodoro")
(pomodoro-break (setq pomodoro--round (1+ pomodoro--round)))))))))
(defun pomodoro-break (round)
(setq pomodoro--last-buffer (current-buffer))
(setq pomodoro--timer
(run-at-time (pomodoro--minutes 5)
nil
(lambda ()
(pomodoro-start "Would you like to continue your pomodoro session?")
(message "Welcome back!"))))
(zone))
(defun pomodoro-cancel-timer ()
(interactive)
(when pomodoro--timer
(cancel-timer pomodoro--timer)
(message "Timer canceled")))
Count line repititions
(defun count-repititions ()
(interactive)
;;
(let ((tracker (make-hash-table :test 'equal))
(buffer (current-buffer)))
(with-temp-buffer
(insert-buffer buffer)
(goto-char (point-min))
(replace-regexp "^[0-9]+:[0-9][0-9]" "")
(delete-blank-lines)
(sort-lines nil (point-min) (point-max))
(goto-char (point-min))
(while (not (eobp))
(delete-blank-lines)
(let ((current-line (string-trim
(buffer-substring-no-properties ;; current-line
(line-beginning-position)
(line-end-position)))))
(when (string-match "^All measurement" current-line)
(puthash current-line
(+ 1 (gethash current-line tracker 0))
tracker)))
(forward-line 1)))
(message "%s" (length (hash-table-keys tracker)))
(with-current-buffer (get-buffer-create "*repititions*")
(erase-buffer)
(maphash (lambda (k v)
(insert (format "%s - %s\n" v k)))
tracker)
(goto-char (point-min))
(sort-numeric-fields 1 (point-min) (point-max)))))
Kebab Case String
(defun kebab-case (string)
"Convert STRING to kebab-case. For example, HelloWorld! becomes hello-world! Note that this downcases the first character but does not add a - before it"
(let ((case-fold-search nil))
(-> (replace-regexp-in-string
"\\([A-Z]\\)"
"-\\1"
string)
(downcase)
(string-trim-left "-"))))