Merge branch 'develop' into add_shellcheck

This commit is contained in:
chrunchyjesus 2019-04-23 19:44:02 +02:00
commit e5e05f9d51
No known key found for this signature in database
GPG key ID: 0C364160C9308A88
584 changed files with 32999 additions and 15507 deletions

View file

@ -1,39 +0,0 @@
Here are some things you should try before filing a bug report:
+ Run `make install` to ensure all plugins are installed.
+ `void-function` or `void-variable` errors could signal an out-of-date autoloads file. Run `make autoloads` or `M-x doom//reload-autoloads` to update it.
+ Scan for common OS/environment issues with `make doctor`.
+ Never debug byte-compiled code. It will interfere in subtle ways. Clean up \*.elc files with `make clean` or `M-x doom//clean-byte-compiled-files`.
+ Check [the FAQ](https://github.com/hlissner/doom-emacs/wiki/FAQ#troubleshooting) to see if your issue is mentioned.
+ Check the relevant module's README.org, if one exists. There may be extra steps to getting certain features to work.
If none of those help, remove this section and fill out the four sections in the template below.
---
### Observed behavior
Describe what happened. Any aids you can include (that you think could be relevant) are a tremendous help; like a screencast gif, video, or link to your customizations for Doom (e.g. a repo or a pastebin).
### Expected behavior
Describe what you _expected_ to happen.
### Steps to reproduce
1. Select these example steps,
2. Delete them,
3. And replace them with precise steps to reproduce your issue.
### System information
<details>
<summary>Click to expand</summary>
```
Replace this line with the output of *one* of these commands:
+ `M-x doom/info` (from inside Emacs)
+ `DEBUG=1 make doctor` (command line)
```
</details>

13
.gitignore vendored
View file

@ -1,8 +1,12 @@
.local/
.cask/
var/
/init.el
modules/private/
modules/private
.yas-compiled-snippets.el
# folders created by Cask
.cask/
cask/
elpa/
# emacs tempfiles that shouldn't be there
.mc-lists.el
@ -22,6 +26,7 @@ network-security.data
*.dat
*.eld
# transient files
# transient/os temp files
*.cache-*
*.log
.directory

View file

@ -1,16 +1,21 @@
language: emacs-lisp
language: generic
sudo: false
branches:
only:
- master
- develop
before_install:
- git clone https://github.com/rejeep/evm.git /home/travis/.evm
- export PATH="/home/travis/.evm/bin:$PATH"
- evm config path /tmp
- evm install $EVM_EMACS --use
- cp init.test.el init.el
- INSECURE=1 YES=1 make install
- evm install $EVM_EMACS --use --skip
- mkdir -p ~/.config/doom
- cp init.test.el ~/.config/doom/init.el
- bin/doom -y -i install
env:
- EVM_EMACS=emacs-25.1-travis
- EVM_EMACS=emacs-25.2-travis
- EVM_EMACS=emacs-25.3-travis
- EVM_EMACS=emacs-26.1-travis
script:
- emacs --version
- make test
- bin/doom version
- bin/doom -d test
- bin/doom -y compile

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016-2017 Henrik Lissner.
Copyright (c) 2016-2019 Henrik Lissner.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

108
Makefile
View file

@ -1,89 +1,77 @@
# Ensure emacs always runs from this makefile's PWD
EMACS_FLAGS=--eval '(setq user-emacs-directory default-directory)' -l core/core.el
EMACS=emacs --quick --batch $(EMACS_FLAGS)
EMACSI=emacs -q $(EMACS_FLAGS)
DOOM = "bin/doom"
MODULES = $(patsubst modules/%/, %, $(sort $(dir $(wildcard modules/*/ modules/*/*/))))
MODULES=$(patsubst modules/%, %, $(shell find modules/ -maxdepth 2 -type d))
all: autoloads autoremove install
all:
@$(DOOM) refresh
## Shortcuts
a: autoloads
i: install
u: update
U: upgrade
r: autoremove
c: compile
cc: compile-core
ce: compile-elpa
cp: compile-plugins
re: recompile
d: doctor
quickstart:
@$(DOOM) quickstart
## Package management
install: init.el .local/autoloads.el
@$(EMACS) -f doom//packages-install
update: init.el .local/autoloads.el
@$(EMACS) -f doom//packages-update
autoremove: init.el .local/autoloads.el
@$(EMACS) -f doom//packages-autoremove
autoloads: init.el
@$(EMACS) -f doom//reload-autoloads
install:
@$(DOOM) install
update:
@$(DOOM) update
autoremove:
@$(DOOM) autoremove
autoloads:
@$(DOOM) autoloads
upgrade:
@$(DOOM) upgrade
## Byte compilation
# compile
# compile-core
compile:
@$(DOOM) compile
compile-core:
@$(DOOM) compile :core
compile-private:
@$(DOOM) compile :private
compile-plugins:
@$(DOOM) compile :plugins
recompile:
@$(DOOM) recompile
clean:
@$(DOOM) clean
# compile-module
# compile-module/submodule
compile: init.el clean
@$(EMACS) -f doom//byte-compile
compile-core: init.el clean
@$(EMACS) -f doom//byte-compile-core
compile-elpa: init.el
@$(EMACS) -f doom//byte-recompile-plugins
$(patsubst %, compile-%, $(MODULES)): init.el .local/autoloads.el
@$(EMACS) -f doom//byte-compile -- $(patsubst compile-%, %, $@)
recompile: init.el
@$(EMACS) -f doom//byte-compile -- -r
clean:
@$(EMACS) -f doom//clean-byte-compiled-files
$(patsubst %, compile-%, $(MODULES)): | .local/autoloads.el
@$(DOOM) $@ $(subst compile-, , $@)
## Unit tests
# test
# test-core
test:
@$(DOOM) test
test-core:
@$(DOOM) test :core
# test-module
# test-module/submodule
test: init.el .local/autoloads.el
@$(EMACS) -f doom//run-tests
test-core $(patsubst %, test-%, $(MODULES)): init.el .local/autoloads.el
@$(EMACS) -f doom//run-tests -- $(subst test-, , $@)
# run tests interactively
testi: init.el .local/autoloads.el
@$(EMACSI) -f doom//run-tests
$(patsubst %, test-%, $(MODULES)):
@$(DOOM) test $(subst test-, , $@)
## Utility tasks
# Runs Emacs from a different folder than ~/.emacs.d
# Runs Emacs from a different folder than ~/.emacs.d; only use this for testing!
run:
@$(EMACSI) -l init.el
@$(DOOM) run $(ARGS)
# Prints debug info about your current setup
info:
@$(DOOM) info
# Diagnoses potential OS/environment issues
doctor:
@bin/doom-doctor
## Internal tasks
init.el:
@$(error No init.el file; create one or copy init.example.el)
.local/autoloads.el:
@$(EMACS) -f doom-initialize-autoloads
@$(DOOM) doctor
.PHONY: all compile test testi clean

272
README.md
View file

@ -1,137 +1,197 @@
![Release tag](https://img.shields.io/github/tag/hlissner/doom-emacs.svg?label=release&style=flat-square)
[![Master Build Status](https://img.shields.io/travis/hlissner/doom-emacs/master.svg?label=master&style=flat-square)](https://travis-ci.org/hlissner/doom-emacs)
[![Develop Build Status](https://img.shields.io/travis/hlissner/doom-emacs/develop.svg?label=develop&style=flat-square)](https://travis-ci.org/hlissner/doom-emacs)
[![MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat-square)](./LICENSE)
<img src="https://raw.githubusercontent.com/hlissner/doom-emacs/screenshots/main.png" alt="Main screenshot" />
[![Main screenshot](/../screenshots/main.png)](/../../tree/screenshots)
- - -
<img src="https://img.shields.io/github/tag/hlissner/doom-emacs.svg?label=release"
alt="Current release"
align="left" />
<a href="https://travis-ci.org/hlissner/doom-emacs">
<img src="https://img.shields.io/travis/hlissner/doom-emacs/master.svg?label=master"
alt="build status (master)"
align="left" />
</a>
<a href="https://travis-ci.org/hlissner/doom-emacs">
<img src="https://img.shields.io/travis/hlissner/doom-emacs/master.svg?label=develop"
alt="build status (develop)"
align="left" />
</a>
<a href="https://discord.gg/bcZ6P3y">
<img src="https://img.shields.io/badge/Discord-blue.svg?logo=discord"
alt="Discord Server"
align="left" />
</a>
<p align="center">
<a href="/../../wiki">Wiki</a> |
<a href="/../../tree/screenshots">Screenshots</a> |
<a href="/../../wiki/FAQ#troubleshooting">Troubleshooting</a> |
<a href="/../../wiki/FAQ">FAQ</a> |
<a href="/../develop/CHANGELOG.org">Changelog</a>
<a href="/../../wiki">wiki</a>&nbsp; |&nbsp;
<a href="/../../tree/screenshots">screenshots</a>&nbsp; |&nbsp;
<a href="/../../faq.org">faq</a>&nbsp; |&nbsp;
<a href="/../../wiki/FAQ#troubleshooting">troubleshooting</a>
<!--a href="CHANGELOG.org">changelog</a-->
</p>
- - -
Quick start
-----------
```bash
git clone https://github.com/hlissner/doom-emacs ~/.emacs.d
~/.emacs.d/bin/doom quickstart
```
> Doom supports Emacs 25.3 and newer, but **Emacs 26.1 is recommended.** Doom
> works best on Linux & MacOS. Your mileage may vary on Windows.
Table of Contents
==================
- [What is Doom Emacs](#what-is-doom-emacs)
- [Doom's mantras](#dooms-mantras)
- [Feature highlights](#feature-highlights)
- [Getting Help](#getting-help)
- [Contributing](#contributing)
What is Doom Emacs
==================
<a href="http://ultravioletbat.deviantart.com/art/Yay-Evil-111710573">
<img src="/../screenshots/cacochan.png" align="right" />
<img src="https://github.com/hlissner/doom-emacs/raw/screenshots/cacochan.png" align="right" />
</a>
It is a story as old as time. A stubborn, shell-dwelling, and melodramatic
vimmer -- envious of the features of modern text editors -- spirals into despair
before finally succumbing to the [dark side][evil-mode]. This is his config.
before succumbing to the [dark side][url:evil-mode]. This is his config.
Doom strives to be fast, fabulous and hacker friendly. It is tailored for
neckbeards with blue belts or better in command-line-fu, Elisp and git.
Doom is a configuration for [GNU Emacs](https://www.gnu.org/software/emacs/). It
can be used as framework for your own configuration, or as a resource for fellow
Emacs enthusiasts who want to learn more about our favorite OS.
> Doom **only** supports Emacs >= 25.1, and is tested on Arch Linux 4.7+ and
> MacOS 10.11. YMMV on other platforms.
Doom's mantras
--------------
- **Gotta go fast.** Startup and runtime speed are high priorities; many
expensive, heavy-handed features and packages have been fine-tuned to that
end.
- **Hacker-friendly.** Doom caters to the command line denizen unafraid of
writing a little (or a lot of) code to tailor their editor. It also inherits
your shell configuration, warts 'n all, and expects frequent trips into the
terminal to manage Doom with its `bin/doom` utility.
- **Opinionated, but not stubborn.** Doom has _many_ opinions spread out across
its 120+ modules designed to iron out idiosynchrosies and provide a better and
more consistent baseline experience of Emacs and its plugins. However, they
mustn't ever compromise your ability to change, rewrite or disable any or all
of it, if you ask nicely.
- **Written to be read.** Doom's source ought to be self documenting and easy to
grok. Modules should be syntactically sweet and concise, and backend logic
should be explicit and abstraction-light. Where complexity arises, comments
and documentation shouldn't be far away.
- - -
Feature Highlights
------------------
- A declarative [package management system][doom:packages] with a command line
interface that combines package.el, [use-package] and [quelpa], allowing you
to install packages from anywhere.
- A [popup management system][doom:popups] with customizable rules to dictate
how temporary/disposable buffers are displayed.
- A vim-centric (and optional) experience with [evil-mode][url:evil-mode],
including ports of several popular vim plugins, <kbd>C-x</kbd> omnicompletion
and a slew of [custom ex commands][doom:commands].
- A Spacemacs-esque [keybinding scheme][doom:bindings], centered around leader
and localleader prefix keys (<kbd>SPC</kbd> and <kbd>SPC</kbd><kbd>m</kbd>, by
default).
- Indentation detection and optional integration with
[editorconfig][url:editorconfig]. Let someone else argue about tabs vs
___***spaces***___.
- Code completion for many languages, powered by
[company-mode][url:company-mode] (some may have external dependencies).
- Project-awareness powered by [projectile][url:projectile], with tools and an
API to navigate and manage projects, as well as project/framework-specific
minor modes and snippets libraries (and the ability to define your own).
- Project search (and replace) utilities, powered by
[the_silver_searcher][url:ag], [ripgrep][url:rg], git-grep and
[wgrep][url:wgrep], with integration for [ivy][url:ivy] (the default) and
[helm][url:helm].
- Isolated and persistent workspaces powered by [persp-mode][url:persp-mode].
Also substitutes as vim tabs.
- Inline/live code evaluation (using [quickrun][url:quickrun]), with REPL
support for a variety of languages.
- A jump-to-definition/references implementation for all languages that tries to
"just work," resorting to mode-specific functionality, before falling back on
[dump-jump][url:dumb-jump].
## Quick start
```bash
git clone https://github.com/hlissner/doom-emacs ~/.emacs.d
cd ~/.emacs.d
cp init.example.el init.el # maybe edit init.el
make install
```
Troubleshooting
===============
Don't forget to run `make` every time you modify init.el!
Encountered strange behavior or an error? Here are some things to try before you
shoot off that bug report:
Visit the wiki for [a more detailed guide on installing, customizing and
grokking Doom][wiki].
- Run `bin/doom refresh`. This ensures Doom is properly set up and its autoloads
files are up-to-date.
- If you have byte-compiled your config (with `bin/doom compile`), see if
`bin/doom clean` makes your issue go away. Never debug issues with a
byte-compiled config, it will only make your job harder.
- Run `bin/doom doctor` to detect common issues in your development environment.
- Search Doom's issue tracker for mention of any error messages you've received.
- [Visit our FAQ][docs:faq] to see if your issue is listed.
## Feature highlights
If all else fails, [file that bug report][github:new-issue]! Please include the
behavior you've observed, the behavior you expected, and any error message in
the \*Messages\* buffer (can be opened with <kbd>SPC h m</kbd> or `M-x
view-echo-area-messages`). It'd be a great help if you included a backtrace with
them as well.
+ A fast, organized and opinionated Emacs configuration with a command line
interface.
+ A custom, declarative [package management system][doom-packages] that combines
package.el, [use-package] and [quelpa], allowing you to manage packages from
the command line and install packages from sources other than ELPA.
+ A [popup management system][doom-popups] (powered by [shackle]) that minimizes
the presence and footprint of temporary and/or disposable buffers.
+ A vim-like experience with [evil-mode], including ports for several vim
plugins, <kbd>C-x</kbd> omnicompletion and a slew of [custom ex
commands][doom-my-commands].
+ Integration with [editorconfig]. Let someone else argue about tabs and spaces.
(spaces, duh).
+ Code completion for many languages, powered by [company-mode] (some languages
may have external dependencies).
+ Project-awareness powered by [projectile], with tools and an API to navigate
and manage projects and their files.
+ Fast project search (and replace) utilities, powered by [the_silver_searcher],
[ripgrep] and [wgrep], with integration for [ivy] (the default), [helm] and
ido.
+ Isolated and persistent workspaces powered by [persp-mode]. Also substitutes
for vim tabs.
+ Inline/live code evaluation (using [quickrun]), including REPLs for a variety
of languages.
We've also got [a Discord server][url:discord]. Hop on! We can help!
## Troubleshooting
Found a problem? Here are some things to try:
+ Run `make install` to ensure all plugins are installed.
+ `void-function` or `void-variable` errors could signal an out-of-date
autoloads file. Run `make autoloads` or `M-x doom//reload-autoloads` to update
it.
+ Scan for common OS/environment issues with `make doctor`.
+ **Never debug byte-compiled code. It will interfere in subtle ways.** Clean up
\*.elc files with `make clean` or `M-x doom//clean-byte-compiled-files`.
+ Check [the FAQ][wiki-troubleshooting] to see if your issue is mentioned.
+ Check the relevant module's README.org, if one exists. There may be extra
steps to getting certain features to work.
If all else has failed, [file a bug report][doom-new-issue].
## Contribute
Contributing
============
Doom (and my Emacs work in general) is a labor of love and incurable madness,
done on my spare time. It wasn't intended for public use, but I enjoy making
Doom a resource for others.
done on my spare time. If you'd like to support my work, I welcome
contributions:
If you'd like to support my efforts, I welcome contributions of any kind:
+ I love pull requests and bug reports. Elisp pointers are especially welcome.
Seriously, don't hesitate to [tell me my Elisp-fu sucks][doom-new-issue]!
+ Talk to me about Emacs workflow, ideas or tooling. Or talk to me about
gamedev, or pixel art, or anime, or programming, or the weather, or band camp.
Whatever. I don't mind. Holler at henrik@lissner.net.
- I love pull requests and bug reports. Check out the [Contributing
Guidelines][docs:contributing] (WIP) to find out how you can help out.
- I welcome Elisp pointers! Don't hesitate to [tell me my Elisp-fu
sucks][github:new-issue] (but please tell me why).
- Hop on [our Discord server][url:discord] and say hi! Help others out, hang out
or talk to me about Emacs, or gamedev, or programming, machine learning,
physics, pixel art, anime, gaming -- anything you like. Nourish this lonely
soul!
- If you'd like to support my work financially, consider buying me a drink
through [liberapay][url:liberapay] or [paypal][url:paypal]. Donations are a
great help. My work here contends with full-time studies, my ventures in indie
gamedev, and my freelance work.
[wiki]: /../../wiki
[wiki-conventions]: /../../wiki/Conventions
[wiki-modules]: /../../wiki/Modules
[wiki-customization]: /../../wiki/Customization
[wiki-troubleshooting]: /../../wiki/FAQ#troubleshooting
<!-- [docs:wiki]: docs/index.org -->
<!-- [docs:wiki-quickstart]: docs/getting-started.org -->
<!-- [docs:wiki-modules]: docs/modules.org -->
<!-- [docs:wiki-customization]: docs/customize.org -->
<!-- [docs:contributing]: docs/contribute.org -->
<!-- [docs:faq]: docs/faq.org -->
[docs:faq]: /../../wiki/FAQ
[doom-my-bindings]: modules/private/hlissner/+bindings.el
[doom-my-commands]: modules/private/hlissner/+commands.el
[doom-new-issue]: https://github.com/hlissner/doom-emacs/issues/new
[doom-packages]: core/autoload/packages.el
[doom-popups]: core/core-popups.el
[doom-theme]: https://github.com/hlissner/emacs-doom-theme
[github:new-issue]: https://github.com/hlissner/doom-emacs/issues/new
[doom:bindings]: modules/config/default/+bindings.el
[doom:commands]: modules/config/default/+evil-commands.el
[doom:packages]: core/autoload/packages.el
[doom:popups]: modules/feature/popup/README.org
[company-mode]: https://github.com/company-mode/company-mode
[editorconfig]: http://editorconfig.org/
[evil-mode]: https://github.com/emacs-evil/evil
[git-gutter-fringe]: https://github.com/syohex/emacs-git-gutter-fringe
[helm]: https://github.com/emacs-helm/helm
[ivy]: https://github.com/abo-abo/swiper
[persp-mode]: https://github.com/Bad-ptr/persp-mode.el
[projectile]: https://github.com/bbatsov/projectile
[quelpa]: https://github.com/quelpa/quelpa
[quickrun]: https://github.com/syohex/emacs-quickrun
[ripgrep]: https://github.com/BurntSushi/ripgrep
[shackle]: https://github.com/wasamasa/shackle
[the_silver_searcher]: https://github.com/ggreer/the_silver_searcher
[use-package]: https://github.com/jwiegley/use-package
[vim]: https://github.com/hlissner/.vim
[wgrep]: https://github.com/mhayashi1120/Emacs-wgrep
[url:discord]: https://discord.gg/bcZ6P3y
[url:liberapay]: https://liberapay.com/hlissner/donate
[url:paypal]: https://paypal.me/henriklissner/10
[url:company-mode]: https://github.com/company-mode/company-mode
[url:doom-themes]: https://github.com/hlissner/emacs-doom-themes
[url:editorconfig]: http://editorconfig.org/
[url:evil-mode]: https://github.com/emacs-evil/evil
[url:helm]: https://github.com/emacs-helm/helm
[url:ivy]: https://github.com/abo-abo/swiper
[url:persp-mode]: https://github.com/Bad-ptr/persp-mode.el
[url:projectile]: https://github.com/bbatsov/projectile
[url:quelpa]: https://github.com/quelpa/quelpa
[url:quickrun]: https://github.com/syohex/emacs-quickrun
[url:ripgrep]: https://github.com/BurntSushi/ripgrep
[url:the_silver_searcher]: https://github.com/ggreer/the_silver_searcher
[url:use-package]: https://github.com/jwiegley/use-package
[url:wgrep]: https://github.com/mhayashi1120/Emacs-wgrep

107
bin/doom Executable file
View file

@ -0,0 +1,107 @@
#!/usr/bin/env bash
":"; [[ $EMACS = *"term"* ]] && EMACS=emacs || EMACS=${EMACS:-emacs} # -*-emacs-lisp-*-
":"; command -v $EMACS >/dev/null || { >&2 echo "Emacs isn't installed"; exit 1; }
":"; VERSION=$($EMACS --version | head -n1)
":"; [[ $VERSION == *\ 2[0-2].[0-1].[0-9] ]] && { echo "You're running $VERSION"; echo "That version is too old to run Doom. Check your PATH"; echo; exit 2; }
":"; DOOMBASE=$(dirname "${BASH_SOURCE:-${(%):-%x}}")/..
":"; [[ $1 == doc || $1 == doctor ]] && { cd "$DOOMBASE"; exec $EMACS --script bin/doom-doctor; exit 0; }
":"; [[ $1 == run ]] && { cd "$DOOMBASE"; shift; exec $EMACS -q --no-splash -l bin/doom "$@"; exit 0; }
":"; exec $EMACS --script "$0" -- $@
":"; exit 0
(defun usage ()
(with-temp-buffer
(insert (format! "%s %s [COMMAND] [ARGS...]\n"
(bold "Usage:")
(file-name-nondirectory load-file-name))
"\n"
"A command line interface for managing Doom Emacs; including\n"
"package management, diagnostics, unit tests, and byte-compilation.\n"
"\n"
"This tool also makes it trivial to launch Emacs out of a different\n"
"folder or with a different private module.\n"
"\n"
(format! (bold "Example:\n"))
" doom install\n"
" doom help update\n"
" doom compile :core lang/php lang/python\n"
" doom run\n"
" doom run -nw file.txt file2.el\n"
" doom run -p ~/.other.doom.d -e ~/.other.emacs.d -nw file.txt\n"
"\n"
(format! (bold "Options:\n"))
" -h --help\t\tSame as help command\n"
" -d --debug\t\tTurns on doom-debug-mode (and debug-on-error)\n"
" -e --emacsd DIR\tUse the emacs config at DIR (e.g. ~/.emacs.d)\n"
" -i --insecure\t\tDisable TLS/SSL validation (not recommended)\n"
" -p --private DIR\tUse the private module at DIR (e.g. ~/.doom.d)\n"
" -y --yes\t\tAuto-accept all confirmation prompts\n\n")
(princ (buffer-string)))
(doom--dispatch-help))
;;
(let ((args (cdr (cdr (cdr command-line-args))))
(emacs-dir (or (getenv "EMACSDIR")
(expand-file-name "../" (file-name-directory (file-truename load-file-name))))))
;; Parse options
(while (ignore-errors (string-prefix-p "-" (car args)))
(pcase (pop args)
((or "-h" "--help")
(push "help" args))
((or "-d" "--debug")
(setenv "DEBUG" "1")
(message "Debug mode on"))
((or "-i" "--insecure")
(setenv "INSECURE" "1")
(message "Insecure mode on"))
((or "-p" "--private")
(setq doom-private-dir (expand-file-name (concat (pop args) "/")))
(setenv "DOOMDIR" doom-private-dir)
(message "DOOMDIR changed to %s" doom-private-dir)
(or (file-directory-p doom-private-dir)
(message "Warning: %s does not exist"
(abbreviate-file-name doom-private-dir))))
((or "-e" "--emacsd")
(setq emacs-dir (expand-file-name (concat (pop args) "/")))
(message "Emacs directory changed to %s" emacs-dir))
((or "-y" "--yes")
(setenv "YES" "1")
(message "Auto-yes mode on"))))
(or (file-directory-p emacs-dir)
(error "%s does not exist" emacs-dir))
;; Bootstrap Doom
(load (expand-file-name "init" emacs-dir)
nil 'nomessage)
(cond ((not noninteractive)
(doom|run-all-startup-hooks))
((and (not (cdr args))
(member (car args) '("help" "h")))
(usage))
((not args)
(message "No command detected, aborting!\n\nRun %s help for documentation."
(file-name-nondirectory load-file-name)))
((let ((default-directory emacs-dir))
(setq argv nil
noninteractive 'doom)
(condition-case e
(doom-dispatch (car args) (cdr args))
(user-error
(signal (car e) (cdr e)))
((debug error)
(message "--------------------------------------------------\n")
(message "There was an unexpected error:")
(message " %s (%s)" (get (car e) 'error-message) (car e))
(dolist (item (cdr e))
(message " %s" item))
(unless debug-on-error
(message
(concat "\nRun the command again with the -d (or --debug) option to enable debug\n"
"mode and, hopefully, generate a stack trace. If you decide to file a bug\n"
"report, please include it!\n\n"
"Emacs outputs to standard error, so you'll need to redirect stderr to\n"
"stdout to pipe this to a file or clipboard!\n\n"
" e.g. doom -d install 2>&1 | clipboard-program"))
(signal 'doom-error e))))))))

View file

@ -1,41 +1,56 @@
#!/usr/bin/env bash
":"; command -v emacs >/dev/null || { >&2 echo "Emacs isn't installed"; exit 1; } # -*-emacs-lisp-*-
":"; [[ $(emacs --version | head -n1) == *\ 2[0-2].[0-1].[0-9] ]] && { echo "You're running $(emacs --version | head -n1)"; echo "That version is too old to run the doctor. Check your PATH"; echo; exit 2; } || exec emacs --quick --script "$0"
":"; VERSION=$(emacs --version | head -n1)
":"; [[ $VERSION == *\ 2[0-2].[0-1].[0-9] ]] && { echo "You're running $VERSION"; echo "That version is too old to run the doctor. Check your PATH"; echo; exit 2; }
":"; exec emacs --quick --script "$0"; exit 0
;; Uses a couple simple heuristics to locate issues with your environment that
;; could interfere with running or setting up DOOM Emacs.
;; The Doom doctor is essentially one big, self-contained elisp shell script
;; that uses a series of simple heuristics to diagnose common issues on your
;; system. Issues that could intefere with Doom Emacs.
;;
;; Doom modules may optionally have a doctor.el file to run their own heuristics
;; in. Doctor scripts may run in versions of Emacs as old as Emacs 23, so make
;; no assumptions about the standard library limited to very basic standard
;; library (e.g. avoid cl/cl-lib, subr-x, map, seq, etc).
;; In case it isn't defined (in really old versions of Emacs, like the one that
;; ships with MacOS).
(defvar user-emacs-directory (expand-file-name "~/.emacs.d/"))
(defvar doom-debug-mode (getenv "DEBUG"))
(unless (equal (expand-file-name user-emacs-directory)
(expand-file-name "~/.emacs.d/"))
(error "Couldn't find ~/.emacs.d"))
;; Ensure Doom doctor always runs out of the current Emacs directory (optionally
;; specified by the EMACSDIR envvar)
(setq user-emacs-directory
(or (getenv "EMACSDIR")
(expand-file-name "../" (file-name-directory load-file-name))))
(unless (file-directory-p user-emacs-directory)
(error "Couldn't find a Doom config!"))
(unless noninteractive
(error "This script must not be run from an interactive session."))
(when (getenv "DEBUG")
(setq debug-on-error t))
(require 'pp)
(defsubst string-trim-right (string &optional regexp)
(if (string-match (concat "\\(?:" (or regexp "[ \t\n\r]+") "\\)\\'") string)
(replace-match "" t t string)
string))
;;
;;; Helpers
(defvar doom-init-p nil)
(defvar doom-warnings 0)
(defvar doom-errors 0)
(defmacro check! (cond &rest body)
(defmacro when! (cond &rest body)
(declare (indent defun))
`(let ((it ,cond))
(when it
,@body
(setq doom-errors (1+ doom-errors)))))
(when it ,@body)))
(defun indented (spc msg)
(declare (indent defun))
(with-temp-buffer
(insert msg)
(indent-rigidly (point-min) (point-max) spc)
(let ((fill-column 80))
(fill-region (point-min) (point-max))
(indent-rigidly (point-min) (point-max) spc))
(when (> spc 2)
(goto-char (point-min))
(beginning-of-line-text)
(delete-char -2)
(insert "> "))
(buffer-string)))
(defun autofill (&rest msgs)
@ -48,34 +63,34 @@
(fill-region (point-min) (point-max))
(buffer-string))))
(defun columns (cols length strings)
(declare (indent defun))
(with-temp-buffer
(let ((sub-format (format "%%-%ds " (1- length)))
col-format)
(dotimes (i (1- cols))
(setq col-format (concat col-format sub-format)))
(setq col-format (concat col-format "%s"))
(while strings
(insert (apply #'format col-format
(let (args)
(dotimes (i cols (nreverse args))
(push (if strings (pop strings) "") args))))
"\n")))
(buffer-string)))
(defun sh (cmd)
(string-trim-right (shell-command-to-string cmd)))
(defun color (code msg &rest args)
(format "\e[%dm%s\e[%dm" code (apply #'format msg args) 0))
(defalias 'msg! #'message)
(defmacro error! (&rest args) `(msg! (color 1 (color 31 ,@args))))
(defmacro warn! (&rest args) `(msg! (color 1 (color 33 ,@args))))
(defmacro success! (&rest args) `(msg! (color 1 (color 32 ,@args))))
(defmacro section! (&rest args) `(msg! (color 34 ,@args)))
(defmacro explain! (&rest args) `(msg! (indented 2 (autofill ,@args))))
(defvar indent 0)
(defvar prefix "")
(defmacro msg! (msg &rest args)
`(message
(indented indent
(format (concat prefix ,msg)
,@args))))
(defmacro error! (&rest args) `(progn (msg! (color 31 ,@args)) (setq doom-errors (+ doom-errors 1))))
(defmacro warn! (&rest args) `(progn (msg! (color 33 ,@args)) (setq doom-warnings (+ doom-warnings 1))))
(defmacro success! (&rest args) `(msg! (color 32 ,@args)))
(defmacro section! (&rest args)
`(msg! (color 1 (color 34 ,@args))))
(defmacro explain! (&rest args)
`(message (indented (+ indent 2) (autofill ,@args))))
(defun elc-check-dir (dir)
(dolist (file (directory-files-recursively dir "\\.elc$"))
(when (file-newer-than-file-p (concat (file-name-sans-extension file) ".el")
file)
(warn! "%s is out-of-date" (abbreviate-file-name file)))))
;;; Polyfills
;; early versions of emacs won't have this
@ -84,66 +99,96 @@
(save-match-data
(string-match regexp string &optional start))))
;; subr-x may not exist in the current version of Emacs
(unless (fboundp 'string-trim-right)
(defsubst string-trim-right (string &optional regexp)
(if (string-match (concat "\\(?:" (or regexp "[ \t\n\r]+") "\\)\\'") string)
(replace-match "" t t string)
string)))
;; --- start a'doctorin' --------------------------------------
(msg! "%s\nRunning Emacs v%s, commit %s\n"
(color 1 "DOOM Doctor")
(color 1 emacs-version)
(if (executable-find "git")
(sh "git rev-parse HEAD")
(msg! (color 1 "Doom Doctor"))
(msg! "Emacs v%s" emacs-version)
(msg! "Doom v%s (%s)"
(or (let ((core-file (expand-file-name "core/core.el" user-emacs-directory)))
(and (file-exists-p core-file)
(with-temp-buffer
(insert-file-contents-literally core-file)
(goto-char (point-min))
(when (re-search-forward "doom-version" nil t)
(forward-char)
(sexp-at-point)))))
"???")
(if (and (executable-find "git")
(file-directory-p (expand-file-name ".git" user-emacs-directory)))
(sh "git log -1 --format=\"%D %h %ci\"")
"n/a"))
(msg! "shell: %s%s"
(getenv "SHELL")
(if (equal (getenv "SHELL") (sh "echo $SHELL"))
""
(color 31 " (mismatch)")))
(when (boundp 'system-configuration-features)
(msg! "Compiled with:\n%s" (indented 2 (autofill system-configuration-features))))
(msg! "uname -a:\n%s\n" (indented 2 (autofill (sh "uname -a"))))
(message "Compiled with:\n%s" (indented 2 system-configuration-features)))
(message "uname -msrv:\n%s\n" (indented 2 (sh "uname -msrv")))
(let (doom-core-packages doom-debug-mode)
(condition-case ex
(progn
(let ((inhibit-message t)
noninteractive)
(load "~/.emacs.d/init.el" nil t))
(doom-initialize-packages)
(doom|finalize)
(success! "Attempt to load DOOM: success! Loaded v%s" doom-version)
(when (executable-find "git")
(msg! "Revision %s\n"
(ignore-errors
(let ((default-directory user-emacs-directory))
(sh "git rev-parse HEAD"))))))
('error (warn! "Attempt to load DOOM: failed\n %s\n"
(or (cdr-safe ex) (car ex))))))
(msg! "----\n")
;; --- is emacs set up properly? ------------------------------
(section! "test-emacs")
(check! (version< emacs-version "25.1")
(section! "Checking Emacs")
(let ((indent 4))
(section! "Checking your Emacs version is 25.3 or newer...")
(when (version< emacs-version "25.3")
(error! "Important: Emacs %s detected [%s]" emacs-version (executable-find "emacs"))
(explain!
"DOOM only supports >= 25.1. Perhaps your PATH wasn't set up properly."
"DOOM only supports >= 25.3. Perhaps your PATH wasn't set up properly."
(when (eq system-type 'darwin)
(concat "\nMacOS users should use homebrew (https://brew.sh) to install Emacs\n"
" brew install emacs --with-modules --with-imagemagick --with-cocoa"))))
(section! "Checking if your version of Emacs has changed recently...")
(let ((version-file (expand-file-name ".local/emacs-version.el" user-emacs-directory))
doom--last-emacs-version)
(when (and (load version-file 'noerror 'nomessage 'nosuffix)
(not (equal emacs-version doom--last-emacs-version)))
(warn! "Your version of Emacs has changed from %S to %S. Recompile your packages!"
doom--last-emacs-version
emacs-version)
(explain! "Byte-code compiled in one version of Emacs may not work in another version."
"It is recommended that you reinstall your plugins or recompile them with"
"`bin/doom compile :plugins'.")))
(section! "Checking for private config conflicts...")
(let ((xdg-dir (concat (or (getenv "XDG_CONFIG_HOME")
"~/.config")
"/doom/"))
(doom-dir (or (getenv "DOOMDIR")
"~/.doom.d/")))
(when (and (not (file-equal-p xdg-dir doom-dir))
(file-directory-p xdg-dir)
(file-directory-p doom-dir))
(warn! "Detected two private configs, in %s and %s"
(abbreviate-file-name xdg-dir)
doom-dir)
(explain! "The second directory will be ignored, as it has lower precedence.")))
(section! "Checking for stale elc files...")
(elc-check-dir user-emacs-directory))
;; --- is the environment set up properly? --------------------
;; windows? windows
(section! "test-windows")
(check! (memq system-type '(windows-nt ms-dos cygwin))
(section! "Checking your system...")
(let ((indent 4))
;; on windows?
(when (memq system-type '(windows-nt ms-dos cygwin))
(warn! "Warning: Windows detected")
(explain! "DOOM was designed for MacOS and Linux. Expect a bumpy ride!"))
;; are all default fonts present
(section! "test-fonts")
;; are all default fonts present?
(section! "Checking your fonts...")
(if (not (fboundp 'find-font))
(progn
(warn! "Warning: unable to detect font")
@ -151,10 +196,10 @@
"version of Emacs is being used!"))
;; all-the-icons fonts
(let ((font-dest (pcase system-type
('gnu/linux (concat (or (getenv "XDG_DATA_HOME")
(concat (getenv "HOME") "/.local/share"))
(`gnu/linux (concat (or (getenv "XDG_DATA_HOME")
"~/.local/share")
"/fonts/"))
('darwin (concat (getenv "HOME") "/Library/Fonts/")))))
(`darwin "~/Library/Fonts/"))))
(when (and font-dest (require 'all-the-icons nil t))
(dolist (font all-the-icons-font-names)
(if (file-exists-p (expand-file-name font font-dest))
@ -163,10 +208,10 @@
font font-dest)
(explain! "You can install it by running `M-x all-the-icons-install-fonts' within Emacs.\n\n"
"This could also mean you've installed them in non-standard locations, in which "
"case, ignore this warning."))))))
"case feel free to ignore this warning."))))))
;; gnutls-cli & openssl
(section! "test-gnutls")
(section! "Checking gnutls/openssl...")
(cond ((executable-find "gnutls-cli"))
((executable-find "openssl")
(let* ((output (sh "openssl ciphers -v"))
@ -176,8 +221,8 @@
(add-to-list 'protos (cadr (split-string row " " t))))
(split-string (sh "openssl ciphers -v") "\n"))
(delq nil protos))))
(check! (not (or (member "TLSv1.1" protocols)
(member "TLSv1.2" protocols)))
(unless (or (member "TLSv1.1" protocols)
(member "TLSv1.2" protocols))
(let ((version (cadr (split-string (sh "openssl version") " " t))))
(warn! "Warning: couldn't find gnutls-cli, and OpenSSL is out-of-date (v%s)" version)
(explain!
@ -187,7 +232,6 @@
"Please consider updating (or install gnutls-cli, which is preferred).")))))
(t
(check! t
(error! "Important: couldn't find either gnutls-cli nor openssl")
(explain!
"You won't be able to install/update packages because Emacs won't be able to "
@ -200,11 +244,12 @@
"But remember that you're leaving your security in the hands of your "
"network, provider, government, neckbearded mother-in-laws, geeky roommates, "
"or just about anyone who knows more about computers than you do!"))))
"or just about anyone who knows more about computers than you do!")))
(section! "test-tls")
(cond ((not (string-match-p "\\_<GNUTLS\\_>" system-configuration-features))
(warn! "Warning: You didn't install Emacs with gnutls support")
;; are certificates validated properly?
(section! "Testing your root certificates...")
(cond ((not (ignore-errors (gnutls-available-p)))
(warn! "Warning: Emacs wasn't installed with gnutls support")
(explain!
"This may cause 'pecular error' errors with the Doom doctor, and is likely to "
"interfere with package management. Your mileage may vary."
@ -216,16 +261,15 @@
" brew install emacs-plus"))))
((not (fboundp 'url-retrieve-synchronously))
(error! "Can't find url-retrieve-synchronously function. Are you running Emacs 24+?"))
(error! "Can't find url-retrieve-synchronously function. Are you sure you're on Emacs 24+?"))
((or (executable-find "gnutls-cli")
(executable-find "openssl"))
(let ((tls-checktrust t)
(gnutls-verify-error t))
(dolist (url '("https://elpa.gnu.org" "https://melpa.org"))
(check! (condition-case-unless-debug e
(if (let ((inhibit-message t)) (url-retrieve-synchronously url))
(ignore (success! "Validated %s" url))
(when! (condition-case-unless-debug e
(unless (let ((inhibit-message t)) (url-retrieve-synchronously url))
'empty)
('timed-out 'timeout)
('error e))
@ -234,31 +278,29 @@
(`timeout (error! "Timed out trying to contact %s" ex))
(_
(error! "Failed to validate %s" url)
(when doom-debug-mode
(explain! (pp-to-string it)))))))
(explain! (pp-to-string it))))))
(dolist (url '("https://self-signed.badssl.com"
"https://wrong.host.badssl.com/"))
(check! (condition-case-unless-debug e
(when! (condition-case-unless-debug e
(if (let ((inhibit-message t)) (url-retrieve-synchronously url))
t
'empty)
('timed-out 'timeout)
('error (ignore (success! "Successfully rejected %s" url))))
('error))
(pcase it
(`empty (error! "Couldn't reach %s" url))
(`timeout (error! "Timed out trying to contact %s" ex))
(_
(error! "Validated %s (this shouldn't happen!)" url)))))))
(t
(error! "Nope!")))
((error! "Nope!")))
;; bsd vs gnu tar
(section! "test-tar")
;; which variant of tar is on your system? bsd or gnu tar?
(section! "Checking for GNU/BSD tar...")
(let ((tar-bin (or (executable-find "gtar")
(executable-find "tar"))))
(if tar-bin
(check! (not (string-match-p "(GNU tar)" (sh (format "%s --version" tar-bin))))
(unless (string-match-p "(GNU tar)" (sh (format "%s --version" tar-bin)))
(warn! "Warning: BSD tar detected")
(explain!
"QUELPA (through package-build) uses the system tar to build plugins, but it "
@ -267,60 +309,91 @@
(when (eq system-type 'darwin)
(concat "\nMacOS users can install gnu-tar via homebrew:\n"
" brew install gnu-tar"))))
(check! t ; very unlikely
(error! "Important: Couldn't find tar")
(explain!
"This is required by package.el and QUELPA to build packages and will "
"prevent you from installing & updating packages."))))
;; --- report! ------------------------------------------------
;; --- is Doom Emacs set up correctly? ------------------------
(when doom-debug-mode
(msg! "\n====\nHave some debug information:\n")
(condition-case-unless-debug ex
(let ((after-init-time (current-time))
noninteractive)
(section! "Checking DOOM Emacs...")
(load (concat user-emacs-directory "core/core.el") nil t)
(unless (file-directory-p doom-private-dir)
(error "No DOOMDIR was found, did you run `doom quickstart` yet?"))
(when (bound-and-true-p doom-modules)
(msg! " + enabled modules:\n%s"
(indented 4
(columns 3 23
(mapcar (lambda (x) (format "+%s" x))
(mapcar #'cdr (doom-module-pairs)))))))
(let ((indent 4))
;; Make sure everything is loaded
(require 'core-cli)
(require 'core-keybinds)
(require 'core-ui)
(require 'core-projects)
(require 'core-editor)
(require 'core-packages)
(success! "Loaded Doom Emacs %s" doom-version)
(when (and (bound-and-true-p doom-packages)
(require 'package nil t))
(msg! " + enabled packages:\n%s"
(indented 4
(columns 2 35
(delq nil
(mapcar (lambda (pkg)
(let ((desc (cadr (assq pkg package-alist))))
(when desc
(package-desc-full-name desc))))
(sort (mapcar #'car doom-packages) #'string-lessp)))))))
;; ...and initialized
(doom-initialize)
(success! "Initialized Doom Emacs" doom-version)
(msg! " + byte-compiled files:\n%s"
(indented 4
(columns 2 39
(let ((files (append (directory-files-recursively doom-core-dir ".elc$")
(directory-files-recursively doom-modules-dir ".elc$"))))
(or (and files (mapcar (lambda (file) (file-relative-name file doom-emacs-dir))
(nreverse files)))
(list "n/a"))))))
(doom-initialize-modules)
(if (hash-table-p doom-modules)
(success! "Initialized %d modules" (hash-table-count doom-modules))
(warn! "Failed to load any modules. Do you have an private init.el?"))
(msg! " + exec-path:\n%s"
(indented 4
(columns 1 79 exec-path)))
(doom-initialize-packages)
(success! "Initialized %d packages" (length doom-packages))
(msg! " + PATH:\n%s"
(indented 4
(columns 1 79 (split-string (getenv "PATH") ":")))))
(section! "Checking Doom core for irregularities...")
(let ((indent (+ indent 2)))
(load (expand-file-name "doctor.el" doom-core-dir) nil 'nomessage))
(section! "Checking for stale elc files in your DOOMDIR...")
(when (file-directory-p doom-private-dir)
(let ((indent (+ indent 2)))
(elc-check-dir doom-private-dir)))
(when doom-modules
(section! "Checking your enabled modules...")
(let ((indent (+ indent 2)))
(advice-add #'require :around #'doom*shut-up)
(maphash
(lambda (key plist)
(let ((prefix (format "%s" (color 1 "(%s %s) " (car key) (cdr key)))))
(condition-case-unless-debug ex
(let ((doctor-file (doom-module-path (car key) (cdr key) "doctor.el"))
(packages-file (doom-module-path (car key) (cdr key) "packages.el")))
(cl-loop with doom--stage = 'packages
for name in (let (doom-packages
doom-disabled-packages)
(load packages-file 'noerror 'nomessage)
(mapcar #'car doom-packages))
unless (or (doom-package-prop name :disable)
(doom-package-prop name :ignore t)
(package-built-in-p name)
(package-installed-p name))
do (error! "%s is not installed" name))
(let ((doom--stage 'doctor))
(load doctor-file 'noerror 'nomessage)))
(file-missing (error! "%s" (error-message-string ex)))
(error (error! "Syntax error: %s" ex)))))
doom-modules)))))
(error
(warn! "Attempt to load DOOM failed\n %s\n"
(or (cdr-safe ex) (car ex)))
(setq doom-modules nil)))
;;
(if (= doom-errors 0)
(success! "Everything seems fine, happy Emacs'ing!")
(message "\n----")
(warn! "There were issues!")
(unless doom-debug-mode
(msg! "\nHopefully these can help you find problems. If not, run this doctor again with DEBUG=1:")
(msg! "\n DEBUG=1 make doctor\n")
(msg! "And file a bug report with its output at https://github.com/hlissner/.emacs.d/issues")))
(message "\n")
(dolist (msg (list (list doom-errors "error" 31)
(list doom-warnings "warning" 33)))
(when (> (car msg) 0)
(message (color (nth 2 msg) (if (= (car msg) 1) "There is %d %s!" "There are %d %ss!")
(car msg) (nth 1 msg)))))
(when (and (zerop doom-errors)
(zerop doom-warnings))
(success! "Everything seems fine, happy Emacs'ing!"))

25
bin/doom.cmd Normal file
View file

@ -0,0 +1,25 @@
:: Forward the ./doom script to Emacs
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
PUSHD "%~dp0" >NUL
SET args=
SET command=%1
:LOOP
SHIFT /1
IF NOT [%1]==[] (
SET args=%args% %1
GOTO :LOOP
)
IF [%command%]==[run] (
start runemacs -Q %args% -l ..\init.el -f "doom|run-all-startup-hooks"
) ELSE (
emacs --quick --script .\doom -- %*
)
POPD >NUL
ECHO ON

View file

@ -3,27 +3,26 @@
# Open an org-capture popup frame from the shell. This opens a temporary emacs
# daemon if emacs isn't already running.
#
# Usage: org-capture [key] [message...]
# Usage: org-capture [-k KEY] [MESSAGE]
# Examples:
# org-capture n "To the mind that is still, the whole universe surrenders."
# org-capture -k n "To the mind that is still, the whole universe surrenders."
set -e
cleanup() {
emacsclient --eval '(kill-emacs)'
emacsclient --eval '(let (kill-emacs-hook) (kill-emacs))'
}
# If emacs isn't running, we start a temporary daemon, solely for this window.
daemon=
if ! pgrep emacs >/dev/null; then
if ! emacsclient -e nil; then
emacs --daemon
trap cleanup EXIT INT TERM
daemon=1
fi
# org-capture key mapped to argument flags
keys=$(emacsclient -e "(+org-capture-available-keys)" | cut -d '"' -f2)
while getopts $keys opt; do
# keys=$(emacsclient -e "(+org-capture-available-keys)" | cut -d '"' -f2)
while getopts hk opt; do
key="\"$opt\""
break
done
@ -33,11 +32,11 @@ shift $((OPTIND-1))
if [[ $daemon ]]; then
emacsclient -a "" \
-c -F '((name . "org-capture") (width . 70) (height . 25))' \
-c -F '((name . "org-capture") (width . 70) (height . 25) (transient . t))' \
-e "(+org-capture/open-frame \"$str\" ${key:-nil})"
else
# Non-daemon servers flicker a lot if frames are created from terminal, so
# we do it internally instead.
# Non-daemon servers flicker a lot if frames are created from terminal, so we
# do it internally instead.
emacsclient -a "" \
-e "(+org-capture/open-frame \"$str\" ${key:-nil})"
fi

View file

@ -2,42 +2,142 @@
":"; exec emacs --quick --script "$0" -- "$@" # -*- mode: emacs-lisp; lexical-binding: t; -*-
;;; bin/org-tangle
;; Extracts source blocks from org files and prints them to stdout. Debug/info
;; messages are directed to stderr and can be ignored. -l/--lang can be used to
;; only tangle blocks of a certain language.
;; Tangles source blocks from org files. Debug/info messages are directed to
;; stderr and can be ignored.
;;
;; -l/--lang LANG
;; Only include blocks in the specified language (e.g. emacs-lisp).
;; -a/--all
;; Tangle all blocks by default (unless it has :tangle nil set or a
;; :notangle: tag)
;; -t/--tag TAG
;; --and TAG
;; --or TAG
;; Only include blocks in trees that have these tags. Combine multiple --and
;; and --or's, or just use --tag (implicit --and).
;; -p/--print
;; Prints tangled code to stdout instead of to files
;;
;; Usage: org-tangle [[-l|--lang] LANG] some-file.org another.org
;; Examples:
;; org-tangle modules/ui/doom/README.org > install_fira_mono.sh
;; org-tangle -l sh modules/some/module/README.org > install_module.sh
;; org-tangle -l sh modules/lang/go/README.org | sh
;; org-tangle --and tagA --and tagB my/literate/config.org
(load (expand-file-name "../core/core.el" (file-name-directory load-file-name)) nil t)
(require 'org-install)
(require 'org)
(require 'cl-lib)
(require 'ob-tangle)
(defun usage ()
(with-temp-buffer
(insert (format "%s %s [OPTIONS] [TARGETS...]\n"
"Usage:"
(file-name-nondirectory load-file-name))
"\n"
"A command line interface for tangling org-mode files. TARGETS can be\n"
"files or folders (which are searched for org files recursively).\n"
"\n"
"This is useful for literate configs that rely on command line\n"
"workflows to build it.\n"
"\n"
"Example:\n"
" org-tangle some-file.org\n"
" org-tangle literate/config/\n"
" org-tangle -p -l sh scripts.org > do_something.sh\n"
" org-tangle -p -l python -t tagA -t tagB file.org | python\n"
"\n"
"Options:\n"
" -a --all\t\tTangle all blocks by default\n"
" -l --lang LANG\tOnly tangle blocks written in LANG\n"
" -p --print\t\tPrint tangled output to stdout than to files\n"
" -t --tag TAG\n"
" --and TAG\n"
" --or TAG\n"
" Lets you tangle org blocks by tag. You may have more than one\n"
" of these options.\n")
(princ (buffer-string))))
(defun *org-babel-tangle (orig-fn &rest args)
"Don't write tangled blocks to files, print them to stdout."
(cl-letf (((symbol-function 'write-region)
(lambda (start end filename &optional append visit lockname mustbenew)
(princ (buffer-string)))))
(apply orig-fn args)))
(advice-add #'org-babel-tangle :around #'*org-babel-tangle)
(let (lang srcs)
(defun *org-babel-tangle-collect-blocks (&optional language tangle-file)
"Like `org-babel-tangle-collect-blocks', but will ignore blocks that are in
trees with the :notangle: tag."
(let ((counter 0) last-heading-pos blocks)
(org-babel-map-src-blocks (buffer-file-name)
(let ((current-heading-pos
(org-with-wide-buffer
(org-with-limited-levels (outline-previous-heading)))))
(if (eq last-heading-pos current-heading-pos) (cl-incf counter)
(setq counter 1)
(setq last-heading-pos current-heading-pos)))
(unless (org-in-commented-heading-p)
(require 'org)
(let* ((tags (org-get-tags-at))
(info (org-babel-get-src-block-info 'light))
(src-lang (nth 0 info))
(src-tfile (cdr (assq :tangle (nth 2 info)))))
(cond ((member "notangle" tags))
((and (or or-tags and-tags)
(or (not and-tags)
(let ((a (cl-intersection and-tags tags :test #'string=))
(b and-tags))
(not (or (cl-set-difference a b :test #'equal)
(cl-set-difference b a :test #'equal)))))
(or (not or-tags)
(cl-intersection or-tags tags :test #'string=))
t))
((or (not (or all-blocks src-tfile))
(string= src-tfile "no") ; tangle blocks by default
(and tangle-file (not (equal tangle-file src-tfile)))
(and language (not (string= language src-lang)))))
;; Add the spec for this block to blocks under its language.
((let ((by-lang (assoc src-lang blocks))
(block (org-babel-tangle-single-block counter)))
(if by-lang
(setcdr by-lang (cons block (cdr by-lang)))
(push (cons src-lang (list block)) blocks))))))))
;; Ensure blocks are in the correct order.
(mapcar (lambda (b) (cons (car b) (nreverse (cdr b)))) blocks)))
(advice-add #'org-babel-tangle-collect-blocks
:override #'*org-babel-tangle-collect-blocks)
(defvar all-blocks nil)
(defvar and-tags nil)
(defvar or-tags nil)
(let (lang srcs and-tags or-tags)
(pop argv)
(while argv
(let ((arg (pop argv)))
(pcase arg
((or "--lang" "-l")
((or "-h" "--help")
(usage)
(error ""))
((or "-a" "--all")
(setq all-blocks t))
((or "-l" "--lang")
(setq lang (pop argv)))
((or "-p" "--print")
(advice-add #'org-babel-tangle :around #'*org-babel-tangle))
((or "-t" "--tag" "--and")
(push (pop argv) and-tags))
("--or"
(push (pop argv) or-tags))
((guard (string-match-p "^--lang=" arg))
(setq lang (cadr (split-string arg "=" t t))))
((guard (file-directory-p arg))
(setq srcs
(append (directory-files-recursively arg "\\.org$")
srcs)))
((guard (file-exists-p arg))
(push arg srcs))
(_
(error "Unknown option or file: %s" arg)))))
(_ (error "Unknown option or file: %s" arg)))))
(dolist (file srcs)
(org-babel-tangle-file file nil lang))

View file

@ -1,77 +1,146 @@
;;; core/autoload/buffers.el -*- lexical-binding: t; -*-
(defvar-local doom-buffer--narrowed-origin nil)
;;;###autoload
(defvar doom-real-buffer-functions
'(doom-dired-buffer-p)
"A list of predicate functions run to determine if a buffer is real, unlike
`doom-unreal-buffer-functions'. They are passed one argument: the buffer to be
tested.
Should any of its function returns non-nil, the rest of the functions are
ignored and the buffer is considered real.
See `doom-real-buffer-p' for more information.")
;;;###autoload
(defvar doom-real-buffer-functions '()
"A list of predicate functions run to determine if a buffer is real. These
functions are iterated over with one argument, the buffer in question. If any
function returns non-nil, the procession stops and the buffer is qualified as
real.")
(defvar doom-unreal-buffer-functions
'(minibufferp doom-special-buffer-p doom-non-file-visiting-buffer-p)
"A list of predicate functions run to determine if a buffer is *not* real,
unlike `doom-real-buffer-functions'. They are passed one argument: the buffer to
be tested.
Should any of these functions return non-nil, the rest of the functions are
ignored and the buffer is considered unreal.
See `doom-real-buffer-p' for more information.")
;;;###autoload
(defvar-local doom-real-buffer-p nil
"If non-nil, this buffer should be considered real no matter what.")
"If non-nil, this buffer should be considered real no matter what. See
`doom-real-buffer-p' for more information.")
;;;###autoload
(defvar doom-fallback-buffer "*scratch*"
(defvar doom-fallback-buffer-name "*scratch*"
"The name of the buffer to fall back to if no other buffers exist (will create
it if it doesn't exist).")
;;
;; Functions
;;
;;;###autoload
(defun doom-buffer-frame-predicate (buf)
"To be used as the default frame buffer-predicate parameter. Returns nil if
BUF should be skipped over by functions like `next-buffer' and `other-buffer'."
(or (doom-real-buffer-p buf)
(eq buf (doom-fallback-buffer))))
;;;###autoload
(defun doom-fallback-buffer ()
"Returns the fallback buffer, creating it if necessary. By default this is the
scratch buffer."
(get-buffer-create doom-fallback-buffer))
scratch buffer. See `doom-fallback-buffer-name' to change this."
(let (buffer-list-update-hook)
(get-buffer-create doom-fallback-buffer-name)))
;;;###autoload
(defalias 'doom-buffer-list #'buffer-list)
;;;###autoload
(defun doom-project-buffer-list ()
"Return a list of buffers belonging to the current project.
(defun doom-project-buffer-list (&optional project)
"Return a list of buffers belonging to the specified PROJECT.
If PROJECT is nil, default to the current project.
If no project is active, return all buffers."
(let ((buffers (doom-buffer-list)))
(if-let* ((project-root (if (doom-project-p) (doom-project-root))))
(if-let* ((project-root
(if project (expand-file-name project)
(doom-project-root))))
(cl-loop for buf in buffers
if (projectile-project-buffer-p buf project-root)
collect buf)
buffers)))
;;;###autoload
(defun doom-real-buffer-list (&optional buffer-list)
"Return a list of buffers that satify `doom-real-buffer-p'."
(cl-loop for buf in (or buffer-list (doom-buffer-list))
if (doom-real-buffer-p buf)
collect buf))
(defun doom-open-projects ()
"Return a list of projects with open buffers."
(cl-loop with projects = (make-hash-table :test 'equal :size 8)
for buffer in (doom-buffer-list)
if (buffer-live-p buffer)
if (doom-real-buffer-p buffer)
if (with-current-buffer buffer (doom-project-root))
do (puthash (abbreviate-file-name it) t projects)
finally return (hash-table-keys projects)))
;;;###autoload
(defun doom-real-buffer-p (&optional buffer-or-name)
"Returns t if BUFFER-OR-NAME is a 'real' buffer. The complete criteria for a
real buffer is:
(defun doom-dired-buffer-p (buf)
"Returns non-nil if BUF is a dired buffer."
(with-current-buffer buf (derived-mode-p 'dired-mode)))
1. The buffer-local value of `doom-real-buffer-p' (variable) is non-nil OR
2. Any function in `doom-real-buffer-functions' must return non-nil when
passed this buffer OR
3. The current buffer:
a) has a `buffer-file-name' defined AND
b) is not in a popup window (see `doom-popup-p') AND
c) is not a special buffer (its name isn't something like *Help*)
;;;###autoload
(defun doom-special-buffer-p (buf)
"Returns non-nil if BUF's name starts and ends with an *."
(equal (substring (buffer-name buf) 0 1) "*"))
;;;###autoload
(defun doom-temp-buffer-p (buf)
"Returns non-nil if BUF is temporary."
(equal (substring (buffer-name buf) 0 1) " "))
;;;###autoload
(defun doom-non-file-visiting-buffer-p (buf)
"Returns non-nil if BUF does not have a value for `buffer-file-name'."
(not (buffer-file-name buf)))
;;;###autoload
(defun doom-real-buffer-list (&optional buffer-list)
"Return a list of buffers that satify `doom-real-buffer-p'."
(cl-remove-if-not #'doom-real-buffer-p (or buffer-list (doom-buffer-list))))
;;;###autoload
(defun doom-real-buffer-p (buffer-or-name)
"Returns t if BUFFER-OR-NAME is a 'real' buffer.
A real buffer is a useful buffer; a first class citizen in Doom. Real ones
should get special treatment, because we will be spending most of our time in
them. Unreal ones should be low-profile and easy to cast aside, so we can focus
on real ones.
The exact criteria for a real buffer is:
1. A non-nil value for the buffer-local value of the `doom-real-buffer-p'
variable OR
2. Any function in `doom-real-buffer-functions' returns non-nil OR
3. None of the functions in `doom-unreal-buffer-functions' must return
non-nil.
If BUFFER-OR-NAME is omitted or nil, the current buffer is tested."
(when-let* ((buf (ignore-errors (window-normalize-buffer buffer-or-name))))
(or (bufferp buffer-or-name)
(stringp buffer-or-name)
(signal 'wrong-type-argument (list '(bufferp stringp) buffer-or-name)))
(when-let* ((buf (get-buffer buffer-or-name)))
(and (buffer-live-p buf)
(not (doom-temp-buffer-p buf))
(or (buffer-local-value 'doom-real-buffer-p buf)
(run-hook-with-args-until-success 'doom-real-buffer-functions buf)
(not (or (doom-popup-p buf)
(minibufferp buf)
(string-match-p "^\\s-*\\*" (buffer-name buf))
(not (buffer-file-name buf)))))))
(not (run-hook-with-args-until-success 'doom-unreal-buffer-functions buf))))))
;;;###autoload
(defun doom-unreal-buffer-p (buffer-or-name)
"Return t if BUFFER-OR-NAME is an 'unreal' buffer.
See `doom-real-buffer-p' for details on what that means."
(not (doom-real-buffer-p buffer-or-name)))
;;;###autoload
(defun doom-buffers-in-mode (modes &optional buffer-list derived-p)
@ -89,10 +158,11 @@ If DERIVED-P, test with `derived-mode-p', otherwise use `eq'."
;;;###autoload
(defun doom-visible-windows (&optional window-list)
"Return a list of the visible, non-popup windows."
(cl-loop for win in (or window-list (window-list))
unless (doom-popup-p win)
collect win))
"Return a list of the visible, non-popup (dedicated) windows."
(cl-loop for window in (or window-list (window-list))
when (or (window-parameter window 'visible)
(not (window-dedicated-p window)))
collect window))
;;;###autoload
(defun doom-visible-buffers (&optional buffer-list)
@ -104,9 +174,7 @@ If DERIVED-P, test with `derived-mode-p', otherwise use `eq'."
;;;###autoload
(defun doom-buried-buffers (&optional buffer-list)
"Get a list of buffers that are buried."
(cl-loop for buf in (or buffer-list (doom-buffer-list))
unless (get-buffer-window buf)
collect buf))
(cl-remove-if #'get-buffer-window (or buffer-list (doom-buffer-list))))
;;;###autoload
(defun doom-matching-buffers (pattern &optional buffer-list)
@ -115,71 +183,12 @@ If DERIVED-P, test with `derived-mode-p', otherwise use `eq'."
when (string-match-p pattern (buffer-name buf))
collect buf))
(defun doom--cycle-real-buffers (&optional n)
"Switch to the next buffer N times (previous, if N < 0), skipping over unreal
buffers. If there's nothing left, switch to `doom-fallback-buffer'. See
`doom-real-buffer-p' for what 'real' means."
(let ((buffers (delq (current-buffer) (doom-real-buffer-list))))
(cond ((or (not buffers)
(zerop (% n (1+ (length buffers)))))
(switch-to-buffer (doom-fallback-buffer) nil t))
((= (length buffers) 1)
(switch-to-buffer (car buffers) nil t))
(t
;; Why this instead of switching straight to the Nth buffer in
;; BUFFERS? Because `switch-to-next-buffer' and
;; `switch-to-prev-buffer' properly update buffer list order.
(cl-loop with move-func =
(if (> n 0) #'switch-to-next-buffer #'switch-to-prev-buffer)
for i to 20
while (not (memq (current-buffer) buffers))
do
(dotimes (_i (abs n))
(funcall move-func)))))
(force-mode-line-update)
(current-buffer)))
;;;###autoload
(defun doom-set-buffer-real (buffer flag)
"Forcibly mark BUFFER as FLAG (non-nil = real)."
(with-current-buffer buffer
(setq doom-real-buffer-p flag)))
;;;###autoload
(defun doom-kill-buffer (&optional buffer dont-save)
"Kill BUFFER (defaults to current buffer), but make sure we land on a real
buffer. Bury the buffer if the buffer is present in another window.
Will prompt to save unsaved buffers when attempting to kill them, unless
DONT-SAVE is non-nil.
See `doom-real-buffer-p' for what 'real' means."
(unless buffer
(setq buffer (current-buffer)))
(when (and (bufferp buffer)
(buffer-live-p buffer))
(let ((buffer-win (get-buffer-window buffer)))
;; deal with modified buffers
(when (and (buffer-file-name buffer)
(buffer-modified-p buffer))
(with-current-buffer buffer
(if (and (not dont-save)
(yes-or-no-p "Buffer is unsaved, save it?"))
(save-buffer)
(set-buffer-modified-p nil))))
;; kill the buffer (or close dedicated window)
(cond ((not buffer-win)
(kill-buffer buffer))
((window-dedicated-p buffer-win)
(unless (window--delete buffer-win t t)
(split-window buffer-win)
(window--delete buffer-win t t)))
(t ; cycle to a real buffer
(with-selected-window buffer-win
(doom--cycle-real-buffers -1)
(kill-buffer buffer)))))
(not (eq (current-buffer) buffer))))
;;;###autoload
(defun doom-kill-buffer-and-windows (buffer)
"Kill the buffer and delete all the windows it's displayed in."
@ -188,54 +197,123 @@ See `doom-real-buffer-p' for what 'real' means."
(delete-window window)))
(kill-buffer buffer))
;;;###autoload
(defun doom-fixup-windows (windows)
"Ensure that each of WINDOWS is showing a real buffer or the fallback buffer."
(dolist (window windows)
(with-selected-window window
(when (doom-unreal-buffer-p (window-buffer))
(previous-buffer)
(when (doom-unreal-buffer-p (window-buffer))
(switch-to-buffer (doom-fallback-buffer)))))))
;;;###autoload
(defun doom-kill-buffer-fixup-windows (buffer)
"Kill the BUFFER and ensure all the windows it was displayed in have switched
to a real buffer or the fallback buffer."
(let ((windows (get-buffer-window-list buffer)))
(kill-buffer buffer)
(doom-fixup-windows (cl-remove-if-not #'window-live-p windows))))
;;;###autoload
(defun doom-kill-buffers-fixup-windows (buffers)
"Kill the BUFFERS and ensure all the windows they were displayed in have
switched to a real buffer or the fallback buffer."
(let ((seen-windows (make-hash-table :test 'eq :size 8)))
(dolist (buffer buffers)
(let ((windows (get-buffer-window-list buffer)))
(kill-buffer buffer)
(dolist (window (cl-remove-if-not #'window-live-p windows))
(puthash window t seen-windows))))
(doom-fixup-windows (hash-table-keys seen-windows))))
;;;###autoload
(defun doom-kill-matching-buffers (pattern &optional buffer-list)
"Kill all buffers (in current workspace OR in BUFFER-LIST) that match the
regex PATTERN. Returns the number of killed buffers."
(let ((buffers (doom-matching-buffers pattern buffer-list)))
(dolist (buf buffers (length buffers))
(doom-kill-buffer buf t))))
(kill-buffer buf))))
;;
;; Hooks
;;;###autoload
(defun doom|mark-buffer-as-real ()
"Hook function that marks the current buffer as real."
(doom-set-buffer-real (current-buffer) t))
;;
;; Advice
;;;###autoload
(defun doom*switch-to-fallback-buffer-maybe (orig-fn)
"Advice for `kill-this-buffer'. If in a dedicated window, delete it. If there
are no real buffers left OR if all remaining buffers are visible in other
windows, switch to `doom-fallback-buffer'. Otherwise, delegate to original
`kill-this-buffer'."
(let ((buf (current-buffer)))
(cond ((window-dedicated-p)
(delete-window))
((eq buf (doom-fallback-buffer))
(message "Can't kill the fallback buffer."))
((doom-real-buffer-p buf)
(if (and buffer-file-name
(buffer-modified-p buf)
(not (y-or-n-p
(format "Buffer %s is modified; kill anyway?" buf))))
(message "Aborted")
(set-buffer-modified-p nil)
(let (buffer-list-update-hook)
(when (or ;; if there aren't more real buffers than visible buffers,
;; then there are no real, non-visible buffers left.
(not (cl-set-difference (doom-real-buffer-list)
(doom-visible-buffers)))
;; if we end up back where we start (or previous-buffer
;; returns nil), we have nowhere left to go
(memq (switch-to-prev-buffer nil t) (list buf 'nil)))
(switch-to-buffer (doom-fallback-buffer)))
(unless (delq (selected-window) (get-buffer-window-list buf nil t))
(kill-buffer buf)))))
((funcall orig-fn)))))
;;
;; Interactive commands
;;
;;;###autoload
(defun doom/kill-this-buffer (&optional interactive-p)
"Use `doom-kill-buffer' on the current buffer."
(interactive (list 'interactive))
(when (and (not (doom-kill-buffer)) interactive-p)
(message "Nowhere left to go!")))
;;;###autoload
(defun doom/kill-this-buffer-in-all-windows (buffer &optional dont-save)
"Kill BUFFER globally and ensure all windows previously showing this buffer
have switched to a real buffer.
have switched to a real buffer or the fallback buffer.
If DONT-SAVE, don't prompt to save modified buffers (discarding their changes)."
(interactive
(list (current-buffer) current-prefix-arg))
(cl-assert (bufferp buffer) t)
(let ((windows (get-buffer-window-list buffer nil t)))
(doom-kill-buffer buffer dont-save)
(cl-loop for win in windows
if (doom-real-buffer-p (window-buffer win))
do (with-selected-window win (doom/previous-buffer)))))
(when (and (buffer-modified-p buffer) dont-save)
(with-current-buffer buffer
(set-buffer-modified-p nil)))
(doom-kill-buffer-fixup-windows buffer))
;;;###autoload
(defun doom/kill-all-buffers (&optional project-p)
"Kill all buffers and closes their windows.
If PROJECT-P (universal argument), kill only buffers that belong to the current
project."
If PROJECT-P (universal argument), don't close windows and only kill buffers
that belong to the current project."
(interactive "P")
(doom/popup-kill-all)
(save-some-buffers)
(unless project-p
(delete-other-windows))
(switch-to-buffer (doom-fallback-buffer))
(let ((buffers (if project-p (doom-project-buffer-list) (doom-buffer-list))))
(mapc #'doom-kill-buffer-and-windows buffers)
(unless (doom-real-buffer-p)
(switch-to-buffer (doom-fallback-buffer)))
(message "Killed %s buffers" (length buffers))))
(mapc #'kill-buffer buffers)
(when (called-interactively-p 'interactive)
(message "Killed %s buffers"
(- (length buffers)
(length (cl-remove-if-not #'buffer-live-p buffers)))))))
;;;###autoload
(defun doom/kill-other-buffers (&optional project-p)
@ -244,13 +322,14 @@ project."
If PROJECT-P (universal argument), kill only buffers that belong to the current
project."
(interactive "P")
(let ((buffers (if project-p (doom-project-buffer-list) (doom-buffer-list)))
(current-buffer (current-buffer)))
(dolist (buf buffers)
(unless (eq buf current-buffer)
(doom-kill-buffer-and-windows buf)))
(let ((buffers
(delq (current-buffer)
(if project-p (doom-project-buffer-list) (doom-buffer-list)))))
(mapc #'doom-kill-buffer-and-windows buffers)
(when (called-interactively-p 'interactive)
(message "Killed %s buffers" (length buffers)))))
(message "Killed %s buffers"
(- (length buffers)
(length (cl-remove-if-not #'buffer-live-p buffers)))))))
;;;###autoload
(defun doom/kill-matching-buffers (pattern &optional project-p)
@ -261,52 +340,45 @@ project."
(interactive
(list (read-regexp "Buffer pattern: ")
current-prefix-arg))
(let* ((buffers (if project-p (doom-project-buffer-list) (doom-buffer-list)))
(n (doom-kill-matching-buffers pattern buffers)))
(let* ((buffers (if project-p (doom-project-buffer-list) (doom-buffer-list))))
(doom-kill-matching-buffers pattern buffers)
(when (called-interactively-p 'interactive)
(message "Killed %s buffers" n))))
(message "Killed %d buffer(s)"
(- (length buffers)
(length (cl-remove-if-not #'buffer-live-p buffers)))))))
;;;###autoload
(defun doom/cleanup-session (&optional all-p)
"Clean up buried buries and orphaned processes in the current workspace. If
ALL-P (universal argument), clean them up globally."
(defun doom/kill-buried-buffers (&optional project-p)
"Kill buffers that are buried.
If PROJECT-P (universal argument), only kill buried buffers belonging to the
current project."
(interactive "P")
(run-hooks 'doom-cleanup-hook)
(let ((buffers (doom-buried-buffers (if all-p (buffer-list))))
(n 0)
kill-buffer-query-functions)
(let ((buffers (doom-buried-buffers (if project-p (doom-project-buffer-list)))))
(mapc #'kill-buffer buffers)
(setq n (+ n (length buffers) (doom/cleanup-processes)))
(when (called-interactively-p 'interactive)
(message "Cleaned up %s buffers" n))))
(message "Killed %d buffer(s)"
(- (length buffers)
(length (cl-remove-if-not #'buffer-live-p buffers)))))))
;;;###autoload
(defun doom/cleanup-processes ()
"Kill all processes that have no visible associated buffers. Return number of
processes killed."
(interactive)
(let ((n 0))
(dolist (p (process-list))
(let ((process-buffer (process-buffer p)))
(when (and (process-live-p p)
(not (string= (process-name p) "server"))
(or (not process-buffer)
(and (bufferp process-buffer)
(not (buffer-live-p process-buffer)))))
(delete-process p)
(cl-incf n))))
n))
;;;###autoload
(defun doom/next-buffer ()
"Switch to the next real buffer, skipping non-real buffers. See
`doom-real-buffer-p' for what 'real' means."
(interactive)
(doom--cycle-real-buffers +1))
;;;###autoload
(defun doom/previous-buffer ()
"Switch to the previous real buffer, skipping non-real buffers. See
`doom-real-buffer-p' for what 'real' means."
(interactive)
(doom--cycle-real-buffers -1))
(defun doom/kill-project-buffers (project)
"Kill buffers for the specified PROJECT."
(interactive
(list (if-let* ((open-projects (doom-open-projects)))
(completing-read
"Kill buffers for project: " open-projects
nil t nil nil
(if-let* ((project-root (doom-project-root))
(project-root (abbreviate-file-name project-root))
((member project-root open-projects)))
project-root))
(message "No projects are open!")
nil)))
(when project
(let ((buffers (doom-project-buffer-list project)))
(doom-kill-buffers-fixup-windows buffers)
(when (called-interactively-p 'interactive)
(message "Killed %d buffer(s)"
(- (length buffers)
(length (cl-remove-if-not #'buffer-live-p buffers))))))))

95
core/autoload/cache.el Normal file
View file

@ -0,0 +1,95 @@
;;; ../core/autoload/cache.el -*- lexical-binding: t; -*-
;; This little library thinly wraps around persistent-soft (which is a pcache
;; wrapper, how about that). It has three purposes:
;;
;; + To encapsulate the cache backend (persistent-soft/pcache in this case), in
;; case it needs to change.
;; + To provide `doom-cache-persist': a mechanism for easily persisting
;; variables across Emacs sessions.
;; + To lazy-load persistent-soft until it is really needed.
;;
;; Like persistent-soft, caches assume a 2-tier structure, where all caches are
;; namespaced by location.
(defvar doom-cache-alists '(t)
"An alist of alists, containing lists of variables for the doom cache library
to persist across Emacs sessions.")
(defvar doom-cache-location 'doom
"The default location for cache files. This symbol is translated into a file
name under `pcache-directory' (by default a subdirectory under
`doom-cache-dir'). One file may contain multiple cache entries.")
(defun doom|save-persistent-cache ()
"Hook to run when an Emacs session is killed. Saves all persisted variables
listed in `doom-cache-alists' to files."
(dolist (alist (butlast doom-cache-alists 1))
(cl-loop with key = (car alist)
for var in (cdr alist)
if (symbol-value var)
do (doom-cache-set var it nil key))))
(add-hook 'kill-emacs-hook #'doom|save-persistent-cache)
;;
;; Library
;;;###autoload
(defmacro with-cache! (location &rest body)
"Runs BODY with a different default `doom-cache-location'."
(declare (indent defun))
`(let ((doom-cache-location ',location))
,@body))
;;;###autoload
(defun doom-cache-persist (location variables)
"Persist VARIABLES (list of symbols) in LOCATION (symbol).
This populates these variables with cached values, if one exists, and saves them
to file when Emacs quits.
Warning: this is incompatible with buffer-local variables."
(dolist (var variables)
(when (doom-cache-exists var location)
(set var (doom-cache-get var location))))
(setf (alist-get location doom-cache-alists)
(append variables (cdr (assq location doom-cache-alists)))))
;;;###autoload
(defun doom-cache-desist (location &optional variables)
"Unregisters VARIABLES (list of symbols) in LOCATION (symbol) from
`doom-cache-alists', thus preventing them from being saved between sessions.
Does not affect the actual variables themselves or their values."
(if variables
(setf (alist-get location doom-cache-alists)
(cl-set-difference (cdr (assq location doom-cache-alists))
variables))
(delq (assq location doom-cache-alists)
doom-cache-alists)))
;;;###autoload
(defun doom-cache-get (key &optional location)
"Retrieve KEY from LOCATION (defaults to `doom-cache-location'), if it exists
and hasn't expired."
(persistent-soft-fetch
key (symbol-name (or location doom-cache-location))))
;;;###autoload
(defun doom-cache-set (key value &optional ttl location)
"Set KEY to VALUE in the cache. TTL is the time (in seconds) until this cache
entry expires. LOCATION is the super-key to store this cache item under; the
default is `doom-cache-location'. "
(persistent-soft-store
key value
(symbol-name (or location doom-cache-location)) ttl))
;;;###autoload
(defun doom-cache-exists (key &optional location)
"Returns t if KEY exists at LOCATION (defaults to `doom-cache-location')."
(persistent-soft-exists-p key (or location doom-cache-location)))
;;;###autoload
(defun doom-cache-clear (&optional location)
"Clear a cache LOCATION (defaults to `doom-cache-location')."
(persistent-soft-flush (or location doom-cache-location)))

68
core/autoload/cli.el Normal file
View file

@ -0,0 +1,68 @@
;;; core/autoload/cli.el -*- lexical-binding: t; -*-
(require 'core-cli)
(defun doom--run (command &optional yes)
(let* ((default-directory doom-emacs-dir)
(doom-auto-accept yes)
(buf (get-buffer-create " *bin/doom*"))
(wconf (current-window-configuration))
(ignore-window-parameters t)
(noninteractive t)
(standard-output
(lambda (char)
(with-current-buffer buf
(insert char)
(when (memq char '(?\n ?\r))
(ansi-color-apply-on-region (line-beginning-position -1) (line-end-position))
(redisplay))))))
(doom-initialize t)
(setq doom-modules (doom-modules))
(doom-initialize-modules t)
(doom-initialize-packages t)
(with-current-buffer (switch-to-buffer buf)
(erase-buffer)
(require 'package)
(redisplay)
(doom-dispatch command nil)
(print! (green "\nDone!"))))
(message (format! (green "Done!"))))
;;;###autoload
(defun doom//autoloads (&optional yes)
"TODO"
(interactive "P")
(doom--run "autoloads" yes))
;;;###autoload
(defun doom//update (&optional yes)
"TODO"
(interactive "P")
(doom--run "update" yes))
;;;###autoload
(defun doom//upgrade (&optional yes)
"TODO"
(interactive "P")
(doom--run "upgrade" yes)
(when (y-or-n-p "You must restart Emacs for the upgrade to take effect. Restart?")
(doom/restart-and-restore)))
;;;###autoload
(defun doom//install (&optional yes)
"TODO"
(interactive "P")
(doom--run "install" yes))
;;;###autoload
(defun doom//autoremove (&optional yes)
"TODO"
(interactive "P")
(doom--run "autoremove" yes))
;;;###autoload
(defun doom//refresh (&optional yes)
"TODO"
(interactive "P")
(doom--run "refresh" yes))

83
core/autoload/config.el Normal file
View file

@ -0,0 +1,83 @@
;;; core/autoload/config.el -*- lexical-binding: t; -*-
;;;###autoload
(defvar doom-reloading-p nil
"TODO")
;;;###autoload
(defun doom/open-private-config ()
"TODO"
(interactive)
(unless (file-directory-p doom-private-dir)
(make-directory doom-private-dir t))
(doom-project-browse doom-private-dir))
;;;###autoload
(defun doom/find-file-in-private-config ()
"TODO"
(interactive)
(doom-project-find-file doom-private-dir))
;;;###autoload
(defun doom/reload (&optional force-p)
"Reloads your private config.
This is experimental! It will try to do as `bin/doom refresh' does, but from
within this Emacs session. i.e. it reload autoloads files (if necessary),
reloads your package list, and lastly, reloads your private config.el.
Runs `doom-reload-hook' afterwards."
(interactive "P")
(require 'core-cli)
(let ((doom-reloading-p t))
(when (getenv "DOOMENV")
(doom-reload-env-file 'force))
(doom-reload-autoloads force-p)
(let (doom-init-p)
(doom-initialize))
(with-demoted-errors "PRIVATE CONFIG ERROR: %s"
(let (doom-init-modules-p)
(doom-initialize-modules)))
(when (bound-and-true-p doom-packages)
(doom/reload-packages))
(run-hook-wrapped 'doom-reload-hook #'doom-try-run-hook))
(message "Finished!"))
;;;###autoload
(defun doom/reload-env ()
"Regenerates and reloads your shell environment.
Uses the same mechanism as 'bin/doom env reload'."
(interactive)
(compile (format "%s env refresh" (expand-file-name "bin/doom" doom-emacs-dir)))
(while compilation-in-progress
(sit-for 1))
(unless (file-readable-p doom-env-file)
(error "Failed to generate env file"))
(load-env-vars doom-env-file)
(setq-default
exec-path (append (split-string (getenv "PATH") ":")
(list exec-directory))
shell-file-name (or (getenv "SHELL")
shell-file-name)))
;;;###autoload
(defun doom/reload-font ()
"Reload `doom-font', `doom-variable-pitch-font', and `doom-unicode-font', if
set."
(interactive)
(when doom-font
(set-frame-font doom-font t))
(doom|init-fonts))
;;;###autoload
(defun doom/reload-theme ()
"Reset the current color theme and fonts."
(interactive)
(let ((theme (or (car-safe custom-enabled-themes) doom-theme)))
(when theme
(mapc #'disable-theme custom-enabled-themes))
(when (and doom-theme (not (memq doom-theme custom-enabled-themes)))
(let (doom--prefer-theme-elc)
(load-theme doom-theme t)))
(doom|init-fonts)))

View file

@ -1,57 +1,90 @@
;;; core/autoload/debug.el -*- lexical-binding: t; -*-
;;;###autoload
(defun doom/what-face (&optional pos)
"Shows all faces and overlay faces at point.
(defun doom-template-insert (template)
"TODO"
(let ((file (expand-file-name (format "templates/%s" template) doom-core-dir)))
(when (file-exists-p file)
(insert-file-contents file))))
Interactively prints the list to the echo area. Noninteractively, returns a list
whose car is the list of faces and cadr is the list of overlay faces."
(interactive)
(let* ((pos (or pos (point)))
(faces (let ((face (get-text-property pos 'face)))
(if (keywordp (car-safe face))
(list face)
(cl-loop for f in (doom-enlist face) collect f))))
(overlays (cl-loop for ov in (overlays-at pos (1+ pos))
nconc (doom-enlist (overlay-get ov 'face)))))
(cond ((called-interactively-p 'any)
(message "%s %s\n%s %s"
(propertize "Faces:" 'face 'font-lock-comment-face)
(if faces
(cl-loop for face in faces
if (listp face)
concat (format "'%s " face)
else
concat (concat (propertize (symbol-name face) 'face face) " "))
;;;###autoload
(defun doom-info ()
"Returns diagnostic information about the current Emacs session in markdown,
ready to be pasted in a bug report on github."
(require 'vc-git)
(let ((default-directory doom-emacs-dir)
(doom-modules (doom-modules)))
(format
(concat "- OS: %s (%s)\n"
"- Emacs: %s (%s)\n"
"- Doom: %s (%s)\n"
"- Graphic display: %s (daemon: %s)\n"
"- System features: %s\n"
"- Details:\n"
" ```elisp\n"
" env bootstrapper: %s\n"
" elc count: %s\n"
" uname -a: %s\n"
" modules: %s\n"
" packages: %s\n"
" exec-path: %s\n"
" ```")
system-type system-configuration
emacs-version (format-time-string "%b %d, %Y" emacs-build-time)
doom-version
(or (string-trim (shell-command-to-string "git log -1 --format=\"%D %h %ci\""))
"n/a")
(propertize "Overlays:" 'face 'font-lock-comment-face)
(if overlays
(cl-loop for ov in overlays
concat (concat (propertize (symbol-name ov) 'face ov) " "))
"n/a")))
(t
(and (or faces overlays)
(list faces overlays))))))
(display-graphic-p) (daemonp)
(bound-and-true-p system-configuration-features)
(cond ((file-exists-p doom-env-file) 'envvar-file)
((featurep 'exec-path-from-shell) 'exec-path-from-shell))
;; details
(length (doom-files-in `(,@doom-modules-dirs
,doom-core-dir
,doom-private-dir)
:type 'files :match "\\.elc$"))
(if IS-WINDOWS
"n/a"
(with-temp-buffer
(unless (zerop (call-process "uname" nil t nil "-msrv"))
(insert (format "%s" system-type)))
(string-trim (buffer-string))))
(or (cl-loop with cat = nil
for key being the hash-keys of doom-modules
if (or (not cat) (not (eq cat (car key))))
do (setq cat (car key))
and collect cat
and collect (cdr key)
else collect
(let ((flags (doom-module-get cat (cdr key) :flags)))
(if flags
`(,(cdr key) ,@flags)
(cdr key))))
"n/a")
(or (ignore-errors
(require 'use-package)
(cl-loop for (name . plist) in (doom-find-packages :private t)
if (use-package-plist-delete (copy-sequence plist) :modules)
collect (format "%s" (cons name it))
else
collect (symbol-name name)))
"n/a")
;; abbreviate $HOME to hide username
(mapcar #'abbreviate-file-name exec-path))))
;;
;; Commands
;;;###autoload
(defun doom-active-minor-modes ()
"Get a list of active minor-mode symbols."
(cl-loop for mode in minor-mode-list
unless (and (boundp mode) (symbol-value mode))
collect mode))
;;;###autoload
(defun doom/what-minor-mode (mode)
"Get information on an active minor mode. Use `describe-minor-mode' for a
selection of all minor-modes, active or not."
(interactive
(list (completing-read "Minor mode: "
(doom-active-minor-modes))))
(describe-minor-mode-from-symbol
(cl-typecase mode
(string (intern mode))
(symbol mode)
(t (error "Expected a symbol/string, got a %s" (type-of mode))))))
(defun doom/info ()
"Collects some debug information about your Emacs session, formats it into
markdown and copies it to your clipboard, ready to be pasted into bug reports!"
(interactive)
(message "Generating Doom info...")
(if noninteractive
(print! (doom-info))
(kill-new (doom-info))
(message "Done! Copied to clipboard.")))
;;;###autoload
(defun doom/am-i-secure ()
@ -68,8 +101,8 @@ selection of all minor-modes, active or not."
(url-retrieve-synchronously bad)
(error nil))
collect bad)))
(error (format "tls seems to be misconfigured (it got %s)."
bad-hosts))
(error "tls seems to be misconfigured (it got %s)."
bad-hosts)
(url-retrieve "https://badssl.com"
(lambda (status)
(if (or (not status) (plist-member status :error))
@ -77,9 +110,216 @@ selection of all minor-modes, active or not."
(message "Your trust roots are set up properly.\n\n%s" (pp-to-string status))
t)))))
;;;###autoload
(defun doom/version ()
"Display the current version of Doom & Emacs, including the current Doom
branch and commit."
(interactive)
(require 'vc-git)
(print! "Doom v%s (Emacs v%s)\nBranch: %s\nCommit: %s"
doom-version
emacs-version
(or (vc-git--symbolic-ref doom-core-dir)
"n/a")
(or (vc-git-working-revision doom-core-dir)
"n/a")))
;;;###autoload
(defun doom/copy-backtrace ()
"Copy the contents of the *Backtrace* window into your clipboard for easy
pasting into a bug report or discord."
(interactive)
(if-let* ((buf (get-buffer "*Backtrace*")))
(with-current-buffer buf
(kill-new
(string-trim (buffer-string))))
(user-error "No backtrace buffer detected")))
;;; Vanilla sandbox
(defvar doom--sandbox-init-doom-p nil)
(defun doom--run-vanilla-sandbox (&optional mode)
(interactive)
(let ((contents (buffer-string))
(file (make-temp-file "doom-sandbox-")))
(require 'package)
(with-temp-file file
(insert
(prin1-to-string
(macroexp-progn
(append `((setq debug-on-error t
package--init-file-ensured t
package-user-dir ,package-user-dir
package-archives ',package-archives
user-emacs-directory ,doom-emacs-dir
doom-modules ,doom-modules))
(pcase mode
(`vanilla-doom+ ; Doom core + modules - private config
`((setq doom-private-dir "/tmp/does/not/exist")
(load-file ,user-init-file)
(doom|run-all-startup-hooks)))
(`vanilla-doom ; only Doom core
`((setq doom-private-dir "/tmp/does/not/exist"
doom-init-modules-p t)
(load-file ,user-init-file)
(doom|run-all-startup-hooks)))
(`vanilla ; nothing loaded
`((package-initialize)))))))
"\n(unwind-protect (progn\n" contents "\n)\n"
(format "(delete-file %S))" file)))
(let ((args (if (eq mode 'doom)
(list "-l" file)
(list "-Q" "-l" file))))
(require 'restart-emacs)
(condition-case e
(cond ((display-graphic-p)
(if (memq system-type '(windows-nt ms-dos))
(restart-emacs--start-gui-on-windows args)
(restart-emacs--start-gui-using-sh args)))
((memq system-type '(windows-nt ms-dos))
(user-error "Cannot start another Emacs from Windows shell."))
((suspend-emacs
(format "%s %s -nw; fg"
(shell-quote-argument (restart-emacs--get-emacs-binary))
(string-join (mapcar #'shell-quote-argument args) " ")))))
(error
(delete-file file)
(signal (car e) (cdr e)))))))
(fset 'doom--run-vanilla-emacs (lambda! (doom--run-vanilla-sandbox 'vanilla)))
(fset 'doom--run-vanilla-doom (lambda! (doom--run-vanilla-sandbox 'vanilla-doom)))
(fset 'doom--run-vanilla-doom+ (lambda! (doom--run-vanilla-sandbox 'vanilla-doom+)))
(fset 'doom--run-full-doom (lambda! (doom--run-vanilla-sandbox 'doom)))
(defvar doom-sandbox-emacs-lisp-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-c") #'doom--run-vanilla-emacs)
(define-key map (kbd "C-c C-d") #'doom--run-vanilla-doom)
(define-key map (kbd "C-c C-p") #'doom--run-vanilla-doom+)
(define-key map (kbd "C-c C-f") #'doom--run-full-doom)
(define-key map (kbd "C-c C-k") #'kill-this-buffer)
map))
(define-derived-mode doom-sandbox-emacs-lisp-mode emacs-lisp-mode "Sandbox Elisp"
"TODO")
;;;###autoload
(defun doom/open-vanilla-sandbox ()
"Open the Emacs Lisp sandbox.
This is a test bed for running Emacs Lisp in an instance of Emacs with varying
amounts of Doom loaded, including:
a) vanilla Emacs (nothing loaded),
b) vanilla Doom (only Doom core) and
c) Doom + modules - your private config.
This is done without sacrificing access to installed packages. Use the sandbox
to reproduce bugs and determine if Doom is to blame."
(interactive)
(let* ((buffer-name "*doom:vanilla-sandbox*")
(exists (get-buffer buffer-name))
(buf (get-buffer-create buffer-name)))
(with-current-buffer buf
(doom-sandbox-emacs-lisp-mode)
(setq header-line-format
(concat "C-c C-c = vanilla Emacs"
" / "
"C-c C-d = Doom core"
" / "
"C-c C-p = Doom core + modules - private config"
" / "
"C-c C-f = Full Doom"
" / "
"C-c C-k to abort"))
(setq-local default-directory doom-emacs-dir)
(unless (buffer-live-p exists)
(doom-template-insert "VANILLA_SANDBOX")
(let ((contents (substitute-command-keys (buffer-string))))
(erase-buffer)
(insert contents "\n")))
(goto-char (point-max)))
(pop-to-buffer buf)))
;;; Reporting bugs
(defun doom--open-bug-report ()
"TODO"
(interactive)
(let ((url "https://github.com/hlissner/doom-emacs/issues/new?body="))
;; TODO Refactor me
(save-restriction
(widen)
(goto-char (point-min))
(re-search-forward "^-------------------------------------------------------------------\n" nil t)
(skip-chars-forward " \n\t")
(condition-case e
(progn
(save-excursion
(when (and (re-search-backward "\\+ [ ] " nil t)
(not (y-or-n-p "You haven't checked all the boxes. Continue anyway?")))
(error "Aborted submit")))
(narrow-to-region (point) (point-max))
(let ((text (buffer-string)))
;; `url-encode-url' doesn't encode ampersands
(setq text (replace-regexp-in-string "&" "%26" text))
(setq url (url-encode-url (concat url text)))
;; HACK: encode some characters according to HTML URL Encoding Reference
;; http://www.w3schools.com/tags/ref_urlencode.asp
(setq url (replace-regexp-in-string "#" "%23" url))
(setq url (replace-regexp-in-string ";" "%3B" url))
(browse-url url)))
(error (signal (car e) (car e)))))))
;;;###autoload
(defun doom/open-bug-report ()
"Open a markdown buffer destinated to populate the New Issue page on Doom
Emacs' issue tracker.
If called when a backtrace buffer is present, it and the output of `doom-info'
will be automatically appended to the result."
(interactive)
;; TODO Refactor me
(let ((backtrace
(when (get-buffer "*Backtrace*")
(with-current-buffer "*Backtrace*"
(string-trim
(buffer-substring-no-properties
(point-min)
(min (point-max) 1000))))))
(buf (get-buffer-create "*doom:vanilla-sandbox*")))
(with-current-buffer buf
(erase-buffer)
(condition-case _ (gfm-mode)
(error (text-mode)))
(doom-template-insert "SUBMIT_BUG_REPORT")
(goto-char (point-max))
(let ((pos (point)))
(save-excursion
(insert
"\n" (doom-info) "\n"
(if (and backtrace (not (string-empty-p backtrace)))
(format "\n<details>\n<summary>Backtrace</summary>\n\n```\n%s\n```\n</details>\n"
backtrace)
"")))
(local-set-key (kbd "C-c C-c") #'doom--open-bug-report)
(local-set-key (kbd "C-c C-k") #'kill-this-buffer)
(setq header-line-format "C-c C-c to submit / C-c C-k to close")
;;
(narrow-to-region (point-min) pos)
(goto-char (point-min)))
(pop-to-buffer buf))))
;;; Profiling
(defvar doom--profiler nil)
;;;###autoload
(defun doom/toggle-profiler ()
"Toggle the Emacs profiler. Run it again to see the profiling report."
(interactive)
(if (not doom--profiler)
(profiler-start 'cpu+mem)
@ -88,13 +328,48 @@ selection of all minor-modes, active or not."
(setq doom--profiler (not doom--profiler)))
;;;###autoload
(defun doom/info ()
"Collects information about this session of Doom Emacs and copies it to the
clipboard. Helpful when filing bug reports!"
(defun doom/profile-emacs ()
"Profile the startup time of Emacs in the background with ESUP.
If INIT-FILE is non-nil, profile that instead of USER-INIT-FILE."
(interactive)
(with-temp-buffer
(message "Producing information about your system...")
(call-process (expand-file-name "bin/doom-doctor" doom-emacs-dir) nil t)
(ansi-color-apply-on-region (point-min) (point-max))
(kill-new (buffer-string))
(message "Done. Copied to clipboard!")))
(require 'esup)
(let ((init-file esup-user-init-file))
(message "Starting esup...")
(esup-reset)
(setq esup-server-process (esup-server-create (esup-select-port)))
(setq esup-server-port (process-contact esup-server-process :service))
(message "esup process started on port %s" esup-server-port)
(let ((process-args
(append `("*esup-child*"
"*esup-child*"
,esup-emacs-path
"-Q"
"--eval=(setq after-init-time nil)"
"-L" ,esup-load-path)
(when (bound-and-true-p early-init-file)
`("-l" ,early-init-file))
`("-l" "esup-child"
,(format "--eval=(let ((load-file-name \"%s\")) (esup-child-run \"%s\" \"%s\" %d))"
init-file
init-file
esup-server-port
esup-depth)
"--eval=(doom|run-all-startup-hooks)"))))
(when esup-run-as-batch-p
(setq process-args (append process-args '("--batch"))))
(setq esup-child-process (apply #'start-process process-args)))
(set-process-sentinel esup-child-process 'esup-child-process-sentinel)))
;;;###autoload
(advice-add #'esup :override #'doom/profile-emacs)
;;;###autoload
(defun doom/toggle-debug-mode (&optional arg)
"Toggle `debug-on-error' and `doom-debug-mode' for verbose logging."
(interactive (list (or current-prefix-arg 'toggle)))
(let ((value
(cond ((eq arg 'toggle) (not doom-debug-mode))
((> (prefix-numeric-value arg) 0)))))
(setq doom-debug-mode value
debug-on-error value)
(message "Debug mode %s" (if value "on" "off"))))

View file

@ -1,246 +0,0 @@
;;; core/autoload/editor.el -*- lexical-binding: t; -*-
;;;###autoload
(defun doom/sudo-find-file (file)
"Open FILE as root."
(interactive
(list (read-file-name "Open as root: ")))
(find-file (if (file-writable-p file)
file
(concat "/sudo:root@localhost:" file))))
;;;###autoload
(defun doom/sudo-this-file ()
"Open the current file as root."
(interactive)
(doom/sudo-find-file (file-truename buffer-file-name)))
;;;###autoload
(defun doom/backward-to-bol-or-indent ()
"Move back to the current line's indentation. If already there, move to the
beginning of the line instead. If at bol, do nothing."
(interactive)
(if (bound-and-true-p visual-line-mode)
(beginning-of-visual-line)
(let ((ci (current-indentation))
(cc (current-column)))
(cond ((or (> cc ci) (= cc 0))
(back-to-indentation))
((<= cc ci)
(beginning-of-visual-line))))))
;;;###autoload
(defun doom/forward-to-last-non-comment-or-eol ()
"Move forward to the last non-blank character in the line, ignoring comments
and trailing whitespace. If already there, move to the real end of the line.
If already there, do nothing."
(interactive)
(let* ((point (point))
(eol (save-excursion (end-of-visual-line) (point)))
(bol (save-excursion (beginning-of-visual-line) (point)))
(eoc (or (if (not comment-use-syntax)
(when (re-search-forward comment-start-skip eol t)
(or (match-end 1) (match-beginning 0)))
(save-excursion
(goto-char eol)
(while (and (sp-point-in-comment)
(> (point) point))
(backward-char))
(when (> (point) point)
(skip-chars-backward " " bol)
(point))))
eol))
(goto-char-fn (if (featurep 'evil) #'evil-goto-char #'goto-char)))
(if (= eoc point)
(funcall goto-char-fn eol)
(unless (= eol point)
(funcall goto-char-fn eoc)))))
(defun doom--surrounded-p ()
(and (looking-back "[[{(]\\(\s+\\|\n\\)?\\(\s\\|\t\\)*" (line-beginning-position))
(let* ((whitespace (match-string 1))
(match-str (concat whitespace (match-string 2) "[])}]")))
(looking-at-p match-str))))
;;;###autoload
(defun doom/dumb-indent ()
"Inserts a tab character (or spaces x tab-width)."
(interactive)
(if indent-tabs-mode
(insert "\t")
(let* ((movement (% (current-column) tab-width))
(spaces (if (= 0 movement) tab-width (- tab-width movement))))
(insert (make-string spaces ? )))))
;;;###autoload
(defun doom/dumb-dedent ()
"Dedents the current line."
(interactive)
(if indent-tabs-mode
(call-interactively #'backward-delete-char)
(unless (bolp)
(save-excursion
(when (> (current-column) (current-indentation))
(back-to-indentation))
(let ((movement (% (current-column) tab-width)))
(delete-char
(- (if (= 0 movement)
tab-width
(- tab-width movement)))))))))
;;;###autoload
(defun doom/backward-kill-to-bol-and-indent ()
"Kill line to the first non-blank character. If invoked again
afterwards, kill line to column 1."
(interactive)
(let ((empty-line-p (save-excursion (beginning-of-line)
(looking-at-p "[ \t]*$"))))
(funcall (if (featurep 'evil)
#'evil-delete
#'delete-region)
(point-at-bol) (point))
(unless empty-line-p
(indent-according-to-mode))))
;;;###autoload
(defun doom/backward-delete-whitespace-to-column ()
"Delete back to the previous column of whitespace, or as much whitespace as
possible, or just one char if that's not possible."
(interactive)
(let* ((delete-backward-char (if (derived-mode-p 'org-mode)
#'org-delete-backward-char
#'delete-backward-char))
(context (sp--get-pair-list-context 'navigate))
(open-pair-re (sp--get-opening-regexp context))
(close-pair-re (sp--get-closing-regexp context))
open-len close-len)
(cond ;; When in strings (sp acts weird with quotes; this is the fix)
;; Also, skip closing delimiters
((and (and (sp--looking-back open-pair-re)
(setq open-len (- (match-beginning 0) (match-end 0))))
(and (looking-at close-pair-re)
(setq close-len (- (match-beginning 0) (match-end 0))))
(string= (plist-get (sp-get-thing t) :op)
(plist-get (sp-get-thing) :cl)))
(delete-char (- 0 open-len))
(delete-char close-len))
;; Delete up to the nearest tab column IF only whitespace between
;; point and bol.
((save-match-data (looking-back "^[\\t ]*" (line-beginning-position)))
(let ((movement (% (current-column) tab-width))
(p (point)))
(when (= movement 0)
(setq movement tab-width))
(save-match-data
(if (string-match "\\w*\\(\\s-+\\)$"
(buffer-substring-no-properties (max (point-min) (- p movement)) p))
(sp-delete-char
(- 0 (- (match-end 1)
(match-beginning 1))))
(call-interactively delete-backward-char)))))
;; Otherwise do a regular delete
(t (call-interactively delete-backward-char)))))
;;;###autoload
(defun doom/inflate-space-maybe ()
"Checks if point is surrounded by {} [] () delimiters and adds a
space on either side of the point if so."
(interactive)
(let ((command (or (command-remapping #'self-insert-command)
#'self-insert-command)))
(cond ((doom--surrounded-p)
(call-interactively command)
(save-excursion (call-interactively command)))
(t
(call-interactively command)))))
;;;###autoload
(defun doom/deflate-space-maybe ()
"Checks if point is surrounded by {} [] () delimiters, and deletes
spaces on either side of the point if so. Resorts to
`doom/backward-delete-whitespace-to-column' otherwise."
(interactive)
(save-match-data
(if (doom--surrounded-p)
(let ((whitespace-match (match-string 1)))
(cond ((not whitespace-match)
(call-interactively #'delete-backward-char))
((string-match "\n" whitespace-match)
(funcall (if (featurep 'evil)
#'evil-delete
#'delete-region)
(point-at-bol) (point))
(call-interactively #'delete-backward-char)
(save-excursion (call-interactively #'delete-char)))
(t (just-one-space 0))))
(doom/backward-delete-whitespace-to-column))))
;;;###autoload
(defun doom/newline-and-indent ()
"Inserts a newline and possibly indents it. Also continues comments if
executed from a commented line; handling special cases for certain languages
with weak native support."
(interactive)
(cond ((sp-point-in-string)
(newline))
((sp-point-in-comment)
(pcase major-mode
((or 'js2-mode 'rjsx-mode)
(call-interactively #'js2-line-break))
((or 'java-mode 'php-mode)
(c-indent-new-comment-line))
((or 'c-mode 'c++-mode 'objc-mode 'css-mode 'scss-mode 'js2-mode)
(newline-and-indent)
(insert "* ")
(indent-according-to-mode))
(_
;; Fix an off-by-one cursor-positioning issue
;; with `indent-new-comment-line'
(let ((col (save-excursion (comment-beginning) (current-column))))
(indent-new-comment-line)
(unless (= col (current-column))
(insert " "))))))
(t
(newline nil t)
(indent-according-to-mode))))
;;;###autoload
(defun doom/retab (&optional beg end)
"Changes all tabs to spaces or spaces to tabs, so that indentation is
consistent throughout a selected region, depending on `indent-tab-mode'."
(interactive "r")
(unless (and beg end)
(setq beg (point-min)
end (point-max)))
(if indent-tabs-mode
(tabify beg end)
(untabify beg end)))
;;;###autoload
(defun doom/narrow-buffer (beg end &optional clone-p)
"Restrict editing in this buffer to the current region, indirectly. With CLONE-P,
clone the buffer and hard-narrow the selection. If mark isn't active, then widen
the buffer (if narrowed).
Inspired from http://demonastery.org/2013/04/emacs-evil-narrow-region/"
(interactive "r")
(cond ((region-active-p)
(deactivate-mark)
(when clone-p
(let ((old-buf (current-buffer)))
(switch-to-buffer (clone-indirect-buffer nil nil))
(setq doom-buffer--narrowed-origin old-buf)))
(narrow-to-region beg end))
(doom-buffer--narrowed-origin
(kill-this-buffer)
(switch-to-buffer doom-buffer--narrowed-origin)
(setq doom-buffer--narrowed-origin nil))
(t
(widen))))
;;;###autoload
(defun doom|enable-delete-trailing-whitespace ()
"Attaches `delete-trailing-whitespace' to a buffer-local `before-save-hook'."
(add-hook 'before-save-hook #'delete-trailing-whitespace nil t))

210
core/autoload/files.el Normal file
View file

@ -0,0 +1,210 @@
;;; core/autoload/files.el -*- lexical-binding: t; -*-
;;
;; Public library
;;;###autoload
(cl-defun doom-files-in
(path-or-paths &rest rest
&key
filter
map
full
nosort
(follow-symlinks t)
(type 'files)
(relative-to (unless full default-directory))
(depth 99999)
(mindepth 0)
(match "/[^.]"))
"Returns a list of files/directories in PATH-OR-PATHS (one string path or a
list of them).
FILTER is a function or symbol that takes one argument (the path). If it returns
non-nil, the entry will be excluded.
MAP is a function or symbol which will be used to transform each entry in the
results.
TYPE determines what kind of path will be included in the results. This can be t
(files and folders), 'files or 'dirs.
By default, this function returns paths relative to PATH-OR-PATHS if it is a
single path. If it a list of paths, this function returns absolute paths.
Otherwise, by setting RELATIVE-TO to a path, the results will be transformed to
be relative to it.
The search recurses up to DEPTH and no further. DEPTH is an integer.
MATCH is a string regexp. Only entries that match it will be included."
(cond
((listp path-or-paths)
(cl-loop for path in path-or-paths
if (file-directory-p path)
nconc (apply #'doom-files-in path (plist-put rest :relative-to relative-to))))
((let ((path path-or-paths)
result)
(when (file-directory-p path)
(dolist (file (directory-files path nil "." nosort))
(unless (member file '("." ".."))
(let ((fullpath (expand-file-name file path)))
(cond ((file-directory-p fullpath)
(when (and (memq type '(t dirs))
(string-match-p match fullpath)
(not (and filter (funcall filter fullpath)))
(not (and (file-symlink-p fullpath)
(not follow-symlinks)))
(<= mindepth 0))
(setq result
(nconc result
(list (cond (map (funcall map fullpath))
(relative-to (file-relative-name fullpath relative-to))
(fullpath))))))
(unless (< depth 1)
(setq result
(nconc result (apply #'doom-files-in fullpath
(append `(:mindepth ,(1- mindepth)
:depth ,(1- depth)
:relative-to ,relative-to)
rest))))))
((and (memq type '(t files))
(string-match-p match fullpath)
(not (and filter (funcall filter fullpath)))
(<= mindepth 0))
(push (if relative-to
(file-relative-name fullpath relative-to)
fullpath)
result))))))
result)))))
;;
;; Helpers
(defun doom--forget-file (old-path &optional new-path)
"Ensure `recentf', `projectile' and `save-place' forget OLD-PATH."
(when (bound-and-true-p recentf-mode)
(when new-path
(recentf-add-file new-path))
(recentf-remove-if-non-kept old-path))
(when (and (bound-and-true-p projectile-mode)
(doom-project-p)
(projectile-file-cached-p old-path (doom-project-root)))
(projectile-purge-file-from-cache old-path))
(when (bound-and-true-p save-place-mode)
(save-place-forget-unreadable-files)))
(defun doom--update-file (path)
(when (featurep 'vc)
(vc-file-clearprops path)
(vc-resynch-buffer path nil t))
(when (featurep 'magit)
(magit-refresh)))
(defun doom--copy-file (old-path new-path &optional force-p)
(let* ((new-path (expand-file-name new-path))
(old-path (file-truename old-path))
(new-path (apply #'expand-file-name
(if (or (directory-name-p new-path)
(file-directory-p new-path))
(list (file-name-nondirectory old-path) new-path)
(list new-path))))
(new-path-dir (file-name-directory new-path))
(project-root (doom-project-root))
(short-new-name (if (and project-root (file-in-directory-p new-path project-root))
(file-relative-name new-path project-root)
(abbreviate-file-name new-path))))
(unless (file-directory-p new-path-dir)
(make-directory new-path-dir t))
(when (buffer-modified-p)
(save-buffer))
(cond ((file-equal-p old-path new-path)
(throw 'status 'overwrite-self))
((and (file-exists-p new-path)
(not force-p)
(not (y-or-n-p (format "File already exists at %s, overwrite?" short-new-name))))
(throw 'status 'aborted))
((file-exists-p old-path)
(copy-file old-path new-path t)
short-new-name)
(short-new-name))))
;;
;; Commands
;;;###autoload
(defun doom/delete-this-file (&optional path force-p)
"Delete FILENAME (defaults to the file associated with current buffer) and
kills the buffer. If FORCE-P, force the deletion (don't ask for confirmation)."
(interactive
(list (file-truename (buffer-file-name))
current-prefix-arg))
(let* ((fbase (file-name-sans-extension (file-name-nondirectory path)))
(buf (current-buffer)))
(cond ((not (file-exists-p path))
(error "File doesn't exist: %s" path))
((not (or force-p (y-or-n-p (format "Really delete %s?" fbase))))
(message "Aborted")
nil)
((unwind-protect
(progn (delete-file path) t)
(let ((short-path (file-relative-name path (doom-project-root))))
(if (file-exists-p path)
(error "Failed to delete %s" short-path)
;; Ensures that windows displaying this buffer will be switched
;; to real buffers (`doom-real-buffer-p')
(doom/kill-this-buffer-in-all-windows buf t)
(doom--forget-file path)
(doom--update-file path)
(message "Successfully deleted %s" short-path))))))))
;;;###autoload
(defun doom/copy-this-file (new-path &optional force-p)
"Copy current buffer's file to NEW-PATH. If FORCE-P, overwrite the destination
file if it exists, without confirmation."
(interactive "F")
(pcase (catch 'status
(when-let* ((dest (doom--copy-file (buffer-file-name) new-path force-p)))
(doom--update-file new-path)
(message "File successfully copied to %s" dest)))
(`overwrite-self (error "Cannot overwrite self"))
(`aborted (message "Aborted"))
(_ t)))
;;;###autoload
(defun doom/move-this-file (new-path &optional force-p)
"Move current buffer's file to NEW-PATH. If FORCE-P, overwrite the destination
file if it exists, without confirmation."
(interactive "FP")
(pcase (catch 'status
(let ((old-path (buffer-file-name))
(new-path (expand-file-name new-path)))
(when-let* ((dest (doom--copy-file old-path new-path force-p)))
(when (file-exists-p old-path)
(delete-file old-path))
(kill-this-buffer)
(doom--forget-file old-path new-path)
(doom--update-file new-path)
(find-file new-path)
(message "File successfully moved to %s" dest))))
(`overwrite-self (error "Cannot overwrite self"))
(`aborted (message "Aborted"))
(_ t)))
;;;###autoload
(defun doom/sudo-find-file (file)
"Open FILE as root."
(interactive
(list (read-file-name "Open as root: ")))
(when (file-writable-p file)
(user-error "File is user writeable, aborting sudo"))
(find-file (if (file-remote-p file)
(concat "/" (file-remote-p file 'method) ":" (file-remote-p file 'user) "@" (file-remote-p file 'host) "|sudo:root@" (file-remote-p file 'host) ":" (file-remote-p file 'localname))
(concat "/sudo:root@localhost:" file))))
;;;###autoload
(defun doom/sudo-this-file ()
"Open the current file as root."
(interactive)
(doom/sudo-find-file (file-truename buffer-file-name)))

View file

@ -1,33 +1,337 @@
;;; core/autoload/help.el -*- lexical-binding: t; -*-
;;;###autoload
(defun doom/describe-setting (setting)
"Open the documentation of SETTING (a keyword defined with `def-setting!')."
(interactive
;; TODO try to read setting from whole line
(list (completing-read "Describe setting%s: "
(sort (mapcar #'car doom-settings) #'string-lessp)
nil t nil nil)))
(let ((fn (cdr (assq (intern setting) doom-settings))))
(unless fn
(error "'%s' is not a valid DOOM setting" setting))
(describe-function fn)))
(defvar doom--module-mode-alist
'((dockerfile-mode :tools docker)
(haxor-mode :lang assembly)
(mips-mode :lang assembly)
(nasm-mode :lang assembly)
(c-mode :lang cc)
(c++-mode :lang cc)
(objc++-mode :lang cc)
(crystal-mode :lang crystal)
(lisp-mode :lang common-lisp)
(csharp-mode :lang csharp)
(clojure-mode :lang clojure)
(graphql-mode :lang data)
(toml-mode :lang data)
(json-mode :lang data)
(yaml-mode :lang data)
(csv-mode :lang data)
(dhall-mode :lang data)
(erlang-mode :lang erlang)
(elixir-mode :lang elixir)
(elm-mode :lang elm)
(emacs-lisp-mode :lang emacs-lisp)
(ess-r-mode :lang ess)
(ess-julia-mode :lang ess)
(go-mode :lang go)
(haskell-mode :lang haskell)
(hy-mode :lang hy)
(java-mode :lang java)
(js2-mode :lang javascript)
(rjsx-mode :lang javascript)
(typescript-mode :lang javascript)
(coffee-mode :lang javascript)
(julia-mode :lang julia)
(latex-mode :lang latex)
(LaTeX-mode :lang latex)
(ledger-mode :lang ledger)
(lua-mode :lang lua)
(markdown-mode :lang markdown)
(gfm-mode :lang markdown)
(nim-mode :lang nim)
(nix-mode :lang nix)
(taureg-mode :lang ocaml)
(org-mode :lang org)
(perl-mode :lang perl)
(php-mode :lang php)
(hack-mode :lang php)
(plantuml-mode :lang plantuml)
(purescript-mode :lang purescript)
(python-mode :lang python)
(restclient-mode :lang rest)
(ruby-mode :lang ruby)
(enh-ruby-mode :lang ruby)
(rust-mode :lang rust)
(scala-mode :lang scala)
(sh-mode :lang sh)
(swift-mode :lang swift)
(web-mode :lang web)
(css-mode :lang web)
(scss-mode :lang web)
(sass-mode :lang web)
(less-css-mode :lang web)
(stylus-mode :lang web))
"TODO")
;;
;; Helpers
;;;###autoload
(defun doom/describe-module (module)
"Open the documentation of MODULE (a string that represents the category and
submodule in the format, e.g. ':feature evil')."
(defun doom-active-minor-modes ()
"Return a list of active minor-mode symbols."
(cl-loop for mode in minor-mode-list
if (and (boundp mode) (symbol-value mode))
collect mode))
;;
;; Commands
;;;###autoload
(defun doom/describe-autodefs (autodef)
"Open the documentation of Doom autodefs.
What is an autodef? It's a function or macro that is always defined, even if its
containing module is disabled (in which case it will safely no-op). This
syntactic sugar lets you use them without needing to check if they are
available."
(interactive
;; TODO try to read module from whole line
(list (completing-read "Describe module: "
(cl-loop for (module . sub) in (reverse (hash-table-keys doom-modules))
collect (format "%s %s" module sub))
nil t)))
(cl-destructuring-bind (category submodule)
(mapcar #'intern (split-string module " "))
(unless (member (cons category submodule) (doom-module-pairs))
(error "'%s' isn't a valid module" module))
(let ((doc-path (expand-file-name "README.org" (doom-module-path category submodule))))
(unless (file-exists-p doc-path)
(error "There is no documentation for this module"))
(find-file doc-path))))
(let* ((settings
(cl-loop with case-fold-search = nil
for sym being the symbols of obarray
for sym-name = (symbol-name sym)
if (and (or (functionp sym)
(macrop sym))
(string-match-p "[a-z]!$" sym-name))
collect sym))
(sym (symbol-at-point))
(autodef
(completing-read
"Describe setter: "
;; TODO Could be cleaner (refactor me!)
(cl-loop with maxwidth = (apply #'max (mapcar #'length (mapcar #'symbol-name settings)))
for def in (sort settings #'string-lessp)
if (get def 'doom-module)
collect
(format (format "%%-%ds%%s" (+ maxwidth 4))
def (propertize (format "%s %s" (car it) (cdr it))
'face 'font-lock-comment-face))
else if (and (string-match-p "^set-.+!$" (symbol-name def))
(symbol-file def)
(file-in-directory-p (symbol-file def) doom-core-dir))
collect
(format (format "%%-%ds%%s" (+ maxwidth 4))
def (propertize (format "core/%s.el" (file-name-sans-extension (file-relative-name (symbol-file def) doom-core-dir)))
'face 'font-lock-comment-face)))
nil t
(when (and (symbolp sym)
(string-match-p "!$" (symbol-name sym)))
(symbol-name sym)))))
(list (and autodef (car (split-string autodef " "))))))
(or (stringp autodef)
(functionp autodef)
(signal 'wrong-type-argument (list '(stringp functionp) autodef)))
(let ((fn (if (functionp autodef)
autodef
(intern-soft autodef))))
(or (fboundp fn)
(error "'%s' is not a valid DOOM autodef" autodef))
(if (fboundp 'helpful-callable)
(helpful-callable fn)
(describe-function fn))))
;;;###autoload
(defun doom/describe-active-minor-mode (mode)
"Get information on an active minor mode. Use `describe-minor-mode' for a
selection of all minor-modes, active or not."
(interactive
(list (completing-read "Minor mode: " (doom-active-minor-modes))))
(describe-minor-mode-from-symbol
(cond ((stringp mode) (intern mode))
((symbolp mode) mode)
((error "Expected a symbol/string, got a %s" (type-of mode))))))
;;;###autoload
(defun doom/describe-module (category module)
"Open the documentation of CATEGORY MODULE.
CATEGORY is a keyword and MODULE is a symbol. e.g. :feature and 'evil.
Automatically selects a) the module at point (in private init files), b) the
module derived from a `featurep!' or `require!' call, c) the module that the
current file is in, or d) the module associated with the current major mode (see
`doom--module-mode-alist')."
(interactive
(let* ((module
(cond ((and buffer-file-name
(eq major-mode 'emacs-lisp-mode)
(file-in-directory-p buffer-file-name doom-private-dir)
(save-excursion (goto-char (point-min))
(re-search-forward "^\\s-*(doom! " nil t))
(thing-at-point 'sexp t)))
((save-excursion
(require 'smartparens)
(ignore-errors
(sp-beginning-of-sexp)
(unless (eq (char-after) ?\()
(backward-char))
(let ((sexp (sexp-at-point)))
(when (memq (car-safe sexp) '(featurep! require!))
(format "%s %s" (nth 1 sexp) (nth 2 sexp)))))))
((and buffer-file-name
(when-let* ((mod (doom-module-from-path buffer-file-name)))
(format "%s %s" (car mod) (cdr mod)))))
((when-let* ((mod (cdr (assq major-mode doom--module-mode-alist))))
(format "%s %s"
(symbol-name (car mod))
(symbol-name (cadr mod)))))))
(module-string
(completing-read
"Describe module: "
(cl-loop for path in (doom-module-load-path 'all)
for (cat . mod) = (doom-module-from-path path)
for format = (format "%s %s" cat mod)
if (doom-module-p cat mod)
collect format
else
collect (propertize format 'face 'font-lock-comment-face))
nil t nil nil module))
(key (split-string module-string " ")))
(list (intern (car key))
(intern (cadr key)))))
(cl-check-type category symbol)
(cl-check-type module symbol)
(let ((path (doom-module-locate-path category module)))
(unless (file-readable-p path)
(error "'%s %s' isn't a valid module; it doesn't exist" category module))
(if-let* ((readme-path (doom-module-locate-path category module "README.org")))
(find-file readme-path)
(if (y-or-n-p (format "The '%s %s' module has no README file. Explore its directory?"
category module))
(doom-project-browse path)
(user-error "Aborted module lookup")))))
(defun doom--describe-package-insert-button (label path &optional regexp)
(declare (indent defun))
(insert-text-button
(string-trim label)
'face 'link
'follow-link t
'action
`(lambda (_)
(unless (file-exists-p ,path)
(user-error "Module doesn't exist"))
(when (window-dedicated-p)
(other-window 1))
(let ((buffer (find-file ,path)))
(when ,(stringp regexp)
(with-current-buffer buffer
(goto-char (point-min))
(if (re-search-forward ,regexp nil t)
(recenter)
(message "Couldn't find the config block"))))))))
;;;###autoload
(global-set-key [remap describe-package] #'doom/describe-package)
(defvar doom--describe-package-list-cache nil)
;;;###autoload
(defun doom/describe-package (package)
"Like `describe-packages', but is Doom aware.
Only shows installed packages. Includes information about where packages are
defined and configured.
If prefix arg is prsent, refresh the cache."
(interactive
(list
(let* ((guess (or (function-called-at-point)
(symbol-at-point))))
(require 'finder-inf nil t)
(require 'core-packages)
(doom-initialize-packages)
(let ((packages
(or (unless current-prefix-arg doom--describe-package-list-cache)
(cl-loop for pkg
in (cl-delete-duplicates
(sort (append (mapcar #'car package-alist)
(mapcar #'car package-archive-contents)
(mapcar #'car package--builtins))
#'string-greaterp))
if (assq pkg package-alist)
collect (symbol-name pkg)
else
collect (propertize (symbol-name pkg) 'face 'font-lock-comment-face)))))
(unless (memq guess packages)
(setq guess nil))
(setq doom--describe-package-list-cache packages)
(intern
(completing-read
(if guess
(format "Describe package (default %s): "
guess)
"Describe package: ")
packages nil t nil nil
(if guess (symbol-name guess))))))))
(describe-package package)
(save-excursion
(with-current-buffer (help-buffer)
(let ((inhibit-read-only t))
(goto-char (point-min))
(when (and (doom-package-installed-p package)
(re-search-forward "^ *Status: " nil t))
(end-of-line)
(let ((indent (make-string (length (match-string 0)) ? )))
(insert "\n" indent "Installed by the following Doom modules:\n")
(dolist (m (get package 'doom-module))
(insert indent)
(doom--describe-package-insert-button
(format " %s %s" (car m) (or (cdr m) ""))
(pcase (car m)
(:core doom-core-dir)
(:private doom-private-dir)
(category (doom-module-path category (cdr m)))))
(insert "\n"))
(package--print-help-section "Source")
(pcase (doom-package-backend package)
(`elpa (insert "[M]ELPA"))
(`quelpa (insert (format "QUELPA %s" (prin1-to-string (doom-package-prop package :recipe)))))
(`emacs (insert "Built-in")))
(insert "\n")
(package--print-help-section "Configs")
(dolist (file (get package 'doom-files))
(doom--describe-package-insert-button
(abbreviate-file-name file)
file
(format "\\((\\(:?after!\\|def-package!\\)[ \t\n]*%s\\|^[ \t]*;; `%s'$\\)"
package package))
(insert "\n" indent))
(delete-char -1)))))))
;;;###autoload
(defun doom/describe-symbol (symbol)
"Show help for SYMBOL, a variable, function or macro."
(interactive
(list (helpful--read-symbol "Symbol: " #'helpful--bound-p)))
(let* ((sym (intern-soft symbol))
(bound (boundp sym))
(fbound (fboundp sym)))
(cond ((and sym bound (not fbound))
(helpful-variable sym))
((and sym fbound (not bound))
(helpful-callable sym))
((apropos (format "^%s\$" symbol)))
((apropos (format "%s" symbol))))))
;;;###autoload
(defalias 'doom/help 'doom/open-manual)
;;;###autoload
(defun doom/open-manual ()
"TODO"
(interactive)
(user-error "This command isn't implemented yet")
;; (find-file (expand-file-name "index.org" doom-docs-dir))
)
;;;###autoload
(defun doom/open-news ()
"TODO"
(interactive)
(user-error "This command isn't implemented yet")
;; (find-file (expand-file-name (concat "news/" doom-version) doom-docs-dir))
)

47
core/autoload/hydras.el Normal file
View file

@ -0,0 +1,47 @@
;;; core/autoload/hydras.el -*- lexical-binding: t; -*-
;;;###autoload (autoload 'doom-text-zoom-hydra/body "core/autoload/hydras" nil t)
(defhydra doom-text-zoom-hydra (:hint t :color red)
"
Text zoom: _j_:zoom in, _k_:zoom out, _0_:reset
"
("j" text-scale-increase "in")
("k" text-scale-decrease "out")
("0" (text-scale-set 0) "reset"))
;;;###autoload (autoload 'doom-window-nav-hydra/body "core/autoload/hydras" nil t)
(defhydra doom-window-nav-hydra (:hint nil)
"
Split: _v_ert _s_:horz
Delete: _c_lose _o_nly
Switch Window: _h_:left _j_:down _k_:up _l_:right
Buffers: _p_revious _n_ext _b_:select _f_ind-file
Resize: _H_:splitter left _J_:splitter down _K_:splitter up _L_:splitter right
Move: _a_:up _z_:down _i_menu
"
("z" scroll-up-line)
("a" scroll-down-line)
("i" idomenu)
("h" windmove-left)
("j" windmove-down)
("k" windmove-up)
("l" windmove-right)
("p" previous-buffer)
("n" next-buffer)
("b" switch-to-buffer)
("f" find-file)
("s" split-window-below)
("v" split-window-right)
("c" delete-window)
("o" delete-other-windows)
("H" hydra-move-splitter-left)
("J" hydra-move-splitter-down)
("K" hydra-move-splitter-up)
("L" hydra-move-splitter-right)
("q" nil))

View file

@ -0,0 +1,92 @@
;;; core/autoload/line-numbers.el -*- lexical-binding: t; -*-
;;;###if (version< emacs-version "26.1")
;; This was lifted out of the display-line-numbers library in Emacs 26.1 and
;; modified to use nlinum for Emacs 25.x users. It should be removed should
;; Emacs 25 support be removed.
;;;###autoload
(defvar display-line-numbers t
"Non-nil means display line numbers.
If the value is t, display the absolute number of each line of a buffer
shown in a window. Absolute line numbers count from the beginning of
the current narrowing, or from buffer beginning. If the value is
relative, display for each line not containing the window's point its
relative number instead, i.e. the number of the line relative to the
line showing the window's point.
In either case, line numbers are displayed at the beginning of each
non-continuation line that displays buffer text, i.e. after each newline
character that comes from the buffer. The value visual is like
relative but counts screen lines instead of buffer lines. In practice
this means that continuation lines count as well when calculating the
relative number of a line.
Lisp programs can disable display of a line number of a particular
buffer line by putting the display-line-numbers-disable text property
or overlay property on the first visible character of that line.")
(defgroup display-line-numbers nil "Display line number preferences"
:group 'emacs)
;;;###autoload
(defcustom display-line-numbers-type t
"The default type of line numbers to use in `display-line-numbers-mode'.
See `display-line-numbers' for value options."
:type '(choice (const :tag "Relative line numbers" relative)
(const :tag "Relative visual line numbers" visual)
(other :tag "Absolute line numbers" t)))
;;;###autoload
(defcustom display-line-numbers-grow-only nil
"If non-nil, do not shrink line number width."
:type 'boolean)
;;;###autoload
(defcustom display-line-numbers-width-start nil
"If non-nil, count number of lines to use for line number width.
When `display-line-numbers-mode' is turned on,
`display-line-numbers-width' is set to the minimum width necessary
to display all line numbers in the buffer."
:type 'boolean)
;;;###autoload
(defun line-number-display-width ()
"Return the width used for displaying line numbers in the
selected window."
(length (save-excursion (goto-char (point-max))
(format-mode-line "%l"))))
(defun display-line-numbers-update-width ()
"Prevent the line number width from shrinking."
(let ((width (line-number-display-width)))
(when (> width (or display-line-numbers-width 1))
(setq display-line-numbers-width width))))
;;;###autoload
(define-minor-mode display-line-numbers-mode
"Toggle display of line numbers in the buffer.
This uses `display-line-numbers' internally.
To change the type of line numbers displayed by default,
customize `display-line-numbers-type'. To change the type while
the mode is on, set `display-line-numbers' directly."
:lighter nil
(cond ((null display-line-numbers-type))
((eq display-line-numbers-type 'relative)
(if display-line-numbers-mode
(nlinum-relative-off)
(nlinum-relative-on)))
((nlinum-mode (if display-line-numbers-mode +1 -1)))))
(defun display-line-numbers--turn-on ()
"Turn on `display-line-numbers-mode'."
(unless (or (minibufferp)
;; taken from linum.el
(and (daemonp) (null (frame-parameter nil 'client))))
(display-line-numbers-mode)))
;;;###autoload
(define-globalized-minor-mode global-display-line-numbers-mode
display-line-numbers-mode display-line-numbers--turn-on)

View file

@ -1,31 +0,0 @@
;;; core/autoload/memoize.el -*- lexical-binding: t; -*-
;;;###autoload
(defvar doom-memoized-table (make-hash-table :test 'equal :size 10)
"A lookup table containing memoized functions. The keys are argument lists,
and the value is the function's return value.")
;;;###autoload
(defun doom-memoize (name)
"Memoizes an existing function. NAME is a symbol."
(let ((func (symbol-function name)))
(put name 'function-documentation
(concat (documentation func) " (memoized)"))
(fset name
`(lambda (&rest args)
(let ((key (cons ',name args)))
(or (gethash key doom-memoized-table)
(puthash key (apply ',func args)
doom-memoized-table)))))))
;;;###autoload
(defmacro def-memoized! (name arglist &rest body)
"Create a memoize'd function. NAME, ARGLIST, DOCSTRING and BODY
have the same meaning as in `defun'."
(declare (indent defun) (doc-string 3))
`(,(if (bound-and-true-p byte-compile-current-file)
'with-no-warnings
'progn)
(defun ,name ,arglist ,@body)
(doom-memoize ',name)))

View file

@ -1,104 +0,0 @@
;;; ../core/autoload/menu.el -*- lexical-binding: t; -*-
;; Command dispatchers: basically M-x, but context sensitive, customizable and
;; persistent across Emacs sessions.
(defvar doom-menu-display-fn #'doom-menu-read-default
"The method to use to prompt the user with the menu. This takes two arguments:
PROMPT (a string) and COMMAND (a list of command plists; see `def-menu!').")
(defun doom-menu-read-default (prompt commands)
"Default method for displaying a completion-select prompt."
(completing-read prompt (mapcar #'car commands)))
(defun doom--menu-read (prompt commands)
(if-let* ((choice (funcall doom-menu-display-fn prompt commands)))
(cdr (assoc choice commands))
(user-error "Aborted")))
(defun doom--menu-exec (plist)
(let ((command (plist-get plist :exec))
(cwd (plist-get plist :cwd)))
(let ((default-directory
(cond ((eq cwd t) (doom-project-root))
((stringp cwd) cwd)
(t default-directory))))
(cond ((stringp command)
(with-current-buffer (get-buffer-create "*compilation*")
(setq command (doom-resolve-vim-path command))
(save-window-excursion
(compile command))
(setq header-line-format
(concat (propertize "$ " 'face 'font-lock-doc-face)
(propertize command 'face 'font-lock-preprocessor-face)))
(doom-resize-window
(doom-popup-buffer (current-buffer)
'(:autokill t :autoclose t)) 12)))
((or (symbolp command)
(functionp command))
(call-interactively command))
((and command (listp command))
(eval command t))
(t
(error "Not a valid command: %s" command))))))
;;;###autoload
(defmacro def-menu! (name desc commands &rest plist)
"Defines a menu and returns a function symbol for invoking it.
A dispatcher is an interactive command named NAME (a symbol). When called, this
dispatcher prompts you to select a command to run. This list is filtered
depending on its properties. Each command is takes the form of:
(DESCRIPTION :exec COMMAND &rest PROPERTIES)
PROPERTIES accepts the following properties:
:when FORM
:unless FORM
:region BOOL
:cwd t|PATH
:project BOOL|DIRECTORY
COMMAND can be a string (a shell command), a symbol (an elisp function) or a
lisp form.
`def-menu!'s PLIST supports the following properties:
:prompt STRING"
(declare (indent defun) (doc-string 2))
(let ((commands-var (intern (format "%s-commands" name)))
(prop-prompt (or (plist-get plist :prompt) "> "))
(prop-sort (plist-get plist :sort)))
`(progn
(defvar ,commands-var
,(if prop-sort
`(cl-sort ,commands #'string-lessp :key #'car)
commands)
,(format "Menu for %s" name))
(defun ,name ()
,desc
(interactive)
(unless ,commands-var
(user-error "The '%s' menu is empty" ',name))
(doom--menu-exec
(or (doom--menu-read
,prop-prompt
(or (cl-remove-if-not
(let ((project-root (doom-project-root)))
(lambda (cmd)
(let ((plist (cdr cmd)))
(and (cond ((not (plist-member plist :region)) t)
((plist-get plist :region) (use-region-p))
(t (not (use-region-p))))
(let ((when (plist-get plist :when))
(unless (plist-get plist :unless))
(project (plist-get plist :project)))
(or (or (not when) (eval when))
(or (not unless) (not (eval unless)))
(and (stringp project)
(file-in-directory-p buffer-file-name project-root))))))))
,commands-var)
(user-error "No commands available here")))
(user-error "No command selected")))))))

View file

@ -1,87 +1,91 @@
;;; core/autoload/message.el -*- lexical-binding: t; -*-
(defconst doom-message-fg
'((reset . 0)
(black . 30)
(red . 31)
(green . 32)
(yellow . 33)
(blue . 34)
(magenta . 35)
(cyan . 36)
(white . 37))
"List of text colors.")
(defvar doom-ansi-alist
'(;; fx
(bold 1 :weight bold)
(dark 2)
(italic 3 :slant italic)
(underscore 4 :underline t)
(blink 5)
(rapid 6)
(contrary 7)
(concealed 8)
(strike 9 :strike-through t)
;; fg
(black 30 term-color-black)
(red 31 term-color-red)
(green 32 term-color-green)
(yellow 33 term-color-yellow)
(blue 34 term-color-blue)
(magenta 35 term-color-magenta)
(cyan 36 term-color-cyan)
(white 37 term-color-white)
;; bg
(on-black 40 term-color-black)
(on-red 41 term-color-red)
(on-green 42 term-color-green)
(on-yellow 43 term-color-yellow)
(on-blue 44 term-color-blue)
(on-magenta 45 term-color-magenta)
(on-cyan 46 term-color-cyan)
(on-white 47 term-color-white))
"TODO")
(defconst doom-message-bg
'((on-black . 40)
(on-red . 41)
(on-green . 42)
(on-yellow . 43)
(on-blue . 44)
(on-magenta . 45)
(on-cyan . 46)
(on-white . 47))
"List of colors to draw text on.")
;;;###autoload
(defun doom-color-apply (style text)
"Apply CODE to formatted MESSAGE with ARGS. CODE is derived from any of
`doom-message-fg', `doom-message-bg' or `doom-message-fx'.
(defconst doom-message-fx
'((bold . 1)
(dark . 2)
(italic . 3)
(underscore . 4)
(blink . 5)
(rapid . 6)
(contrary . 7)
(concealed . 8)
(strike . 9))
"List of styles.")
In a noninteractive session, this wraps the result in ansi color codes.
Otherwise, it maps colors to a term-color-* face."
(let ((code (cadr (assq style doom-ansi-alist))))
(if noninteractive
(format "\e[%dm%s\e[%dm"
(cadr (assq style doom-ansi-alist))
text 0)
(require 'term) ; piggyback on term's color faces
(propertize
text 'face
(append (get-text-property 0 'face text)
(cond ((>= code 40)
`(:background ,(caddr (assq style doom-ansi-alist))))
((>= code 30)
`(:foreground ,(face-foreground (caddr (assq style doom-ansi-alist)))))
((cddr (assq style doom-ansi-alist)))))))))
(defun doom--short-color-replace (forms)
"Replace color-name functions with calls to `doom-color-apply'."
(cond ((null forms) nil)
((listp forms)
(append (cond ((not (symbolp (car forms)))
(list (doom--short-color-replace (car forms))))
((assq (car forms) doom-ansi-alist)
`(doom-color-apply ',(car forms)))
((eq (car forms) 'color)
(pop forms)
`(doom-color-apply ,(car forms)))
((list (car forms))))
(doom--short-color-replace (cdr forms))
nil))
(forms)))
;;;###autoload
(defmacro format! (message &rest args)
"An alternative to `format' that strips out ANSI codes if used in an
interactive session."
`(cl-flet*
(,@(cl-loop for rule
in (append doom-message-fg doom-message-bg doom-message-fx)
collect
`(,(car rule)
(lambda (message &rest args)
(apply #'doom-ansi-apply ',(car rule) message args))))
(color
(lambda (code format &rest args)
(apply #'doom-ansi-apply code format args))))
(format ,message ,@args)))
"An alternative to `format' that understands (color ...) and converts them
into faces or ANSI codes depending on the type of sesssion we're in."
`(format ,@(doom--short-color-replace `(,message ,@args))))
;;;###autoload
(defmacro message! (message &rest args)
"An alternative to `message' that strips out ANSI codes if used in an
interactive session."
`(if noninteractive
(message (format! ,message ,@args))
(let ((buf (get-buffer-create " *doom messages*")))
(with-current-buffer buf
(goto-char (point-max))
(let ((beg (point))
end)
(insert (format! ,message ,@args))
(insert "\n")
(setq end (point))
(ansi-color-apply-on-region beg end)))
(with-selected-window (doom-popup-buffer buf)
(goto-char (point-max))))))
(defmacro print! (message &rest args)
"Uses `message' in interactive sessions and `princ' otherwise (prints to
standard out).
;;;###autoload
(defmacro debug! (message &rest args)
"Out a debug message if `doom-debug-mode' is non-nil. Otherwise, ignore this."
(when doom-debug-mode
`(message ,message ,@args)))
Can be colored using (color ...) blocks:
;;;###autoload
(defun doom-ansi-apply (code format &rest args)
(let ((rule (or (assq code doom-message-fg)
(assq code doom-message-bg)
(assq code doom-message-fx))))
(format "\e[%dm%s\e[%dm"
(cdr rule)
(apply #'format format args)
0)))
(print! \"Hello %s\" (bold (blue \"How are you?\")))
(print! \"Hello %s\" (red \"World\"))
(print! (green \"Great %s!\") \"success\")
Uses faces in interactive sessions and ANSI codes otherwise."
`(progn (princ (format! ,message ,@args))
(terpri)))

View file

@ -1,25 +0,0 @@
;;; core/autoload/minibuffer.el -*- lexical-binding: t; -*-
;;;###autoload
(defun doom/minibuffer-kill-word ()
"Kill a word, backwards, but only if the cursor is after
`minibuffer-prompt-end', to prevent the 'Text is read-only' warning from
monopolizing the minibuffer."
(interactive)
(when (> (point) (minibuffer-prompt-end))
(call-interactively #'backward-kill-word)))
;;;###autoload
(defun doom/minibuffer-kill-line ()
"Kill the entire line, but only if the cursor is after
`minibuffer-prompt-end', to prevent the 'Text is read-only' warning from
monopolizing the minibuffer."
(interactive)
(when (> (point) (minibuffer-prompt-end))
(call-interactively #'backward-kill-sentence)))
;;;###autoload
(defun doom/minibuffer-undo ()
"Undo an edit in the minibuffer without throwing errors."
(interactive)
(ignore-errors (call-interactively #'undo)))

View file

@ -1,56 +1,60 @@
;;; core/autoload/packages.el -*- lexical-binding: t; -*-
(require 'use-package)
(require 'quelpa)
(load! "cache")
(defvar doom--last-refresh nil)
;;; Private functions
(defun doom--packages-choose (prompt)
(let ((table (cl-loop for pkg in package-alist
unless (package-built-in-p (cdr pkg))
collect (cons (package-desc-full-name (cdr pkg))
(cdr pkg)))))
(cdr (assoc (completing-read prompt
(mapcar #'car table)
nil t)
table))))
(defun doom--refresh-pkg-cache ()
"Clear the cache for `doom-refresh-packages-maybe'."
(setq doom--refreshed-p nil)
(doom-cache-set 'last-pkg-refresh nil))
;;
;; Library
;;;###autoload
(defun doom-refresh-packages (&optional force-p)
"Refresh ELPA packages."
(defun doom-refresh-packages-maybe (&optional force-p)
"Refresh ELPA packages, if it hasn't been refreshed recently."
(when force-p
(doom-refresh-clear-cache))
(unless (or (persistent-soft-fetch 'last-pkg-refresh "emacs")
(doom--refresh-pkg-cache))
(unless (or (doom-cache-get 'last-pkg-refresh)
doom--refreshed-p)
(condition-case-unless-debug ex
(condition-case e
(progn
(message "Refreshing package archives")
(package-refresh-contents)
(persistent-soft-store 'last-pkg-refresh t "emacs" 900))
('error
(doom-refresh-clear-cache)
(message "Failed to refresh packages: (%s) %s"
(car ex) (error-message-string ex))))))
;;;###autoload
(defun doom-refresh-clear-cache ()
"Clear the cache for `doom-refresh-packages'."
(setq doom--refreshed-p nil)
(persistent-soft-store 'last-pkg-refresh nil "emacs"))
(doom-cache-set 'last-pkg-refresh t 1200))
((debug error)
(doom--refresh-pkg-cache)
(signal 'doom-error e)))))
;;;###autoload
(defun doom-package-backend (name &optional noerror)
"Get which backend the package NAME was installed with. Can either be elpa or
quelpa. Throws an error if NOERROR is nil and the package isn't installed."
(cl-assert (symbolp name) t)
(cond ((and (or (quelpa-setup-p)
(error "Could not initialize quelpa"))
(assq name quelpa-cache))
'quelpa)
((assq name package-alist)
'elpa)
((package-built-in-p name)
'emacs)
((not noerror)
(error "%s package is not installed" name))))
"Get which backend the package NAME was installed with. Can either be elpa,
quelpa or emacs (built-in). Throws an error if NOERROR is nil and the package
isn't installed."
(cl-check-type name symbol)
(cond ((assq name quelpa-cache) 'quelpa)
((assq name package-alist) 'elpa)
((package-built-in-p name) 'emacs)
((not noerror) (error "%s package is not installed" name))))
;;;###autoload
(defun doom-package-outdated-p (name)
"Determine whether NAME (a symbol) is outdated or not. If outdated, returns a
list, whose car is NAME, and cdr the current version list and latest version
list of the package."
(cl-assert (symbolp name) t)
(doom-initialize-packages)
(cl-check-type name symbol)
(when-let* ((desc (cadr (assq name package-alist))))
(let* ((old-version (package-desc-version desc))
(new-version
@ -67,24 +71,32 @@ list of the package."
(let ((desc (cadr (assq name package-archive-contents))))
(when (package-desc-p desc)
(package-desc-version desc)))))))
(when (and (listp old-version) (listp new-version)
(version-list-< old-version new-version))
(unless (and (listp old-version) (listp new-version))
(error "Couldn't get version for %s" name))
(when (version-list-< old-version new-version)
(list name old-version new-version)))))
;;;###autoload
(defun doom-package-prop (name prop)
(defun doom-package-installed-p (name)
"TODO"
(and (package-installed-p name)
(when-let* ((desc (cadr (assq name package-alist))))
(let ((dir (package-desc-dir desc)))
(file-directory-p dir)))))
;;;###autoload
(defun doom-package-prop (name prop &optional eval)
"Return PROPerty in NAME's plist."
(cl-assert (symbolp name) t)
(cl-assert (keywordp prop) t)
(doom-initialize-packages)
(plist-get (cdr (assq name doom-packages)) prop))
(cl-check-type name symbol)
(cl-check-type prop keyword)
(let ((value (plist-get (cdr (assq name doom-packages)) prop)))
(if eval (eval value) value)))
;;;###autoload
(defun doom-package-different-backend-p (name)
"Return t if NAME (a package's symbol) has a new backend than what it was
installed with. Returns nil otherwise, or if package isn't installed."
(cl-assert (symbolp name) t)
(doom-initialize-packages)
"Return t if a package named NAME (a symbol) has a new backend than what it
was installed with. Returns nil otherwise, or if package isn't installed."
(cl-check-type name symbol)
(and (package-installed-p name)
(let* ((plist (cdr (assq name doom-packages)))
(old-backend (doom-package-backend name 'noerror))
@ -92,38 +104,207 @@ installed with. Returns nil otherwise, or if package isn't installed."
(not (eq old-backend new-backend)))))
;;;###autoload
(defun doom-get-packages (&optional installed-only-p)
"Retrieves a list of explicitly installed packages (i.e. non-dependencies).
Each element is a cons cell, whose car is the package symbol and whose cdr is
the quelpa recipe (if any).
BACKEND can be 'quelpa or 'elpa, and will instruct this function to return only
the packages relevant to that backend.
Warning: this function is expensive; it re-evaluates all of doom's config files.
Be careful not to use it in a loop.
If INSTALLED-ONLY-P, only return packages that are installed."
(doom-initialize-packages t)
(cl-loop with packages = (append doom-core-packages (mapcar #'car doom-packages))
for sym in (cl-delete-duplicates packages)
if (and (or (not installed-only-p)
(package-installed-p sym))
(or (assq sym doom-packages)
(and (assq sym package-alist)
(list sym))))
collect it))
(defun doom-package-different-recipe-p (name)
"Return t if a package named NAME (a symbol) has a different recipe than it
was installed with."
(cl-check-type name symbol)
(and (package-installed-p name)
(when-let* ((quelpa-recipe (assq name quelpa-cache))
(doom-recipe (assq name doom-packages)))
(not (equal (cdr quelpa-recipe)
(cdr (plist-get (cdr doom-recipe) :recipe)))))))
;;;###autoload
(defun doom-get-depending-on (name)
(cl-defun doom-find-packages (&key (installed 'any)
(private 'any)
(disabled 'any)
(pinned 'any)
(ignored 'any)
(core 'any)
sort
changed
backend
deps)
"Retrieves a list of primary packages (i.e. non-dependencies). Each element is
a cons cell, whose car is the package symbol and whose cdr is the quelpa recipe
(if any).
You can build a filtering criteria using one or more of the following
properties:
:backend 'quelpa|'elpa|'emacs|'any
Include packages installed through 'quelpa, 'elpa or 'emacs. 'any is the
wildcard.
:installed BOOL|'any
t = only include installed packages
nil = exclude installed packages
:private BOOL|'any
t = only include user-installed packages
nil = exclude user-installed packages
:core BOOL|'any
t = only include Doom core packages
nil = exclude Doom core packages
:disabled BOOL|'any
t = only include disabled packages
nil = exclude disabled packages
:ignored BOOL|'any
t = only include ignored packages
nil = exclude ignored packages
:pinned BOOL|ARCHIVE
Only return packages that are pinned (t), not pinned (nil) or pinned to a
specific archive (stringp)
:deps BOOL
Includes the package's dependencies (t) or not (nil).
The resulting list is sorted unless :sort nil is passed to this function.
Warning: this function is expensive, as it re-evaluates your all packages.el
files."
(cl-loop with packages = doom-packages
for (sym . plist)
in (if sort
(cl-sort (copy-sequence doom-packages) #'string-lessp :key #'car)
packages)
if (and (or (not backend)
(eq (doom-package-backend sym t) backend))
(or (eq ignored 'any)
(let* ((form (plist-get plist :ignore))
(value (eval form)))
(if ignored value (not value))))
(or (eq disabled 'any)
(if disabled
(plist-get plist :disable)
(not (plist-get plist :disable))))
(or (eq installed 'any)
(if installed
(doom-package-installed-p sym)
(not (doom-package-installed-p sym))))
(or (eq private 'any)
(let ((modules (plist-get plist :modules)))
(if private
(assq :private modules)
(not (assq :private modules)))))
(or (eq core 'any)
(let ((modules (plist-get plist :modules)))
(if core
(assq :core modules)
(not (assq :core modules)))))
(or (eq pinned 'any)
(cond ((eq pinned 't)
(plist-get plist :pin))
((null pinned)
(not (plist-get plist :pin)))
((equal (plist-get plist :pin) pinned)))))
collect (cons sym plist)
and if (and deps (not (package-built-in-p sym)))
nconc
(cl-loop for pkg in (doom-get-dependencies-for sym 'recursive 'noerror)
if (or (eq installed 'any)
(if installed
(doom-package-installed-p pkg)
(not (doom-package-installed-p pkg))))
collect (cons pkg (cdr (assq pkg doom-packages))))))
(defun doom--read-module-packages-file (file &optional raw noerror)
(with-temp-buffer ; prevent buffer-local settings from propagating
(condition-case e
(if (not raw)
(load file noerror t t)
(when (file-readable-p file)
(insert-file-contents file)
(while (re-search-forward "(package! " nil t)
(save-excursion
(goto-char (match-beginning 0))
(cl-destructuring-bind (name . plist) (cdr (sexp-at-point))
(push (cons name
(plist-put plist :modules
(cond ((file-in-directory-p file doom-private-dir)
(list :private))
((file-in-directory-p file doom-core-dir)
(list :core))
((doom-module-from-path file)))))
doom-packages))))))
((debug error)
(signal 'doom-package-error
(list (or (doom-module-from-path file)
'(:private . packages))
e))))))
;;;###autoload
(defun doom-package-list (&optional all-p)
"Retrieve a list of explicitly declared packages from enabled modules.
This excludes core packages listed in `doom-core-packages'.
If ALL-P, gather packages unconditionally across all modules, including disabled
ones."
(let ((noninteractive t)
(doom--stage 'packages)
(doom-modules (doom-modules))
doom-packages
doom-disabled-packages
package-pinned-packages)
(doom--read-module-packages-file (expand-file-name "packages.el" doom-core-dir) all-p)
(let ((private-packages (expand-file-name "packages.el" doom-private-dir)))
(unless all-p
;; We load the private packages file twice to ensure disabled packages
;; are seen ASAP, and a second time to ensure privately overridden
;; packages are properly overwritten.
(doom--read-module-packages-file private-packages nil t))
(if all-p
(mapc #'doom--read-module-packages-file
(doom-files-in doom-modules-dir
:depth 2
:full t
:match "/packages\\.el$"))
(cl-loop for key being the hash-keys of doom-modules
for path = (doom-module-path (car key) (cdr key) "packages.el")
for doom--current-module = key
do (doom--read-module-packages-file path nil t)))
(doom--read-module-packages-file private-packages all-p t))
(append (cl-loop for package in doom-core-packages
collect (list package :modules '((:core internal))))
(nreverse doom-packages))))
;;;###autoload
(defun doom-get-package-alist ()
"Returns a list of all desired packages, their dependencies and their desc
objects, in the order of their `package! blocks.'"
(cl-remove-duplicates
(cl-loop for name in (mapcar #'car doom-packages)
if (assq name package-alist)
nconc (cl-loop for dep in (package--get-deps name)
if (assq dep package-alist)
collect (cons dep (cadr it)))
and collect (cons name (cadr it)))
:key #'car
:from-end t))
;;;###autoload
(defun doom-get-depending-on (name &optional noerror)
"Return a list of packages that depend on the package named NAME."
(when-let* ((desc (cadr (assq name package-alist))))
(mapcar #'package-desc-name (package--used-elsewhere-p desc nil t))))
(cl-check-type name symbol)
(unless (package-built-in-p name)
(if-let* ((desc (cadr (assq name package-alist))))
(mapcar #'package-desc-name (package--used-elsewhere-p desc nil t))
(unless noerror
(error "Couldn't find %s, is it installed?" name)))))
;;;###autoload
(defun doom-get-dependencies-for (name &optional only)
(defun doom-get-dependencies-for (name &optional recursive noerror)
"Return a list of dependencies for a package."
(package--get-deps name only))
(cl-check-type name symbol)
;; can't get dependencies for built-in packages
(unless (package-built-in-p name)
(if-let* ((desc (cadr (assq name package-alist))))
(let* ((deps (mapcar #'car (package-desc-reqs desc)))
(deps (cl-remove-if #'package-built-in-p deps)))
(if recursive
(nconc deps (mapcan (lambda (dep) (doom-get-dependencies-for dep t t))
deps))
deps))
(unless noerror
(error "Couldn't find %s, is it installed?" name)))))
;;;###autoload
(defun doom-get-outdated-packages (&optional include-frozen-p)
@ -132,44 +313,56 @@ containing (PACKAGE-SYMBOL OLD-VERSION-LIST NEW-VERSION-LIST).
If INCLUDE-FROZEN-P is non-nil, check frozen packages as well.
Used by `doom//packages-update'."
(let (quelpa-pkgs elpa-pkgs)
;; Separate quelpa from elpa packages
(dolist (pkg (doom-get-packages t))
(let ((sym (car pkg)))
(when (and (or (not (doom-package-prop sym :freeze))
Used by `doom-packages-update'."
(doom-refresh-packages-maybe doom-debug-mode)
(let-alist
(seq-group-by
#'doom-package-backend
(cl-loop for package in (mapcar #'car package-alist)
when (and (or (not (doom-package-prop package :freeze 'eval))
include-frozen-p)
(not (doom-package-prop sym :ignore))
(not (doom-package-different-backend-p sym)))
(push sym
(if (eq (doom-package-backend sym) 'quelpa)
quelpa-pkgs
elpa-pkgs)))))
(not (doom-package-prop package :ignore 'eval))
(not (doom-package-different-backend-p package)))
collect package))
;; The bottleneck in this process is quelpa's version checks, so check them
;; asynchronously.
(let (futures)
(dolist (pkg quelpa-pkgs)
(debug! "New thread for: %s" pkg)
(push (async-start
(cl-loop with partitions = (min 2 (/ (length .quelpa) 4))
for package-list in (seq-partition .quelpa partitions)
do (doom-log "New thread for: %s" package-list)
collect
(async-start
`(lambda ()
(setq user-emacs-directory ,user-emacs-directory)
(let ((noninteractive t))
(load ,(expand-file-name "core.el" doom-core-dir)))
(doom-package-outdated-p ',pkg)))
futures))
(delq nil
(append (mapcar #'doom-package-outdated-p elpa-pkgs)
(mapcar #'async-get (reverse futures)))))))
(let ((gc-cons-threshold ,doom-gc-cons-upper-limit)
(doom-init-p t)
(noninteractive t)
(load-path ',load-path)
(package-alist ',package-alist)
(package-archive-contents ',package-archive-contents)
(package-selected-packages ',package-selected-packages)
(doom-packages ',doom-packages)
(doom-modules ',doom-modules)
(quelpa-cache ',quelpa-cache)
(user-emacs-directory ,user-emacs-directory)
doom-private-dir)
(load ,(expand-file-name "core.el" doom-core-dir))
(load ,(expand-file-name "autoload/packages.el" doom-core-dir))
(require 'package)
(require 'quelpa)
(delq nil (mapcar #'doom-package-outdated-p ',package-list)))))
into futures
finally return
(append (delq nil (mapcar #'doom-package-outdated-p .elpa))
(mapcan #'async-get futures)
nil))))
;;;###autoload
(defun doom-get-orphaned-packages ()
"Return a list of symbols representing packages that are no longer needed or
depended on.
Used by `doom//packages-autoremove'."
(doom-initialize-packages t)
Used by `doom-packages-autoremove'."
(let ((package-selected-packages
(append (mapcar #'car doom-packages) doom-core-packages)))
(mapcar #'car (doom-find-packages :ignored nil :disabled nil))))
(append (package--removable-packages)
(cl-loop for pkg in package-selected-packages
if (and (doom-package-different-backend-p pkg)
@ -177,114 +370,84 @@ Used by `doom//packages-autoremove'."
collect pkg))))
;;;###autoload
(defun doom-get-missing-packages (&optional include-ignored-p)
(defun doom-get-missing-packages ()
"Return a list of requested packages that aren't installed or built-in, but
are enabled (with a `package!' directive). Each element is a list whose CAR is
the package symbol, and whose CDR is a plist taken from that package's
`package!' declaration.
If INCLUDE-IGNORED-P is non-nil, includes missing packages that are ignored,
i.e. they have an :ignore property.
Used by `doom//packages-install'."
(cl-loop for desc in (doom-get-packages)
for (name . plist) = desc
if (and (or include-ignored-p
(not (plist-get plist :ignore)))
(or (plist-get plist :pin)
(not (assq name package--builtins)))
(or (not (assq name package-alist))
(doom-package-different-backend-p name)))
collect desc))
;;;###autoload
(defun doom*package-delete (desc &rest _)
"Update `quelpa-cache' upon a successful `package-delete'."
(let ((name (package-desc-name desc)))
(when (and (not (package-installed-p name))
(quelpa-setup-p)
(assq name quelpa-cache))
(setq quelpa-cache (assq-delete-all name quelpa-cache))
(quelpa-save-cache)
(let ((path (expand-file-name (symbol-name name) quelpa-build-dir)))
(when (file-exists-p path)
(delete-directory path t))))))
;;; Private functions
(defsubst doom--sort-alpha (it other)
(string-lessp (symbol-name (car it))
(symbol-name (car other))))
(defun doom--packages-choose (prompt)
(let ((table (cl-loop for pkg in package-alist
unless (package-built-in-p (cdr pkg))
collect (cons (package-desc-full-name (cdr pkg))
(cdr pkg)))))
(cdr (assoc (completing-read prompt
(mapcar #'car table)
nil t)
table))))
(defmacro doom--condition-case! (&rest body)
`(condition-case-unless-debug ex
(condition-case ex2
(progn ,@body)
('file-error
(message! (bold (red " FILE ERROR: %s" (error-message-string ex2))))
(message! " Trying again...")
(quiet! (doom-refresh-packages t))
,@body))
('user-error
(message! (bold (red " ERROR: (%s) %s"
(car ex)
(error-message-string ex)))))
('error
(doom-refresh-clear-cache)
(message! (bold (red " FATAL ERROR: (%s) %s"
(car ex)
(error-message-string ex)))))))
Used by `doom-packages-install'."
(cl-loop for (name . plist)
in (doom-find-packages :ignored nil
:disabled nil
:deps t)
if (and (or (plist-get plist :pin)
(not (package-built-in-p name)))
(or (not (doom-package-installed-p name))
(doom-package-different-backend-p name)
(doom-package-different-recipe-p name)))
collect (cons name plist)))
;;
;; Main functions
;;
(defun doom--delete-package-files (name-or-desc)
(let ((pkg-build-dir
(if (package-desc-p name-or-desc)
(package-desc-dir name-or-desc)
(expand-file-name (symbol-name name-or-desc) quelpa-build-dir))))
(when (file-directory-p pkg-build-dir)
(delete-directory pkg-build-dir t))))
;;;###autoload
(defun doom-install-package (name &optional plist)
"Installs package NAME with optional quelpa RECIPE (see `quelpa-recipe' for an
example; the package name can be omitted)."
(doom-initialize-packages)
(when (package-installed-p name)
(when (doom-package-different-backend-p name)
(doom-delete-package name t))
(user-error "%s is already installed" name))
(cl-check-type name symbol)
(when (and (package-installed-p name)
(not (package-built-in-p name)))
(if (or (doom-package-different-backend-p name)
(doom-package-different-recipe-p name))
(doom-delete-package name t)
(user-error "%s is already installed" name)))
(let* ((inhibit-message (not doom-debug-mode))
(plist (or plist (cdr (assq name doom-packages))))
(recipe (plist-get plist :recipe))
quelpa-upgrade-p)
(if recipe
(quelpa recipe)
(plist (or plist (cdr (assq name doom-packages)))))
(if-let* ((recipe (plist-get plist :recipe)))
(condition-case e
(let (quelpa-upgrade-p)
(quelpa recipe))
((debug error)
(doom--delete-package-files name)
(signal (car e) (cdr e))))
(package-install name))
(when (package-installed-p name)
(cl-pushnew (cons name plist) doom-packages :test #'eq :key #'car)
t)))
(if (not (package-installed-p name))
(doom--delete-package-files name)
(add-to-list 'package-selected-packages name nil 'eq)
(setf (alist-get name doom-packages) plist)
name)))
;;;###autoload
(defun doom-update-package (name &optional force-p)
"Updates package NAME (a symbol) if it is out of date, using quelpa or
package.el as appropriate."
(cl-check-type name symbol)
(unless (package-installed-p name)
(user-error "%s isn't installed" name))
(error "%s isn't installed" name))
(when (doom-package-different-backend-p name)
(user-error "%s's backend has changed and must be uninstalled first" name))
(when (or force-p (doom-package-outdated-p name))
(let ((inhibit-message (not doom-debug-mode))
(desc (cadr (assq name package-alist))))
(pcase (doom-package-backend name)
('quelpa
(or (quelpa-setup-p)
(error "Failed to initialize quelpa"))
(`quelpa
(condition-case e
(let ((quelpa-upgrade-p t))
(quelpa (assq name quelpa-cache))))
('elpa
(quelpa (assq name quelpa-cache)))
((debug error)
(doom--delete-package-files name)
(signal (car e) (cdr e)))))
(`elpa
(let* ((archive (cadr (assq name package-archive-contents)))
(packages
(if (package-desc-p archive)
@ -292,201 +455,37 @@ package.el as appropriate."
(package-compute-transaction () (list (list archive))))))
(package-download-transaction packages))))
(unless (doom-package-outdated-p name)
(when-let* ((old-dir (package-desc-dir desc)))
(when (file-directory-p old-dir)
(delete-directory old-dir t)))
(doom--delete-package-files desc)
t))))
;;;###autoload
(defun doom-delete-package (name &optional force-p)
"Uninstalls package NAME if it exists, and clears it from `quelpa-cache'."
(cl-check-type name symbol)
(unless (package-installed-p name)
(user-error "%s isn't installed" name))
(let ((inhibit-message (not doom-debug-mode))
(spec (assq name quelpa-cache))
quelpa-p)
(unless (quelpa-setup-p)
(error "Could not initialize QUELPA"))
(when (assq name quelpa-cache)
(setq quelpa-cache (assq-delete-all name quelpa-cache))
(when spec
(setq quelpa-cache (delq spec quelpa-cache))
(quelpa-save-cache)
(setq quelpa-p t))
(package-delete (cadr (assq name package-alist)) force-p)
(unless (package-installed-p name)
(let ((pkg-build-dir (expand-file-name (symbol-name name) quelpa-build-dir)))
(when (and quelpa-p (file-directory-p pkg-build-dir))
(delete-directory pkg-build-dir t)))
t)))
;;
;; Batch/interactive commands
;;
;;;###autoload
(defun doom//packages-install ()
"Interactive command for installing missing packages."
(interactive)
(message! "Looking for packages to install...")
(let ((packages (doom-get-missing-packages)))
(cond ((not packages)
(message! (green "No packages to install!")))
((not (or (getenv "YES")
(y-or-n-p
(format "%s packages will be installed:\n\n%s\n\nProceed?"
(length packages)
(mapconcat
(lambda (pkg)
(format "+ %s (%s)"
(car pkg)
(cond ((doom-package-different-backend-p (car pkg))
(if (plist-get (cdr pkg) :recipe)
"ELPA -> QUELPA"
"QUELPA -> ELPA"))
((plist-get (cdr pkg) :recipe)
"QUELPA")
(t
"ELPA"))))
(sort (cl-copy-list packages) #'doom--sort-alpha)
"\n")))))
(message! (yellow "Aborted!")))
(t
(doom-refresh-packages doom-debug-mode)
(dolist (pkg packages)
(message! "Installing %s" (car pkg))
(doom--condition-case!
(message! "%s%s"
(cond ((and (package-installed-p (car pkg))
(not (doom-package-different-backend-p (car pkg))))
(dark (white "⚠ ALREADY INSTALLED")))
((doom-install-package (car pkg) (cdr pkg))
(green "✓ DONE"))
(t
(red "✕ FAILED")))
(if (plist-member (cdr pkg) :pin)
(format " [pinned: %s]" (plist-get (cdr pkg) :pin))
""))))
(message! (bold (green "Finished!")))
(doom//reload-load-path)))))
;;;###autoload
(defun doom//packages-update ()
"Interactive command for updating packages."
(interactive)
(doom-refresh-packages doom-debug-mode)
(message! "Looking for outdated packages...")
(let ((packages (sort (doom-get-outdated-packages) #'doom--sort-alpha)))
(cond ((not packages)
(message! (green "Everything is up-to-date")))
((not (or (getenv "YES")
(y-or-n-p
(format "%s packages will be updated:\n\n%s\n\nProceed?"
(length packages)
(let ((max-len
(or (car (sort (mapcar (lambda (it) (length (symbol-name (car it)))) packages)
(lambda (it other) (> it other))))
10)))
(mapconcat
(lambda (pkg)
(format (format "+ %%-%ds %%-%ds -> %%s" (+ max-len 2) 14)
(symbol-name (car pkg))
(package-version-join (cadr pkg))
(package-version-join (cl-caddr pkg))))
packages
"\n"))))))
(message! (yellow "Aborted!")))
(t
(dolist (pkg packages)
(message! "Updating %s" (car pkg))
(doom--condition-case!
(message!
(let ((result (doom-update-package (car pkg) t)))
(color (if result 'green 'red)
(if result "✓ DONE" "✕ FAILED"))))))
(message! (bold (green "Finished!")))
(doom//reload-load-path)))))
;;;###autoload
(defun doom//packages-autoremove ()
"Interactive command for auto-removing orphaned packages."
(interactive)
(message! "Looking for orphaned packages...")
(let ((packages (doom-get-orphaned-packages)))
(cond ((not packages)
(message! (green "No unused packages to remove")))
((not
(or (getenv "YES")
(y-or-n-p
(format
"%s packages will be deleted:\n\n%s\n\nProceed?"
(length packages)
(mapconcat
(lambda (sym)
(format "+ %s (%s)" sym
(let ((backend (doom-package-backend sym)))
(if (doom-package-different-backend-p sym)
(if (eq backend 'quelpa)
"QUELPA->ELPA"
"ELPA->QUELPA")
(upcase (symbol-name backend))))))
(sort (cl-copy-list packages) #'string-lessp)
"\n")))))
(message! (yellow "Aborted!")))
(t
(dolist (pkg packages)
(doom--condition-case!
(message!
(let ((result (doom-delete-package pkg t)))
(color (if result 'green 'red)
"%s %s"
(if result "✓ Removed" "✕ Failed to remove")
pkg)))))
(message! (bold (green "Finished!")))
(doom//reload-load-path)))))
(doom--delete-package-files name)
(not (package-installed-p name))))
;;
;; Interactive commands
;;
;;;###autoload
(defalias 'doom/install-package #'package-install)
;;;###autoload
(defun doom/reinstall-package (desc)
"Reinstalls package package with optional quelpa RECIPE (see `quelpa-recipe' for
an example; the package package can be omitted)."
(declare (interactive-only t))
(interactive
(list (doom--packages-choose "Reinstall package: ")))
(let ((package (package-desc-name desc)))
(doom-delete-package package t)
(doom-install-package package (cdr (assq package doom-packages)))))
;;;###autoload
(defun doom/delete-package (desc)
"Prompts the user with a list of packages and deletes the selected package.
Use this interactively. Use `doom-delete-package' for direct calls."
(declare (interactive-only t))
(interactive
(list (doom--packages-choose "Delete package: ")))
(let ((package (package-desc-name desc)))
(if (package-installed-p package)
(if (y-or-n-p (format "%s will be deleted. Confirm?" package))
(message "%s %s"
(if (doom-delete-package package t)
"Deleted"
"Failed to delete")
package)
(message "Aborted"))
(message "%s isn't installed" package))))
(defun doom/reload-packages ()
"Reload `doom-packages', `package' and `quelpa'."
(interactive)
(message "Reloading packages")
(doom-initialize-packages t)
(message "Reloading packages...DONE"))
;;;###autoload
(defun doom/update-package (pkg)
@ -496,12 +495,15 @@ calls."
(declare (interactive-only t))
(interactive
(let* ((packages (doom-get-outdated-packages))
(package (if packages
(selection (if packages
(completing-read "Update package: "
(mapcar #'car packages)
nil t)
(user-error "All packages are up to date"))))
(list (cdr (assq (car (assoc package package-alist)) packages)))))
(user-error "All packages are up to date")))
(name (car (assoc (intern selection) package-alist))))
(unless name
(user-error "'%s' is already up-to-date" selection))
(list (assq name packages))))
(cl-destructuring-bind (package old-version new-version) pkg
(if-let* ((desc (doom-package-outdated-p package)))
(let ((old-v-str (package-version-join old-version))
@ -514,10 +516,31 @@ calls."
(message "Aborted")))
(message "%s is up-to-date" package))))
;;
;; Advice
;;;###autoload
(defun doom/refresh-packages (&optional force-p)
"Synchronize package metadata with the sources in `package-archives'. If
FORCE-P (the universal argument) is set, ignore the cache."
(declare (interactive-only t))
(interactive "P")
(doom-refresh-packages force-p))
(defun doom*package-delete (desc &rest _)
"Update `quelpa-cache' upon a successful `package-delete'."
(let ((name (package-desc-name desc)))
(unless (package-installed-p name)
(when-let* ((spec (assq name quelpa-cache)))
(setq quelpa-cache (delq spec quelpa-cache))
(quelpa-save-cache)
(doom--delete-package-files name)))))
;;
;; Make package.el cooperate with Doom
;; Updates QUELPA after deleting a package
;;;###autoload
(advice-add #'package-delete :after #'doom*package-delete)
;; Replace with Doom variants
;;;###autoload
(advice-add #'package-autoremove :override #'doom//autoremove)
;;;###autoload
(advice-add #'package-install-selected-packages :override #'doom//install)

View file

@ -1,416 +0,0 @@
;;; core/autoload/popups.el -*- lexical-binding: t; -*-
;;;###autoload
(defun doom-popup-p (&optional target)
"Return t if TARGET (a window or buffer) is a popup. Uses current window if
omitted."
(when-let* ((target (or target (selected-window))))
(cond ((bufferp target)
(and (buffer-live-p target)
(buffer-local-value 'doom-popup-mode target)))
((windowp target)
(and (window-live-p target)
(window-parameter target 'popup))))))
;;;###autoload
(defun doom-popup-buffer (buffer &optional plist extend-p)
"Display BUFFER in a shackle popup with PLIST rules. See `shackle-rules' for
possible rules. If EXTEND-P is non-nil, don't overwrite the original rules for
this popup, just the specified properties. Returns the new popup window."
(declare (indent defun))
(unless (bufferp buffer)
(error "%s is not a valid buffer" buffer))
(shackle-display-buffer
buffer
nil (or (if extend-p
(append plist (shackle-match buffer))
plist)
(shackle-match buffer))))
;;;###autoload
(defun doom-popup-switch-to-buffer (buffer)
"Switch the current (or closest) pop-up window to BUFFER."
(unless (doom-popup-p)
(if-let* ((popups (doom-popup-windows)))
(select-window (car popups))
(error "No popups to switch to")))
(set-window-dedicated-p nil nil)
(switch-to-buffer buffer nil t)
(prog1 (selected-window)
(set-window-dedicated-p nil t)))
;;;###autoload
(defun doom-popup-fit-to-buffer (&optional window max-size)
"Fit WINDOW to the size of its content."
(unless (string-empty-p (buffer-string))
(let* ((window-size (doom-popup-size window))
(max-size (or max-size (doom-popup-property :size window)))
(size (+ 2 (if (floatp max-size) (truncate (* max-size window-size)) window-size))))
(fit-window-to-buffer window size nil size))))
;;;###autoload
(defun doom-popup-move (direction)
"Move a popup window to another side of the frame, in DIRECTION, which can be
one of the following: 'left 'right 'above 'below"
(when (doom-popup-p)
(let ((buffer (current-buffer))
(doom-popup-inhibit-autokill t))
(doom/popup-close)
(doom-popup-buffer buffer `(:align ,direction) 'extend))))
;;;###autoload
(defun doom-popup-file (file &optional plist extend-p)
"Display FILE in a shackle popup, with PLIST rules. See `shackle-rules' for
possible rules."
(unless (file-exists-p file)
(user-error "Can't display file in popup, it doesn't exist: %s" file))
(doom-popup-buffer (find-file-noselect file t) plist extend-p))
;;;###autoload
(defun doom-popup-windows (&optional filter-static-p)
"Get a list of open pop up windows."
(cl-loop for window in doom-popup-windows
if (and (doom-popup-p window)
(not (and filter-static-p
(doom-popup-property :static window))))
collect window))
;;;###autoload
(defun doom-popup-properties (window-or-buffer)
"Returns a window's popup property list, if possible. The buffer-local
`doom-popup-rules' always takes priority, but this will fall back to the popup
window parameter."
(cond ((windowp window-or-buffer)
(or (window-parameter window-or-buffer 'popup)
(doom-popup-properties (window-buffer window-or-buffer))))
((bufferp window-or-buffer)
(buffer-local-value 'doom-popup-rules window-or-buffer))))
;;;###autoload
(defun doom-popup-property (prop &optional window)
"Returns a `doom-popup-rules' PROPerty from WINDOW."
(or (plist-get (doom-popup-properties (or window (selected-window)))
prop)
(pcase prop
(:size shackle-default-size)
(:align shackle-default-alignment))))
;;;###autoload
(defun doom-popup-side (&optional window)
"Return what side a popup WINDOW came from ('left 'right 'above or 'below)."
(let ((align (doom-popup-property :align window)))
(when (eq align t)
(setq align shackle-default-alignment))
(when (functionp align)
(setq align (funcall align)))
align))
;;;###autoload
(defun doom-popup-size (&optional window)
"Return the size of a popup WINDOW."
(pcase (doom-popup-side window)
((or 'left 'right) (window-width window))
((or 'above 'below) (window-height window))))
(defun doom--popup-data (window)
(when-let* ((buffer (window-buffer window)))
`(,(buffer-name buffer)
:file ,(buffer-file-name buffer)
:rules ,(window-parameter window 'popup)
:size ,(doom-popup-size window))))
;;;###autoload
(defmacro with-popup-rules! (rules &rest body)
"TODO"
(declare (indent defun))
`(let (shackle-rules)
,@(cl-loop for rule in rules
collect `(set! :popup ,@rule))
,@body))
;;;###autoload
(defmacro save-popups! (&rest body)
"Sets aside all popups before executing the original function, usually to
prevent the popup(s) from messing up the UI (or vice versa)."
`(let ((in-popup-p (doom-popup-p))
(popups (doom-popup-windows))
(doom-popup-remember-history t)
(doom-popup-inhibit-autokill t))
(when popups
(mapc #'doom/popup-close popups))
(unwind-protect
(progn ,@body)
(when popups
(let ((origin (selected-window)))
(doom/popup-restore)
(unless in-popup-p
(select-window origin)))))))
;; --- Commands ---------------------------
;;;###autoload
(defun doom/popup-restore ()
"Restore the last open popups. If the buffers have been killed, and
represented real files, they will be restored. Dead special buffers or buffers
with non-nil :autokill properties will not be.
Returns t if popups were restored, nil otherwise."
(interactive)
(unless doom-popup-history
(error "No popups to restore"))
(let (any-p)
(dolist (spec doom-popup-history)
(let ((buffer (get-buffer (car spec)))
(file (plist-get (cdr spec) :file))
(rules (plist-get (cdr spec) :rules))
(size (plist-get (cdr spec) :size)))
(when (and (not buffer) file)
(setq buffer
(if-let* ((buf (get-file-buffer file)))
(clone-indirect-buffer (buffer-name buf) nil t)
(find-file-noselect file t))))
(when size
(setq rules (plist-put rules :size size)))
(when (and buffer (doom-popup-buffer buffer rules) (not any-p))
(setq any-p t))))
(when any-p
(setq doom-popup-history '()))
any-p))
;;;###autoload
(defun doom/popup-toggle ()
"Toggle popups on and off. If used outside of popups (and popups are
available), it will select the nearest popup window."
(interactive)
(when (doom-popup-p)
(if doom-popup-other-window
(select-window doom-popup-other-window)
(other-window 1)))
(if (doom-popup-windows t)
(let ((doom-popup-inhibit-autokill t))
(doom/popup-close-all t))
(doom/popup-restore)))
;;;###autoload
(defun doom/popup-close (&optional window)
"Find and close WINDOW if it's a popup. If WINDOW is omitted, defaults to
`selected-window'. The contained buffer is buried, unless it has an :autokill
property."
(interactive)
(when (doom-popup-p window)
(delete-window (or window (selected-window)))))
;;;###autoload
(defun doom/popup-close-all (&optional force-p)
"Closes most open popups.
Does not close popups that are :static or don't have an :autoclose property (see
`shackle-rules').
If FORCE-P is non-nil (or this function is called interactively), ignore popups'
:autoclose property. This command will never close :static popups."
(interactive
(list (called-interactively-p 'interactive)))
(when-let* ((popups (doom-popup-windows t)))
(let (success doom-popup-remember-history)
(setq doom-popup-history (delq nil (mapcar #'doom--popup-data popups)))
(dolist (window popups success)
(when (or force-p (doom-popup-property :autoclose window))
(delete-window window)
(setq success t))))))
;;;###autoload
(defun doom/popup-kill-all ()
"Like `doom/popup-close-all', but kill *all* popups, including :static ones,
without leaving any trace behind (muahaha)."
(interactive)
(when-let* ((popups (doom-popup-windows)))
(let (doom-popup-remember-history)
(setq doom-popup-history nil)
(mapc #'delete-window popups))))
;;;###autoload
(defun doom/popup-close-maybe ()
"Close the current popup *if* its window doesn't have a noesc parameter."
(interactive)
(if (doom-popup-property :noesc)
(call-interactively
(if (featurep 'evil)
#'evil-force-normal-state
#'keyboard-quit))
(quit-restore-window nil 'kill)))
;;;###autoload
(defun doom/popup-this-buffer ()
"Display currently selected buffer in a popup window."
(interactive)
(doom-popup-buffer (current-buffer) '(:align t :autokill t)))
;;;###autoload
(defun doom/popup-toggle-messages ()
"Toggle *Messages* buffer."
(interactive)
(if-let* ((win (get-buffer-window "*Messages*")))
(doom/popup-close win)
(doom-popup-buffer (get-buffer "*Messages*"))))
;;;###autoload
(defun doom/other-popup (count)
"Cycle through popup windows. Like `other-window', but for popups."
(interactive "p")
(if-let* ((popups (if (doom-popup-p)
(cdr (memq (selected-window) doom-popup-windows))
(setq doom-popup-other-window (selected-window))
doom-popup-windows)))
(ignore-errors (select-window (nth (mod (1- count) (length popups)) popups)))
(unless (eq (selected-window) doom-popup-other-window)
(when doom-popup-other-window
(select-window doom-popup-other-window t)
(cl-decf count))
(when (/= count 0)
(other-window count)))))
;;;###autoload
(defalias 'other-popup #'doom/other-popup)
;;;###autoload
(defun doom/popup-raise (&optional window)
"Turn a popup window into a normal window."
(interactive)
(let ((window (or window (selected-window))))
(unless (doom-popup-p window)
(user-error "Not a valid popup to raise"))
(with-selected-window window
(doom-popup-mode -1))))
;;;###autoload
(defun doom/popup-move-top () "See `doom-popup-move'." (interactive) (doom-popup-move 'above))
;;;###autoload
(defun doom/popup-move-bottom () "See `doom-popup-move'." (interactive) (doom-popup-move 'below))
;;;###autoload
(defun doom/popup-move-left () "See `doom-popup-move'." (interactive) (doom-popup-move 'left))
;;;###autoload
(defun doom/popup-move-right () "See `doom-popup-move'." (interactive) (doom-popup-move 'right))
;; --- doom-popup-mode --------------------
;;;###autoload
(define-minor-mode doom-popup-mode
"Minor mode for popup windows."
:init-value nil
:keymap doom-popup-mode-map
(let ((window (selected-window)))
;; If `doom-popup-rules' isn't set for some reason, try to set it
(setq-local doom-popup-rules (doom-popup-properties window))
;; Ensure that buffer-opening functions/commands (like
;; `switch-to-buffer-other-window' won't use this window).
(set-window-parameter window 'no-other-window doom-popup-mode)
;; Makes popup window resist interactively changing its buffer.
(set-window-dedicated-p window doom-popup-mode)
(cond (doom-popup-mode
(when doom-popup-no-fringes
(set-window-fringes window 0 0 fringes-outside-margins))
;; Save metadata into window parameters so it can be saved by window
;; config persisting plugins like workgroups or persp-mode.
(set-window-parameter window 'popup (or doom-popup-rules t))
(when doom-popup-rules
(cl-loop for param in doom-popup-window-parameters
when (plist-get doom-popup-rules param)
do (set-window-parameter window param it))))
(t
(when doom-popup-no-fringes
(set-window-fringes window
doom-fringe-size doom-fringe-size
fringes-outside-margins))
;; Ensure window parameters are cleaned up
(set-window-parameter window 'popup nil)
(dolist (param doom-popup-window-parameters)
(set-window-parameter window param nil))))))
(put 'doom-popup-mode 'permanent-local t)
;;;###autoload
(defun doom|hide-modeline-in-popup ()
"Don't show modeline in popup windows without a :modeline rule. If one exists
and it's a symbol, use `doom-modeline' to grab the format. If non-nil, show the
mode-line as normal. If nil (or omitted, by default), then hide the modeline
entirely."
(if doom-popup-mode
(let ((modeline (plist-get doom-popup-rules :modeline)))
(cond ((or (eq modeline 'nil)
(not modeline))
(doom-hide-modeline-mode +1))
((and (symbolp modeline)
(not (eq modeline 't)))
(setq-local doom--modeline-format (doom-modeline modeline))
(when doom--modeline-format
(doom-hide-modeline-mode +1)))))
(when doom-hide-modeline-mode
(doom-hide-modeline-mode -1))))
;; --- Advice functions -------------------
;;;###autoload
(defun doom*shackle-always-align (plist)
"Ensure popups are always aligned and selected by default. Eliminates the need
for :align t on every rule."
(when plist
(unless (or (plist-member plist :align)
(plist-member plist :same)
(plist-member plist :frame))
(plist-put plist :align t))
(unless (or (plist-member plist :select)
(plist-member plist :noselect))
(plist-put plist :select t)))
plist)
;;;###autoload
(defun doom*popup-init (orig-fn &rest args)
"Initializes a window as a popup window by enabling `doom-popup-mode' in it
and setting `doom-popup-rules' within it. Returns the window."
(unless (doom-popup-p)
(setq doom-popup-other-window (selected-window)))
(let* ((target (car args))
(plist (or (nth 2 args)
(cond ((windowp target)
(and (window-live-p target)
(shackle-match (window-buffer target))))
((bufferp target)
(and (buffer-live-p target)
(shackle-match target))))))
(buffer (get-buffer target))
(window-min-height (if (plist-get plist :modeline) 4 2))
window)
(when (and (doom-real-buffer-p buffer)
(get-buffer-window-list buffer nil t))
(setq plist (append (list :autokill t) plist))
(setcar args (clone-indirect-buffer (buffer-name target) nil t)))
(unless (setq window (apply orig-fn args))
(error "No popup window was found for %s: %s" target plist))
(cl-pushnew window doom-popup-windows :test #'eq)
(with-selected-window window
(unless (eq plist t)
(setq-local doom-popup-rules plist))
(doom-popup-mode +1)
(when (plist-get plist :autofit)
(doom-popup-fit-to-buffer window)))
window))
;;;###autoload
(defun doom*popups-save (orig-fn &rest args)
"Sets aside all popups before executing the original function, usually to
prevent the popup(s) from messing up the UI (or vice versa)."
(save-popups! (apply orig-fn args)))
;;;###autoload
(defun doom*delete-popup-window (&optional window)
"Ensure that popups are deleted properly, and killed if they have :autokill
properties."
(or window (setq window (selected-window)))
(when (doom-popup-p window)
(setq doom-popup-windows (delq window doom-popup-windows))
(when doom-popup-remember-history
(setq doom-popup-history (list (doom--popup-data window))))))

127
core/autoload/projects.el Normal file
View file

@ -0,0 +1,127 @@
;;; core/autoload/projects.el -*- lexical-binding: t; -*-
(defvar projectile-project-root nil)
;;;###autoload
(autoload 'projectile-relevant-known-projects "projectile")
;;
;; Macros
;;;###autoload
(defmacro without-project-cache! (&rest body)
"Run BODY with projectile's project-root cache disabled. This is necessary if
you want to interactive with a project other than the one you're in."
`(let ((projectile-project-root-cache (make-hash-table :test 'equal))
projectile-project-name
projectile-project-root
projectile-require-project-root)
,@body))
;;;###autoload
(defmacro project-file-exists-p! (files)
"Checks if the project has the specified FILES.
Paths are relative to the project root, unless they start with ./ or ../ (in
which case they're relative to `default-directory'). If they start with a slash,
they are absolute."
`(file-exists-p! ,files (doom-project-root)))
;;
;; Commands
;;;###autoload
(defun doom/find-file-in-other-project (project-root)
"Preforms `projectile-find-file' in a known project of your choosing."
(interactive
(list
(completing-read "Find file in project: " (projectile-relevant-known-projects)
nil nil nil nil (doom-project-root))))
(unless (file-directory-p project-root)
(error "Project directory '%s' doesn't exist" project-root))
(doom-project-find-file project-root))
;;;###autoload
(defun doom/browse-in-other-project (project-root)
"Preforms `find-file' in a known project of your choosing."
(interactive
(list
(completing-read "Browse in project: " (projectile-relevant-known-projects)
nil nil nil nil (doom-project-root))))
(unless (file-directory-p project-root)
(error "Project directory '%s' doesn't exist" project-root))
(doom-project-browse project-root))
;;
;; Library
;;;###autoload
(defun doom-project-p (&optional dir)
"Return t if DIR (defaults to `default-directory') is a valid project."
(and (doom-project-root dir)
t))
;;;###autoload
(defun doom-project-root (&optional dir)
"Return the project root of DIR (defaults to `default-directory').
Returns nil if not in a project."
(let ((projectile-project-root (unless dir projectile-project-root))
projectile-require-project-root)
(projectile-project-root dir)))
;;;###autoload
(defun doom-project-name (&optional dir)
"Return the name of the current project.
Returns '-' if not in a valid project."
(if-let* ((project-root (or (doom-project-root dir)
(if dir (expand-file-name dir)))))
(funcall projectile-project-name-function project-root)
"-"))
;;;###autoload
(defun doom-project-expand (name &optional dir)
"Expand NAME to project root."
(expand-file-name name (doom-project-root dir)))
;;;###autoload
(defun doom-project-find-file (dir)
"Jump to a file in DIR (searched recursively).
If DIR is not a project, it will be indexed (but not cached)."
(unless (file-directory-p dir)
(error "Directory %S does not exist" dir))
(let* ((default-directory (file-truename (expand-file-name dir)))
(project-root (doom-project-root default-directory))
(projectile-project-root default-directory)
(projectile-enable-caching projectile-enable-caching))
(cond ((and project-root (file-equal-p project-root projectile-project-root))
(unless (doom-project-p projectile-project-root)
;; Disable caching if this is not a real project; caching
;; non-projects easily has the potential to inflate the projectile
;; cache beyond reason.
(setq projectile-enable-caching nil))
(call-interactively
;; Intentionally avoid `helm-projectile-find-file', because it runs
;; asynchronously, and thus doesn't see the lexical `default-directory'
(if (featurep! :completion ivy)
#'counsel-projectile-find-file
#'projectile-find-file)))
((fboundp 'project-find-file-in) ; emacs 26.1+ only
(project-find-file-in nil (list default-directory) nil))
((fboundp 'counsel-file-jump) ; ivy only
(call-interactively #'counsel-file-jump))
((call-interactively #'find-file)))))
;;;###autoload
(defun doom-project-browse (dir)
"Traverse a file structure starting linearly from DIR."
(let ((default-directory (file-truename (expand-file-name dir))))
(call-interactively
(cond ((featurep! :completion ivy)
#'counsel-find-file)
((featurep! :completion helm)
#'helm-find-files)
(#'find-file)))))

View file

@ -1,53 +1,146 @@
;;; core/autoload/scratch.el -*- lexical-binding: t; -*-
(defvar doom-scratch-files-dir (concat doom-etc-dir "scratch/")
"Where to store project scratch files, created by
`doom/open-project-scratch-buffer'.")
(defvar doom-scratch-default-file "__default"
"The default file name for a project-less scratch buffer.
Will be saved in `doom-scratch-dir'.")
(defvar doom-scratch-dir (concat doom-etc-dir "scratch")
"Where to save persistent scratch buffers.")
(defvar doom-scratch-buffer-major-mode nil
"What major mode to use in scratch buffers. This can be one of the
following:
t Inherits the major mode of the last buffer you had selected.
nil Uses `fundamental-mode'
MAJOR-MODE Any major mode symbol")
(defvar doom-scratch-buffers nil
"A list of active scratch buffers.")
(defvar-local doom-scratch-current-project nil
"The name of the project associated with the current scratch buffer.")
(defvar doom-scratch-buffer-hook ()
"The hooks to run after a scratch buffer is made.")
"The hooks to run after a scratch buffer is created.")
(defun doom--create-scratch-buffer (&optional project-p)
(let ((text (and (region-active-p)
(buffer-substring-no-properties
(region-beginning) (region-end))))
(mode major-mode)
(derived-p (derived-mode-p 'prog-mode 'text-mode))
(old-project (doom-project-root)))
(unless (file-directory-p doom-scratch-files-dir)
(mkdir doom-scratch-files-dir t))
(with-current-buffer
(if project-p
(find-file-noselect
(expand-file-name (replace-regexp-in-string
"\\." "_" (projectile-project-name)
t t)
doom-scratch-files-dir)
nil t)
(get-buffer-create "*doom:scratch*"))
(when project-p
(rename-buffer (format "*doom:scratch (%s)*" (projectile-project-name))))
(setq default-directory old-project)
(when (and (not (eq major-mode mode))
derived-p
(defun doom--load-persistent-scratch-buffer (name)
(let ((scratch-file (expand-file-name (or name doom-scratch-default-file)
doom-scratch-dir)))
(make-directory doom-scratch-dir t)
(if (not (file-readable-p scratch-file))
nil
(erase-buffer)
(insert-file-contents scratch-file)
(set-auto-mode)
t)))
;;;###autoload
(defun doom-scratch-buffer (&optional mode directory project-name)
"Return a scratchpad buffer in major MODE."
(let* ((buffer-name (if project-name
(format "*doom:scratch (%s)*" project-name)
"*doom:scratch*"))
(buffer (get-buffer buffer-name)))
(with-current-buffer (get-buffer-create buffer-name)
(unless buffer
(setq buffer (current-buffer)
default-directory directory
doom-scratch-current-project project-name)
(setq doom-scratch-buffers (cl-delete-if-not #'buffer-live-p doom-scratch-buffers))
(cl-pushnew buffer doom-scratch-buffers)
(doom--load-persistent-scratch-buffer project-name)
(when (and (eq major-mode 'fundamental-mode)
(functionp mode))
(funcall mode))
(if text (insert text))
(run-hooks 'doom-scratch-buffer-hook)
(current-buffer))))
(add-hook 'kill-buffer-hook #'doom|persist-scratch-buffer nil 'local)
(run-hooks 'doom-scratch-buffer-created-hook))
buffer)))
;;
;;; Persistent scratch buffer
;;;###autoload
(defun doom/open-scratch-buffer ()
"Opens a temporary scratch buffer in a popup window. It is discarded once it
is closed. If a region is active, copy it to the scratch buffer."
(interactive)
(doom-popup-buffer (doom--create-scratch-buffer)))
(defun doom|persist-scratch-buffer ()
"Save the current buffer to `doom-scratch-dir'."
(write-region
(point-min) (point-max)
(expand-file-name (or doom-scratch-current-project doom-scratch-default-file)
doom-scratch-dir)))
;;;###autoload
(defun doom/open-project-scratch-buffer ()
"Opens a (persistent) scratch buffer associated with the current project in a
popup window. Scratch buffers are stored in `doom-scratch-files-dir'. If a
region is active, copy it to the scratch buffer."
(interactive)
(doom-popup-buffer (doom--create-scratch-buffer t)))
(defun doom|persist-scratch-buffers ()
"Save all scratch buffers to `doom-scratch-dir'."
(dolist (buffer (cl-delete-if-not #'buffer-live-p doom-scratch-buffers))
(with-current-buffer buffer
(doom|persist-scratch-buffer))))
;;;###autoload
(add-hook 'kill-emacs-hook #'doom|persist-scratch-buffers)
;;
;;; Commands
;;;###autoload
(defun doom/open-scratch-buffer (&optional arg project-p)
"Opens the (persistent) scratch buffer in a popup.
If passed the prefix ARG, switch to it in the current window.
If PROJECT-P is non-nil, open a persistent scratch buffer associated with the
current project."
(interactive "P")
(let (projectile-enable-caching)
(funcall
(if arg
#'switch-to-buffer
#'pop-to-buffer)
(doom-scratch-buffer
(cond ((eq doom-scratch-buffer-major-mode t)
(unless (or buffer-read-only
(derived-mode-p 'special-mode)
(string-match-p "^ ?\\*" (buffer-name)))
major-mode))
((null doom-scratch-buffer-major-mode)
nil)
((symbolp doom-scratch-buffer-major-mode)
doom-scratch-buffer-major-mode))
default-directory
(when project-p
(doom-project-name))))))
;;;###autoload
(defun doom/open-project-scratch-buffer (&optional arg)
"Opens the (persistent) project scratch buffer in a popup.
If passed the prefix ARG, switch to it in the current window."
(interactive "P")
(doom/open-scratch-buffer arg 'project))
;;;###autoload
(defun doom/revert-scratch-buffer ()
"Revert scratch buffer to last persistent state."
(interactive)
(unless (string-match-p "^\\*doom:scratch" (buffer-name))
(user-error "Not in a scratch buffer"))
(when (doom--load-persistent-scratch-buffer doom-scratch-current-project)
(message "Reloaded scratch buffer")))
;;;###autoload
(defun doom/delete-persistent-scratch-file (&optional arg)
"Deletes a scratch buffer file in `doom-scratch-dir'.
If prefix ARG, delete all persistent scratches."
(interactive)
(if arg
(progn
(delete-directory doom-scratch-dir t)
(message "Cleared %S" (abbreviate-file-name doom-scratch-dir)))
(make-directory doom-scratch-dir t)
(let ((file (read-file-name "Delete scratch file > " doom-scratch-dir "scratch")))
(if (not (file-exists-p file))
(message "%S does not exist" (abbreviate-file-name file))
(delete-file file)
(message "Successfully deleted %S" (abbreviate-file-name file))))))

119
core/autoload/sessions.el Normal file
View file

@ -0,0 +1,119 @@
;;; core/autoload/sessions.el -*- lexical-binding: t; -*-
;;
;;; Helpers
;;;###autoload
(defun doom-save-session (&optional file)
"TODO"
(setq file (expand-file-name (or file (doom-session-file))))
(cond ((require 'persp-mode nil t)
(unless persp-mode (persp-mode +1))
(setq persp-auto-save-opt 0)
(persp-save-state-to-file file))
((and (require 'frameset nil t)
(require 'restart-emacs nil t))
(let ((frameset-filter-alist (append '((client . restart-emacs--record-tty-file))
frameset-filter-alist))
(desktop-base-file-name (file-name-nondirectory file))
(desktop-dirname (file-name-directory file))
(desktop-restore-eager t)
desktop-file-modtime)
(make-directory desktop-dirname t)
(desktop-save desktop-dirname t)))
((error "No session backend to save session with"))))
;;;###autoload
(defun doom-load-session (&optional file)
"TODO"
(setq file (expand-file-name (or file (doom-session-file))))
(message "Attempting to load %s" file)
(cond ((require 'persp-mode nil t)
(unless persp-mode (persp-mode +1))
(persp-load-state-from-file file))
((and (require 'frameset nil t)
(require 'restart-emacs nil t))
(restart-emacs--restore-frames-using-desktop file))
((error "No session backend to load session with"))))
;;;###autoload
(defun doom-session-file ()
"TODO"
(cond ((require 'persp-mode nil t)
(expand-file-name persp-auto-save-fname persp-save-dir))
((require 'desktop nil t)
(desktop-full-file-name))
((error "No session backend available"))))
;;
;;; Command line switch
;;;###autoload
(defun doom-restore-session-handler (&rest _)
"TODO"
(add-hook 'window-setup-hook #'doom-load-session 'append))
;;;###autoload
(add-to-list 'command-switch-alist (cons "--restore" #'doom-restore-session-handler))
;;
;;; Commands
;;;###autoload
(defun doom/quickload-session ()
"TODO"
(interactive)
(message "Restoring session...")
(doom-load-session)
(message "Session restored. Welcome back."))
;;;###autoload
(defun doom/quicksave-session ()
"TODO"
(interactive)
(message "Saving session")
(doom-save-session)
(message "Saving session...DONE"))
;;;###autoload
(defun doom/load-session (file)
"TODO"
(interactive
(let ((session-file (doom-session-file)))
(list (or (read-file-name "Session to restore: "
(file-name-directory session-file)
nil t
(file-name-nondirectory session-file))
(user-error "No session selected. Aborting")))))
(unless file
(error "No session file selected"))
(message "Loading '%s' session" file)
(doom-load-session file))
;;;###autoload
(defun doom/save-session (file)
"TODO"
(interactive
(let ((session-file (doom-session-file)))
(list (or (read-file-name "Save session to: "
(file-name-directory session-file)
nil nil
(file-name-nondirectory session-file))
(user-error "No session selected. Aborting")))))
(unless file
(error "No session file selected"))
(message "Saving '%s' session" file)
(doom-save-session file))
;;;###autoload
(defalias 'doom/restart #'restart-emacs)
;;;###autoload
(defun doom/restart-and-restore (&optional debug)
"TODO"
(interactive "P")
(doom/quicksave-session)
(restart-emacs
(delq nil (list (if debug "--debug-init") "--restore"))))

View file

@ -1,68 +0,0 @@
;;; core/autoload/system.el -*- lexical-binding: t; -*-
;;;###autoload
(defun doom-system-os (&optional os)
"Returns the OS: arch, debian, macos, general linux, cygwin or windows. If OS
is given, returns t if it matches the current system, and nil otherwise."
(let* ((gnu-linux-p (eq system-type 'gnu/linux))
(type (cond ((and gnu-linux-p (file-exists-p "/etc/arch-release"))
'arch)
((and gnu-linux-p (file-exists-p "/etc/debian_version"))
'debian)
(gnu-linux-p
'linux)
((eq system-type 'darwin)
'macos)
((memq system-type '(windows-nt cygwin))
'windows)
(t (error "Unknown OS: %s" system-type)))))
(or (and os (eq os type))
type)))
;;;###autoload
(defun doom-sh (command &rest args)
"Runs a shell command and prints any output to the DOOM buffer."
(let ((cmd-list (split-string command " ")))
(cond ((equal (car cmd-list) "sudo")
(apply #'doom-sudo (string-join (cdr cmd-list) " ") args))
((let ((bin (executable-find "npm")))
(and (file-exists-p bin)
(not (file-writable-p bin))))
(apply #'doom-sudo (string-join cmd-list " ") args))
(t
(princ (shell-command-to-string (apply #'format command args)))))))
(defvar tramp-verbose)
;;;###autoload
(defun doom-sudo (command &rest args)
"Like `doom-sh', but runs as root (prompts for password)."
(let ((tramp-verbose 2))
(with-current-buffer (get-buffer-create "*doom-sudo*")
(unless (string-prefix-p "/sudo::/" default-directory)
(cd "/sudo::/"))
(princ (shell-command-to-string (apply #'format command args))))))
;;;###autoload
(defun doom-fetch (fetcher location dest)
"Clone a remote version-controlled repo at REPO-URL to PATH, if it exists.
Requires the corresponding client, e.g. git for git repos, hg for mercurial,
etc."
(let* ((command (pcase fetcher
(:github "git clone --recursive https://github.com/%s.git")
(:git "git clone --recursive %s")
(:gist "git clone https://gist.github.com/%s.git")
;; TODO Add hg
(_ (error "%s is not a valid fetcher" fetcher))))
(argv (split-string command " " t))
(args (format (string-join (cdr argv) " ") location))
(bin (executable-find (car argv)))
(dest (expand-file-name dest)))
(unless bin
(error "%s couldn't be found" command))
(unless (file-directory-p dest)
(funcall (if noninteractive
(lambda (c) (princ (shell-command-to-string c)))
#'async-shell-command)
(format "%s %s %s" bin args (shell-quote-argument dest)))
(message! "Cloning %s -> %s" location (file-relative-name dest)))))

View file

@ -1,166 +0,0 @@
;;; core/autoload/test.el -*- lexical-binding: t; no-byte-compile: t; -*-
;;;###autoload
(defun doom//run-tests (&optional modules)
"Run all loaded tests, specified by MODULES (a list of module cons cells) or
command line args following a double dash (each arg should be in the
'module/submodule' format).
If neither is available, run all tests in all enabled modules."
(interactive)
(condition-case-unless-debug ex
(let (targets)
;; ensure DOOM is initialized
(let (noninteractive)
(load (expand-file-name "core/core.el" user-emacs-directory) nil t)
(doom-initialize-modules nil))
;; collect targets
(cond ((and argv (equal (car argv) "--"))
(cl-loop for arg in (cdr argv)
if (equal arg "core")
do (push (expand-file-name "test/" doom-core-dir) targets)
else
collect
(cl-destructuring-bind (car &optional cdr) (split-string arg "/" t)
(cons (intern (concat ":" car))
(and cdr (intern cdr))))
into args
finally do
(setq modules args argv nil)))
(modules
(unless (cl-loop for module in modules
unless (and (consp module)
(keywordp (car module))
(symbolp (cdr module)))
return t)
(error "Expected a list of cons, got: %s" modules)))
(t
(let ((noninteractive t)
doom-modules)
(load (expand-file-name "init.test.el" user-emacs-directory) nil t)
(setq modules (doom-module-pairs)
targets (list (expand-file-name "test/" doom-core-dir))))))
;; resolve targets to a list of test files and load them
(cl-loop with targets =
(append targets
(cl-loop for (module . submodule) in modules
if submodule
collect (doom-module-path module submodule "test/")
else
nconc
(cl-loop with module-name = (substring (symbol-name module) 1)
with module-path = (expand-file-name module-name doom-modules-dir)
for path in (directory-files module-path t "^\\w")
collect (expand-file-name "test/" path))))
for dir in targets
if (file-directory-p dir)
nconc (reverse (directory-files-recursively dir "\\.el$"))
into items
finally do (quiet! (mapc #'load-file items)))
;; run all loaded tests
(if noninteractive
(ert-run-tests-batch-and-exit)
(call-interactively #'ert-run-tests-interactively)))
('error
(lwarn 'doom-test :error
"%s -> %s"
(car ex) (error-message-string ex)))))
;; --- Test helpers -----------------------
(defmacro def-test! (name &rest body)
"Define a namespaced ERT test."
(declare (indent defun) (doc-string 2))
(let (plist)
(while (keywordp (car body))
(push (pop body) plist))
(setq plist (reverse plist))
(when (plist-get plist :skip)
(setq body `((ert-skip nil) ,@body)))
(when-let* ((modes (doom-enlist (plist-get plist :minor-mode))))
(dolist (mode modes)
(setq body `((with-minor-mode!! ,mode ,@body)))))
(when-let* ((before (plist-get plist :before)))
(setq body `(,@before ,@body)))
(when-let* ((after (plist-get plist :after)))
(setq body `(,@body @after)))
`(ert-deftest
,(cl-loop with path = (file-relative-name (file-name-sans-extension load-file-name)
doom-emacs-dir)
for (rep . with) in '(("/test/" . "/") ("/" . ":"))
do (setq path (replace-regexp-in-string rep with path t t))
finally return (intern (format "%s::%s" path name)))
()
(with-temp-buffer
(save-mark-and-excursion
(save-window-excursion
,@body))))))
(defmacro should-buffer!! (initial expected &rest body)
"Test that a buffer with INITIAL text, run BODY, then test it against EXPECTED.
INITIAL will recognize cursor markers in the form {[0-9]}. A {0} marker marks
where the cursor should be after setup. Otherwise, the cursor will be placed at
`point-min'.
EXPECTED will recognize one (optional) cursor marker: {|}, this is the
'expected' location of the cursor after BODY is finished, and will be tested
against."
(declare (indent 2))
`(with-temp-buffer
(cl-loop for line in ',initial
do (insert line "\n"))
(goto-char (point-min))
(let (marker-list)
(save-excursion
(while (re-search-forward "{\\([0-9]\\)}" nil t)
(push (cons (match-string 1)
(set-marker (make-marker) (match-beginning 0)))
marker-list)
(replace-match "" t t))
(if (not marker-list)
(goto-char (point-min))
(sort marker-list
(lambda (m1 m2) (< (marker-position m1)
(marker-position m2))))
(when (equal (caar marker-list) "0")
(goto-char!! 0)))
,@body
(let ((result-text (buffer-substring-no-properties (point-min) (point-max)))
(point (point))
same-point
expected-text)
(with-temp-buffer
(cl-loop for line in ',expected
do (insert line "\n"))
(save-excursion
(goto-char 1)
(when (re-search-forward "{|}" nil t)
(setq same-point (= point (match-beginning 0)))
(replace-match "" t t)))
(setq expected-text (buffer-substring-no-properties (point-min) (point-max)))
(should (equal expected-text result-text))
(should same-point)))))))
(defmacro goto-char!! (index)
"Meant to be used with `should-buffer!!'. Will move the cursor to one of the
cursor markers. e.g. Go to marker {2} with (goto-char!! 2)."
`(goto-char (point!! ,index)))
(defmacro point!! (index)
"Meant to be used with `should-buffer!!'. Returns the position of a cursor
marker. e.g. {2} can be retrieved with (point!! 2)."
`(cdr (assoc ,(cond ((numberp index) (number-to-string index))
((symbolp index) (symbol-name index))
((stringp index) index))
marker-list)))
(defmacro with-minor-mode!! (mode &rest body)
"TODO"
(declare (indent defun))
`(progn (,mode +1)
,@body
(,mode -1)))

181
core/autoload/text.el Normal file
View file

@ -0,0 +1,181 @@
;;; core/autoload/text.el -*- lexical-binding: t; -*-
;;;###autoload
(defun doom-surrounded-p (pair &optional inline balanced)
"Returns t if point is surrounded by a brace delimiter: {[(
If INLINE is non-nil, only returns t if braces are on the same line, and
whitespace is balanced on either side of the cursor.
If INLINE is nil, returns t if the opening and closing braces are on adjacent
lines, above and below, with only whitespace in between."
(when pair
(let ((beg (plist-get pair :beg))
(end (plist-get pair :end))
(pt (point)))
(when (and (> pt beg) (< pt end))
(when-let* ((cl (plist-get pair :cl))
(op (plist-get pair :op)))
(and (not (string= op ""))
(not (string= cl ""))
(let ((nbeg (+ (length op) beg))
(nend (- end (length cl))))
(let ((content (buffer-substring-no-properties nbeg nend)))
(and (string-match-p (format "[ %s]*" (if inline "" "\n")) content)
(or (not balanced)
(= (- pt nbeg) (- nend pt))))))))))))
;;
;; Commands
;;;###autoload
(defun doom/backward-to-bol-or-indent ()
"Jump between the indentation column (first non-whitespace character) and the
beginning of the line. The opposite of
`doom/forward-to-last-non-comment-or-eol'."
(interactive)
(let ((pos (point))
(indent (save-excursion
(beginning-of-visual-line)
(skip-chars-forward " \t\r")
(point))))
(cond ((or (> pos indent) (= pos (line-beginning-position)))
(goto-char indent))
((<= pos indent)
(beginning-of-visual-line)))))
;;;###autoload
(defun doom/forward-to-last-non-comment-or-eol ()
"Jumps between the last non-blank, non-comment character in the line and the
true end of the line. The opposite of `doom/backward-to-bol-or-indent'."
(interactive)
(let ((eol (save-excursion (if visual-line-mode
(end-of-visual-line)
(end-of-line))
(point))))
(if (or (and (< (point) eol)
(sp-point-in-comment))
(not (sp-point-in-comment eol)))
(goto-char eol)
(let* ((bol (save-excursion (beginning-of-visual-line) (point)))
(boc (or (save-excursion
(if (not comment-use-syntax)
(progn
(goto-char bol)
(when (re-search-forward comment-start-skip eol t)
(or (match-end 1) (match-beginning 0))))
(goto-char eol)
(while (and (sp-point-in-comment)
(> (point) bol))
(backward-char))
(skip-chars-backward " " bol)
(point)))
eol)))
(cond ((= boc (point))
(goto-char eol))
((/= bol boc)
(goto-char boc)))))))
;;;###autoload
(defun doom/dumb-indent ()
"Inserts a tab character (or spaces x tab-width)."
(interactive)
(if indent-tabs-mode
(insert "\t")
(let* ((movement (% (current-column) tab-width))
(spaces (if (= 0 movement) tab-width (- tab-width movement))))
(insert (make-string spaces ? )))))
;;;###autoload
(defun doom/dumb-dedent ()
"Dedents the current line."
(interactive)
(if indent-tabs-mode
(call-interactively #'backward-delete-char)
(unless (bolp)
(save-excursion
(when (> (current-column) (current-indentation))
(back-to-indentation))
(let ((movement (% (current-column) tab-width)))
(delete-char
(- (if (= 0 movement)
tab-width
(- tab-width movement)))))))))
;;;###autoload
(defun doom/backward-kill-to-bol-and-indent ()
"Kill line to the first non-blank character. If invoked again
afterwards, kill line to beginning of line."
(interactive)
(let ((empty-line-p (save-excursion (beginning-of-line)
(looking-at-p "[ \t]*$"))))
(funcall (if (fboundp 'evil-delete)
#'evil-delete
#'delete-region)
(point-at-bol) (point))
(unless empty-line-p
(indent-according-to-mode))))
;;;###autoload
(defun doom/retab (arg &optional beg end)
"Converts tabs-to-spaces or spaces-to-tabs within BEG and END (defaults to
buffer start and end, to make indentation consistent. Which it does depends on
the value of `indent-tab-mode'.
If ARG (universal argument) is non-nil, retab the current buffer using the
opposite indentation style."
(interactive "Pr")
(unless (and beg end)
(setq beg (point-min)
end (point-max)))
(let ((indent-tabs-mode (if arg (not indent-tabs-mode) indent-tabs-mode)))
(if indent-tabs-mode
(tabify beg end)
(untabify beg end))))
;;;###autoload
(defun doom/delete-trailing-newlines ()
"Trim trailing newlines.
Respects `require-final-newline'."
(interactive)
(goto-char (point-max))
(skip-chars-backward " \t\n\v")
(when (looking-at "\n\\(\n\\|\\'\\)")
(forward-char 1))
(when require-final-newline
(unless (bolp)
(insert "\n")))
(when (looking-at "\n+")
(replace-match "")))
;;;###autoload
(defun doom/dos2unix ()
"Convert the current buffer to a Unix file encoding."
(interactive)
(set-buffer-file-coding-system 'undecided-unix nil))
;;;###autoload
(defun doom/unix2dos ()
"Convert the current buffer to a DOS file encoding."
(interactive)
(set-buffer-file-coding-system 'undecided-dos nil))
;;
;; Hooks
;;;###autoload
(defun doom|enable-delete-trailing-whitespace ()
"Enables the automatic deletion of trailing whitespaces upon file save.
i.e. enables `ws-butler-mode' in the current buffer."
(ws-butler-mode +1))
;;;###autoload
(defun doom|disable-delete-trailing-whitespace ()
"Disables the automatic deletion of trailing whitespaces upon file save.
i.e. disables `ws-butler-mode' in the current buffer."
(ws-butler-mode -1))

View file

@ -1,29 +1,7 @@
;;; core/autoload/ui.el -*- lexical-binding: t; -*-
;;;###autoload
(defun doom/toggle-fullscreen ()
"Toggle fullscreen Emacs (non-native on MacOS)."
(interactive)
(set-frame-parameter
nil 'fullscreen
(unless (frame-parameter nil 'fullscreen)
'fullboth)))
;;;###autoload
(defun doom/toggle-line-numbers (&optional arg)
"Toggle `linum-mode'."
(interactive "P")
(cond ((boundp 'display-line-numbers)
(setq display-line-numbers
(pcase arg
('(4) 'relative)
(1 t)
(-1 nil)
(_ (not display-line-numbers)))))
((featurep 'nlinum)
(nlinum-mode (or arg (if nlinum-mode -1 +1))))
(t
(error "No line number plugin detected"))))
;;
;; Public library
;;;###autoload
(defun doom-resize-window (window new-size &optional horizontal force-p)
@ -35,32 +13,73 @@ If FORCE-P is omitted when `window-size-fixed' is non-nil, resizing will fail."
horizontal))))
;;;###autoload
(defun doom/window-zoom ()
"Close other windows to focus on this one. Activate again to undo this. If the
window changes before then, the undo expires.
(defun doom-quit-p (&optional prompt)
"Prompt the user for confirmation when killing Emacs.
Alternatively, use `doom/window-enlargen'."
(interactive)
(if (and (one-window-p)
(assoc ?_ register-alist))
(jump-to-register ?_)
(window-configuration-to-register ?_)
(delete-other-windows)))
Returns t if it is safe to kill this session. Does not prompt if no real buffers
are open."
(or (not (ignore-errors (doom-real-buffer-list)))
(yes-or-no-p (format " %s" (or prompt "Quit Emacs?")))
(ignore (message "Aborted"))))
;;
;; Advice
(defvar doom--window-enlargened nil)
;;;###autoload
(defun doom/window-enlargen ()
"Enlargen the current window to focus on this one. Does not close other
windows (unlike `doom/window-zoom') Activate again to undo."
(defun doom*recenter (&rest _)
"Generic advisor for recentering window (typically :after other functions)."
(recenter))
;;;###autoload
(defun doom*shut-up (orig-fn &rest args)
"Generic advisor for silencing noisy functions."
(quiet! (apply orig-fn args)))
;;
;; Hooks
;;;###autoload
(defun doom|apply-ansi-color-to-compilation-buffer ()
"Applies ansi codes to the compilation buffers. Meant for
`compilation-filter-hook'."
(with-silent-modifications
(ansi-color-apply-on-region compilation-filter-start (point))))
;;
;; Commands
;;;###autoload
(defun doom/toggle-line-numbers ()
"Toggle line numbers.
Cycles through regular, relative and no line numbers. The order depends on what
`display-line-numbers-type' is set to. If you're using Emacs 26+, and
visual-line-mode is on, this skips relative and uses visual instead.
See `display-line-numbers' for what these values mean."
(interactive)
(setq doom--window-enlargened
(if (and doom--window-enlargened
(assoc ?_ register-alist))
(ignore (jump-to-register ?_))
(window-configuration-to-register ?_)
(doom-resize-window nil (truncate (/ (frame-width) 1.2)) t)
(doom-resize-window nil (truncate (/ (frame-height) 1.2)))
t)))
(defvar doom--line-number-style display-line-numbers-type)
(let* ((styles `(t ,(if (and EMACS26+ visual-line-mode) 'visual 'relative) nil))
(order (cons display-line-numbers-type (remq display-line-numbers-type styles)))
(queue (memq doom--line-number-style order))
(next (if (= (length queue) 1)
(car order)
(car (cdr queue)))))
(setq doom--line-number-style next)
(if EMACS26+
(setq display-line-numbers next)
(pcase next
(`t (nlinum-relative-off) (nlinum-mode +1))
(`relative (nlinum-relative-on))
(`nil (nlinum-mode -1))))
(message "Switched to %s line numbers"
(pcase next
(`t "normal")
(`nil "disabled")
(_ (symbol-name next))))))
;;;###autoload
(defun doom/delete-frame ()
@ -71,26 +90,123 @@ windows (unlike `doom/window-zoom') Activate again to undo."
(delete-frame))
(save-buffers-kill-emacs)))
;;;###autoload
(defun doom/window-maximize-buffer ()
"Close other windows to focus on this one. Activate again to undo this. If the
window changes before then, the undo expires.
Alternatively, use `doom/window-enlargen'."
(interactive)
(if (and (one-window-p)
(assq ?_ register-alist))
(jump-to-register ?_)
(window-configuration-to-register ?_)
(delete-other-windows)))
(defvar doom--window-enlargened nil)
;;;###autoload
(defun doom/window-enlargen ()
"Enlargen the current window to focus on this one. Does not close other
windows (unlike `doom/window-maximize-buffer') Activate again to undo."
(interactive)
(setq doom--window-enlargened
(if (and doom--window-enlargened
(assq ?_ register-alist))
(ignore (ignore-errors (jump-to-register ?_)))
(window-configuration-to-register ?_)
(if (window-dedicated-p)
;; `window-resize' and `window-max-delta' don't respect
;; `ignore-window-parameters', so we gotta force it to.
(cl-letf* ((old-window-resize (symbol-function #'window-resize))
(old-window-max-delta (symbol-function #'window-max-delta))
((symbol-function #'window-resize)
(lambda (window delta &optional horizontal _ignore pixelwise)
(funcall old-window-resize window delta horizontal
t pixelwise)))
((symbol-function #'window-max-delta)
(lambda (&optional window horizontal _ignore trail noup nodown pixelwise)
(funcall old-window-max-delta window horizontal t
trail noup nodown pixelwise))))
(maximize-window))
(maximize-window))
t)))
;;;###autoload
(defun doom/window-maximize-horizontally ()
"Delete all windows to the left and right of the current window."
(interactive)
(require 'windmove)
(save-excursion
(while (ignore-errors (windmove-left)) (delete-window))
(while (ignore-errors (windmove-right)) (delete-window))))
;;;###autoload
(defun doom/window-maximize-vertically ()
"Delete all windows above and below the current window."
(interactive)
(require 'windmove)
(save-excursion
(while (ignore-errors (windmove-up)) (delete-window))
(while (ignore-errors (windmove-down)) (delete-window))))
;;;###autoload
(defun doom/set-frame-opacity (opacity)
"Interactively change the current frame's opacity.
OPACITY is an integer between 0 to 100, inclusive."
(interactive
(list (read-number "Opacity (0-100): "
(or (frame-parameter nil 'alpha)
100))))
(set-frame-parameter nil 'alpha opacity))
(defvar-local doom--buffer-narrowed-origin nil)
;;;###autoload
(defun doom/clone-and-narrow-buffer (beg end &optional clone-p)
"Restrict editing in this buffer to the current region, indirectly. With CLONE-P,
clone the buffer and hard-narrow the selection. If mark isn't active, then widen
the buffer (if narrowed).
Inspired from http://demonastery.org/2013/04/emacs-evil-narrow-region/"
(interactive "rP")
(cond ((or (region-active-p)
(and beg end))
(deactivate-mark)
(when clone-p
(let ((old-buf (current-buffer)))
(switch-to-buffer (clone-indirect-buffer nil nil))
(setq doom--buffer-narrowed-origin old-buf)))
(narrow-to-region beg end))
(doom--buffer-narrowed-origin
(kill-this-buffer)
(switch-to-buffer doom--buffer-narrowed-origin)
(setq doom--buffer-narrowed-origin nil))
(t
(widen))))
;;
;; Modes
;;;###autoload
(define-minor-mode doom-big-font-mode
"A global mode that resizes the font, for streams, screen-sharing and
presentations."
presentations.
Uses `doom-big-font' when enabled."
:init-value nil
:lighter " BIG"
:global t
(unless (fontp doom-big-font)
(user-error "`doom-big-font' isn't set to a valid font"))
(if doom-big-font-mode
(set-frame-font doom-big-font t t)
(unless doom-big-font
(user-error "`doom-big-font' must be set to a valid font"))
(unless doom-font
(user-error "`doom-font' must be set to a valid font"))
(let ((doom-font (if doom-big-font-mode
doom-big-font
doom-font)))
(setf (alist-get 'font default-frame-alist)
(cond ((null doom-font))
((stringp doom-font) doom-font)
((fontp doom-font) (font-xlfd-name doom-font))
((signal 'wrong-type-argument (list '(fontp stringp) doom-font)))))
(set-frame-font doom-font t t)))
;;;###autoload
(defun doom//reload-theme ()
"Reset the color theme currently in use."
(interactive)
(let ((theme (or (car-safe custom-enabled-themes) doom-theme)))
(when theme
(mapc #'disable-theme custom-enabled-themes))
(run-hooks 'doom-pre-reload-theme-hook)
(doom|init-ui)
(run-hooks 'doom-post-reload-theme-hook)))

368
core/cli/autoloads.el Normal file
View file

@ -0,0 +1,368 @@
;;; core/cli/autoloads.el -*- lexical-binding: t; -*-
(dispatcher! (autoloads a) (doom-reload-autoloads nil 'force)
"Regenerates Doom's autoloads file.
This file tells Emacs where to find your module's autoloaded functions and
plugins.")
;; external variables
(defvar autoload-timestamps)
(defvar generated-autoload-load-name)
(defvar generated-autoload-file)
;;
;; Helpers
(defvar doom-autoload-excluded-packages '(marshal gh)
"Packages that have silly or destructive autoload files that try to load
everyone in the universe and their dog, causing errors that make babies cry. No
one wants that.")
(defun doom-delete-autoloads-file (file)
"Delete FILE (an autoloads file), and delete the accompanying *.elc file, if
it exists."
(cl-check-type file string)
(when (file-exists-p file)
(when-let* ((buf (find-buffer-visiting doom-autoload-file)))
(with-current-buffer buf
(set-buffer-modified-p nil))
(kill-buffer buf))
(delete-file file)
(ignore-errors (delete-file (byte-compile-dest-file file)))
(message "Deleted old %s" (file-name-nondirectory file))))
(defun doom--warn-refresh-session ()
(print! (bold (green "\nFinished!")))
(message "If you have a running Emacs Session, you will need to restart it or")
(message "reload Doom for changes to take effect:\n")
(message " M-x doom/restart-and-restore")
(message " M-x doom/restart")
(message " M-x doom/reload"))
(defun doom--do-load (&rest files)
(if (and noninteractive (not (daemonp)))
(add-hook 'kill-emacs-hook #'doom--warn-refresh-session)
(dolist (file files)
(load-file (byte-compile-dest-file file)))))
(defun doom--byte-compile-file (file)
(let ((short-name (file-name-nondirectory file))
(byte-compile-dynamic-docstrings t))
(condition-case e
(when (byte-compile-file file)
;; Give autoloads file a chance to report error
(load (if doom-debug-mode
file
(byte-compile-dest-file file))
nil t)
(unless noninteractive
(message "Finished compiling %s" short-name)))
((debug error)
(let ((backup-file (concat file ".bk")))
(message "Copied backup to %s" backup-file)
(copy-file file backup-file 'overwrite))
(doom-delete-autoloads-file file)
(signal 'doom-autoload-error (list short-name e))))))
(defun doom-reload-autoloads (&optional file force-p)
"Reloads FILE (an autoload file), if it needs reloading.
FILE should be one of `doom-autoload-file' or `doom-package-autoload-file'. If
it is nil, it will try to reload both. If FORCE-P (universal argument) do it
even if it doesn't need reloading!"
(or (null file)
(stringp file)
(signal 'wrong-type-argument (list 'stringp file)))
(if (stringp file)
(cond ((file-equal-p file doom-autoload-file)
(doom-reload-doom-autoloads force-p))
((file-equal-p file doom-package-autoload-file)
(doom-reload-package-autoloads force-p))
((error "Invalid autoloads file: %s" file)))
(doom-reload-doom-autoloads force-p)
(doom-reload-package-autoloads force-p)))
;;
;; Doom autoloads
(defun doom--file-cookie-p (file)
"Returns the return value of the ;;;###if predicate form in FILE."
(with-temp-buffer
(insert-file-contents-literally file nil 0 256)
(if (and (re-search-forward "^;;;###if " nil t)
(<= (line-number-at-pos) 3))
(let ((load-file-name file))
(eval (sexp-at-point)))
t)))
(defun doom--generate-header (func)
(goto-char (point-min))
(insert ";; -*- lexical-binding:t -*-\n"
";; This file is autogenerated by `" (symbol-name func) "', DO NOT EDIT !!\n\n"))
(defun doom--generate-autoloads (targets)
(require 'autoload)
(dolist (file targets)
(let* ((file (file-truename file))
(generated-autoload-file doom-autoload-file)
(generated-autoload-load-name (file-name-sans-extension file))
(noninteractive (not doom-debug-mode))
autoload-timestamps)
(print!
(cond ((not (doom--file-cookie-p file))
"⚠ Ignoring %s")
((autoload-generate-file-autoloads file (current-buffer))
(yellow "✕ Nothing in %s"))
((green "✓ Scanned %s")))
(if (file-in-directory-p file default-directory)
(file-relative-name file)
(abbreviate-file-name file))))))
(defun doom--expand-autoloads ()
(let ((load-path
;; NOTE With `doom-private-dir' in `load-path', Doom autoloads files
;; will be unable to declare autoloads for the built-in autoload.el
;; Emacs package, should $DOOMDIR/autoload.el exist. Not sure why
;; they'd want to though, so it's an acceptable compromise.
(append (list doom-private-dir)
doom-modules-dirs
load-path))
cache)
(while (re-search-forward "^\\s-*(autoload\\s-+'[^ ]+\\s-+\"\\([^\"]*\\)\"" nil t)
(let ((path (match-string 1)))
(replace-match
(or (cdr (assoc path cache))
(when-let* ((libpath (locate-library path))
(libpath (file-name-sans-extension libpath)))
(push (cons path (abbreviate-file-name libpath)) cache)
libpath)
path)
t t nil 1)))))
(defun doom--generate-autodefs (targets enabled-targets)
(goto-char (point-max))
(search-backward ";;;***" nil t)
(save-excursion (insert "\n"))
(dolist (path targets)
(insert
(with-temp-buffer
(insert-file-contents path)
(let ((member-p (or (member path enabled-targets)
(file-in-directory-p path doom-core-dir)))
forms)
(while (re-search-forward "^;;;###autodef *\\([^\n]+\\)?\n" nil t)
(let* ((sexp (sexp-at-point))
(alt-sexp (match-string 1))
(type (car sexp))
(name (doom-unquote (cadr sexp)))
(origin (cond ((doom-module-from-path path))
((file-in-directory-p path doom-private-dir)
`(:private . ,(intern (file-name-base path))))
((file-in-directory-p path doom-emacs-dir)
`(:core . ,(intern (file-name-base path))))))
(doom-file-form
`(put ',name 'doom-file ,(abbreviate-file-name path))))
(cond ((and (not member-p) alt-sexp)
(push (read alt-sexp) forms))
((memq type '(defun defmacro cl-defun cl-defmacro))
(cl-destructuring-bind (_ name arglist &rest body) sexp
(let ((docstring (if (stringp (car body))
(pop body)
"No documentation.")))
(push (if member-p
(make-autoload sexp (abbreviate-file-name (file-name-sans-extension path)))
(push doom-file-form forms)
(setq docstring (format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s"
origin docstring))
(condition-case-unless-debug e
(if alt-sexp
(read alt-sexp)
(append (list (pcase type
(`defun 'defmacro)
(`cl-defun `cl-defmacro)
(_ type))
name arglist docstring)
(cl-loop for arg in arglist
if (and (symbolp arg)
(not (keywordp arg))
(not (memq arg cl--lambda-list-keywords)))
collect arg into syms
else if (listp arg)
collect (car arg) into syms
finally return (if syms `((ignore ,@syms))))))
('error
(message "Ignoring autodef %s (%s)"
name e)
nil)))
forms)
(push `(put ',name 'doom-module ',origin) forms))))
((eq type 'defalias)
(cl-destructuring-bind (_type name target &optional docstring) sexp
(let ((name (doom-unquote name))
(target (doom-unquote target)))
(unless member-p
(setq docstring (format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s"
origin docstring))
(setq target #'ignore))
(push doom-file-form forms)
(push `(put ',name 'doom-module ',origin) forms)
(push `(defalias ',name #',target ,docstring)
forms))))
(member-p
(push sexp forms)))))
(if forms
(concat (string-join (mapcar #'prin1-to-string (reverse forms)) "\n")
"\n")
""))))))
(defun doom--cleanup-autoloads ()
(goto-char (point-min))
(when (re-search-forward "^;;\\(;[^\n]*\\| no-byte-compile: t\\)\n" nil t)
(replace-match "" t t)))
(defun doom-reload-doom-autoloads (&optional force-p)
"Refreshes the autoloads.el file, specified by `doom-autoload-file', if
necessary (or if FORCE-P is non-nil).
It scans and reads core/autoload/*.el, modules/*/*/autoload.el and
modules/*/*/autoload/*.el, and generates `doom-autoload-file'. This file tells
Emacs where to find lazy-loaded functions.
This should be run whenever your `doom!' block, or a module autoload file, is
modified."
(let* ((default-directory doom-emacs-dir)
(doom-modules (doom-modules))
(targets
(file-expand-wildcards
(expand-file-name "autoload/*.el" doom-core-dir)))
(enabled-targets (copy-sequence targets))
case-fold-search)
(dolist (path (doom-module-load-path t))
(let* ((auto-dir (expand-file-name "autoload" path))
(auto-file (expand-file-name "autoload.el" path))
(module (doom-module-from-path auto-file))
(module-p (or (doom-module-p (car module) (cdr module))
(file-equal-p path doom-private-dir))))
(when (file-exists-p auto-file)
(push auto-file targets)
(if module-p (push auto-file enabled-targets)))
(dolist (file (doom-files-in auto-dir :match "\\.el$" :full t))
(push file targets)
(if module-p (push file enabled-targets)))))
(if (and (not force-p)
(not doom-emacs-changed-p)
(file-exists-p doom-autoload-file)
(not (file-newer-than-file-p (expand-file-name "init.el" doom-private-dir)
doom-autoload-file))
(not (cl-loop for file in targets
if (file-newer-than-file-p file doom-autoload-file)
return t)))
(progn (print! (green "Doom core autoloads is up-to-date"))
(doom-initialize-autoloads doom-autoload-file)
nil)
(doom-delete-autoloads-file doom-autoload-file)
(message "Generating new autoloads.el")
(make-directory (file-name-directory doom-autoload-file) t)
(with-temp-file doom-autoload-file
(doom--generate-header 'doom-reload-doom-autoloads)
(save-excursion
(doom--generate-autoloads (reverse enabled-targets)))
;; Replace autoload paths (only for module autoloads) with absolute
;; paths for faster resolution during load and simpler `load-path'
(save-excursion
(doom--expand-autoloads)
(print! (green "✓ Expanded module autoload paths")))
;; Generates stub definitions for functions/macros defined in disabled
;; modules, so that you will never get a void-function when you use
;; them.
(save-excursion
(doom--generate-autodefs (reverse targets) enabled-targets)
(print! (green "✓ Generated autodefs")))
;; Remove byte-compile inhibiting file variables so we can byte-compile
;; the file, and autoload comments.
(doom--cleanup-autoloads)
(print! (green "✓ Clean up autoloads")))
;; Byte compile it to give the file a chance to reveal errors.
(doom--byte-compile-file doom-autoload-file)
(doom--do-load doom-autoload-file)
t)))
;;
;; Package autoloads
(defun doom--generate-package-autoloads ()
(dolist (spec (doom-get-package-alist))
(if-let* ((pkg (car spec))
(desc (cdr spec)))
(unless (memq pkg doom-autoload-excluded-packages)
(let ((file (concat (package--autoloads-file-name desc) ".el")))
(when (file-exists-p file)
(insert "(let ((load-file-name " (prin1-to-string (abbreviate-file-name file)) "))\n")
(insert-file-contents file)
(while (re-search-forward "^\\(?:;;\\(.*\n\\)\\|\n\\|(provide '[^\n]+\\)" nil t)
(unless (nth 8 (syntax-ppss))
(replace-match "" t t)))
(unless (bolp) (insert "\n"))
(insert ")\n"))))
(message "Couldn't find package desc for %s" (car spec)))))
(defun doom--generate-var-cache ()
(doom-initialize-packages)
(prin1 `(setq load-path ',load-path
auto-mode-alist ',auto-mode-alist
Info-directory-list ',Info-directory-list
doom-disabled-packages ',(mapcar #'car (doom-find-packages :disabled t))
package-activated-list ',package-activated-list)
(current-buffer)))
(defun doom--cleanup-package-autoloads ()
(while (re-search-forward "^\\s-*\\((\\(?:add-to-list\\|\\(?:when\\|if\\) (boundp\\)\\s-+'\\(?:load-path\\|auto-mode-alist\\)\\)" nil t)
(goto-char (match-beginning 1))
(kill-sexp)))
(defun doom-reload-package-autoloads (&optional force-p)
"Compiles `doom-package-autoload-file' from the autoloads files of all
installed packages. It also caches `load-path', `Info-directory-list',
`doom-disabled-packages', `package-activated-list' and `auto-mode-alist'.
Will do nothing if none of your installed packages have been modified. If
FORCE-P (universal argument) is non-nil, regenerate it anyway.
This should be run whenever your `doom!' block or update your packages."
(if (and (not force-p)
(not doom-emacs-changed-p)
(file-exists-p doom-package-autoload-file)
(not (file-newer-than-file-p doom-packages-dir doom-package-autoload-file))
(not (ignore-errors
(cl-loop for key being the hash-keys of (doom-modules)
for path = (doom-module-path (car key) (cdr key) "packages.el")
if (file-newer-than-file-p path doom-package-autoload-file)
return t))))
(ignore (print! (green "Doom package autoloads is up-to-date"))
(doom-initialize-autoloads doom-package-autoload-file))
(let (case-fold-search)
(doom-delete-autoloads-file doom-package-autoload-file)
(with-temp-file doom-package-autoload-file
(doom--generate-header 'doom-reload-package-autoloads)
(save-excursion
;; Cache important and expensive-to-initialize state here.
(doom--generate-var-cache)
(print! (green "✓ Cached package state"))
;; Concatenate the autoloads of all installed packages.
(doom--generate-package-autoloads)
(print! (green "✓ Package autoloads included")))
;; Remove `load-path' and `auto-mode-alist' modifications (most of them,
;; at least); they are cached later, so all those membership checks are
;; unnecessary overhead.
(doom--cleanup-package-autoloads)
(print! (green "✓ Removed load-path/auto-mode-alist entries"))))
(doom--byte-compile-file doom-package-autoload-file)
(doom--do-load doom-package-autoload-file)
t))

190
core/cli/byte-compile.el Normal file
View file

@ -0,0 +1,190 @@
;;; core/cli/byte-compile.el -*- lexical-binding: t; -*-
(dispatcher! (compile c) (doom-byte-compile args)
"Byte-compiles your config or selected modules.
compile [TARGETS...]
compile :core :private lang/python
compile feature lang
Accepts :core, :private and :plugins as special arguments, indicating you want
to byte-compile Doom's core files, your private config or your ELPA plugins,
respectively.")
(dispatcher! (recompile rc) (doom-byte-compile args 'recompile)
"Re-byte-compiles outdated *.elc files.")
(dispatcher! clean (doom-clean-byte-compiled-files)
"Delete all *.elc files.")
;;
;; Helpers
(defun doom--byte-compile-ignore-file-p (path)
(let ((filename (file-name-nondirectory path)))
(or (string-prefix-p "." filename)
(string-prefix-p "test-" filename)
(not (equal (file-name-extension path) "el")))))
(defun doom-byte-compile (&optional modules recompile-p)
"Byte compiles your emacs configuration.
init.el is always byte-compiled by this.
If MODULES is specified (a list of module strings, e.g. \"lang/php\"), those are
byte-compiled. Otherwise, all enabled modules are byte-compiled, including Doom
core. It always ignores unit tests and files with `no-byte-compile' enabled.
WARNING: byte-compilation yields marginal gains and makes debugging new issues
difficult. It is recommended you don't use it unless you understand the
reprecussions.
Use `doom-clean-byte-compiled-files' or `make clean' to reverse
byte-compilation.
If RECOMPILE-P is non-nil, only recompile out-of-date files."
(let ((default-directory doom-emacs-dir)
(total-ok 0)
(total-fail 0)
(total-noop 0)
compile-plugins-p
targets)
(dolist (module (delete-dups modules) (nreverse targets))
(pcase module
(":core" (push doom-core-dir targets))
(":private" (push doom-private-dir targets))
(":plugins"
(cl-loop for (_name . desc) in (doom-get-package-alist)
do (package--compile desc))
(setq compile-plugins-p t
modules (delete ":plugins" modules)))
((pred file-directory-p)
(push module targets))
((pred (string-match "^\\([^/]+\\)/\\([^/]+\\)$"))
(push (doom-module-locate-path
(doom-keyword-intern (match-string 1 module))
(intern (match-string 2 module)))
targets))))
(cl-block 'byte-compile
;; If we're just here to byte-compile our plugins, we're done!
(and (not modules)
compile-plugins-p
(cl-return-from 'byte-compile t))
(unless (or (equal modules '(":core"))
recompile-p)
(unless (or doom-auto-accept
(y-or-n-p
(concat "Warning: byte compiling is for advanced users. It will interfere with your\n"
"efforts to debug issues. It is not recommended you do it if you frequently\n"
"tinker with your Emacs config.\n\n"
"Alternatively, use `bin/doom compile :core` instead to byte-compile only the\n"
"Doom core files, as these don't change often.\n\n"
"If you have issues, please make sure byte-compilation isn't the cause by using\n"
"`bin/doom clean` to clear out your *.elc files.\n\n"
"Byte-compile anyway?")))
(message "Aborting.")
(cl-return-from 'byte-compile)))
(when (and (not recompile-p)
(or (null modules)
(equal modules '(":core"))))
(doom-clean-byte-compiled-files))
(let (doom-emacs-changed-p
noninteractive)
;; But first we must be sure that Doom and your private config have been
;; fully loaded. Which usually aren't so in an noninteractive session.
(unless (and (doom-initialize-autoloads doom-autoload-file)
(doom-initialize-autoloads doom-package-autoload-file))
(doom-reload-autoloads))
(doom-initialize)
(doom-initialize-modules 'force))
;; If no targets were supplied, then we use your module list.
(unless modules
(let ((doom-modules-dirs (delete (expand-file-name "modules/" doom-private-dir)
doom-modules-dirs)))
(setq targets
(append (list doom-core-dir)
(delete doom-private-dir (doom-module-load-path))))))
;; Assemble el files we want to compile; taking into account that
;; MODULES may be a list of MODULE/SUBMODULE strings from the command
;; line.
(let ((target-files (doom-files-in targets :filter #'doom--byte-compile-ignore-file-p)))
(when (or (not modules)
(member ":core" modules))
(push (expand-file-name "init.el" doom-emacs-dir)
target-files))
(unless target-files
(if targets
(message "Couldn't find any valid targets")
(message "No targets to %scompile" (if recompile-p "re" "")))
(cl-return-from 'byte-compile))
(require 'use-package)
(condition-case e
(let ((use-package-defaults use-package-defaults)
(use-package-expand-minimally t)
(load-path load-path)
kill-emacs-hook kill-buffer-query-functions)
;; Prevent packages from being loaded at compile time if they
;; don't meet their own predicates.
(push (list :no-require t
(lambda (_name args)
(or (when-let* ((pred (or (plist-get args :if)
(plist-get args :when))))
(not (eval pred t)))
(when-let* ((pred (plist-get args :unless)))
(eval pred t)))))
use-package-defaults)
(dolist (target (cl-delete-duplicates (mapcar #'file-truename target-files) :test #'equal))
(if (or (not recompile-p)
(let ((elc-file (byte-compile-dest-file target)))
(and (file-exists-p elc-file)
(file-newer-than-file-p target elc-file))))
(let ((result (if (or (string-match-p "/\\(?:packages\\|doctor\\)\\.el$" target)
(not (doom--file-cookie-p target)))
'no-byte-compile
(byte-compile-file target)))
(short-name (if (file-in-directory-p target doom-emacs-dir)
(file-relative-name target doom-emacs-dir)
(abbreviate-file-name target))))
(cl-incf
(cond ((eq result 'no-byte-compile)
(print! (dark (white "⚠ Ignored %s")) short-name)
total-noop)
((null result)
(print! (red "✕ Failed to compile %s") short-name)
total-fail)
(t
(print! (green "✓ Compiled %s") short-name)
(load target t t)
total-ok))))
(cl-incf total-noop)))
(print! (bold (color (if (= total-fail 0) 'green 'red)
"%s %d/%d file(s) (%d ignored)"))
(if recompile-p "Recompiled" "Compiled")
total-ok (- (length target-files) total-noop)
total-noop)
(or (= total-fail 0)
(error "Failed to compile some files")))
((debug error)
(print! (red "\nThere were breaking errors.\n\n%s")
"Reverting changes...")
(signal 'doom-error (list 'byte-compile e))))))))
(defun doom-clean-byte-compiled-files ()
"Delete all the compiled elc files in your Emacs configuration and private
module. This does not include your byte-compiled, third party packages.'"
(cl-loop with default-directory = doom-emacs-dir
for path
in (append (doom-files-in doom-emacs-dir :match "\\.elc$" :depth 0)
(doom-files-in doom-private-dir :match "\\.elc$" :depth 1)
(doom-files-in doom-core-dir :match "\\.elc$")
(doom-files-in doom-modules-dirs :match "\\.elc$" :depth 4))
for truepath = (file-truename path)
if (file-exists-p path)
do (delete-file path)
and do
(print! (green "✓ Deleted %s")
(if (file-in-directory-p truepath default-directory)
(file-relative-name truepath)
(abbreviate-file-name truepath)))
finally do (print! (bold (green "Everything is clean")))))

7
core/cli/debug.el Normal file
View file

@ -0,0 +1,7 @@
;;; core/cli/debug.el -*- lexical-binding: t; -*-
(dispatcher! info (doom/info)
"Output system info in markdown for bug reports.")
(dispatcher! (version v) (doom/version)
"Reports the version of Doom and Emacs.")

123
core/cli/env.el Normal file
View file

@ -0,0 +1,123 @@
;;; core/cli/env.el -*- lexical-binding: t; -*-
(dispatcher! env
(let ((env-file (abbreviate-file-name doom-env-file)))
(pcase (car args)
((or "refresh" "re")
(doom-reload-env-file 'force))
("enable"
(setenv "DOOMENV" "1")
(print! (green "Enabling auto-reload of %S") env-file)
(doom-reload-env-file 'force)
(print! (green "Done! `doom reload' will now refresh your envvar file.")))
("clear"
(setenv "DOOMENV" nil)
(unless (file-exists-p env-file)
(user-error "%S does not exist to be cleared" env-file))
(delete-file env-file)
(print! (green "Disabled envvar file by deleting %S") env-file))
(_
(message "No valid subcommand provided. See `doom help env`."))))
"Manages your envvars file.
env [SUBCOMMAND]
Available subcommands:
refresh Create or regenerate your envvar file
enable enable auto-reloading of your envvars file (on `doom refresh`)
clear deletes your envvar file (if it exists) and disables auto-reloading
An envvars file (its location is controlled by the `doom-env-file' variable)
will contain a list of environment variables scraped from your shell environment
and loaded when Doom starts (if it exists). This is necessary when Emacs can't
be launched from your shell environment (e.g. on MacOS or certain app launchers
on Linux).
To generate a file, run `doom env refresh`. If you'd like this file to be
auto-reloaded when running `doom refresh`, run `doom env enable` instead (only
needs to be run once).")
;;
;; Helpers
(defvar doom-env-ignored-vars
'("DBUS_SESSION_BUS_ADDRESS"
"GPG_AGENT_INFO"
"SSH_AGENT_PID"
"SSH_AUTH_SOCK"
;; Doom envvars
"INSECURE"
"DEBUG"
"YES")
"Environment variables to not save in `doom-env-file'.")
(defvar doom-env-executable
(if IS-WINDOWS
"set"
(executable-find "env"))
"The program to use to scrape your shell environment with.
It is rare that you'll need to change this.")
(defvar doom-env-switches
(if IS-WINDOWS
'("-c")
;; Execute twice, once in a non-interactive login shell and once in an
;; interactive shell in order to capture all the init files possible.
'("-lc" "-ic"))
"The `shell-command-switch'es to use on `doom-env-executable'.
This is a list of strings. Each entry is run separately and in sequence with
`doom-env-executable' to scrape envvars from your shell environment.")
;; Borrows heavily from Spacemacs' `spacemacs//init-spacemacs-env'.
(defun doom-reload-env-file (&optional force-p)
"Generates `doom-env-file', if it doesn't exist (or FORCE-P is non-nil).
Runs `doom-env-executable' X times, where X = length of `doom-env-switches', to
scrape the variables from your shell environment. Duplicates are removed. The
order of `doom-env-switches' determines priority."
(when (or force-p (not (file-exists-p doom-env-file)))
(with-temp-file doom-env-file
(message "%s envvars file at %S"
(if (file-exists-p doom-env-file)
"Regenerating"
"Generating")
(abbreviate-file-name doom-env-file))
(let ((process-environment doom-site-process-environment))
(insert
(concat
"# -*- mode: dotenv -*-\n"
"# ---------------------------------------------------------------------------\n"
"# This file was auto-generated by Doom by running:\n"
"#\n"
(cl-loop for switch in doom-env-switches
concat (format "# %s %s %s\n"
shell-file-name
switch
doom-env-executable))
"#\n"
"# It contains all environment variables scraped from your default shell\n"
"# (excluding variables blacklisted in doom-env-ignored-vars).\n"
"#\n"
"# It is NOT safe to edit this file. Changes will be overwritten next time\n"
"# that `doom env refresh` is executed. Alternatively, create your own env file\n"
"# in your DOOMDIR and load that with `(load-env-vars FILE)`.\n"
"#\n"
"# To auto-regenerate this file when `doom reload` is run, use `doom env enable'\n"
"# or set DOOMENV=1 in your shell environment/config.\n"
"# ---------------------------------------------------------------------------\n\n"))
(let ((env-point (point)))
(dolist (shell-command-switch doom-env-switches)
(message "Scraping env from '%s %s %s'"
shell-file-name
shell-command-switch
doom-env-executable)
(insert (shell-command-to-string doom-env-executable)))
;; sort the environment variables
(sort-lines nil env-point (point-max))
;; remove adjacent duplicated lines
(delete-duplicate-lines env-point (point-max) nil t)
;; remove ignored environment variables
(dolist (var doom-env-ignored-vars)
(flush-lines (concat "^" var "=") env-point (point-max))))))))

179
core/cli/packages.el Normal file
View file

@ -0,0 +1,179 @@
;; -*- no-byte-compile: t; -*-
;;; core/cli/packages.el
(dispatcher! (install i) (doom--do #'doom-packages-install)
"Installs requested packages that aren't installed.")
(dispatcher! (update u) (doom--do #'doom-packages-update)
"Updates packages.")
(dispatcher! (autoremove r) (doom--do #'doom-packages-autoremove)
"Removes packages that are no longer needed.")
;;
;; Helpers
(defmacro doom--condition-case! (&rest body)
`(condition-case-unless-debug e
(progn ,@body)
('user-error
(print! (bold (red " NOTICE: %s")) e))
('file-error
(print! (bold (red " FILE ERROR: %s")) (error-message-string e))
(print! " Trying again...")
(quiet! (doom-refresh-packages-maybe t))
,@body)
('error
(print! (bold (red " FATAL ERROR: %s\n Run again with the -d flag for details")) e))))
(defsubst doom--do (fn)
(doom-reload-doom-autoloads)
(when (funcall fn doom-auto-accept)
(doom-reload-package-autoloads)))
;;
;; Library
(defun doom-packages-install (&optional auto-accept-p)
"Interactive command for installing missing packages."
(print! "Looking for packages to install...")
(let ((packages (doom-get-missing-packages)))
(cond ((not packages)
(print! (green "No packages to install!"))
nil)
((not (or auto-accept-p
(y-or-n-p
(format "%s packages will be installed:\n\n%s\n\nProceed?"
(length packages)
(mapconcat
(lambda (pkg)
(format "+ %s (%s)"
(car pkg)
(cond ((doom-package-different-recipe-p (car pkg))
"new recipe")
((doom-package-different-backend-p (car pkg))
(if (plist-get (cdr pkg) :recipe)
"ELPA->QUELPA"
"QUELPA->ELPA"))
((plist-get (cdr pkg) :recipe)
"QUELPA")
("ELPA"))))
(cl-sort (cl-copy-list packages) #'string-lessp
:key #'car)
"\n")))))
(user-error "Aborted!"))
((let (success)
(doom-refresh-packages-maybe doom-debug-mode)
(dolist (pkg packages)
(print! "Installing %s" (car pkg))
(doom--condition-case!
(let ((result
(or (and (doom-package-installed-p (car pkg))
(not (doom-package-different-backend-p (car pkg)))
(not (doom-package-different-recipe-p (car pkg)))
'already-installed)
(and (doom-install-package (car pkg) (cdr pkg))
(setq success t)
'success)
'failure))
(pin-label
(and (plist-member (cdr pkg) :pin)
(format " [pinned: %s]" (plist-get (cdr pkg) :pin)))))
(print! "%s%s"
(pcase result
(`already-installed (dark (white "⚠ ALREADY INSTALLED")))
(`success (green "✓ DONE"))
(`failure (red "✕ FAILED")))
(or pin-label "")))))
(print! (bold (green "Finished!")))
(when success
(set-file-times doom-packages-dir)
(doom-delete-autoloads-file doom-package-autoload-file))
success)))))
(defun doom-packages-update (&optional auto-accept-p)
"Interactive command for updating packages."
(print! "Looking for outdated packages...")
(let ((packages (cl-sort (cl-copy-list (doom-get-outdated-packages)) #'string-lessp
:key #'car)))
(cond ((not packages)
(print! (green "Everything is up-to-date"))
nil)
((not (or auto-accept-p
(y-or-n-p
(format "%s packages will be updated:\n\n%s\n\nProceed?"
(length packages)
(let ((max-len
(or (car (sort (mapcar (lambda (it) (length (symbol-name (car it)))) packages)
#'>))
10)))
(mapconcat
(lambda (pkg)
(format (format "+ %%-%ds %%-%ds -> %%s" (+ max-len 2) 14)
(symbol-name (car pkg))
(package-version-join (cadr pkg))
(package-version-join (cl-caddr pkg))))
packages
"\n"))))))
(user-error "Aborted!"))
((let (success)
(dolist (pkg packages)
(print! "Updating %s" (car pkg))
(doom--condition-case!
(print!
(let ((result (doom-update-package (car pkg) t)))
(when result (setq success t))
(color (if result 'green 'red)
(if result "✓ DONE" "✕ FAILED"))))))
(print! (bold (green "Finished!")))
(when success
(set-file-times doom-packages-dir)
(doom-delete-autoloads-file doom-package-autoload-file))
success)))))
(defun doom-packages-autoremove (&optional auto-accept-p)
"Interactive command for auto-removing orphaned packages."
(print! "Looking for orphaned packages...")
(let ((packages (doom-get-orphaned-packages)))
(cond ((not packages)
(print! (green "No unused packages to remove"))
nil)
((not
(or auto-accept-p
(y-or-n-p
(format "%s packages will be deleted:\n\n%s\n\nProceed?"
(length packages)
(mapconcat
(lambda (sym)
(let ((backend (doom-package-backend sym)))
(format "+ %s (%s)" sym
(if (doom-package-different-backend-p sym)
(pcase backend
(`quelpa "QUELPA->ELPA")
(`elpa "ELPA->QUELPA")
(_ "removed"))
(upcase (symbol-name backend))))))
(sort (cl-copy-list packages) #'string-lessp)
"\n")))))
(user-error "Aborted!"))
((let (success)
(dolist (pkg packages)
(doom--condition-case!
(let ((result (doom-delete-package pkg t)))
(if result (setq success t))
(print! (color (if result 'green 'red) "%s %s")
(if result "✓ Removed" "✕ Failed to remove")
pkg))))
(print! (bold (green "Finished!")))
(when success
(set-file-times doom-packages-dir)
(doom-delete-autoloads-file doom-package-autoload-file))
success)))))

85
core/cli/patch-macos.el Normal file
View file

@ -0,0 +1,85 @@
;;; core/cli/patch-macos.el -*- lexical-binding: t; -*-
(dispatcher! (patch-macos)
(doom-patch-macos (or (member "--undo" args)
(member "-u" args))
(doom--find-emacsapp-path))
"Patches Emacs.app to respect your shell environment.
A common issue with GUI Emacs on MacOS is that it launches in an environment
independent of your shell configuration, including your PATH and any other
utilities like rbenv, rvm or virtualenv.
This patch fixes this by patching Emacs.app (in /Applications or
~/Applications). It will:
1. Move Contents/MacOS/Emacs to Contents/MacOS/RunEmacs
2. And replace Contents/MacOS/Emacs with the following wrapper script:
#!/bin/bash
args=\"$@\"
pwd=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\"; pwd -P)\"
exec \"$SHELL\" -l -c \"$pwd/RunEmacs $args\"
This ensures that Emacs is always aware of your shell environment, regardless of
how it is launched.
It can be undone with the --undo or -u options.
Alternatively, you can install the exec-path-from-shell Emacs plugin, which will
scrape your shell environment remotely, at startup. However, this can be slow
depending on your shell configuration and isn't always reliable.")
;;
;; Library
(defun doom--find-emacsapp-path ()
(or (getenv "EMACS_APP_PATH")
(cl-loop for dir in (list "/usr/local/opt/emacs"
"/usr/local/opt/emacs-plus"
"/Applications"
"~/Applications")
for appdir = (concat dir "/Emacs.app")
if (file-directory-p appdir)
return appdir)
(user-error "Couldn't find Emacs.app")))
(defun doom-patch-macos (undo-p appdir)
"Patches Emacs.app to respect your shell environment."
(unless IS-MAC
(user-error "You don't seem to be running MacOS"))
(unless (file-directory-p appdir)
(user-error "Couldn't find '%s'" appdir))
(let ((oldbin (expand-file-name "Contents/MacOS/Emacs" appdir))
(newbin (expand-file-name "Contents/MacOS/RunEmacs" appdir)))
(cond (undo-p
(unless (file-exists-p newbin)
(user-error "Emacs.app is not patched"))
(copy-file newbin oldbin 'ok-if-already-exists nil nil 'preserve-permissions)
(unless (file-exists-p oldbin)
(error "Failed to copy %s to %s" newbin oldbin))
(delete-file newbin)
(message "%s successfully unpatched" appdir))
((file-exists-p newbin)
(user-error "%s is already patched" appdir))
((or doom-auto-accept
(y-or-n-p
(concat "Doom would like to patch your Emacs.app bundle so that it respects\n"
"your shell configuration. For more information on why and how, run\n\n"
" bin/doom help patch-macos\n\n"
"Patch Emacs.app?")))
(message "Patching '%s'" appdir)
(copy-file oldbin newbin nil nil nil 'preserve-permissions)
(unless (file-exists-p newbin)
(error "Failed to copy %s to %s" oldbin newbin))
(with-temp-buffer
(insert "#!/bin/bash\n"
"args=\"$@\"\n"
"pwd=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\"; pwd -P)\"\n"
"exec \"$SHELL\" -l -c \"$pwd/RunEmacs $args\"")
(write-file oldbin)
(chmod oldbin (file-modes newbin)))
(message "%s successfully patched" appdir)))))

92
core/cli/quickstart.el Normal file
View file

@ -0,0 +1,92 @@
;;; core/cli/quickstart.el -*- lexical-binding: t; -*-
(dispatcher! (quickstart qs) (apply #'doom-quickstart args)
"Quickly deploy a private module and Doom.
This deploys a barebones config to ~/.doom.d (if it doesn't already exist). The
destination can be changed with the -p option, e.g.
doom -p ~/.config/doom quickstart
Quickstart understands the following switches:
--no-config Don't deploy dummy config to ~/.doom.d
--no-install Don't auto-install packages
--no-env Don't generate an envvars file (see `doom help env`)
--no-fonts Don't install (or prompt to install) all-the-icons fonts
This command is idempotent and is safe to reuse.")
;;
;; Library
(defun doom-quickstart (&rest args)
"Quickly deploy a private module and Doom.
This deploys a barebones config to `doom-private-dir', installs all missing
packages and regenerates the autoloads file."
;; Create `doom-private-dir'
(let ((short-private-dir (abbreviate-file-name doom-private-dir)))
(if (member "--no-config" args)
(print! (yellow "Not copying private config template, as requested"))
(if (file-directory-p doom-private-dir)
(print! (yellow "%s directory already exists. Skipping.") short-private-dir)
(print! "Creating %s" short-private-dir)
(make-directory doom-private-dir t)
(print! (green "Done!"))
;; Create init.el, config.el & packages.el
(dolist (file (list (cons "init.el"
(lambda ()
(insert-file-contents (expand-file-name "init.example.el" doom-emacs-dir))))
(cons "config.el"
(lambda ()
(insert (format ";;; %sconfig.el -*- lexical-binding: t; -*-\n\n"
short-private-dir)
";; Place your private configuration here\n")))
(cons "packages.el"
(lambda ()
(insert (format ";; -*- no-byte-compile: t; -*-\n;;; %spackages.el\n\n"
short-private-dir)
";;; Examples:\n"
";; (package! some-package)\n"
";; (package! another-package :recipe (:fetcher github :repo \"username/repo\"))\n"
";; (package! builtin-package :disable t)\n")))))
(cl-destructuring-bind (path . fn) file
(print! "Creating %s%s" short-private-dir path)
(with-temp-file (expand-file-name path doom-private-dir)
(funcall fn))
(print! (green "Done!")))))))
;; In case no init.el was present the first time `doom-initialize-modules' was
;; called in core.el (e.g. on first install)
(doom-initialize-modules)
;; Ask if Emacs.app should be patched
(if (member "--no-env" args)
(print! (yellow "Not generating envvars file, as requested"))
(when (or doom-auto-accept
(y-or-n-p "Generate an env file? (see `doom help env` for details)"))
(doom-reload-env-file 'force-p)))
;; Install Doom packages
(if (member "--no-install" args)
(print! (yellow "Not installing plugins, as requested"))
(print! "Installing plugins")
(doom-packages-install doom-auto-accept))
(print! "Regenerating autoloads files")
(doom-reload-autoloads nil 'force-p)
(if (member "--no-fonts" args)
(print! (yellow "Not installing fonts, as requested"))
(when (or doom-auto-accept
(y-or-n-p "Download and install all-the-icon's fonts?"))
(require 'all-the-icons)
(all-the-icons-install-fonts 'yes)))
(print! (bold (green "\nFinished! Doom is ready to go!\n")))
(with-temp-buffer
(doom-template-insert "QUICKSTART_INTRO")
(print! (buffer-string))))

69
core/cli/test.el Normal file
View file

@ -0,0 +1,69 @@
;;; core/cli/test.el -*- lexical-binding: t; -*-
(dispatcher! test (doom-run-tests args)
"Run Doom unit tests.")
;;
;; Library
(defun doom-run-tests (&optional modules)
"Run all loaded tests, specified by MODULES (a list of module cons cells) or
command line args following a double dash (each arg should be in the
'module/submodule' format).
If neither is available, run all tests in all enabled modules."
;; Core libraries aren't fully loaded in a noninteractive session, so we
;; reload it with `noninteractive' set to nil to force them to.
(quiet! (doom-reload-autoloads))
(let ((doom-modules (doom-modules))
noninteractive)
(let ((target-paths
;; Convert targets into a list of string paths, pointing to the root
;; directory of modules
(cond ((stringp (car modules)) ; command line
(save-match-data
(cl-loop for arg in modules
if (string= arg ":core") collect doom-core-dir
else if (string-match-p "/" arg)
nconc (mapcar (apply-partially #'expand-file-name arg)
doom-modules-dirs)
else
nconc (cl-loop for dir in doom-modules-dirs
for path = (expand-file-name arg dir)
if (file-directory-p path)
nconc (doom-files-in path :type 'dirs :depth 1 :full t))
finally do (setq argv nil))))
(modules ; cons-cells given to MODULES
(cl-loop for (module . submodule) in modules
if (doom-module-locate-path module submodule)
collect it))
((append (list doom-core-dir)
(doom-module-load-path))))))
;; Load all the unit test files...
(require 'buttercup)
(mapc (lambda (file) (load file :noerror (not doom-debug-mode)))
(doom-files-in (mapcar (apply-partially #'expand-file-name "test/")
target-paths)
:match "\\.el$" :full t))
;; ... then run them
(when doom-debug-mode
(setq buttercup-stack-frame-style 'pretty))
(let ((split-width-threshold 0)
(split-height-threshold 0)
(window-min-width 0)
(window-min-height 0))
(buttercup-run)))))
;;
;; Test library
(defmacro insert! (&rest text)
"Insert TEXT in buffer, then move cursor to last {0} marker."
`(progn
(insert ,@text)
(when (search-backward "{0}" nil t)
(replace-match "" t t))))

85
core/cli/upgrade.el Normal file
View file

@ -0,0 +1,85 @@
;;; core/cli/upgrade.el -*- lexical-binding: t; -*-
(dispatcher! (upgrade up) (doom-upgrade)
"Checks out the latest Doom on this branch.
Doing so is equivalent to:
cd ~/.emacs.d
git pull
bin/doom clean
bin/doom refresh
bin/doom update")
;;
;; Quality of Life Commands
(defvar doom-repo-url "https://github.com/hlissner/doom-emacs"
"TODO")
(defvar doom-repo-remote "_upgrade"
"TODO")
(defun doom--working-tree-dirty-p (dir)
(with-temp-buffer
(let ((default-directory dir))
(if (zerop (process-file "git" nil (current-buffer) nil
"status" "--porcelain" "-uno"))
(string-match-p "[^ \t\n]" (buffer-string))
(error "Failed to check working tree in %s" dir)))))
(defun doom-upgrade ()
"Upgrade Doom to the latest version non-destructively."
(require 'vc-git)
(let* ((gitdir (expand-file-name ".git" doom-emacs-dir))
(branch (vc-git--symbolic-ref doom-emacs-dir))
(default-directory doom-emacs-dir))
(unless (file-exists-p gitdir)
(error "Couldn't find %s. Was Doom cloned properly?"
(abbreviate-file-name gitdir)))
(unless branch
(error "Couldn't detect what branch you're using. Is Doom detached?"))
(when (doom--working-tree-dirty-p doom-emacs-dir)
(user-error "Refusing to upgrade because Doom has been modified. Stash or undo your changes"))
(with-temp-buffer
(let ((buf (current-buffer)))
(condition-case-unless-debug e
(progn
(process-file "git" nil buf nil "remote" "remove" doom-repo-remote)
(unless (zerop (process-file "git" nil buf nil "remote" "add"
doom-repo-remote doom-repo-url))
(error "Failed to add %s to remotes" doom-repo-remote))
(unless (zerop (process-file "git" nil buf nil "fetch" "--tags"
doom-repo-remote branch))
(error "Failed to fetch from upstream"))
(let ((current-rev (vc-git-working-revision doom-emacs-dir))
(rev (string-trim (shell-command-to-string (format "git rev-parse %s/%s" doom-repo-remote branch)))))
(unless rev
(error "Couldn't detect Doom's version. Is %s a repo?"
(abbreviate-file-name doom-emacs-dir)))
(when (equal current-rev rev)
(user-error "Doom is up to date!"))
(message "Updates for Doom are available!\n\n Old revision: %s\n New revision: %s\n"
current-rev rev)
(message "Comparision diff: https://github.com/hlissner/doom-emacs/compare/%s...%s\n"
(substring current-rev 0 10) (substring rev 0 10))
;; TODO Display newsletter diff
(unless (or doom-auto-accept (y-or-n-p "Proceed?"))
(user-error "Aborted"))
(message "Removing byte-compiled files from your config (if any)")
(doom-clean-byte-compiled-files)
(unless (zerop (process-file "git" nil buf nil "reset" "--hard"
(format "%s/%s" doom-repo-remote branch)))
(error "An error occurred while checking out the latest commit\n\n%s"
(buffer-string)))
(unless (equal (vc-git-working-revision doom-emacs-dir) rev)
(error "Failed to checkout latest commit.\n\n%s" (buffer-string)))
(doom-refresh 'force)
(doom-packages-update doom-auto-accept)
(message "Done! Please restart Emacs for changes to take effect")))
(user-error
(message "%s Aborting." (error-message-string e)))
(error
(message "There was an unexpected error.\n\n%s\n\nOutput:\n%s"
(car e)
(buffer-string))))))))

164
core/core-cli.el Normal file
View file

@ -0,0 +1,164 @@
;;; -*- lexical-binding: t; no-byte-compile: t; -*-
;; Eagerly load these libraries because this module may be loaded in a session
;; that hasn't been fully initialized (where autoloads files haven't been
;; generated or `load-path' populated).
(load! "autoload/debug")
(load! "autoload/files")
(load! "autoload/message")
(load! "autoload/packages")
;;
;; Dispatcher API
(defvar doom-auto-accept (getenv "YES")
"If non-nil, Doom will auto-accept any confirmation prompts during batch
commands like `doom-packages-install', `doom-packages-update' and
`doom-packages-autoremove'.")
(defconst doom--dispatch-command-alist ())
(defconst doom--dispatch-alias-alist ())
(defun doom--dispatch-format (desc &optional short)
(with-temp-buffer
(let ((fill-column 72))
(insert desc)
(goto-char (point-min))
(while (re-search-forward "\n\n[^ \n]" nil t)
(fill-paragraph)))
(if (not short)
(buffer-string)
(goto-char (point-min))
(buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))))
(defun doom--dispatch-help (&optional command desc &rest args)
"Display help documentation for a dispatcher command. If COMMAND and DESC are
omitted, show all available commands, their aliases and brief descriptions."
(if command
(princ (doom--dispatch-format desc))
(print! (bold "%-10s\t%s\t%s") "Command:" "Alias" "Description")
(dolist (spec (cl-sort doom--dispatch-command-alist #'string-lessp
:key #'car))
(cl-destructuring-bind (command &key desc _body) spec
(let ((aliases (cl-loop for (alias . cmd) in doom--dispatch-alias-alist
if (eq cmd command)
collect (symbol-name alias))))
(print! " %-10s\t%s\t%s"
command (if aliases (string-join aliases ",") "")
(doom--dispatch-format desc t)))))))
(defun doom-dispatch (cmd args &optional show-help)
"Parses ARGS and invokes a dispatcher.
If SHOW-HELP is non-nil, show the documentation for said dispatcher."
(when (equal cmd "help")
(setq show-help t)
(when args
(setq cmd (car args)
args (cdr args))))
(cl-destructuring-bind (command &key desc body)
(let ((sym (intern cmd)))
(or (assq sym doom--dispatch-command-alist)
(assq (cdr (assq sym doom--dispatch-alias-alist))
doom--dispatch-command-alist)
(user-error "Invalid command: %s" sym)))
(if show-help
(apply #'doom--dispatch-help command desc args)
(funcall body args))))
(defmacro dispatcher! (command form &optional docstring)
"Define a dispatcher command. COMMAND is a symbol or a list of symbols
representing the aliases for this command. DESC is a string description. The
first line should be short (under 60 letters), as it will be displayed for
bin/doom help.
BODY will be run when this dispatcher is called."
(declare (indent defun) (doc-string 3))
(cl-destructuring-bind (cmd &rest aliases)
(doom-enlist command)
(macroexp-progn
(append
(when aliases
`((dolist (alias ',aliases)
(setf (alist-get alias doom--dispatch-alias-alist) ',cmd))))
`((setf (alist-get ',cmd doom--dispatch-command-alist)
(list :desc ,docstring
:body (lambda (args) (ignore args) ,form))))))))
;;
;; Dummy dispatch commands (no-op because they're handled especially)
(dispatcher! run :noop
"Run Doom Emacs from bin/doom's parent directory.
All arguments are passed on to Emacs (except for -p and -e).
doom run
doom run -nw init.el
WARNING: this command exists for convenience and testing. Doom will suffer
additional overhead by being started this way. For the best performance, it is
best to run Doom out of ~/.emacs.d and ~/.doom.d.")
(dispatcher! (doctor doc) :noop
"Checks for issues with your environment & Doom config.
Use the doctor to diagnose common problems or list missing dependencies in
enabled modules.")
(dispatcher! (help h) :noop
"Look up additional information about a command.")
;;
;; Real dispatch commands
(load! "cli/autoloads")
(load! "cli/byte-compile")
(load! "cli/debug")
(load! "cli/env")
(load! "cli/packages")
(load! "cli/patch-macos")
(load! "cli/quickstart")
(load! "cli/upgrade")
(load! "cli/test")
;;
(defun doom-refresh (&optional force-p)
"Ensure Doom is in a working state by checking autoloads and packages, and
recompiling any changed compiled files. This is the shotgun solution to most
problems with doom."
(when (getenv "DOOMENV")
(doom-reload-env-file 'force))
(doom-reload-doom-autoloads force-p)
(unwind-protect
(progn
(ignore-errors
(doom-packages-autoremove doom-auto-accept))
(ignore-errors
(doom-packages-install doom-auto-accept)))
(doom-reload-package-autoloads force-p)
(doom-byte-compile nil 'recompile)))
(dispatcher! (refresh re) (doom-refresh 'force)
"Refresh Doom.
This is the equivalent of running autoremove, install, autoloads, then
recompile. Run this whenever you:
1. Modify your `doom!' block,
2. Add or remove `package!' blocks to your config,
3. Add or remove autoloaded functions in module autoloaded files.
4. Update Doom outside of Doom (e.g. with git)
It will ensure that unneeded packages are removed, all needed packages are
installed, autoloads files are up-to-date and no byte-compiled files have gone
stale.")
(provide 'core-cli)
;;; core-cli.el ends here

View file

@ -1,16 +1,27 @@
;;; core-editor.el -*- lexical-binding: t; -*-
(defvar doom-large-file-size 1
(defvar doom-large-file-size 2
"Size (in MB) above which the user will be prompted to open the file literally
to avoid performance issues. Opening literally means that no major or minor
modes are active and the buffer is read-only.")
(defvar doom-large-file-modes-list
'(archive-mode tar-mode jka-compr git-commit-mode image-mode
doc-view-mode doc-view-mode-maybe ebrowse-tree-mode pdf-view-mode)
'(fundamental-mode special-mode archive-mode tar-mode jka-compr
git-commit-mode image-mode doc-view-mode doc-view-mode-maybe
ebrowse-tree-mode pdf-view-mode tags-table-mode)
"Major modes that `doom|check-large-file' will ignore.")
(defvar-local doom-inhibit-indent-detection nil
"A buffer-local flag that indicates whether `dtrt-indent' should try to detect
indentation settings or not. This should be set by editorconfig if it
successfully sets indent_style/indent_size.")
(defvar doom-detect-indentation-excluded-modes '(fundamental-mode)
"A list of major modes in which indentation should be automatically
detected.")
(setq-default
large-file-warning-threshold 15000000
vc-follow-symlinks t
;; Save clipboard contents into kill-ring before replacing them
save-interprogram-paste-before-kill t
@ -23,10 +34,10 @@ modes are active and the buffer is read-only.")
sentence-end-double-space nil
word-wrap t
;; Scrolling
hscroll-margin 1
hscroll-margin 2
hscroll-step 1
scroll-conservatively 1001
scroll-margin 0
scroll-margin 2
scroll-preserve-screen-position t
;; Whitespace (see `editorconfig')
indent-tabs-mode nil
@ -36,208 +47,248 @@ modes are active and the buffer is read-only.")
tabify-regexp "^\t* [ \t]+" ; for :retab
;; Wrapping
truncate-lines t
truncate-partial-width-windows 50
;; whitespace-mode
whitespace-line-column fill-column
whitespace-style
'(face indentation tabs tab-mark spaces space-mark newline newline-mark
trailing lines-tail)
whitespace-display-mappings
'((tab-mark ?\t [? ?\t])
(newline-mark ?\n [ ?\n])
(space-mark ?\ [] [?.])))
truncate-partial-width-windows 50)
;; ediff
(setq ediff-diff-options "-w"
ediff-split-window-function #'split-window-horizontally
ediff-window-setup-function #'ediff-setup-windows-plain)
;; Remove hscroll-margin in shells, otherwise it causes jumpiness
(setq-hook! '(eshell-mode-hook term-mode-hook) hscroll-margin 0)
(defun doom|dont-kill-scratch-buffer ()
"Don't kill the scratch buffer."
(or (not (string= (buffer-name) "*scratch*"))
(ignore (bury-buffer))))
(add-hook 'kill-buffer-query-functions #'doom|dont-kill-scratch-buffer)
;; temporary windows often have q bound to `quit-window', which only buries the
;; contained buffer. I rarely don't want that buffer killed, so...
(defun doom*quit-window (orig-fn &optional kill window)
(funcall orig-fn (not kill) window))
(advice-add #'quit-window :around #'doom*quit-window)
(defun doom|check-large-file ()
"Check if the buffer's file is large (see `doom-large-file-size'). If so, ask
for confirmation to open it literally (read-only, disabled undo and in
fundamental-mode) for performance sake."
(let* ((filename (buffer-file-name))
(size (nth 7 (file-attributes filename))))
(when (and (not (memq major-mode doom-large-file-modes-list))
size (> size (* 1024 1024 doom-large-file-size))
(y-or-n-p
(format (concat "%s is a large file, open literally to "
"avoid performance issues?")
(file-relative-name filename))))
(defun doom*optimize-literal-mode-for-large-files (buffer)
(with-current-buffer buffer
(when find-file-literally
(setq buffer-read-only t)
(buffer-disable-undo)
(fundamental-mode))))
(add-hook 'find-file-hook #'doom|check-large-file)
(buffer-disable-undo))
buffer))
(advice-add #'find-file-noselect-1 :filter-return #'doom*optimize-literal-mode-for-large-files)
(push '("/LICENSE$" . text-mode) auto-mode-alist)
;;
;; Extra file extensions to support
(push '("/LICENSE\\'" . text-mode) auto-mode-alist)
;;
;; Built-in plugins
;;
(def-package! server
:when (display-graphic-p)
:after-call (pre-command-hook after-find-file)
:config
(when-let* ((name (getenv "EMACS_SERVER_NAME")))
(setq server-name name))
(unless (server-running-p)
(server-start)))
(def-package! autorevert
;; revert buffers for changed files
(global-auto-revert-mode 1)
:after-call after-find-file
:config
(setq auto-revert-verbose nil)
(global-auto-revert-mode +1))
;; enabled by default in Emacs 25+. No thanks.
(electric-indent-mode -1)
;; savehist / saveplace
(def-package! savehist
;; persist variables across sessions
:defer-incrementally (custom)
:after-call post-command-hook
:config
(setq savehist-file (concat doom-cache-dir "savehist")
savehist-save-minibuffer-history t
savehist-autosave-interval nil ; save on kill only
savehist-additional-variables '(kill-ring search-ring regexp-search-ring)
save-place-file (concat doom-cache-dir "saveplace"))
(add-hook! 'doom-init-hook #'(savehist-mode save-place-mode))
savehist-additional-variables '(kill-ring search-ring regexp-search-ring))
(savehist-mode +1)
(defun doom|unpropertize-kill-ring ()
"Remove text properties from `kill-ring' in the interest of shrinking the
savehist file."
(setq kill-ring (cl-loop for item in kill-ring
if (stringp item)
collect (substring-no-properties item)
else if item collect it)))
(add-hook 'kill-emacs-hook #'doom|unpropertize-kill-ring))
(def-package! saveplace
;; persistent point location in buffers
:after-call (after-find-file dired-initial-position-hook)
:config
(setq save-place-file (concat doom-cache-dir "saveplace"))
(defun doom*recenter-on-load-saveplace (&rest _)
"Recenter on cursor when loading a saved place."
(if buffer-file-name (ignore-errors (recenter))))
(advice-add #'save-place-find-file-hook
:after-while #'doom*recenter-on-load-saveplace)
(save-place-mode +1))
;; Keep track of recently opened files
(def-package! recentf
:hook (doom-init . recentf-mode)
;; Keep track of recently opened files
:defer-incrementally (easymenu tree-widget timer)
:after-call after-find-file
:commands recentf-open-files
:config
(setq recentf-save-file (concat doom-cache-dir "recentf")
recentf-auto-cleanup 'never
recentf-max-menu-items 0
recentf-max-saved-items 300
recentf-filename-handlers '(file-truename)
recentf-filename-handlers '(file-truename abbreviate-file-name)
recentf-exclude
(list "^/tmp/" "^/ssh:" "\\.?ido\\.last$" "\\.revive$" "/TAGS$"
(list #'file-remote-p "\\.\\(?:gz\\|gif\\|svg\\|png\\|jpe?g\\)$"
"^/tmp/" "^/ssh:" "\\.?ido\\.last$" "\\.revive$" "/TAGS$"
"^/var/folders/.+$"
;; ignore private DOOM temp files (but not all of them)
(concat "^" (file-truename doom-local-dir)))))
(lambda (file) (file-in-directory-p file doom-local-dir))))
(unless noninteractive
(add-hook 'kill-emacs-hook #'recentf-cleanup)
(quiet! (recentf-mode +1))))
;;
;; Core Plugins
;;
;; Packages
;; Handles whitespace (tabs/spaces) settings externally. This way projects can
;; specify their own formatting rules.
(def-package! editorconfig
:config
(add-hook 'doom-init-hook #'editorconfig-mode)
;; editorconfig cannot procure the correct settings for extension-less files.
;; Executable scripts with a shebang line, for example. So why not use Emacs'
;; major mode to drop editorconfig a hint? This is accomplished by temporarily
;; appending an extension to `buffer-file-name' when we talk to editorconfig.
(defvar doom-editorconfig-mode-alist
'((sh-mode . "sh")
(python-mode . "py")
(ruby-mode . "rb")
(perl-mode . "pl")
(php-mode . "php"))
"An alist mapping major modes to extensions. Used by
`doom*editorconfig-smart-detection' to give editorconfig filetype hints.")
(defun doom*editorconfig-smart-detection (orig-fn &rest args)
"Retrieve the properties for the current file. If it doesn't have an
extension, try to guess one."
(let ((buffer-file-name
(if (file-name-extension buffer-file-name)
buffer-file-name
(format "%s%s" buffer-file-name
(let ((ext (cdr (assq major-mode doom-editorconfig-mode-alist))))
(or (and ext (concat "." ext))
""))))))
(apply orig-fn args)))
(advice-add #'editorconfig-call-editorconfig-exec :around #'doom*editorconfig-smart-detection)
;; Editorconfig makes indentation too rigid in Lisp modes, so tell
;; editorconfig to ignore indentation. I prefer dynamic indentation support
;; built into Emacs.
(dolist (mode '(emacs-lisp-mode lisp-mode))
(setq editorconfig-indentation-alist
(assq-delete-all mode editorconfig-indentation-alist)))
(defvar whitespace-style)
(defun doom|editorconfig-whitespace-mode-maybe (&rest _)
"Show whitespace-mode when file uses TABS (ew)."
(when indent-tabs-mode
(let ((whitespace-style '(face tabs tab-mark trailing-lines tail)))
(whitespace-mode +1))))
(add-hook 'editorconfig-custom-hooks #'doom|editorconfig-whitespace-mode-maybe))
(def-package! editorconfig-conf-mode
:mode "\\.?editorconfig$")
;; Auto-close delimiters and blocks as you type
(def-package! smartparens
:hook (doom-init . smartparens-global-mode)
;; Auto-close delimiters and blocks as you type. It's more powerful than that,
;; but that is all Doom uses it for.
:after-call (doom-switch-buffer-hook after-find-file)
:commands (sp-pair sp-local-pair sp-with-modes sp-point-in-comment sp-point-in-string)
:config
(require 'smartparens-config)
(setq sp-autowrap-region nil ; let evil-surround handle this
sp-highlight-pair-overlay nil
(setq sp-highlight-pair-overlay nil
sp-highlight-wrap-overlay nil
sp-highlight-wrap-tag-overlay nil
sp-show-pair-from-inside t
sp-cancel-autoskip-on-backward-movement nil
sp-show-pair-delay 0
sp-max-pair-length 3)
sp-show-pair-delay 0.1
sp-max-pair-length 4
sp-max-prefix-length 50
sp-escape-quotes-after-insert nil) ; not smart enough
;; disable smartparens in evil-mode's replace state (they conflict)
;; Smartparens' navigation feature is neat, but does not justify how expensive
;; it is. It's also less useful for evil users. This may need to be
;; reactivated for non-evil users though. Needs more testing!
(defun doom|disable-smartparens-navigate-skip-match ()
(setq sp-navigate-skip-match nil
sp-navigate-consider-sgml-tags nil))
(add-hook 'after-change-major-mode-hook #'doom|disable-smartparens-navigate-skip-match)
;; autopairing in `eval-expression' and `evil-ex'
(defun doom|init-smartparens-in-eval-expression ()
"Enable `smartparens-mode' in the minibuffer, during `eval-expression' or
`evil-ex'."
(when (memq this-command '(eval-expression evil-ex))
(smartparens-mode)))
(add-hook 'minibuffer-setup-hook #'doom|init-smartparens-in-eval-expression)
(sp-local-pair 'minibuffer-inactive-mode "'" nil :actions nil)
;; smartparens breaks evil-mode's replace state
(add-hook 'evil-replace-state-entry-hook #'turn-off-smartparens-mode)
(add-hook 'evil-replace-state-exit-hook #'turn-on-smartparens-mode)
(sp-local-pair '(xml-mode nxml-mode php-mode) "<!--" "-->"
:post-handlers '(("| " "SPC"))))
(smartparens-global-mode +1))
(def-package! dtrt-indent
;; Automatic detection of indent settings
:unless noninteractive
:defer t
:init
(defun doom|detect-indentation ()
(unless (or (not after-init-time)
doom-inhibit-indent-detection
(member (substring (buffer-name) 0 1) '(" " "*"))
(memq major-mode doom-detect-indentation-excluded-modes))
(dtrt-indent-mode +1)))
(add-hook! '(change-major-mode-after-body-hook read-only-mode-hook)
#'doom|detect-indentation)
:config
(setq dtrt-indent-verbosity (if doom-debug-mode 2 0))
;; always keep tab-width up-to-date
(push '(t tab-width) dtrt-indent-hook-generic-mapping-list)
(defvar dtrt-indent-run-after-smie)
(defun doom*fix-broken-smie-modes (orig-fn arg)
"Some smie modes throw errors when trying to guess their indentation, like
`nim-mode'. This prevents them from leaving Emacs in a broken state."
(let ((dtrt-indent-run-after-smie dtrt-indent-run-after-smie))
(cl-letf* ((old-smie-config-guess (symbol-function 'smie-config-guess))
((symbol-function 'smie-config-guess)
(lambda ()
(condition-case e (funcall old-smie-config-guess)
(error (setq dtrt-indent-run-after-smie t)
(message "[WARNING] Indent detection: %s"
(error-message-string e))
(message "")))))) ; warn silently
(funcall orig-fn arg))))
(advice-add #'dtrt-indent-mode :around #'doom*fix-broken-smie-modes))
;; Branching undo
(def-package! undo-tree
;; Branching & persistent undo
:after-call (doom-switch-buffer-hook after-find-file)
:config
(add-hook 'doom-init-hook #'global-undo-tree-mode)
;; persistent undo history is known to cause undo history corruption, which
;; can be very destructive! So disable it!
(setq undo-tree-auto-save-history nil
(setq undo-tree-auto-save-history t
;; undo-in-region is known to cause undo history corruption, which can
;; be very destructive! Disabling it deters the error, but does not fix
;; it entirely!
undo-tree-enable-undo-in-region nil
undo-tree-history-directory-alist
(list (cons "." (concat doom-cache-dir "undo-tree-hist/")))))
`(("." . ,(concat doom-cache-dir "undo-tree-hist/"))))
(global-undo-tree-mode +1)
;; compress undo history with xz/gzip
(and (fset 'doom*undo-tree-make-history-save-file-name
(cond ((executable-find "zstd") (lambda (file) (concat file ".zst")))
((executable-find "gzip") (lambda (file) (concat file ".gz")))))
(advice-add #'undo-tree-make-history-save-file-name :filter-return
#'doom*undo-tree-make-history-save-file-name))
;;
;; Autoloaded Plugins
;;
(defun doom*strip-text-properties-from-undo-history (&rest _)
(dolist (item buffer-undo-list)
(and (consp item)
(stringp (car item))
(setcar item (substring-no-properties (car item))))))
(advice-add #'undo-list-transfer-to-tree :before #'doom*strip-text-properties-from-undo-history))
(def-package! ace-link
:commands (ace-link-help ace-link-org))
(def-package! avy
:commands (avy-goto-char-2 avy-goto-line)
:config
(setq avy-all-windows nil
avy-background t))
(def-package! command-log-mode
:commands (command-log-mode global-command-log-mode)
:commands global-command-log-mode
:config
(set! :popup "*command-log*" :size 40 :align 'right :noselect t)
(setq command-log-mode-auto-show t
command-log-mode-open-log-turns-on-mode t))
command-log-mode-open-log-turns-on-mode nil
command-log-mode-is-global t
command-log-mode-window-size 50))
(def-package! expand-region
:commands (er/expand-region er/contract-region er/mark-symbol er/mark-word))
(def-package! help-fns+ ; Improved help commands
:commands (describe-buffer describe-command describe-file
describe-keymap describe-option describe-option-of-type))
;; `helpful' --- a better *help* buffer
(def-package! helpful
:commands helpful--read-symbol
:init
(define-key!
[remap describe-function] #'helpful-callable
[remap describe-command] #'helpful-command
[remap describe-variable] #'helpful-variable
[remap describe-key] #'helpful-key
[remap describe-symbol] #'doom/describe-symbol)
(def-package! pcre2el
:commands rxt-quote-pcre)
(after! apropos
;; patch apropos buttons to call helpful instead of help
(dolist (fun-bt '(apropos-function apropos-macro apropos-command))
(button-type-put
fun-bt 'action
(lambda (button)
(helpful-callable (button-get button 'apropos-symbol)))))
(dolist (var-bt '(apropos-variable apropos-user-option))
(button-type-put
var-bt 'action
(lambda (button)
(helpful-variable (button-get button 'apropos-symbol)))))))
(def-package! smart-forward
:commands (smart-up smart-down smart-backward smart-forward))
(def-package! wgrep
:commands (wgrep-setup wgrep-change-to-wgrep-mode)
:config (setq wgrep-auto-save-buffer t))
(def-package! ws-butler
;; a less intrusive `delete-trailing-whitespaces' on save
:after-call (after-find-file)
:config
(setq ws-butler-global-exempt-modes
(append ws-butler-global-exempt-modes
'(special-mode comint-mode term-mode eshell-mode)))
(ws-butler-global-mode))
(provide 'core-editor)
;;; core-editor.el ends here

View file

@ -2,13 +2,210 @@
;; A centralized keybinds system, integrated with `which-key' to preview
;; available keybindings. All built into one powerful macro: `map!'. If evil is
;; never loaded, then evil bindings set with `map!' will be ignored.
;; never loaded, then evil bindings set with `map!' are ignored (i.e. omitted
;; entirely for performance reasons).
(defvar doom-leader-key "SPC"
"The leader prefix key, for global commands.")
"The leader prefix key for Evil users.
This needs to be changed from $DOOMDIR/init.el.")
(defvar doom-leader-alt-key "M-SPC"
"An alternative leader prefix key, used for Insert and Emacs states, and for
non-evil users.
This needs to be changed from $DOOMDIR/init.el.")
(defvar doom-localleader-key "SPC m"
"The localleader prefix key, for major-mode specific commands.")
"The localleader prefix key, for major-mode specific commands.
This needs to be changed from $DOOMDIR/init.el.")
(defvar doom-localleader-alt-key "M-SPC m"
"The localleader prefix key, for major-mode specific commands. Used for Insert
and Emacs states, and for non-evil users.
This needs to be changed from $DOOMDIR/init.el.")
(defvar doom-leader-map (make-sparse-keymap)
"An overriding keymap for <leader> keys.")
(defvar doom-which-key-leader-prefix-regexp nil)
;;
;;; Universal, non-nuclear escape
;; `keyboard-quit' is too much of a nuclear option. I wanted an ESC/C-g to
;; do-what-I-mean. It serves four purposes (in order):
;;
;; 1. Quit active states; e.g. highlights, searches, snippets, iedit,
;; multiple-cursors, recording macros, etc.
;; 2. Close popup windows remotely (if it is allowed to)
;; 3. Refresh buffer indicators, like git-gutter and flycheck
;; 4. Or fall back to `keyboard-quit'
;;
;; And it should do these things incrementally, rather than all at once. And it
;; shouldn't interfere with recording macros or the minibuffer. This may require
;; you press ESC/C-g two or three times on some occasions to reach
;; `keyboard-quit', but this is much more intuitive.
(defvar doom-escape-hook nil
"A hook run after C-g is pressed (or ESC in normal mode, for evil users). Both
trigger `doom/escape'.
If any hook returns non-nil, all hooks after it are ignored.")
(defun doom/escape ()
"Run `doom-escape-hook'."
(interactive)
(cond ((minibuffer-window-active-p (minibuffer-window))
;; quit the minibuffer if open.
(abort-recursive-edit))
;; Run all escape hooks. If any returns non-nil, then stop there.
((run-hook-with-args-until-success 'doom-escape-hook))
;; don't abort macros
((or defining-kbd-macro executing-kbd-macro) nil)
;; Back to the default
((keyboard-quit))))
(global-set-key [remap keyboard-quit] #'doom/escape)
;;
;;; General + leader/localleader keys
(require 'general)
;; Convenience aliases
(defalias 'define-key! #'general-def)
(defalias 'unmap! #'general-unbind)
;; `map!' uses this instead of `define-leader-key!' because it consumes 20-30%
;; more startup time, so we reimplement it ourselves.
(defmacro doom--define-leader-key (&rest keys)
(let (prefix forms wkforms)
(while keys
(let ((key (pop keys))
(def (pop keys)))
(if (keywordp key)
(when (memq key '(:prefix :infix))
(setq prefix def))
(when prefix
(setq key `(general--concat t ,prefix ,key)))
(let* ((udef (cdr-safe (doom-unquote def)))
(bdef (if (general--extended-def-p udef)
(general--extract-def (general--normalize-extended-def udef))
def)))
(unless (eq bdef :ignore)
(push `(define-key doom-leader-map (general--kbd ,key)
,bdef)
forms))
(when-let* ((desc (plist-get udef :which-key)))
(push `(which-key-add-key-based-replacements
(general--concat t doom-leader-alt-key ,key)
,desc)
wkforms)
(push `(which-key-add-key-based-replacements
(general--concat t doom-leader-key ,key)
,desc)
wkforms))))))
(macroexp-progn
(append (nreverse forms)
(when wkforms
`((after! which-key
,@(nreverse wkforms))))))))
(defmacro define-leader-key! (&rest args)
"Define <leader> keys.
Uses `general-define-key' under the hood, but does not support :states,
:wk-full-keys or :keymaps. Use `map!' for a more convenient interface.
See `doom-leader-key' and `doom-leader-alt-key' to change the leader prefix."
`(general-define-key
:states nil
:wk-full-keys nil
:keymaps 'doom-leader-map
,@args))
(defmacro define-localleader-key! (&rest args)
"Define <localleader> key.
Uses `general-define-key' under the hood, but does not support :major-modes,
:states, :prefix or :non-normal-prefix. Use `map!' for a more convenient
interface.
See `doom-localleader-key' and `doom-localleader-alt-key' to change the
localleader prefix."
(if (featurep 'evil)
;; :non-normal-prefix doesn't apply to non-evil sessions (only evil's
;; emacs state)
`(general-define-key
:states '(normal visual motion emacs)
:major-modes t
:prefix doom-localleader-key
:non-normal-prefix doom-localleader-alt-key
,@args)
`(general-define-key
:major-modes t
:prefix doom-localleader-alt-key
,@args)))
;; We use a prefix commands instead of general's :prefix/:non-normal-prefix
;; properties because general is incredibly slow binding keys en mass with them
;; in conjunction with :states -- an effective doubling of Doom's startup time!
(define-prefix-command 'doom/leader 'doom-leader-map)
(define-key doom-leader-map [override-state] 'all)
;; Bind `doom-leader-key' and `doom-leader-alt-key' as late as possible to give
;; the user a chance to modify them.
(defun doom|init-leader-keys ()
"Bind `doom-leader-key' and `doom-leader-alt-key'."
(let ((map general-override-mode-map))
(if (not (featurep 'evil))
(define-key map (kbd doom-leader-alt-key) 'doom/leader)
(evil-define-key* '(normal visual motion) map (kbd doom-leader-key) 'doom/leader)
(evil-define-key* '(emacs insert) map (kbd doom-leader-alt-key) 'doom/leader))
(general-override-mode +1))
(unless (stringp doom-which-key-leader-prefix-regexp)
(setq doom-which-key-leader-prefix-regexp
(concat "\\(?:"
(cl-loop for key in (append (list doom-leader-key doom-leader-alt-key)
(where-is-internal 'doom/leader))
for keystr = (if (stringp key) key (key-description key))
collect (regexp-quote keystr) into keys
finally return (string-join keys "\\|"))
"\\)"))))
(add-hook 'doom-after-init-modules-hook #'doom|init-leader-keys)
;;
;;; Packages
(def-package! which-key
:defer 1
:after-call pre-command-hook
:init
(setq which-key-sort-order #'which-key-prefix-then-key-order
which-key-sort-uppercase-first nil
which-key-add-column-padding 1
which-key-max-display-columns nil
which-key-min-display-lines 6
which-key-side-window-slot -10)
:config
;; general improvements to which-key readability
(set-face-attribute 'which-key-local-map-description-face nil :weight 'bold)
(which-key-setup-side-window-bottom)
(setq-hook! 'which-key-init-buffer-hook line-spacing 3)
(which-key-mode +1))
;;;###package hydra
(setq lv-use-seperator t)
;;
;;; `map!' macro
(defvar doom-evil-state-alist
'((?n . normal)
@ -21,87 +218,6 @@
(?g . global))
"A list of cons cells that map a letter to a evil state symbol.")
;;
(def-package! which-key
:config
(setq which-key-sort-order #'which-key-prefix-then-key-order
which-key-sort-uppercase-first nil
which-key-add-column-padding 1
which-key-max-display-columns nil
which-key-min-display-lines 5)
;; embolden local bindings
(set-face-attribute 'which-key-local-map-description-face nil :weight 'bold)
(which-key-setup-side-window-bottom)
(add-hook 'doom-init-hook #'which-key-mode))
(def-package! hydra
:init
;; In case I later need to wrap defhydra in any special functionality.
(defalias 'def-hydra! 'defhydra)
(defalias 'def-hydra-radio! 'defhydradio)
:config
(setq lv-use-seperator t)
(def-hydra! doom@text-zoom (:hint t :color red)
"
Text zoom: _j_:zoom in, _k_:zoom out, _0_:reset
"
("j" text-scale-increase "in")
("k" text-scale-decrease "out")
("0" (text-scale-set 0) "reset"))
(def-hydra! doom@window-nav (:hint nil)
"
Split: _v_ert _s_:horz
Delete: _c_lose _o_nly
Switch Window: _h_:left _j_:down _k_:up _l_:right
Buffers: _p_revious _n_ext _b_:select _f_ind-file
Resize: _H_:splitter left _J_:splitter down _K_:splitter up _L_:splitter right
Move: _a_:up _z_:down _i_menu
"
("z" scroll-up-line)
("a" scroll-down-line)
("i" idomenu)
("h" windmove-left)
("j" windmove-down)
("k" windmove-up)
("l" windmove-right)
("p" doom/previous-buffer)
("n" doom/next-buffer)
("b" switch-to-buffer)
("f" find-file)
("s" split-window-below)
("v" split-window-right)
("c" delete-window)
("o" delete-other-windows)
("H" hydra-move-splitter-left)
("J" hydra-move-splitter-down)
("K" hydra-move-splitter-up)
("L" hydra-move-splitter-right)
("q" nil)))
;;
(defun doom--keybind-register (key desc &optional modes)
"Register a description for KEY with `which-key' in MODES.
KEYS should be a string in kbd format.
DESC should be a string describing what KEY does.
MODES should be a list of major mode symbols."
(if modes
(dolist (mode modes)
(which-key-add-major-mode-key-based-replacements mode key desc))
(which-key-add-key-based-replacements key desc)))
(defun doom--keyword-to-states (keyword)
"Convert a KEYWORD into a list of evil state symbols.
@ -113,32 +229,160 @@ For example, :nvi will map to (list 'normal 'visual 'insert). See
;; Register keywords for proper indentation (see `map!')
(put ':after 'lisp-indent-function 'defun)
(put ':desc 'lisp-indent-function 'defun)
(put ':leader 'lisp-indent-function 'defun)
(put ':local 'lisp-indent-function 'defun)
(put ':localleader 'lisp-indent-function 'defun)
(put ':map 'lisp-indent-function 'defun)
(put ':map* 'lisp-indent-function 'defun)
(put ':mode 'lisp-indent-function 'defun)
(put ':prefix 'lisp-indent-function 'defun)
(put ':textobj 'lisp-indent-function 'defun)
(put ':unless 'lisp-indent-function 'defun)
(put ':when 'lisp-indent-function 'defun)
(put :after 'lisp-indent-function 'defun)
(put :desc 'lisp-indent-function 'defun)
(put :leader 'lisp-indent-function 'defun)
(put :localleader 'lisp-indent-function 'defun)
(put :map 'lisp-indent-function 'defun)
(put :keymap 'lisp-indent-function 'defun)
(put :mode 'lisp-indent-function 'defun)
(put :prefix 'lisp-indent-function 'defun)
(put :unless 'lisp-indent-function 'defun)
(put :when 'lisp-indent-function 'defun)
;; specials
(defvar doom--keymaps nil)
(defvar doom--prefix nil)
(defvar doom--defer nil)
(defvar doom--local nil)
(defvar doom--map-forms nil)
(defvar doom--map-fn nil)
(defvar doom--map-batch-forms nil)
(defvar doom--map-state '(:dummy t))
(defvar doom--map-parent-state nil)
(defvar doom--map-evil-p nil)
(after! evil (setq doom--map-evil-p t))
(defun doom--map-process (rest)
(let ((doom--map-fn doom--map-fn)
doom--map-state
doom--map-forms
desc)
(while rest
(let ((key (pop rest)))
(cond ((listp key)
(doom--map-nested nil key))
((keywordp key)
(pcase key
(:leader
(doom--map-commit)
(setq doom--map-fn 'doom--define-leader-key))
(:localleader
(doom--map-commit)
(setq doom--map-fn 'define-localleader-key!))
(:after
(doom--map-nested (list 'after! (pop rest)) rest)
(setq rest nil))
(:desc
(setq desc (pop rest)))
((or :map :map* :keymap)
(doom--map-set :keymaps `(quote ,(doom-enlist (pop rest)))))
(:mode
(push (cl-loop for m in (doom-enlist (pop rest))
collect (intern (concat (symbol-name m) "-map")))
rest)
(push :map rest))
((or :when :unless)
(doom--map-nested (list (intern (doom-keyword-name key)) (pop rest)) rest)
(setq rest nil))
(:prefix
(cl-destructuring-bind (prefix . desc) (doom-enlist (pop rest))
(doom--map-set (if doom--map-fn :infix :prefix)
prefix)
(when (stringp desc)
(setq rest (append (list :desc desc "" nil) rest)))))
(:textobj
(let* ((key (pop rest))
(inner (pop rest))
(outer (pop rest)))
(push `(map! (:map evil-inner-text-objects-map ,key ,inner)
(:map evil-outer-text-objects-map ,key ,outer))
doom--map-forms)))
(_
(condition-case _
(doom--map-def (pop rest) (pop rest) (doom--keyword-to-states key) desc)
(error
(error "Not a valid `map!' property: %s" key)))
(setq desc nil))))
((doom--map-def key (pop rest) nil desc)
(setq desc nil)))))
(doom--map-commit)
(macroexp-progn (nreverse (delq nil doom--map-forms)))))
(defun doom--map-append-keys (prop)
(let ((a (plist-get doom--map-parent-state prop))
(b (plist-get doom--map-state prop)))
(if (and a b)
`(general--concat nil ,a ,b)
(or a b))))
(defun doom--map-nested (wrapper rest)
(doom--map-commit)
(let ((doom--map-parent-state (doom--map-state)))
(push (if wrapper
(append wrapper (list (doom--map-process rest)))
(doom--map-process rest))
doom--map-forms)))
(defun doom--map-set (prop &optional value)
(unless (equal (plist-get doom--map-state prop) value)
(doom--map-commit))
(setq doom--map-state (plist-put doom--map-state prop value)))
(defun doom--map-def (key def &optional states desc)
(when (or (memq 'global states)
(null states))
(setq states (cons 'nil (delq 'global states))))
(when desc
(let (unquoted)
(cond ((and (listp def)
(keywordp (car-safe (setq unquoted (doom-unquote def)))))
(setq def (list 'quote (plist-put unquoted :which-key desc))))
((setq def (cons 'list
(if (and (equal key "")
(null def))
`(:ignore t :which-key ,desc)
(plist-put (general--normalize-extended-def def)
:which-key desc))))))))
(dolist (state states)
(push (list key def)
(alist-get state doom--map-batch-forms)))
t)
(defun doom--map-commit ()
(when doom--map-batch-forms
(cl-loop with attrs = (doom--map-state)
for (state . defs) in doom--map-batch-forms
if (or doom--map-evil-p (not state))
collect `(,(or doom--map-fn 'general-define-key)
,@(if state `(:states ',state)) ,@attrs
,@(mapcan #'identity (nreverse defs)))
into forms
finally do (push (macroexp-progn forms) doom--map-forms))
(setq doom--map-batch-forms nil)))
(defun doom--map-state ()
(let ((plist
(append (list :prefix (doom--map-append-keys :prefix)
:infix (doom--map-append-keys :infix)
:keymaps
(append (plist-get doom--map-parent-state :keymaps)
(plist-get doom--map-state :keymaps)))
doom--map-state
nil))
newplist)
(while plist
(let ((key (pop plist))
(val (pop plist)))
(when (and val (not (plist-member newplist key)))
(push val newplist)
(push key newplist))))
newplist))
;;
(defmacro map! (&rest rest)
"A nightmare of a key-binding macro that will use `evil-define-key*',
`define-key', `local-set-key' and `global-set-key' depending on context and
plist key flags (and whether evil is loaded or not). It was designed to make
binding multiple keys more concise, like in vim.
"A convenience macro for defining keybinds, powered by `general'.
If evil isn't loaded, it will ignore evil-specific bindings.
If evil isn't loaded, evil-specific bindings are ignored.
States
:n normal
@ -148,153 +392,40 @@ States
:o operator
:m motion
:r replace
:g global (binds the key without evil `current-global-map')
These can be combined (order doesn't matter), e.g. :nvi will apply to
normal, visual and insert mode. The state resets after the following
key=>def pair.
These can be combined in any order, e.g. :nvi will apply to normal, visual and
insert mode. The state resets after the following key=>def pair. If states are
omitted the keybind will be global (no emacs state; this is different from
evil's Emacs state and will work in the absence of `evil-mode').
If states are omitted the keybind will be global.
Properties
:leader [...] an alias for (:prefix doom-leader-key ...)
:localleader [...] bind to localleader; requires a keymap
:mode [MODE(s)] [...] inner keybinds are applied to major MODE(s)
:map [KEYMAP(s)] [...] inner keybinds are applied to KEYMAP(S)
:keymap [KEYMAP(s)] [...] same as :map
:prefix [PREFIX] [...] set keybind prefix for following keys
:after [FEATURE] [...] apply keybinds when [FEATURE] loads
:textobj KEY INNER-FN OUTER-FN define a text object keybind pair
:if [CONDITION] [...]
:when [CONDITION] [...]
:unless [CONDITION] [...]
This can be customized with `doom-evil-state-alist'.
:textobj is a special state that takes a key and two commands, one for the
inner binding, another for the outer.
Flags
(:mode [MODE(s)] [...]) inner keybinds are applied to major MODE(s)
(:map [KEYMAP(s)] [...]) inner keybinds are applied to KEYMAP(S)
(:map* [KEYMAP(s)] [...]) same as :map, but deferred
(:prefix [PREFIX] [...]) assign prefix to all inner keybindings
(:after [FEATURE] [...]) apply keybinds when [FEATURE] loads
(:local [...]) make bindings buffer local; incompatible with keymaps!
Conditional keybinds
(:when [CONDITION] [...])
(:unless [CONDITION] [...])
Any of the above properties may be nested, so that they only apply to a
certain group of keybinds.
Example
(map! :map magit-mode-map
:m \"C-r\" 'do-something ; assign C-r in motion state
:nv \"q\" 'magit-mode-quit-window ; assign to 'q' in normal and visual states
:m \"C-r\" 'do-something ; C-r in motion state
:nv \"q\" 'magit-mode-quit-window ; q in normal+visual states
\"C-x C-r\" 'a-global-keybind
:g \"C-x C-r\" 'another-global-keybind ; same as above
(:when IS-MAC
:n \"M-s\" 'some-fn
:i \"M-o\" (lambda (interactive) (message \"Hi\"))))"
(let ((doom--keymaps doom--keymaps)
(doom--prefix doom--prefix)
(doom--defer doom--defer)
(doom--local doom--local)
key def states forms desc modes)
(while rest
(setq key (pop rest))
(cond
;; it's a sub expr
((listp key)
(push (macroexpand `(map! ,@key)) forms))
;; it's a flag
((keywordp key)
(cond ((eq key :leader)
(push 'doom-leader-key rest)
(setq key :prefix
desc "<leader>"))
((eq key :localleader)
(push 'doom-localleader-key rest)
(setq key :prefix
desc "<localleader>")))
(pcase key
(:when (push `(if ,(pop rest) ,(macroexpand `(map! ,@rest))) forms) (setq rest '()))
(:unless (push `(if (not ,(pop rest)) ,(macroexpand `(map! ,@rest))) forms) (setq rest '()))
(:after (push `(after! ,(pop rest) ,(macroexpand `(map! ,@rest))) forms) (setq rest '()))
(:desc (setq desc (pop rest)))
(:map* (setq doom--defer t) (push :map rest))
(:map
(setq doom--keymaps (doom-enlist (pop rest))))
(:mode
(setq modes (doom-enlist (pop rest)))
(unless doom--keymaps
(setq doom--keymaps
(cl-loop for m in modes
collect (intern (format "%s-map" (symbol-name m)))))))
(:textobj
(let* ((key (pop rest))
(inner (pop rest))
(outer (pop rest)))
(push (macroexpand `(map! (:map evil-inner-text-objects-map ,key ,inner)
(:map evil-outer-text-objects-map ,key ,outer)))
forms)))
(:prefix
(let ((def (pop rest)))
(setq doom--prefix `(vconcat ,doom--prefix (kbd ,def)))
(when desc
(push `(doom--keybind-register ,(key-description (eval doom--prefix))
,desc ',modes)
forms)
(setq desc nil))))
(:local
(setq doom--local t))
(_ ; might be a state doom--prefix
(setq states (doom--keyword-to-states key)))))
;; It's a key-def pair
((or (stringp key)
(characterp key)
(vectorp key)
(symbolp key))
(unwind-protect
(catch 'skip
(when (symbolp key)
(setq key `(kbd ,key)))
(when (stringp key)
(setq key (kbd key)))
(when doom--prefix
(setq key (append doom--prefix (list key))))
(unless (> (length rest) 0)
(user-error "map! has no definition for %s key" key))
(setq def (pop rest))
(when desc
(push `(doom--keybind-register ,(key-description (eval key))
,desc ',modes)
forms))
(cond ((and doom--local doom--keymaps)
(push `(lwarn 'doom-map :warning
"Can't local bind '%s' key to a keymap; skipped"
,key)
forms)
(throw 'skip 'local))
((and doom--keymaps states)
(unless (featurep 'evil)
(throw 'skip 'evil))
(dolist (keymap doom--keymaps)
(when (memq 'global states)
(push `(define-key ,keymap ,key ,def) forms))
(when-let* ((states (delq 'global states)))
(push `(,(if doom--defer 'evil-define-key 'evil-define-key*)
',states ,keymap ,key ,def)
forms))))
(states
(unless (featurep 'evil)
(throw 'skip 'evil))
(dolist (state states)
(push `(define-key
,(if (eq state 'global)
'(current-global-map)
(intern (format "evil-%s-state-%smap" state (if doom--local "local-" ""))))
,key ,def)
forms)))
(doom--keymaps
(dolist (keymap doom--keymaps)
(push `(define-key ,keymap ,key ,def) forms)))
(t
(push `(,(if doom--local 'local-set-key 'global-set-key) ,key ,def)
forms))))
(setq states '()
doom--local nil
desc nil)))
(t (user-error "Invalid key %s" key))))
`(progn ,@(nreverse forms))))
(doom--map-process rest))
(provide 'core-keybinds)
;;; core-keybinds.el ends here

View file

@ -1,35 +1,54 @@
;;; core-lib.el -*- lexical-binding: t; -*-
(require 'subr-x)
(load "async-autoloads" nil t)
(load "persistent-soft-autoloads" nil t)
(dolist (sym '(json-read json-read-file json-read-from-string json-encode))
(autoload sym "json"))
(eval-and-compile
(when (version< emacs-version "26")
(with-no-warnings
(defalias 'if-let* #'if-let)
(defalias 'when-let* #'when-let))))
;;
;; Helpers
;;
;;; Helpers
(defun doom--resolve-path-forms (paths &optional root)
(cond ((stringp paths)
`(file-exists-p
(expand-file-name
,paths ,(if (or (string-prefix-p "./" paths)
(string-prefix-p "../" paths))
'default-directory
(or root `(doom-project-root))))))
((listp paths)
(cl-loop for i in paths
collect (doom--resolve-path-forms i root)))
(t paths)))
(defun doom--resolve-path-forms (spec &optional directory)
"Converts a simple nested series of or/and forms into a series of
`file-exists-p' checks.
For example
(doom--resolve-path-forms
'(or A (and B C))
\"~\")
Returns (approximately):
'(let* ((_directory \"~\")
(A (expand-file-name A _directory))
(B (expand-file-name B _directory))
(C (expand-file-name C _directory)))
(or (and (file-exists-p A) A)
(and (if (file-exists-p B) B)
(if (file-exists-p C) C))))
This is used by `associate!', `file-exists-p!' and `project-file-exists-p!'."
(declare (pure t) (side-effect-free t))
(cond ((stringp spec)
`(let ((--file-- ,(if (file-name-absolute-p spec)
spec
`(expand-file-name ,spec ,directory))))
(and (file-exists-p --file--)
--file--)))
((and (listp spec)
(memq (car spec) '(or and)))
`(,(car spec)
,@(cl-loop for i in (cdr spec)
collect (doom--resolve-path-forms i directory))))
((or (symbolp spec)
(listp spec))
`(let ((--file-- ,(if (and directory
(or (not (stringp directory))
(file-name-absolute-p directory)))
`(expand-file-name ,spec ,directory)
spec)))
(and (file-exists-p --file--)
--file--)))
(spec)))
(defun doom--resolve-hook-forms (hooks)
(declare (pure t) (side-effect-free t))
(cl-loop with quoted-p = (eq (car-safe hooks) 'quote)
for hook in (doom-enlist (doom-unquote hooks))
if (eq (car-safe hook) 'quote)
@ -38,151 +57,234 @@
collect hook
else collect (intern (format "%s-hook" (symbol-name hook)))))
(defun doom--assert-stage-p (stage macro)
(cl-assert (eq stage doom--stage)
nil
"Found %s call in non-%s.el file (%s)"
macro (symbol-name stage)
(let ((path (FILE!)))
(if (file-in-directory-p path doom-emacs-dir)
(file-relative-name path doom-emacs-dir)
(abbreviate-file-name path)))))
;;
;;; Public library
(defun doom-unquote (exp)
"Return EXP unquoted."
(declare (pure t) (side-effect-free t))
(while (memq (car-safe exp) '(quote function))
(setq exp (cadr exp)))
exp)
(defun doom-enlist (exp)
"Return EXP wrapped in a list, or as-is if already a list."
(declare (pure t) (side-effect-free t))
(if (listp exp) exp (list exp)))
(defun doom-resolve-vim-path (file-name)
"Take a path and resolve any vim-like filename modifiers in it. On top of the
classical vim modifiers, this adds support for:
(defun doom-keyword-intern (str)
"Converts STR (a string) into a keyword (`keywordp')."
(declare (pure t) (side-effect-free t))
(cl-check-type str string)
(intern (concat ":" str)))
%:P Resolves to `doom-project-root'.
(defun doom-keyword-name (keyword)
"Returns the string name of KEYWORD (`keywordp') minus the leading colon."
(declare (pure t) (side-effect-free t))
(cl-check-type :test keyword)
(substring (symbol-name keyword) 1))
See http://vimdoc.sourceforge.net/htmldoc/cmdline.html#filename-modifiers."
(let* (case-fold-search
(regexp (concat "\\(?:^\\|[^\\\\]\\)"
"\\([#%]\\)"
"\\(\\(?::\\(?:[PphtreS~.]\\|g?s[^:\t\n ]+\\)\\)*\\)"))
(matches
(cl-loop with i = 0
while (and (< i (length file-name))
(string-match regexp file-name i))
do (setq i (1+ (match-beginning 0)))
and collect
(cl-loop for j to (/ (length (match-data)) 2)
collect (match-string j file-name)))))
(dolist (match matches)
(let ((flags (split-string (car (cdr (cdr match))) ":" t))
(path (and buffer-file-name
(pcase (car (cdr match))
("%" (file-relative-name buffer-file-name))
("#" (save-excursion (other-window 1) (file-relative-name buffer-file-name))))))
flag global)
(if (not path)
(setq path "")
(while flags
(setq flag (pop flags))
(when (string-suffix-p "\\" flag)
(setq flag (concat flag (pop flags))))
(when (string-prefix-p "gs" flag)
(setq global t
flag (substring flag 1)))
(setq path
(or (pcase (substring flag 0 1)
("p" (expand-file-name path))
("~" (concat "~/" (file-relative-name path "~")))
("." (file-relative-name path default-directory))
("t" (file-name-nondirectory (directory-file-name path)))
("r" (file-name-sans-extension path))
("e" (file-name-extension path))
("S" (shell-quote-argument path))
("h"
(let ((parent (file-name-directory (expand-file-name path))))
(unless (equal (file-truename path)
(file-truename parent))
(if (file-name-absolute-p path)
(directory-file-name parent)
(file-relative-name parent)))))
("s"
(if (featurep 'evil)
(when-let* ((args (evil-delimited-arguments (substring flag 1) 2)))
(let ((pattern (evil-transform-vim-style-regexp (car args)))
(replace (cadr args)))
(replace-regexp-in-string
(if global pattern (concat "\\(" pattern "\\).*\\'"))
(evil-transform-vim-style-regexp replace) path t t
(unless global 1))))
path))
("P"
(let ((default-directory (file-name-directory (expand-file-name path))))
(abbreviate-file-name (doom-project-root))))
(_ path))
"")))
;; strip trailing slash, if applicable
(when (and (not (string= path "")) (equal (substring path -1) "/"))
(setq path (substring path 0 -1))))
(setq file-name
(replace-regexp-in-string (format "\\(?:^\\|[^\\\\]\\)\\(%s\\)"
(regexp-quote (string-trim-left (car match))))
path file-name t t 1))))
(replace-regexp-in-string regexp "\\1" file-name t)))
(defmacro doom-log (format-string &rest args)
"Log to *Messages* if `doom-debug-mode' is on.
Does not interrupt the minibuffer if it is in use, but still logs to *Messages*.
Accepts the same arguments as `message'."
`(when doom-debug-mode
(let ((inhibit-message (active-minibuffer-window)))
(message
,(concat (propertize "DOOM " 'face 'font-lock-comment-face)
format-string
(when doom--current-module
(propertize
(format " [%s/%s]"
(doom-keyword-name (car doom--current-module))
(cdr doom--current-module))
'face 'warning)))
,@args))))
(defun FILE! ()
"Return the emacs lisp file this macro is called from."
(cond ((bound-and-true-p byte-compile-current-file))
(load-file-name)
(buffer-file-name)
((stringp (car-safe current-load-list)) (car current-load-list))))
(defun DIR! ()
"Returns the directory of the emacs lisp file this macro is called from."
(let ((file (FILE!)))
(and file (file-name-directory file))))
;;
;; Library
;;
;; Macros
(defmacro λ! (&rest body)
"A shortcut for inline interactive lambdas."
(declare (doc-string 1))
`(lambda () (interactive) ,@body))
(defmacro after! (feature &rest forms)
"A smart wrapper around `with-eval-after-load'. Supresses warnings during
compilation."
(defalias 'lambda! 'λ!)
(defmacro pushnew! (place &rest values)
"Like `cl-pushnew', but will prepend VALUES to PLACE.
The order VALUES is preserved."
`(dolist (--value-- (nreverse (list ,@values)))
(cl-pushnew --value-- ,place)))
(defmacro delq! (elt list &optional fetcher)
"Delete ELT from LIST in-place."
`(setq ,list
(delq ,(if fetcher
`(funcall ,fetcher ,elt ,list)
elt)
,list)))
(defmacro defer-until! (condition &rest body)
"Run BODY when CONDITION is true (checks on `after-load-functions'). Meant to
serve as a predicated alternative to `after!'."
(declare (indent defun) (debug t))
`(,(if (or (not (bound-and-true-p byte-compile-current-file))
(if (symbolp feature)
(require feature nil :no-error)
(load feature :no-message :no-error)))
`(if ,condition
(progn ,@body)
,(let ((fun (make-symbol "doom|delay-form-")))
`(progn
(fset ',fun (lambda (&rest args)
(when ,(or condition t)
(remove-hook 'after-load-functions #',fun)
(unintern ',fun nil)
(ignore args)
,@body)))
(put ',fun 'permanent-local-hook t)
(add-hook 'after-load-functions #',fun)))))
(defmacro defer-feature! (feature &optional mode)
"Pretend FEATURE hasn't been loaded yet, until FEATURE-hook is triggered.
Some packages (like `elisp-mode' and `lisp-mode') are loaded immediately at
startup, which will prematurely trigger `after!' (and `with-eval-after-load')
blocks. To get around this we make Emacs believe FEATURE hasn't been loaded yet,
then wait until FEATURE-hook (or MODE-hook, if MODE is provided) is triggered to
reverse this and trigger `after!' blocks at a more reasonable time."
(let ((advice-fn (intern (format "doom|defer-feature-%s" feature)))
(mode (or mode feature)))
`(progn
(setq features (delq ',feature features))
(advice-add #',mode :before #',advice-fn)
(defun ,advice-fn (&rest _)
;; Some plugins (like yasnippet) will invoke a mode early, e.g. to
;; parse some code. This would prematurely trigger this function. This
;; checks for that:
(when (and ,(intern (format "%s-hook" mode))
(not delay-mode-hooks))
;; Otherwise, announce to the world this package has been loaded, so
;; `after!' handlers can respond and configure elisp-mode as
;; expected.
(provide ',feature)
(advice-remove #',mode #',advice-fn))))))
(defmacro after! (targets &rest body)
"A smart wrapper around `with-eval-after-load' that:
1. Suppresses warnings at compile-time
2. No-ops for TARGETS that are disabled by the user (via `package!')
3. Supports compound TARGETS statements (see below)
BODY is evaluated once TARGETS are loaded. TARGETS can either be:
- An unquoted package symbol (the name of a package)
(after! helm ...)
- An unquoted list of package symbols
(after! (magit git-gutter) ...)
- An unquoted, nested list of compound package lists, using :or/:any and/or :and/:all
(after! (:or package-a package-b ...) ...)
(after! (:and package-a package-b ...) ...)
(after! (:and package-a (:or package-b package-c) ...) ...)
Note that:
- :or and :any are equivalent
- :and and :all are equivalent
- If these are omitted, :and is assumed."
(declare (indent defun) (debug t))
(unless (and (symbolp targets)
(memq targets (bound-and-true-p doom-disabled-packages)))
(list (if (or (not (bound-and-true-p byte-compile-current-file))
(dolist (next (doom-enlist targets))
(unless (keywordp next)
(if (symbolp next)
(require next nil :no-error)
(load next :no-message :no-error)))))
#'progn
#'with-no-warnings)
(with-eval-after-load ',feature ,@forms)))
(if (symbolp targets)
`(with-eval-after-load ',targets ,@body)
(pcase (car-safe targets)
((or :or :any)
(macroexp-progn
(cl-loop for next in (cdr targets)
collect `(after! ,next ,@body))))
((or :and :all)
(dolist (next (cdr targets))
(setq body `((after! ,next ,@body))))
(car body))
(_ `(after! (:and ,@targets) ,@body)))))))
(defmacro quiet! (&rest forms)
"Run FORMS without making any noise."
`(if doom-debug-mode
(progn ,@forms)
"Run FORMS without making any output."
`(cond (noninteractive
(let ((old-fn (symbol-function 'write-region)))
(cl-letf* ((standard-output (lambda (&rest _)))
(cl-letf ((standard-output (lambda (&rest _)))
((symbol-function 'load-file) (lambda (file) (load file nil t)))
((symbol-function 'message) (lambda (&rest _)))
((symbol-function 'write-region)
(lambda (start end filename &optional append visit lockname mustbenew)
(unless visit (setq visit 'no-message))
(funcall old-fn start end filename append visit lockname mustbenew)))
(inhibit-message t)
(funcall old-fn start end filename append visit lockname mustbenew))))
,@forms)))
((or doom-debug-mode debug-on-error debug-on-quit)
,@forms)
((let ((inhibit-message t)
(save-silently t))
,@forms))))
(prog1 ,@forms (message ""))))))
(defvar doom--transient-counter 0)
(defmacro add-transient-hook! (hook &rest forms)
"Attaches transient forms to a HOOK.
(defmacro add-transient-hook! (hook-or-function &rest forms)
"Attaches a self-removing function to HOOK-OR-FUNCTION.
HOOK can be a quoted hook or a sharp-quoted function (which will be advised).
FORMS are evaluated once when that function/hook is first invoked, then never
again.
These forms will be evaluated once when that function/hook is first invoked,
then it detaches itself."
HOOK-OR-FUNCTION can be a quoted hook or a sharp-quoted function (which will be
advised)."
(declare (indent 1))
(let ((append (eq (car forms) :after))
(fn (intern (format "doom-transient-hook-%s" (cl-incf doom--transient-counter)))))
`(when ,hook
(let ((append (if (eq (car forms) :after) (pop forms)))
(fn (if (symbolp (car forms))
(intern (format "doom|transient-hook-%s" (pop forms)))
(make-symbol "doom|transient-hook-"))))
`(progn
(fset ',fn
(lambda (&rest _)
,@forms
(cond ((functionp ,hook) (advice-remove ,hook #',fn))
((symbolp ,hook) (remove-hook ,hook #',fn)))
(cond ((functionp ,hook-or-function) (advice-remove ,hook-or-function #',fn))
((symbolp ,hook-or-function) (remove-hook ,hook-or-function #',fn)))
(unintern ',fn nil)))
(cond ((functionp ,hook)
(advice-add ,hook ,(if append :after :before) #',fn))
((symbolp ,hook)
(add-hook ,hook #',fn ,append))))))
(cond ((functionp ,hook-or-function)
(advice-add ,hook-or-function ,(if append :after :before) #',fn))
((symbolp ,hook-or-function)
(put ',fn 'permanent-local-hook t)
(add-hook ,hook-or-function #',fn ,append))))))
(defmacro add-hook! (&rest args)
"A convenience macro for `add-hook'. Takes, in order:
@ -195,7 +297,7 @@ then it detaches itself."
3. A function, list of functions, or body forms to be wrapped in a lambda.
Examples:
(add-hook! 'some-mode-hook 'enable-something)
(add-hook! 'some-mode-hook 'enable-something) (same as `add-hook')
(add-hook! some-mode '(enable-something and-another))
(add-hook! '(one-mode-hook second-mode-hook) 'enable-something)
(add-hook! (one-mode second-mode) 'enable-something)
@ -205,7 +307,9 @@ Examples:
(add-hook! :append :local (one-mode second-mode) (setq v 5) (setq a 2))
Body forms can access the hook's arguments through the let-bound variable
`args'."
`args'.
\(fn [:append :local] HOOKS FUNCTIONS)"
(declare (indent defun) (debug t))
(let ((hook-fn 'add-hook)
append-p local-p)
@ -232,81 +336,150 @@ Body forms can access the hook's arguments through the let-bound variable
`(remove-hook ',hook ,fn ,local-p)
`(add-hook ',hook ,fn ,append-p ,local-p))
forms)))
`(progn ,@(nreverse forms)))))
`(progn ,@(if append-p (nreverse forms) forms)))))
(defmacro remove-hook! (&rest args)
"Convenience macro for `remove-hook'. Takes the same arguments as
`add-hook!'."
`add-hook!'.
\(fn [:append :local] HOOKS FUNCTIONS)"
(declare (indent defun) (debug t))
`(add-hook! :remove ,@args))
(defmacro associate! (mode &rest plist)
"Associate a minor mode to certain patterns and project files."
(defmacro setq-hook! (hooks &rest rest)
"Convenience macro for setting buffer-local variables in a hook.
(setq-hook! 'markdown-mode-hook
line-spacing 2
fill-column 80)"
(declare (indent 1))
(unless (= 0 (% (length rest) 2))
(signal 'wrong-number-of-arguments (list #'evenp (length rest))))
`(add-hook! :append ,hooks
,@(let (forms)
(while rest
(let ((var (pop rest))
(val (pop rest)))
(push `(setq-local ,var ,val) forms)))
(nreverse forms))))
(defun advice-add! (symbols where functions)
"Variadic version of `advice-add'.
SYMBOLS and FUNCTIONS can be lists of functions."
(let ((functions (if (functionp functions)
(list functions)
functions)))
(dolist (s (doom-enlist symbols))
(dolist (f (doom-enlist functions))
(advice-add s where f)))))
(defun advice-remove! (symbols where-or-fns &optional functions)
"Variadic version of `advice-remove'.
WHERE-OR-FNS is ignored if FUNCTIONS is provided. This lets you substitute
advice-add with advice-remove and evaluate them without having to modify every
statement."
(unless functions
(setq functions where-or-fns
where-or-fns nil))
(let ((functions (if (functionp functions)
(list functions)
functions)))
(dolist (s (doom-enlist symbols))
(dolist (f (doom-enlist functions))
(advice-remove s f)))))
(cl-defmacro associate! (mode &key modes match files when)
"Enables a minor mode if certain conditions are met.
The available conditions are:
:modes SYMBOL_LIST
A list of major/minor modes in which this minor mode may apply.
:match REGEXP
A regexp to be tested against the current file path.
:files SPEC
Accepts what `project-file-exists-p!' accepts. Checks if certain files exist
relative to the project root.
:when FORM
Whenever FORM returns non-nil."
(declare (indent 1))
(unless noninteractive
(let ((modes (plist-get plist :modes))
(match (plist-get plist :match))
(files (plist-get plist :files))
(pred-form (plist-get plist :when)))
(cond ((or files modes pred-form)
(cond ((or files modes when)
(when (and files
(not (or (listp files)
(stringp files))))
(user-error "associate! :files expects a string or list of strings"))
(let ((hook-name (intern (format "doom--init-mode-%s" mode))))
`(progn
(defun ,hook-name ()
(when (and (boundp ',mode)
(not ,mode)
(fset ',hook-name
(lambda ()
(and (fboundp ',mode)
(not (bound-and-true-p ,mode))
(and buffer-file-name (not (file-remote-p buffer-file-name)))
,(if match `(if buffer-file-name (string-match-p ,match buffer-file-name)) t)
,(if files (doom--resolve-path-forms files) t)
,(or pred-form t))
(,mode 1)))
,(or (not match)
`(if buffer-file-name (string-match-p ,match buffer-file-name)))
,(or (not files)
(doom--resolve-path-forms
(if (stringp (car files)) (cons 'and files) files)
'(doom-project-root)))
,(or when t)
(,mode 1))))
,@(if (and modes (listp modes))
(cl-loop for hook in (doom--resolve-hook-forms modes)
collect `(add-hook ',hook ',hook-name))
`((add-hook 'after-change-major-mode-hook ',hook-name))))))
collect `(add-hook ',hook #',hook-name))
`((add-hook 'after-change-major-mode-hook #',hook-name))))))
(match
`(push (cons ,match ',mode) doom-auto-minor-mode-alist))
(t (user-error "associate! invalid rules for mode [%s] (modes %s) (match %s) (files %s)"
mode modes match files))))))
`(add-to-list 'doom-auto-minor-mode-alist '(,match . ,mode)))
((user-error "Invalid `associate!' rules for mode [%s] (:modes %s :match %s :files %s :when %s)"
mode modes match files when)))))
(defmacro file-exists-p! (spec &optional directory)
"Returns non-nil if the files in SPEC all exist.
;; I needed a way to reliably cross-configure modules without worrying about
;; whether they were enabled or not, so I wrote `set!'. If a setting doesn't
;; exist at runtime, the `set!' call is ignored (and omitted when
;; byte-compiled).
(defvar doom-settings nil)
Returns the last file found to meet the rules set by SPEC. SPEC can be a single
file or a list of forms/files. It understands nested (and ...) and (or ...), as
well.
(defmacro def-setting! (keyword arglist &optional docstring &rest forms)
"Define a setting. Like `defmacro', this should return a form to be executed
when called with `set!'. FORMS are not evaluated until `set!' calls it.
DIRECTORY is where to look for the files in SPEC if they aren't absolute.
See `doom/describe-setting' for a list of available settings.
For example:
(file-exists-p! (or doom-core-dir \"~/.config\" \"some-file\") \"~\")"
(if directory
`(let ((--directory-- ,directory))
,(doom--resolve-path-forms spec '--directory--))
(doom--resolve-path-forms spec)))
Do not use this for configuring Doom core."
(declare (indent defun) (doc-string 3))
(unless (keywordp keyword)
(error "Not a valid property name: %s" keyword))
(let ((fn (intern (format "doom--set%s" keyword))))
`(progn
(defun ,fn ,arglist
,docstring
,@forms)
(cl-pushnew ',(cons keyword fn) doom-settings :test #'eq :key #'car))))
(defmacro load! (filename &optional path noerror)
"Load a file relative to the current executing file (`load-file-name').
(defmacro set! (keyword &rest values)
"Set an option defined by `def-setting!'. Skip if doesn't exist. See
`doom/describe-setting' for a list of available settings."
(declare (indent defun))
(unless values
(error "Empty set! for %s" keyword))
(let ((fn (cdr (assq keyword doom-settings))))
(if fn
(apply fn values)
(when doom-debug-mode
(message "No setting found for %s" keyword)
nil))))
FILENAME is either a file path string or a form that should evaluate to such a
string at run time. PATH is where to look for the file (a string representing a
directory path). If omitted, the lookup is relative to either `load-file-name',
`byte-compile-current-file' or `buffer-file-name' (checked in that order).
If NOERROR is non-nil, don't throw an error if the file doesn't exist."
(unless path
(setq path (or (DIR!)
(error "Could not detect path to look for '%s' in"
filename))))
(let ((file (if path `(expand-file-name ,filename ,path) filename)))
`(condition-case e
(load ,file ,noerror ,(not doom-debug-mode))
((debug doom-error) (signal (car e) (cdr e)))
((debug error)
(let* ((source (file-name-sans-extension ,file))
(err (cond ((file-in-directory-p source doom-core-dir)
(cons 'doom-error doom-core-dir))
((file-in-directory-p source doom-private-dir)
(cons 'doom-private-error doom-private-dir))
((cons 'doom-module-error doom-emacs-dir)))))
(signal (car err)
(list (file-relative-name
(concat source ".el")
(cdr err))
e)))))))
(provide 'core-lib)
;;; core-lib.el ends here

472
core/core-modules.el Normal file
View file

@ -0,0 +1,472 @@
;;; core-modules.el --- module & package management system -*- lexical-binding: t; -*-
(defvar doom-init-modules-p nil
"Non-nil if `doom-initialize-modules' has run.")
(defvar doom-modules ()
"A hash table of enabled modules. Set by `doom-initialize-modules'.")
(defvar doom-modules-dirs
(list (expand-file-name "modules/" doom-private-dir)
doom-modules-dir)
"A list of module root directories. Order determines priority.")
(defvar doom-inhibit-module-warnings (not noninteractive)
"If non-nil, don't emit deprecated or missing module warnings at startup.")
(defconst doom-obsolete-modules
'((:feature (version-control (:emacs vc) (:ui vc-gutter))
(spellcheck (:tools flyspell))
(syntax-checker (:tools flycheck)))
(:tools (rotate-text (:editor rotate-text)))
(:emacs (electric-indent (:emacs electric))
(hideshow (:editor fold)))
(:ui (doom-modeline (:ui modeline)))
(:ui (fci (:ui fill-column)))
(:ui (evil-goggles (:ui ophints))))
"An alist of deprecated modules, mapping deprecated modules to an optional new
location (which will create an alias). Each CAR and CDR is a (CATEGORY .
MODULES). E.g.
((:emacs . electric-indent) . (:emacs electric))
((:feature . version-control) (:emacs vc) (:ui . vc-gutter))
A warning will be put out if these deprecated modules are used.")
(defvar doom--current-module nil)
(defvar doom--current-flags nil)
;;
;;; Custom hooks
(defvar doom-before-init-modules-hook nil
"A list of hooks to run before Doom's modules' config.el files are loaded, but
after their init.el files are loaded.")
(defvar doom-init-modules-hook nil
"A list of hooks to run after Doom's modules' config.el files have loaded, but
before the user's private module.")
(defvaralias 'doom-after-init-modules-hook 'after-init-hook)
(define-obsolete-variable-alias 'doom-post-init-hook 'doom-init-modules-hook "2.1.0")
(define-obsolete-variable-alias 'doom-init-hook 'doom-before-init-modules-hook "2.1.0")
;;
;;; Bootstrap API
(defun doom-initialize-modules (&optional force-p)
"Loads the init.el in `doom-private-dir' and sets up hooks for a healthy
session of Dooming. Will noop if used more than once, unless FORCE-P is
non-nil."
(when (and (or force-p
(not doom-init-modules-p))
(not (setq doom-modules nil))
(load! "init" doom-private-dir t))
(setq doom-init-modules-p t)
(maphash (lambda (key plist)
(let ((doom--current-module key)
(doom--current-flags (plist-get plist :flags)))
(load! "init" (plist-get plist :path) t)))
doom-modules)
(run-hook-wrapped 'doom-before-init-modules-hook #'doom-try-run-hook)
(unless noninteractive
(maphash (lambda (key plist)
(let ((doom--current-module key)
(doom--current-flags (plist-get plist :flags)))
(load! "config" (plist-get plist :path) t)))
doom-modules)
(run-hook-wrapped 'doom-init-modules-hook #'doom-try-run-hook)
(load! "config" doom-private-dir t)
(unless custom-file
(setq custom-file (concat doom-local-dir "custom.el")))
(when (stringp custom-file)
(load custom-file t t t)))))
;;
;;; Module API
(defun doom-module-p (category module)
"Returns t if CATEGORY MODULE is enabled (ie. present in `doom-modules')."
(declare (pure t) (side-effect-free t))
(and (hash-table-p doom-modules)
(gethash (cons category module) doom-modules)
t))
(defun doom-module-get (category module &optional property)
"Returns the plist for CATEGORY MODULE. Gets PROPERTY, specifically, if set."
(declare (pure t) (side-effect-free t))
(when-let* ((plist (gethash (cons category module) doom-modules)))
(if property
(plist-get plist property)
plist)))
(defun doom-module-put (category module &rest plist)
"Set a PROPERTY for CATEGORY MODULE to VALUE. PLIST should be additional pairs
of PROPERTY and VALUEs.
\(fn CATEGORY MODULE PROPERTY VALUE &rest [PROPERTY VALUE [...]])"
(if-let* ((old-plist (doom-module-get category module)))
(progn
(when plist
(when (cl-oddp (length plist))
(signal 'wrong-number-of-arguments (list (length plist))))
(while plist
(plist-put old-plist (pop plist) (pop plist))))
(puthash (cons category module) old-plist doom-modules))
(puthash (cons category module) plist doom-modules)))
(defun doom-module-set (category module &rest plist)
"Enables a module by adding it to `doom-modules'.
CATEGORY is a keyword, module is a symbol, PLIST is a plist that accepts the
following properties:
:flags [SYMBOL LIST] list of enabled category flags
:path [STRING] path to category root directory
Example:
(doom-module-set :lang 'haskell :flags '(+intero))"
(puthash (cons category module)
plist
doom-modules))
(defun doom-module-path (category module &optional file)
"Like `expand-file-name', but expands FILE relative to CATEGORY (keywordp) and
MODULE (symbol).
If the category isn't enabled this will always return nil. For finding disabled
modules use `doom-module-locate-path'."
(let ((path (doom-module-get category module :path))
file-name-handler-alist)
(if file (expand-file-name file path)
path)))
(defun doom-module-locate-path (category &optional module file)
"Searches `doom-modules-dirs' to find the path to a module.
CATEGORY is a keyword (e.g. :lang) and MODULE is a symbol (e.g. 'python). FILE
is a string that will be appended to the resulting path. If no path exists, this
returns nil, otherwise an absolute path.
This doesn't require modules to be enabled. For enabled modules us
`doom-module-path'."
(when (keywordp category)
(setq category (doom-keyword-name category)))
(when (and module (symbolp module))
(setq module (symbol-name module)))
(cl-loop with file-name-handler-alist = nil
for default-directory in doom-modules-dirs
for path = (concat category "/" module "/" file)
if (file-exists-p path)
return (expand-file-name path)))
(defun doom-module-from-path (&optional path)
"Returns a cons cell (CATEGORY . MODULE) derived from PATH (a file path)."
(or doom--current-module
(let* (file-name-handler-alist
(path (or path (FILE!))))
(save-match-data
(setq path (file-truename path))
(when (string-match "/modules/\\([^/]+\\)/\\([^/]+\\)\\(?:/.*\\)?$" path)
(when-let* ((category (match-string 1 path))
(module (match-string 2 path)))
(cons (doom-keyword-intern category)
(intern module))))))))
(defun doom-module-load-path (&optional all-p)
"Return a list of absolute file paths to activated modules. If ALL-P is
non-nil, return paths of possible modules, activated or otherwise."
(declare (pure t) (side-effect-free t))
(append (if all-p
(doom-files-in doom-modules-dirs
:type 'dirs
:mindepth 1
:depth 1
:full t)
(cl-loop for plist being the hash-values of (doom-modules)
collect (plist-get plist :path)))
(list doom-private-dir)))
(defun doom-modules (&optional refresh-p)
"Minimally initialize `doom-modules' (a hash table) and return it."
(or (unless refresh-p doom-modules)
(let ((noninteractive t)
doom-modules
doom-init-modules-p)
(load! "init" doom-private-dir t)
(or doom-modules
(make-hash-table :test 'equal
:size 20
:rehash-threshold 1.0)))))
;;
;;; Use-package modifications
(autoload 'use-package "use-package-core" nil nil t)
(setq use-package-compute-statistics doom-debug-mode
use-package-verbose doom-debug-mode
use-package-minimum-reported-time (if doom-debug-mode 0 0.1)
use-package-expand-minimally (not noninteractive))
;; Adds two new keywords to `use-package' (and consequently, `def-package!') to
;; expand its lazy-loading capabilities. They are:
;;
;; :after-call SYMBOL|LIST
;; :defer-incrementally SYMBOL|LIST|t
;;
;; Check out `def-package!'s documentation for more about these two.
(defvar doom--deferred-packages-alist '(t))
(after! use-package-core
;; :ensure and :pin don't work well with Doom, so we forcibly remove them.
(dolist (keyword '(:ensure :pin))
(setq use-package-keywords (delq keyword use-package-keywords)))
;; Insert new deferring keywords
(dolist (keyword '(:defer-incrementally :after-call))
(add-to-list 'use-package-deferring-keywords keyword nil #'eq)
(setq use-package-keywords
(use-package-list-insert keyword use-package-keywords :after)))
(defalias 'use-package-normalize/:defer-incrementally 'use-package-normalize-symlist)
(defun use-package-handler/:defer-incrementally (name _keyword targets rest state)
(use-package-concat
`((doom-load-packages-incrementally
',(if (equal targets '(t))
(list name)
(append targets (list name)))))
(use-package-process-keywords name rest state)))
(defalias 'use-package-normalize/:after-call 'use-package-normalize-symlist)
(defun use-package-handler/:after-call (name _keyword hooks rest state)
(if (plist-get state :demand)
(use-package-process-keywords name rest state)
(let ((fn (intern (format "doom|transient-hook--load-%s" name))))
(use-package-concat
`((fset ',fn
(lambda (&rest _)
(doom-log "Loading deferred package %s from %s" ',name ',fn)
(condition-case e
(require ',name)
((debug error)
(message "Failed to load deferred package %s: %s" ',name e)))
(when-let* ((deferral-list (assq ',name doom--deferred-packages-alist)))
(dolist (hook (cdr deferral-list))
(if (functionp hook)
(advice-remove hook #',fn)
(remove-hook hook #',fn)))
(setq doom--deferred-packages-alist
(delq deferral-list doom--deferred-packages-alist))))))
(let (forms)
(dolist (hook hooks forms)
(push (if (functionp hook)
`(advice-add #',hook :before #',fn)
`(add-hook ',hook #',fn))
forms)))
`((unless (assq ',name doom--deferred-packages-alist)
(push '(,name) doom--deferred-packages-alist))
(nconc (assq ',name doom--deferred-packages-alist)
'(,@hooks)))
(use-package-process-keywords name rest state))))))
;;
;;; Module config macros
(defmacro doom! (&rest modules)
"Bootstraps DOOM Emacs and its modules.
The bootstrap process involves making sure the essential directories exist, core
packages are installed, `doom-autoload-file' is loaded, `doom-packages-file'
cache exists (and is loaded) and, finally, loads your private init.el (which
should contain your `doom!' block).
If the cache exists, much of this function isn't run, which substantially
reduces startup time.
The overall load order of Doom is as follows:
~/.emacs.d/init.el
~/.emacs.d/core/core.el
$DOOMDIR/init.el
{$DOOMDIR,~/.emacs.d}/modules/*/*/init.el
`doom-before-init-modules-hook'
{$DOOMDIR,~/.emacs.d}/modules/*/*/config.el
`doom-init-modules-hook'
$DOOMDIR/config.el
`doom-after-init-modules-hook'
`after-init-hook'
`emacs-startup-hook'
`window-setup-hook'
Module load order is determined by your `doom!' block. See `doom-modules-dirs'
for a list of all recognized module trees. Order defines precedence (from most
to least)."
(unless doom-modules
(setq doom-modules
(make-hash-table :test 'equal
:size (if modules (length modules) 150)
:rehash-threshold 1.0)))
(let ((inhibit-message doom-inhibit-module-warnings)
category m)
(while modules
(setq m (pop modules))
(cond ((keywordp m) (setq category m))
((not category) (error "No module category specified for %s" m))
((catch 'doom-modules
(let* ((module (if (listp m) (car m) m))
(flags (if (listp m) (cdr m))))
(when-let* ((obsolete (assq category doom-obsolete-modules))
(new (assq module obsolete)))
(let ((newkeys (cdr new)))
(if (null newkeys)
(message "WARNING %s is deprecated" key)
(message "WARNING %s is deprecated, enabling %s instead"
(list category module) newkeys)
(push category modules)
(dolist (key newkeys)
(push (if flags
(nconc (cdr key) flags)
(cdr key))
modules)
(push (car key) modules))
(throw 'doom-modules t))))
(if-let* ((path (doom-module-locate-path category module)))
(doom-module-set category module :flags flags :path path)
(message "WARNING Couldn't find the %s %s module" category module))))))))
(when noninteractive
(setq doom-inhibit-module-warnings t))
`(setq doom-modules ',doom-modules))
(defvar doom-disabled-packages)
(defmacro def-package! (name &rest plist)
"This is a thin wrapper around `use-package'.
It is ignored if the NAME package is disabled.
Supports two special properties over `use-package':
:after-call SYMBOL|LIST
Takes a symbol or list of symbols representing functions or hook variables.
The first time any of these functions or hooks are executed, the package is
loaded. e.g.
(def-package! projectile
:after-call (pre-command-hook after-find-file dired-before-readin-hook)
...)
:defer-incrementally SYMBOL|LIST|t
Takes a symbol or list of symbols representing packages that will be loaded
incrementally at startup before this one. This is helpful for large packages
like magit or org, which load a lot of dependencies on first load. This lets
you load them piece-meal during idle periods, so that when you finally do need
the package, it'll load quicker. e.g.
NAME is implicitly added if this property is present and non-nil. No need to
specify it. A value of `t' implies NAME, e.g.
(def-package! x
;; This is equivalent to :defer-incrementally (x)
:defer-incrementally t
...)"
(unless (or (memq name doom-disabled-packages)
;; At compile-time, use-package will forcibly load its package to
;; prevent compile-time errors. However, Doom users can
;; intentionally disable packages, resulting if file-missing
;; package errors, so we preform this check at compile time:
(and (bound-and-true-p byte-compile-current-file)
(not (locate-library (symbol-name name)))))
`(use-package ,name ,@plist)))
(defmacro def-package-hook! (package when &rest body)
"Reconfigures a package's `def-package!' block.
Only use this macro in a module's init.el file.
Under the hood, this uses use-package's `use-package-inject-hooks'.
PACKAGE is a symbol; the package's name.
WHEN should be one of the following:
:pre-init :post-init :pre-config :post-config
WARNING: If :pre-init or :pre-config hooks return nil, the original
`def-package!''s :init/:config block (respectively) is overwritten, so remember
to have them return non-nil (or exploit that to overwrite Doom's config)."
(declare (indent defun))
(doom--assert-stage-p 'init #'package!)
(unless (memq when '(:pre-init :post-init :pre-config :post-config))
(error "'%s' isn't a valid hook for def-package-hook!" when))
`(progn
(setq use-package-inject-hooks t)
(add-hook!
',(intern (format "use-package--%s--%s-hook"
package
(substring (symbol-name when) 1)))
,@body)))
(defmacro require! (category module &rest flags)
"Loads the CATEGORY MODULE module with FLAGS.
CATEGORY is a keyword, MODULE is a symbol and FLAGS are symbols.
(require! :lang php +lsp)
This is for testing and internal use. This is not the correct way to enable a
module."
`(let ((doom-modules ,doom-modules)
(module-path (doom-module-locate-path ,category ',module)))
(doom-module-set
,category ',module
,@(let ((plist (doom-module-get category module)))
(when flags
(plist-put plist :flags flags))
(unless (plist-member plist :path)
(plist-put plist :path (doom-module-locate-path category module)))
plist))
(if (directory-name-p module-path)
(condition-case-unless-debug ex
(let ((doom--current-module ',(cons category module))
(doom--current-flags ',flags))
(load! "init" module-path :noerror)
(let ((doom--stage 'config))
(load! "config" module-path :noerror)))
('error
(lwarn 'doom-modules :error
"%s in '%s %s' -> %s"
(car ex) ,category ',module
(error-message-string ex))))
(warn 'doom-modules :warning "Couldn't find module '%s %s'"
,category ',module))))
(defmacro featurep! (category &optional module flag)
"Returns t if CATEGORY MODULE is enabled.
If FLAG is provided, returns t if CATEGORY MODULE has FLAG enabled.
(featurep! :config default)
Module FLAGs are set in your config's `doom!' block, typically in
~/.emacs.d/init.el. Like so:
:config (default +flag1 -flag2)
CATEGORY and MODULE can be omitted When this macro is used from inside a module
(except your DOOMDIR, which is a special moduel). e.g. (featurep! +flag)"
(and (cond (flag (memq flag (doom-module-get category module :flags)))
(module (doom-module-p category module))
(doom--current-flags (memq category doom--current-flags))
((let ((module-pair
(or doom--current-module
(doom-module-from-path (FILE!)))))
(unless module-pair
(error "featurep! call couldn't auto-detect what module its in (from %s)" (FILE!)))
(memq category (doom-module-get (car module-pair) (cdr module-pair) :flags)))))
t))
(provide 'core-modules)
;;; core-modules.el ends here

View file

@ -1,24 +1,33 @@
;;; core-os.el -*- lexical-binding: t; -*-
(defconst IS-MAC (eq system-type 'darwin))
(defconst IS-LINUX (eq system-type 'gnu/linux))
;; TODO Remove me later (deprecated)
(defmacro set-env! (&rest _))
;; clipboard
(setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)
;; Use a shared clipboard
select-enable-clipboard t
select-enable-primary t)
(setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
;; fewer opts to process for systems that don't need them
(unless IS-MAC (setq command-line-ns-option-alist nil))
(unless IS-LINUX (setq command-line-x-option-alist nil))
;; Fix the clipboard in terminal or daemon Emacs (non-GUI)
(defun doom|init-clipboard-in-tty-emacs ()
(if IS-MAC
(if (require 'osx-clipboard nil t) (osx-clipboard-mode))
(if (require 'xclip nil t) (xclip-mode))))
(add-hook 'tty-setup-hook #'doom|init-clipboard-in-tty-emacs)
;; Enable mouse in terminal Emacs
(add-hook 'tty-setup-hook #'xterm-mouse-mode)
(after! evil
;; stop copying each visual state move to the clipboard:
;; https://bitbucket.org/lyro/evil/issue/336/osx-visual-state-copies-the-region-on
;; Most of this code grokked from:
;; http://stackoverflow.com/questions/15873346/elisp-rename-macro
(advice-add #'evil-visual-update-x-selection :override #'ignore))
;; grokked from: http://stackoverflow.com/questions/15873346/elisp-rename-macro
(advice-add #'evil-visual-update-x-selection :override #'ignore)
(cond (IS-MAC
(setq mac-command-modifier 'meta
mac-option-modifier 'alt
(setq mac-command-modifier 'super
mac-option-modifier 'meta
;; sane trackpad/mouse scroll settings
mac-redisplay-dont-reset-vscroll t
mac-mouse-wheel-smooth-scroll nil
@ -27,31 +36,25 @@
;; Curse Lion and its sudden but inevitable fullscreen mode!
;; NOTE Meaningless to railwaycat's emacs-mac build
ns-use-native-fullscreen nil
;; Don't open files from the workspace in a new frame
;; Visit files opened outside of Emacs in existing frame, rather
;; than a new one
ns-pop-up-frames nil)
(cond ((display-graphic-p)
;; A known problem with GUI Emacs on MacOS: it runs in an isolated
;; environment, so envvars will be wrong. That includes the PATH
;; Emacs picks up. `exec-path-from-shell' fixes this. This is slow
;; and benefits greatly from compilation.
(setq exec-path
(or (eval-when-compile
(when (require 'exec-path-from-shell nil t)
(setq exec-path-from-shell-check-startup-files nil
exec-path-from-shell-arguments (delete "-i" exec-path-from-shell-arguments))
(nconc exec-path-from-shell-variables '("GOPATH" "GOROOT" "PYTHONPATH"))
(exec-path-from-shell-initialize)
exec-path))
exec-path)))
(t
(when (require 'osx-clipboard nil t)
(osx-clipboard-mode +1)))))
;; Syncs ns frame parameters with theme (and fixes mismatching text color
;; in the frame title)
(when (and (or (daemonp)
(display-graphic-p))
(require 'ns-auto-titlebar nil t))
(add-hook 'doom-load-theme-hook #'ns-auto-titlebar-mode)))
(IS-LINUX
;; native tooltips are ugly!
(setq x-gtk-use-system-tooltips nil)
))
(setq x-gtk-use-system-tooltips nil ; native tooltips are ugly!
x-underline-at-descent-line t)) ; draw underline lower
(IS-WINDOWS
(setq w32-get-true-file-attributes nil) ; fix file io slowdowns
(when (display-graphic-p)
(setenv "GIT_ASKPASS" "git-gui--askpass"))))
(provide 'core-os)
;;; core-os.el ends here

View file

@ -1,17 +1,14 @@
;;; core-packages.el --- package management system -*- lexical-binding: t; -*-
;;; core/core-packages.el -*- lexical-binding: t; -*-
;; Emacs package management is opinionated. Unfortunately, so am I. I've bound
;; together `use-package', `quelpa' and package.el to create my own,
;; rolling-release, lazily-loaded package management system for Emacs.
;; Emacs package management is opinionated, and so am I. I've bound together
;; `use-package', `quelpa' and package.el to create my own, rolling-release,
;; lazily-loaded package management system for Emacs.
;;
;; The three key commands are:
;;
;; + `make install` or `doom//packages-install': Installs packages that are
;; wanted, but not installed.
;; + `make update` or `doom//packages-update': Updates packages that are
;; out-of-date.
;; + `make autoremove` or `doom//packages-autoremove': Uninstalls packages that
;; are no longer needed.
;; + `bin/doom install`: Installs packages that are wanted, but not installed.
;; + `bin/doom update`: Updates packages that are out-of-date.
;; + `bin/doom autoremove`: Uninstalls packages that are no longer needed.
;;
;; This system reads packages.el files located in each activated module (and one
;; in `doom-core-dir'). These contain `package!' blocks that tell DOOM what
@ -35,118 +32,104 @@
;; just Emacs. Arguably, my config is still over-complicated, but shhh, it's
;; fine. Everything is fine.
;;
;; You should be able to use package.el commands without any conflicts, but to
;; be absolutely certain use the doom alternatives:
;;
;; + `package-install': `doom/install-package'
;; + `package-reinstall': `doom/reinstall-package'
;; + `package-delete': `doom/delete-package'
;; + `package-update': `doom/update-package'
;; + `package-autoremove': `doom//packages-autoremove'
;; + `package-refresh-contents': `doom/refresh-packages'
;; You should be able to use package.el commands without any conflicts.
;;
;; See core/autoload/packages.el for more functions.
(defvar doom-init-p nil
"Non-nil if doom is done initializing (once `doom-post-init-hook' is done). If
this is nil after Emacs has started something is wrong.")
(defvar doom-init-time nil
"The time it took, in seconds, for DOOM Emacs to initialize.")
(defvar doom-modules ()
"A hash table of enabled modules. Set by `doom-initialize-modules'.")
(defvar doom-packages ()
"A list of enabled packages. Each element is a sublist, whose CAR is the
package's name as a symbol, and whose CDR is the plist supplied to its
`package!' declaration. Set by `doom-initialize-packages'.")
(defvar doom-core-packages
'(persistent-soft use-package quelpa async)
'(persistent-soft use-package quelpa async load-env-vars)
"A list of packages that must be installed (and will be auto-installed if
missing) and shouldn't be deleted.")
(defvar doom-disabled-packages ()
"A list of packages that should be ignored by `def-package!'.")
(defvar doom-reload-hook nil
"A list of hooks to run when `doom/reload-load-path' is called.")
(defvar doom--site-load-path load-path
"The load path of built in Emacs libraries.")
(defvar doom--package-load-path ()
"The load path of package libraries installed via ELPA and QUELPA.")
(defvar doom--base-load-path
(append (list doom-core-dir doom-modules-dir)
doom--site-load-path)
"A backup of `load-path' before it was altered by `doom-initialize'. Used as a
base by `doom!' and for calculating how many packages exist.")
(defvar doom--refreshed-p nil)
(setq package--init-file-ensured t
package-user-dir (expand-file-name "elpa" doom-packages-dir)
package-gnupghome-dir (expand-file-name "gpg" doom-packages-dir)
package-enable-at-startup nil
package-archives
'(("gnu" . "https://elpa.gnu.org/packages/")
("melpa" . "https://melpa.org/packages/"))
("melpa" . "https://melpa.org/packages/")
("org" . "https://orgmode.org/elpa/"))
;; I omit Marmalade because its packages are manually submitted rather
;; than pulled, so packages are often out of date with upstream.
;; security settings
gnutls-verify-error (not (getenv "INSECURE")) ; you shouldn't use this
tls-checktrust gnutls-verify-error
tls-program (list "gnutls-cli --x509cafile %t -p %p %h"
;; compatibility fallbacks
"gnutls-cli -p %p %h"
"openssl s_client -connect %h:%p -no_ssl2 -no_ssl3 -ign_eof")
use-package-verbose doom-debug-mode
use-package-minimum-reported-time (if doom-debug-mode 0 0.1)
;; Don't track MELPA, we'll use package.el for that
quelpa-checkout-melpa-p nil
quelpa-update-melpa-p nil
quelpa-melpa-recipe-stores nil
quelpa-self-upgrade-p nil
quelpa-verbose doom-debug-mode
quelpa-dir (expand-file-name "quelpa" doom-packages-dir)
quelpa-dir (expand-file-name "quelpa" doom-packages-dir))
byte-compile-dynamic nil
byte-compile-verbose doom-debug-mode
byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local))
;; accommodate INSECURE setting
(unless gnutls-verify-error
(dolist (archive package-archives)
(setcdr archive (replace-regexp-in-string "^https://" "http://" (cdr archive) t nil))))
;;
;; Bootstrap function
;; Bootstrapper
(defun doom-initialize-packages (&optional force-p)
"Ensures that Doom's package management system, package.el and quelpa are
initialized, and `doom-packages', `packages-alist' and `quelpa-cache' are
populated, if they aren't already.
If FORCE-P is non-nil, do it anyway.
If FORCE-P is 'internal, only (re)populate `doom-packages'.
Use this before any of package.el, quelpa or Doom's package management's API to
ensure all the necessary package metadata is initialized and available for
them."
(let ((load-prefer-newer t)) ; reduce stale code issues
;; package.el and quelpa handle themselves if their state changes during the
;; current session, but if you change an packages.el file in a module,
;; there's no non-trivial way to detect that, so to reload only
;; doom-packages pass 'internal as FORCE-P or use `doom/reload-packages'.
(unless (eq force-p 'internal)
;; `package-alist'
(when (or force-p (not (bound-and-true-p package-alist)))
(doom-ensure-packages-initialized 'force)
(setq load-path (cl-delete-if-not #'file-directory-p load-path)))
;; `quelpa-cache'
(when (or force-p (not (bound-and-true-p quelpa-cache)))
;; ensure un-byte-compiled version of quelpa is loaded
(unless (featurep 'quelpa)
(load (locate-library "quelpa.el") nil t t))
(setq quelpa-initialized-p nil)
(or (quelpa-setup-p)
(error "Could not initialize quelpa"))))
;; `doom-packages'
(when (or force-p (not doom-packages))
(setq doom-packages (doom-package-list)))))
;;
;; Package API
(defun doom-initialize (&optional force-p)
"Initialize installed packages (using package.el) and ensure the core packages
are installed.
If you byte-compile core/core.el, this function will be avoided to speed up
startup."
;; Called early during initialization; only use native (and cl-lib) functions!
(when (or force-p (not doom-init-p))
;; Speed things up with a `load-path' for only the bare essentials
(let ((load-path doom--base-load-path))
;; Ensure core folders exist, otherwise we get errors
(dolist (dir (list doom-local-dir doom-etc-dir doom-cache-dir doom-packages-dir))
(unless (file-directory-p dir)
(make-directory dir t)))
;; Ensure package.el is initialized; we use its state
(setq package-activated-list nil)
(condition-case _ (package-initialize t)
(defun doom-ensure-packages-initialized (&optional force-p)
"Make sure package.el is initialized."
(when (or force-p (not (bound-and-true-p package--initialized)))
(require 'package)
(setq package-activated-list nil
package--initialized nil)
(let (byte-compile-warnings)
(condition-case _
(package-initialize)
('error (package-refresh-contents)
(setq doom--refreshed-p t)
(package-initialize t)))
;; Ensure core packages are installed
(let ((core-packages (cl-remove-if #'package-installed-p doom-core-packages)))
(when core-packages
(package-initialize))))))
(defun doom-ensure-core-packages ()
"Make sure `doom-core-packages' are installed."
(when-let* ((core-packages (cl-remove-if #'package-installed-p doom-core-packages)))
(message "Installing core packages")
(unless doom--refreshed-p
(package-refresh-contents))
@ -157,277 +140,12 @@ startup."
(message "✓ Installed %s" package)
(error "✕ Couldn't install %s" package)))
(message "Installing core packages...done")))
(setq doom-init-p t))))
(defun doom-initialize-load-path (&optional force-p)
(when (or force-p (not doom--package-load-path))
;; We could let `package-initialize' fill `load-path', but it does more than
;; that alone (like load autoload files). If you want something prematurely
;; optimizated right, ya gotta do it yourself.
;;
;; Also, in some edge cases involving package initialization during a
;; non-interactive session, `package-initialize' fails to fill `load-path'.
(setq doom--package-load-path (directory-files package-user-dir t "^[^.]" t)
load-path (append doom--base-load-path doom--package-load-path))))
(defun doom-initialize-autoloads ()
"Ensures that `doom-autoload-file' exists and is loaded. Otherwise run
`doom/reload-autoloads' to generate it."
(unless (file-exists-p doom-autoload-file)
(quiet! (doom//reload-autoloads))))
(defun doom-initialize-packages (&optional force-p load-p)
"Crawls across your emacs.d to fill `doom-modules' (from init.el) and
`doom-packages' (from packages.el files), if they aren't set already.
If FORCE-P is non-nil, do it even if they are.
This aggressively reloads core autoload files."
(doom-initialize-load-path force-p)
(with-temp-buffer ; prevent buffer-local settings from propagating
(cl-flet
((_load
(file &optional noerror interactive)
(condition-case-unless-debug ex
(let ((load-prefer-newer t)
(noninteractive (not interactive)))
(load file noerror :nomessage :nosuffix))
('error
(lwarn 'doom-initialize-packages :warning
"%s in %s: %s"
(car ex)
(file-relative-name file doom-emacs-dir)
(error-message-string ex))))))
(when (or force-p (not doom-modules))
(setq doom-modules nil
doom-packages nil)
(_load (concat doom-core-dir "core.el") nil 'interactive)
(_load (expand-file-name "init.el" doom-emacs-dir))
(when load-p
(mapc #'_load (file-expand-wildcards (expand-file-name "autoload/*.el" doom-core-dir)))
(_load (expand-file-name "init.el" doom-emacs-dir) nil 'interactive)))
(when (or force-p (not doom-packages))
(setq doom-packages nil)
(_load (expand-file-name "packages.el" doom-core-dir))
(cl-loop for (module . submodule) in (doom-module-pairs)
for path = (doom-module-path module submodule "packages.el")
do (_load path 'noerror))))))
(defun doom-initialize-modules (modules)
"Adds MODULES to `doom-modules'. MODULES must be in mplist format.
e.g '(:feature evil :lang emacs-lisp javascript java)"
(unless doom-modules
(setq doom-modules (make-hash-table :test #'equal
:size (+ 5 (length modules))
:rehash-threshold 1.0)))
(let (mode)
(dolist (m modules)
(cond ((keywordp m) (setq mode m))
((not mode) (error "No namespace specified on `doom!' for %s" m))
((listp m) (doom-module-enable mode (car m) (cdr m)))
(t (doom-module-enable mode m))))))
(defun doom-module-path (module submodule &optional file)
"Get the full path to a module: e.g. :lang emacs-lisp maps to
~/.emacs.d/modules/lang/emacs-lisp/ and will append FILE if non-nil."
(when (keywordp module)
(setq module (substring (symbol-name module) 1)))
(when (symbolp submodule)
(setq submodule (symbol-name submodule)))
(expand-file-name (concat module "/" submodule "/" file)
doom-modules-dir))
(defun doom-module-from-path (path)
"Get module cons cell (MODULE . SUBMODULE) for PATH, if possible."
(when-let* ((path (file-relative-name (file-truename path) (file-truename doom-modules-dir))))
(let ((segments (split-string path "/")))
(cons (intern (concat ":" (car segments)))
(intern (cadr segments))))))
(defun doom-module-paths (&optional append-file)
"Returns a list of absolute file paths to activated modules, with APPEND-FILE
added, if the file exists."
(cl-loop for (module . submodule) in (doom-module-pairs)
for path = (doom-module-path module submodule append-file)
if (file-exists-p path)
collect path))
(defun doom-module-get (module submodule)
"Returns a list of flags provided for MODULE SUBMODULE."
(gethash (cons module submodule) doom-modules))
(defun doom-module-enabled-p (module submodule)
"Returns t if MODULE->SUBMODULE is present in `doom-modules'."
(and (doom-module-get module submodule) t))
(defun doom-module-enable (module submodule &optional flags)
"Adds MODULE and SUBMODULE to `doom-modules', overwriting it if it exists.
MODULE is a keyword, SUBMODULE is a symbol. e.g. :lang 'emacs-lisp.
Used by `require!' and `depends-on!'."
(let ((key (cons module submodule)))
(puthash key
(or (doom-enlist flags)
(gethash key doom-modules)
'(t))
doom-modules)))
(defun doom-module-pairs ()
"Returns `doom-modules' as a list of (MODULE . SUBMODULE) cons cells. The list
is sorted by order of insertion unless ALL-P is non-nil. If ALL-P is non-nil,
include all modules, enabled or otherwise."
(unless (hash-table-p doom-modules)
(error "doom-modules is uninitialized"))
(cl-loop for key being the hash-keys of doom-modules
collect key))
(defun doom-packages--display-benchmark ()
(message "Doom loaded %s packages across %d modules in %.03fs"
;; Certainly imprecise, especially where custom additions to
;; load-path are concerned, but I don't mind a [small] margin of
;; error in the plugin count in exchange for faster startup.
(length doom--package-load-path)
(hash-table-size doom-modules)
(setq doom-init-time (float-time (time-subtract after-init-time before-init-time)))))
;;
;; Macros
;;
;; Module package macros
(autoload 'use-package "use-package" nil nil 'macro)
(defmacro doom! (&rest modules)
"Bootstrap DOOM Emacs.
MODULES is an malformed plist of modules to load."
(doom-initialize-modules modules)
`(let (file-name-handler-alist)
(setq doom-modules ',doom-modules)
(unless noninteractive
(message "Doom initialized")
,@(cl-loop for (module . submodule) in (doom-module-pairs)
for module-path = (doom-module-path module submodule)
collect `(load! init ,module-path t) into inits
collect `(load! config ,module-path t) into configs
finally return (append inits configs))
(when (display-graphic-p)
(require 'server)
(unless (server-running-p)
(server-start)))
(add-hook 'doom-init-hook #'doom-packages--display-benchmark t)
(message "Doom modules initialized"))))
(defmacro def-package! (name &rest plist)
"A thin wrapper around `use-package'."
;; Ignore package if NAME is in `doom-disabled-packages'
(when (and (memq name doom-disabled-packages)
(not (memq :disabled plist)))
(setq plist `(:disabled t ,@plist)))
;; If byte-compiling, ignore this package if it doesn't meet the condition.
;; This avoids false-positive load errors.
(unless (and (bound-and-true-p byte-compile-current-file)
(or (and (plist-member plist :if) (not (eval (plist-get plist :if))))
(and (plist-member plist :when) (not (eval (plist-get plist :when))))
(and (plist-member plist :unless) (eval (plist-get plist :unless)))))
`(use-package ,name ,@plist)))
(defmacro def-package-hook! (package when &rest body)
"Reconfigures a package's `def-package!' block.
Under the hood, this uses use-package's `use-package-inject-hooks'.
PACKAGE is a symbol; the package's name.
WHEN should be one of the following:
:pre-init :post-init :pre-config :post-config :disable
If WHEN is :disable then BODY is ignored, and DOOM will be instructed to ignore
all `def-package!' blocks for PACKAGE.
WARNING: If :pre-init or :pre-config hooks return nil, the original
`def-package!''s :init/:config block (respectively) is overwritten, so remember
to have them return non-nil (or exploit that to overwrite Doom's config)."
(declare (indent defun))
(cond ((eq when :disable)
(push package doom-disabled-packages)
nil)
((memq when '(:pre-init :post-init :pre-config :post-config))
`(progn
(setq use-package-inject-hooks t)
(add-hook!
',(intern (format "use-package--%s--%s-hook"
package
(substring (symbol-name when) 1)))
,@body)))
(t
(error "'%s' isn't a valid hook for def-package-hook!" when))))
(defmacro load! (filesym &optional path noerror)
"Load a file relative to the current executing file (`load-file-name').
FILESYM is either a symbol or string representing the file to load. PATH is
where to look for the file (a string representing a directory path). If omitted,
the lookup is relative to `load-file-name', `byte-compile-current-file' or
`buffer-file-name' (in that order).
If NOERROR is non-nil, don't throw an error if the file doesn't exist."
(cl-assert (symbolp filesym) t)
(let ((path (or path
(and load-file-name (file-name-directory load-file-name))
(and (bound-and-true-p byte-compile-current-file)
(file-name-directory byte-compile-current-file))
(and buffer-file-name
(file-name-directory buffer-file-name))
(error "Could not detect path to look for '%s' in" filesym)))
(filename (symbol-name filesym)))
(let ((file (expand-file-name (concat filename ".el") path)))
(if (file-exists-p file)
`(load ,(file-name-sans-extension file) ,noerror
,(not doom-debug-mode))
(unless noerror
(error "Could not load file '%s' from '%s'" file path))))))
(defmacro require! (module submodule &optional flags reload-p)
"Loads the module specified by MODULE (a property) and SUBMODULE (a symbol).
The module is only loaded once. If RELOAD-P is non-nil, load it again."
(when (or reload-p (not (doom-module-enabled-p module submodule)))
(let ((module-path (doom-module-path module submodule)))
(if (not (file-directory-p module-path))
(lwarn 'doom-modules :warning "Couldn't find module '%s %s'"
module submodule)
(doom-module-enable module submodule flags)
`(condition-case-unless-debug ex
(load! config ,module-path t)
('error
(lwarn 'doom-modules :error
"%s in '%s %s' -> %s"
(car ex) ,module ',submodule
(error-message-string ex))))))))
(defmacro featurep! (module &optional submodule flag)
"A convenience macro wrapper for `doom-module-enabled-p'. It is evaluated at
compile-time/macro-expansion time."
(unless submodule
(let* ((path (or load-file-name byte-compile-current-file))
(module-pair (doom-module-from-path path)))
(unless module-pair
(error "featurep! couldn't detect what module I'm in! (in %s)" path))
(setq flag module
module (car module-pair)
submodule (cdr module-pair))))
(if flag
(and (memq flag (doom-module-get module submodule)) t)
(doom-module-enabled-p module submodule)))
;;
;; Declarative macros
;;
(defmacro package! (name &rest plist)
(cl-defmacro package! (name &rest plist &key built-in recipe pin disable _ignore _freeze)
"Declares a package and how to install it (if applicable).
This macro is declarative and does not load nor install packages. It is used to
@ -438,285 +156,104 @@ Only use this macro in a module's packages.el file.
Accepts the following properties:
:recipe RECIPE Takes a MELPA-style recipe (see `quelpa-recipe' in
`quelpa' for an example); for packages to be installed
from external sources.
:pin ARCHIVE-NAME Instructs ELPA to only look for this package in
ARCHIVE-NAME. e.g. \"org\". Ignored if RECIPE is present.
:ignore FORM Do not install this package if FORM is non-nil.
:freeze FORM Do not update this package if FORM is non-nil."
:recipe RECIPE
Takes a MELPA-style recipe (see `quelpa-recipe' in `quelpa' for an example);
for packages to be installed from external sources.
:pin ARCHIVE-NAME
Instructs ELPA to only look for this package in ARCHIVE-NAME. e.g. \"org\".
Ignored if RECIPE is present.
:disable BOOL
Do not install or update this package AND disable all of its `def-package!'
blocks.
:ignore FORM
Do not install this package.
:freeze FORM
Do not update this package if FORM is non-nil.
:built-in BOOL
Same as :ignore if the package is a built-in Emacs package.
Returns t if package is successfully registered, and nil if it was disabled
elsewhere."
(declare (indent defun))
(let* ((old-plist (assq name doom-packages))
(pkg-recipe (or (plist-get plist :recipe)
(and old-plist (plist-get old-plist :recipe))))
(pkg-pin (or (plist-get plist :pin)
(and old-plist (plist-get old-plist :pin)))))
(when pkg-recipe
(when (= 0 (% (length pkg-recipe) 2))
(plist-put plist :recipe (cons name pkg-recipe)))
(when pkg-pin
(plist-put plist :pin nil)))
(dolist (prop '(:ignore :freeze))
(when-let* ((val (plist-get plist prop)))
(plist-put plist prop (eval val))))
`(progn
(when ,(and pkg-pin t)
(cl-pushnew (cons ',name ,pkg-pin) package-pinned-packages
:test #'eq :key #'car))
(when ,(and old-plist t)
(assq-delete-all ',name doom-packages))
(push ',(cons name plist) doom-packages))))
(doom--assert-stage-p 'packages #'package!)
(let ((old-plist (cdr (assq name doom-packages))))
(when recipe
(when (cl-evenp (length recipe))
(setq plist (plist-put plist :recipe (cons name recipe))))
(setq pin nil
plist (plist-put plist :pin nil)))
(let ((module-list (plist-get old-plist :modules))
(module (or doom--current-module
(let ((file (FILE!)))
(cond ((file-in-directory-p file doom-private-dir)
(list :private))
((file-in-directory-p file doom-core-dir)
(list :core))
((doom-module-from-path file)))))))
(doom-log "Registered package '%s'%s"
name (if recipe (format " with recipe %s" recipe) ""))
(unless (member module module-list)
(setq module-list (append module-list (list module) nil)
plist (plist-put plist :modules module-list))))
(when (and built-in (locate-library (symbol-name name) nil doom-site-load-path))
(doom-log "Ignoring built-in package '%s'" name)
(setq plist (plist-put plist :ignore t)))
(while plist
(unless (null (cadr plist))
(setq old-plist (plist-put old-plist (car plist) (cadr plist))))
(pop plist)
(pop plist))
(setq plist old-plist)
(macroexp-progn
(append (when disable
(doom-log "Disabling package '%s'" name)
`((add-to-list 'doom-disabled-packages ',name nil 'eq)))
(when pin
(doom-log "Pinning package '%s' to '%s'" name pin)
`((setf (alist-get ',name package-pinned-packages) ,pin)))
`((setf (alist-get ',name doom-packages) ',plist)
(not (memq ',name doom-disabled-packages)))))))
(defmacro depends-on! (module submodule)
"Declares that this module depends on another.
(defmacro packages! (&rest packages)
"A convenience macro for `package!' for declaring multiple packages at once.
Only use this macro in a module's packages.el file.
Only use this macro in a module's packages.el file."
(doom--assert-stage-p 'packages #'packages!)
(macroexp-progn
(cl-loop for desc in packages
collect (macroexpand `(package! ,@(doom-enlist desc))))))
MODULE is a keyword, and SUBMODULE is a symbol. Under the hood, this simply
loads MODULE SUBMODULE's packages.el file."
(doom-module-enable module submodule)
`(load! packages ,(doom-module-path module submodule) t))
(defmacro disable-packages! (&rest packages)
"A convenience macro like `package!', but allows you to disable multiple
packages at once.
Only use this macro in a module's packages.el file."
(doom--assert-stage-p 'packages #'disable-packages!)
(macroexp-progn
(cl-loop for pkg in packages
collect (macroexpand `(package! ,pkg :disable t)))))
;;
;; Commands
;;
(defmacro depends-on! (category module &rest flags)
"Declares that this CATEGORY depends on another.
(defun doom-packages--read-if-cookies (file)
"Returns the value of the ;;;###if predicate form in FILE."
(with-temp-buffer
(insert-file-contents-literally file nil 0 256)
(if (and (re-search-forward "^;;;###if " nil t)
(<= (line-number-at-pos) 3))
(let ((load-file-name file))
(eval (sexp-at-point)))
t)))
Emits a warning if CATEGORY MODULE isn't enabled, or is enabled without FLAGS.
(defun doom-packages--async-run (fn)
(let* ((default-directory doom-emacs-dir)
(compilation-filter-hook
(list (lambda () (ansi-color-apply-on-region compilation-filter-start (point))))))
(compile (format "%s --quick --batch -l core/core.el -f %s"
(executable-find "emacs")
(symbol-name fn)))
(while compilation-in-progress
(sit-for 1))))
(defun doom//reload-load-path ()
"Reload `load-path' and recompile files (if necessary).
Use this when `load-path' is out of sync with your plugins. This should only
happen if you manually modify/update/install packages from outside Emacs, while
an Emacs session is running.
This isn't necessary if you use Doom's package management commands because they
call `doom/reload-load-path' remotely (through emacsclient)."
(interactive)
(byte-recompile-file (expand-file-name "core.el" doom-core-dir) t)
(cond (noninteractive
(require 'server)
(when (server-running-p)
(message "Reloading active Emacs session...")
(server-eval-at server-name '(doom//reload-load-path))))
((let ((noninteractive t))
(doom-initialize-load-path t)
(message "%d packages reloaded" (length doom--package-load-path))
(run-hooks 'doom-reload-hook)))))
(defun doom//reload-autoloads ()
"Refreshes the autoloads.el file, specified by `doom-autoload-file'.
It scans and reads core/autoload/*.el, modules/*/*/autoload.el and
modules/*/*/autoload/*.el, and generates an autoloads file at the path specified
by `doom-autoload-file'. This file tells Emacs where to find lazy-loaded
functions.
This should be run whenever init.el or an autoload file is modified. Running
'make autoloads' from the commandline executes this command."
(interactive)
;; This function must not use autoloaded functions or external dependencies.
;; It must assume nothing is set up!
(if (not noninteractive)
;; This is done in another instance to protect the current session's
;; state. `doom-initialize-packages' will have side effects otherwise.
(and (doom-packages--async-run 'doom//reload-autoloads)
(load doom-autoload-file))
(doom-initialize-packages t)
(let ((targets
(file-expand-wildcards
(expand-file-name "autoload/*.el" doom-core-dir))))
(dolist (path (doom-module-paths))
(let ((auto-dir (expand-file-name "autoload" path))
(auto-file (expand-file-name "autoload.el" path)))
(when (file-exists-p auto-file)
(push auto-file targets))
(when (file-directory-p auto-dir)
(dolist (file (directory-files-recursively auto-dir "\\.el$"))
(push file targets)))))
(when (file-exists-p doom-autoload-file)
(delete-file doom-autoload-file)
(message "Deleted old autoloads.el"))
(dolist (file (reverse targets))
(message
(cond ((not (doom-packages--read-if-cookies file))
"⚠ Ignoring %s")
((update-file-autoloads file nil doom-autoload-file)
"✕ Nothing in %s")
(t
"✓ Scanned %s"))
(file-relative-name file doom-emacs-dir)))
(make-directory (file-name-directory doom-autoload-file) t)
(let ((buf (get-file-buffer doom-autoload-file))
current-sexp)
(unwind-protect
(condition-case-unless-debug ex
(with-current-buffer buf
(save-buffer)
(goto-char (point-min))
(while (re-search-forward "^(" nil t)
(save-excursion
(backward-char)
(setq current-sexp (read (thing-at-point 'sexp t)))
(eval current-sexp t))
(forward-char))
(message "Finished generating autoloads.el!"))
('error
(delete-file doom-autoload-file)
(error "Error in autoloads.el: (%s %s ...) %s -- %s"
(nth 0 current-sexp)
(nth 1 current-sexp)
(car ex) (error-message-string ex))))
(kill-buffer buf))))))
(defun doom//byte-compile (&optional modules recompile-p)
"Byte compiles your emacs configuration.
init.el is always byte-compiled by this.
If MODULES is specified (a list of module strings, e.g. \"lang/php\"), those are
byte-compiled. Otherwise, all enabled modules are byte-compiled, including Doom
core. It always ignores unit tests and files with `no-byte-compile' enabled.
Doom was designed to benefit from byte-compilation, but the process may take a
while. Also, while your config files are byte-compiled, changes to them will not
take effect! Use `doom//clean-byte-compiled-files' or `make clean' to remove
these files.
If RECOMPILE-P is non-nil, only recompile out-of-date files."
(interactive
(list nil current-prefix-arg))
(let ((default-directory doom-emacs-dir)
(recompile-p (or recompile-p
(and (member "-r" (cdr argv)) t))))
(if (not noninteractive)
;; This is done in another instance to protect the current session's
;; state. `doom-initialize-packages' will have side effects otherwise.
(doom-packages--async-run 'doom//byte-compile)
(let ((total-ok 0)
(total-fail 0)
(total-noop 0)
(modules (or modules (cdr argv)))
compile-targets)
(doom-initialize-packages t t)
(setq compile-targets
(cl-loop for target
in (or modules (append (list doom-core-dir) (doom-module-paths)))
if (equal target "core")
nconc (nreverse (directory-files-recursively doom-core-dir "\\.el$"))
else if (file-directory-p target)
nconc (nreverse (directory-files-recursively target "\\.el$"))
else if (file-directory-p (expand-file-name target doom-modules-dir))
nconc (nreverse (directory-files-recursively (expand-file-name target doom-modules-dir) "\\.el$"))
else if (file-exists-p target)
collect target
finally do (setq argv nil)))
(unless compile-targets
(error "No targets to compile"))
(let ((use-package-expand-minimally t))
(push (expand-file-name "init.el" doom-emacs-dir) compile-targets)
(condition-case ex
(progn
(dolist (target compile-targets)
(when (or (not recompile-p)
(let ((elc-file (byte-compile-dest-file target)))
(and (file-exists-p elc-file)
(file-newer-than-file-p file elc-file))))
(let ((result (if (doom-packages--read-if-cookies target)
(byte-compile-file target)
'no-byte-compile))
(short-name (file-relative-name target doom-emacs-dir)))
(cl-incf
(cond ((eq result 'no-byte-compile)
(message! (dark (white "⚠ Ignored %s" short-name)))
total-noop)
((null result)
(message! (red "✕ Failed to compile %s" short-name))
total-fail)
(t
(message! (green "✓ Compiled %s" short-name))
(quiet! (load target t t))
total-ok))))))
(message!
(bold
(color (if (= total-fail 0) 'green 'red)
"%s %s file(s) %s"
(if recompile-p "Recompiled" "Compiled")
(format "%d/%d" total-ok (- (length compile-targets) total-noop))
(format "(%s ignored)" total-noop)))))
(error
(message! (red "\n%%s\n\n%%s\n\n%%s")
"There were breaking errors."
(error-message-string ex)
"Reverting changes...")
(doom//clean-byte-compiled-files)
(message! (green "Finished (nothing was byte-compiled)")))))))))
(defun doom//byte-compile-core (&optional recompile-p)
"Byte compile the core Doom files.
This is faster than `doom//byte-compile', still yields considerable performance
benefits, and is more reliable in an ever-changing Emacs config (since you won't
likely change core files directly).
If RECOMPILE-P is non-nil, only recompile out-of-date core files."
(interactive "P")
(if (not noninteractive)
;; This is done in another instance to protect the current session's
;; state. `doom-initialize-packages' will have side effects otherwise.
(doom-packages--async-run 'doom//byte-compile-core)
(doom//byte-compile (list "core") recompile-p)))
(defun doom//byte-recompile-plugins ()
"Recompile all installed plugins. If you're getting odd errors after upgrading
(or downgrading) Emacs, this may fix it."
(interactive)
(byte-recompile-directory package-user-dir 0 t))
(defun doom//clean-byte-compiled-files ()
"Delete all the compiled elc files in your Emacs configuration. This excludes
compiled packages.'"
(interactive)
(let ((targets (append (list (expand-file-name "init.elc" doom-emacs-dir))
(directory-files-recursively doom-core-dir "\\.elc$")
(directory-files-recursively doom-modules-dir "\\.elc$")))
(default-directory doom-emacs-dir))
(unless (cl-loop for path in targets
if (file-exists-p path)
collect path
and do (delete-file path)
and do (message "✓ Deleted %s" (file-relative-name path)))
(message "Everything is clean"))))
;;
;; Package.el modifications
;;
;; Updates QUELPA after deleting a package
(advice-add #'package-delete :after #'doom*package-delete)
;; It isn't safe to use `package-autoremove', so get rid of it
(advice-add #'package-autoremove :override #'doom//packages-autoremove)
Only use this macro in a CATEGORY's packages.el file."
(doom--assert-stage-p 'packages #'depends-on!)
`(let ((desired-flags ',flags))
(unless (doom-module-locate-path ,category ',module)
(error "The '%s %s' module is required, but doesn't exist"
,category ',module))
(unless (doom-module-p ,category ',module)
(error "The '%s %s' module is required, but disabled"
,category ',module))
(let ((flags (doom-module-get ,category ',module :flags)))
(when (and desired-flags
(/= (length (cl-intersection flags desired-flags))
(length desired-flags)))
(error "The '%s %s' module is missing the required %S flag(s)"
,category ',module
(cl-set-difference desired-flags flags))))))
(provide 'core-packages)
;;; core-packages.el ends here

View file

@ -1,538 +0,0 @@
;;; core-popups.el -*- lexical-binding: t; -*-
;; I want a "real"-buffer-first policy in my Emacsian utpoia; popup buffers
;; ought to be second-class citizens to "real" buffers. No need for a wall or
;; controversial immigration policies -- all we need is `shackle' (and it will
;; actually work).
;;
;; The gist is: popups should be displayed on one side of the frame, away from
;; 'real' buffers. They should be easy to dispose of when we don't want to see
;; them and easily brought back in case we change our minds. Also, popups should
;; typically have no mode-line.
;;
;; Be warned, this requires a lot of hackery voodoo that could break with an
;; emacs update or an update to any of the packages it tries to tame (like helm
;; or org-mode).
(defvar doom-popup-history nil
"A list of popups that were last closed. Used by `doom/popup-restore' and
`doom*popups-save'.")
(defvar doom-popup-other-window nil
"The last window selected before a popup was opened.")
(defvar doom-popup-no-fringes t
"If non-nil, disable fringes in popup windows.")
(defvar doom-popup-windows ()
"A list of open popup windows.")
(defvar-local doom-popup-rules nil
"The shackle rule that caused this buffer to be recognized as a popup. Don't
edit this directly.")
(put 'doom-popup-rules 'permanent-local t)
(defvar doom-popup-window-parameters
'(:noesc :modeline :autokill :autoclose :autofit :static)
"A list of window parameters that are set (and cleared) when `doom-popup-mode
is enabled/disabled.'")
(defvar doom-popup-remember-history t
"Don't modify this directly. If non-nil, DOOM will remember the last popup(s)
that was/were open in `doom-popup-history'.")
(defvar doom-popup-inhibit-autokill nil
"Don't modify this directly. When it is non-nil, no buffers will be killed
when their associated popup windows are closed, despite their :autokill
property.")
(defvar doom-popup-mode-map (make-sparse-keymap)
"Active keymap in popup windows.")
(def-setting! :popup (&rest rules)
"Prepend a new popup rule to `shackle-rules' (see for format details).
Several custom properties have been added that are not part of shackle, but are
recognized by DOOM's popup system. They are:
:noesc If non-nil, the popup won't be closed if you press ESC from *inside*
its window. Used by `doom/popup-close-maybe'.
:modeline By default, mode-lines are hidden in popups unless this is non-nil.
If it is a symbol, it'll use `doom-modeline' to fetch a modeline
config (in `doom-popup-mode').
:autokill If non-nil, the popup's buffer will be killed when the popup is
closed. Used by `doom*delete-popup-window'. NOTE
`doom/popup-restore' can't restore non-file popups that have an
:autokill property.
:autoclose If non-nil, close popup if ESC is pressed from outside the popup
window.
:autofit If non-nil, resize the popup to fit its content. Uses the value of
the :size property as the maximum height/width. This will not work
if the popup has no content when displayed.
:static If non-nil, don't treat this window like a popup. This makes it
impervious to being automatically closed or tracked in popup
history. Excellent for permanent sidebars."
(if (cl-every #'listp (mapcar #'doom-unquote rules))
`(setq shackle-rules (nconc (list ,@rules) shackle-rules))
`(push (list ,@rules) shackle-rules)))
;;
;;
;;
;; (defvar doom-popup-parameters
;; '(:esc :modeline :transient :fit :align :size)
;; "TODO")
;; (defvar doom-popup-whitelist
;; '(("^ ?\\*" :size 15 :noselect t :autokill t :autoclose t))
;; "TODO")
(defvar doom-popup-blacklist
'("^\\*magit")
"TODO")
;;
;; Bootstrap
;;
(def-package! shackle
:init
(setq shackle-default-alignment 'below
shackle-default-size 8
shackle-rules
'(("^\\*eww" :regexp t :size 0.5 :select t :autokill t :noesc t)
("^\\*ftp " :noselect t :autokill t :noesc t)
;; doom
("^\\*doom:scratch" :regexp t :size 15 :noesc t :select t :modeline t :autokill t :static t)
("^\\*doom:" :regexp t :size 0.35 :noesc t :select t)
("^ ?\\*doom " :regexp t :noselect t :autokill t :autoclose t :autofit t)
;; built-in (emacs)
("*compilation*" :size 0.25 :noselect t :autokill t :autoclose t)
("*ert*" :same t :modeline t)
("*info*" :size 0.5 :select t :autokill t)
("*Backtrace*" :size 20 :noselect t)
("*Warnings*" :size 12 :noselect t :autofit t)
("*Messages*" :size 12 :noselect t)
("*Help*" :size 0.3 :autokill t)
("^\\*.*Shell Command.*\\*$" :regexp t :size 20 :noselect t :autokill t)
(apropos-mode :size 0.3 :autokill t :autoclose t)
(Buffer-menu-mode :size 20 :autokill t)
(comint-mode :noesc t)
(grep-mode :size 25 :noselect t :autokill t)
(profiler-report-mode :size 0.3 :regexp t :autokill t :modeline minimal)
(tabulated-list-mode :noesc t)
("^ ?\\*" :regexp t :size 15 :noselect t :autokill t :autoclose t)))
:config
;; NOTE This is a temporary fix while I rewrite core-popups
(defun doom-display-buffer-condition (buffer _action)
(and (cl-loop for re in doom-popup-blacklist
when (string-match-p re buffer)
return nil
finally return t)
(shackle-match buffer)))
(defun doom-display-buffer-action (buffer alist)
(shackle-display-buffer buffer alist (shackle-match buffer)))
(defun doom|autokill-popups ()
(or (not (doom-popup-p))
(prog1 (when (and (not doom-popup-inhibit-autokill)
(plist-get doom-popup-rules :autokill))
(doom-popup-mode -1)
(when-let* ((process (get-buffer-process (current-buffer))))
(set-process-query-on-exit-flag process nil))
t))))
(add-hook! doom-post-init
(setq display-buffer-alist
(cons '(doom-display-buffer-condition doom-display-buffer-action)
display-buffer-alist))
(add-hook 'kill-buffer-query-functions #'doom|autokill-popups))
;; no modeline in popups
(add-hook 'doom-popup-mode-hook #'doom|hide-modeline-in-popup)
;; ensure every rule without an :align, :same or :frame property has an
;; implicit :align (see `shackle-default-alignment')
(advice-add #'shackle--match :filter-return #'doom*shackle-always-align)
;; bootstrap popup system
(advice-add #'shackle-display-buffer :around #'doom*popup-init)
(advice-add #'balance-windows :around #'doom*popups-save)
(advice-add #'delete-window :before #'doom*delete-popup-window)
;; Tell `window-state-get' and `current-window-configuration' to recognize
;; these custom parameters. Helpful for `persp-mode' and persisting window
;; configs that have popups in them.
(dolist (param `(popup ,@doom-popup-window-parameters))
(push (cons param 'writable) window-persistent-parameters))
(let ((map doom-popup-mode-map))
(define-key map [escape] #'doom/popup-close-maybe)
(define-key map (kbd "ESC") #'doom/popup-close-maybe)
(define-key map [remap quit-window] #'doom/popup-close-maybe)
(define-key map [remap doom/kill-this-buffer] #'doom/popup-close-maybe)
(define-key map [remap split-window-right] #'ignore)
(define-key map [remap split-window-below] #'ignore)
(define-key map [remap split-window-horizontally] #'ignore)
(define-key map [remap split-window-vertically] #'ignore)
(define-key map [remap mouse-split-window-horizontally] #'ignore)
(define-key map [remap mouse-split-window-vertically] #'ignore)))
;;
;; Hacks
;;
(progn ; hacks for built-in functions
(defun doom*suppress-pop-to-buffer-same-window (orig-fn &rest args)
(cl-letf (((symbol-function 'pop-to-buffer-same-window)
(symbol-function 'pop-to-buffer)))
(apply orig-fn args)))
(advice-add #'info :around #'doom*suppress-pop-to-buffer-same-window)
(advice-add #'eww :around #'doom*suppress-pop-to-buffer-same-window)
(advice-add #'eww-browse-url :around #'doom*suppress-pop-to-buffer-same-window)
(defun doom*popup-buffer-menu (&optional arg)
"Open `buffer-menu' in a popup window."
(interactive "P")
(with-selected-window (doom-popup-buffer (list-buffers-noselect arg))
(setq mode-line-format "Commands: d, s, x, u; f, o, 1, 2, m, v; ~, %; q to quit; ? for help.")))
(advice-add #'buffer-menu :override #'doom*popup-buffer-menu))
(after! comint
(defun doom|popup-close-comint-buffer ()
(when (and (doom-popup-p)
(derived-mode-p 'comint-mode)
(not (process-live-p (get-buffer-process (current-buffer)))))
(delete-window)))
(add-hook '+evil-esc-hook #'doom|popup-close-comint-buffer t))
(after! eshell
;; By tying buffer life to its process, we ensure that we land back in the
;; eshell buffer after term dies. May cause problems with short-lived
;; processes.
;; FIXME replace with a 'kill buffer' keybinding.
(setq eshell-destroy-buffer-when-process-dies t)
;; When eshell runs a visual command (see `eshell-visual-commands'), it spawns
;; a term buffer to run it in, but where it spawns it is the problem...
(defun doom*eshell-undedicate-popup (orig-fn &rest args)
"Force spawned term buffer to share with the eshell popup (if necessary)."
(when (doom-popup-p)
(set-window-dedicated-p nil nil)
(add-transient-hook! #'eshell-query-kill-processes :after
(set-window-dedicated-p nil t)))
(apply orig-fn args))
(advice-add #'eshell-exec-visual :around #'doom*eshell-undedicate-popup))
(after! evil
(let ((map doom-popup-mode-map))
(define-key map [remap evil-window-delete] #'doom/popup-close-maybe)
(define-key map [remap evil-save-modified-and-close] #'doom/popup-close-maybe)
(define-key map [remap evil-window-move-very-bottom] #'doom/popup-move-bottom)
(define-key map [remap evil-window-move-very-top] #'doom/popup-move-top)
(define-key map [remap evil-window-move-far-left] #'doom/popup-move-left)
(define-key map [remap evil-window-move-far-right] #'doom/popup-move-right)
(define-key map [remap evil-window-split] #'ignore)
(define-key map [remap evil-window-vsplit] #'ignore))
(defun doom|popup-close-maybe ()
"If current window is a popup, close it. If minibuffer is open, close it. If
not in a popup, close all popups with an :autoclose property."
(if (doom-popup-p)
(unless (doom-popup-property :noesc)
(delete-window))
(doom/popup-close-all)))
(add-hook '+evil-esc-hook #'doom|popup-close-maybe t)
;; Make evil-mode cooperate with popups
(advice-add #'evil-command-window :override #'doom*popup-evil-command-window)
(advice-add #'evil-command-window-execute :override #'doom*popup-evil-command-window-execute)
(defun doom*popup-evil-command-window (hist cmd-key execute-fn)
"The evil command window has a mind of its own (uses `switch-to-buffer'). We
monkey patch it to use pop-to-buffer, and to remember the previous window."
(when (eq major-mode 'evil-command-window-mode)
(user-error "Cannot recursively open command line window"))
(dolist (win (window-list))
(when (equal (buffer-name (window-buffer win))
"*Command Line*")
(kill-buffer (window-buffer win))
(delete-window win)))
(setq evil-command-window-current-buffer (current-buffer))
(ignore-errors (kill-buffer "*Command Line*"))
(with-current-buffer (pop-to-buffer "*Command Line*")
(setq-local evil-command-window-execute-fn execute-fn)
(setq-local evil-command-window-cmd-key cmd-key)
(evil-command-window-mode)
(evil-command-window-insert-commands hist)))
(defun doom*popup-evil-command-window-execute ()
"Execute the command under the cursor in the appropriate buffer, rather than
the command buffer."
(interactive)
(let ((result (buffer-substring (line-beginning-position)
(line-end-position)))
(execute-fn evil-command-window-execute-fn)
(popup (selected-window)))
(select-window doom-popup-other-window)
(unless (equal evil-command-window-current-buffer (current-buffer))
(user-error "Originating buffer is no longer active"))
;; (kill-buffer "*Command Line*")
(doom/popup-close popup)
(funcall execute-fn result)
(setq evil-command-window-current-buffer nil)))
;; Don't mess with popups
(advice-add #'doom-evil-window-move :around #'doom*popups-save)
(advice-add #'evil-window-move-very-bottom :around #'doom*popups-save)
(advice-add #'evil-window-move-very-top :around #'doom*popups-save)
(advice-add #'evil-window-move-far-left :around #'doom*popups-save)
(advice-add #'evil-window-move-far-right :around #'doom*popups-save)
;; Don't block moving to/from popup windows
(defun doom*ignore-window-parameters-in-popups (dir &optional arg window)
(window-in-direction (cond ((eq dir 'up) 'above)
((eq dir 'down) 'below)
(t dir))
window t arg windmove-wrap-around t))
(advice-add #'windmove-find-other-window :override #'doom*ignore-window-parameters-in-popups))
(after! helm
;; Helm tries to clean up after itself, but shackle has already done this,
;; causing problems. This fixes that. To reproduce, add a helm rule in
;; `shackle-rules', open two splits side-by-side, move to the buffer on the
;; right and invoke helm. It will close all but the left-most buffer.
(setq-default helm-reuse-last-window-split-state t
helm-split-window-in-side-p t)
(after! helm-swoop
(setq helm-swoop-split-window-function #'pop-to-buffer))
(after! helm-ag
;; This prevents helm-ag from switching between windows and buffers.
(defun doom*helm-ag-edit-done (orig-fn &rest args)
(cl-letf (((symbol-function 'select-window) #'ignore))
(apply orig-fn args))
(doom/popup-close))
(advice-add #'helm-ag--edit-commit :around #'doom*helm-ag-edit-done)
(advice-add #'helm-ag--edit-abort :around #'doom*helm-ag-edit-done)
(defun doom*helm-ag-edit (orig-fn &rest args)
(cl-letf (((symbol-function 'other-window) #'ignore)
((symbol-function 'switch-to-buffer) #'doom-popup-buffer))
(apply orig-fn args)
(with-current-buffer (get-buffer "*helm-ag-edit*")
(use-local-map helm-ag-edit-map))))
(advice-add #'helm-ag--edit :around #'doom*helm-ag-edit)))
(defsubst doom--switch-from-popup (location)
(doom/popup-close)
(switch-to-buffer (car location) nil t)
(if (not (cdr location))
(message "Unable to find location in file")
(goto-char (cdr location))
(recenter)))
(after! help-mode
;; Help buffers use `other-window' to decide where to open followed links,
;; which can be unpredictable. It should *only* replace the original buffer we
;; opened the popup from. To fix this these three button types need to be
;; redefined to set aside the popup before following a link.
(define-button-type 'help-function-def
:supertype 'help-xref
'help-function
(lambda (fun file)
(require 'find-func)
(when (eq file 'C-source)
(setq file (help-C-file-name (indirect-function fun) 'fun)))
(doom--switch-from-popup (find-function-search-for-symbol fun nil file))))
(define-button-type 'help-variable-def
:supertype 'help-xref
'help-function
(lambda (var &optional file)
(when (eq file 'C-source)
(setq file (help-C-file-name var 'var)))
(doom--switch-from-popup (find-variable-noselect var file))))
(define-button-type 'help-face-def
:supertype 'help-xref
'help-function
(lambda (fun file)
(require 'find-func)
(doom--switch-from-popup (find-function-search-for-symbol fun 'defface file)))))
(after! magit
(add-hook 'magit-mode-hook #'doom-hide-modeline-mode))
(after! mu4e
(defun doom*mu4e-popup-window (buf _height)
(doom-popup-buffer buf '(:size 10 :noselect t))
buf)
(advice-add #'mu4e~temp-window :override #'doom*mu4e-popup-window))
(after! multi-term
(setq multi-term-buffer-name "doom:terminal"))
(after! neotree
;; Neotree has its own window/popup management built-in, which is difficult to
;; police. For example, switching perspectives will cause neotree to forget it
;; is a neotree pane.
;;
;; By handing neotree over to shackle, which is better integrated into the
;; rest of my config (and persp-mode), this is no longer a problem.
(set! :popup " *NeoTree*" :align neo-window-position :size neo-window-width :static t)
(defun +evil-neotree-display-fn (buf _alist)
"Hand neotree off to shackle."
(let ((win (doom-popup-buffer buf)))
(setq neo-global--buffer (window-buffer win)
neo-global--window win)))
(setq neo-display-action '(+evil-neotree-display-fn))
(defun +evil|neotree-fix-popup ()
"Repair neotree state whenever its popup state is restored. This ensures
that `doom*popup-save' won't break it."
(when (equal (buffer-name) neo-buffer-name)
(setq neo-global--window (selected-window))
;; Fix neotree shrinking when closing nearby vertical splits
(when neo-window-fixed-size
(doom-resize-window neo-global--window neo-window-width t t))))
(add-hook 'doom-popup-mode-hook #'+evil|neotree-fix-popup))
(after! persp-mode
(defun doom*persp-mode-restore-popups (&rest _)
"Restore popup windows when loading a perspective from file."
(dolist (window (window-list))
(when-let* ((plist (doom-popup-properties window)))
(with-selected-window window
(unless doom-popup-mode
(setq-local doom-popup-rules plist)
(doom-popup-mode +1))))))
(advice-add #'persp-load-state-from-file :after #'doom*persp-mode-restore-popups))
(after! quickrun
;; don't auto-focus quickrun windows, shackle handles that
(setq quickrun-focus-p nil))
(after! twittering-mode
(setq twittering-pop-to-buffer-function #'pop-to-buffer))
(after! wgrep
;; close the popup after you're done with a wgrep buffer
(advice-add #'wgrep-abort-changes :after #'doom/popup-close)
(advice-add #'wgrep-finish-edit :after #'doom/popup-close))
(after! xref
(defun doom*xref-follow-and-close (orig-fn &rest args)
"Jump to the xref on the current line, select its window and close the popup
you came from."
(interactive)
(let ((popup-p (doom-popup-p))
(window (selected-window)))
(apply orig-fn args)
(when popup-p (doom/popup-close window))))
(advice-add #'xref-goto-xref :around #'doom*xref-follow-and-close))
;;
;; Major modes
;;
(after! plantuml-mode
(defun doom*plantuml-preview-in-popup-window (orig-fn &rest args)
(save-window-excursion
(apply orig-fn args))
(pop-to-buffer plantuml-preview-buffer))
(advice-add #'plantuml-preview-string
:around #'doom*plantuml-preview-in-popup-window))
;; Ensure these settings are loaded as late as possible, giving other modules a
;; chance to reconfigure org popup settings before the defaults kick in.
(defun doom|init-org-popups ()
(add-hook! org-load
(set! :popup
'("*Calendar*" :size 0.4 :noselect t)
'(" *Org todo*" :size 5 :noselect t)
'("*Org Note*" :size 10)
'("*Org Select*" :size 20 :noselect t)
'("*Org Links*" :size 5 :noselect t)
'("*Org Export Dispatcher*" :noselect t)
'(" *Agenda Commands*" :noselect t)
'("^\\*Org Agenda" :regexp t :size 20)
'("*Org Clock*" :noselect t)
'("^\\*Org Src" :regexp t :size 0.35 :noesc t)
'("*Edit Formulas*" :size 10)
'("^\\*Org-Babel" :regexp t :size 25 :noselect t)
'("^CAPTURE.*\\.org$" :regexp t :size 20))
;; Org has a scorched-earth window management system I'm not fond of. i.e.
;; it kills all windows and monopolizes the frame. No thanks. We can do
;; better with shackle's help.
(defun doom*suppress-delete-other-windows (orig-fn &rest args)
(cl-letf (((symbol-function 'delete-other-windows)
(symbol-function 'ignore)))
(apply orig-fn args)))
(advice-add #'org-add-log-note :around #'doom*suppress-delete-other-windows)
(advice-add #'org-capture-place-template :around #'doom*suppress-delete-other-windows)
(advice-add #'org-export--dispatch-ui :around #'doom*suppress-delete-other-windows)
;; Hand off the src-block window to a shackle popup window.
(defun doom*org-src-pop-to-buffer (buffer _context)
"Open the src-edit in a way that shackle can detect."
(if (eq org-src-window-setup 'switch-invisibly)
(set-buffer buffer)
(pop-to-buffer buffer)))
(advice-add #'org-src-switch-to-buffer :override #'doom*org-src-pop-to-buffer)
;; Ensure todo, agenda, and other minor popups are delegated to shackle.
(defun doom*org-pop-to-buffer (&rest args)
"Use `pop-to-buffer' instead of `switch-to-buffer' to open buffer.'"
(let ((buf (car args)))
(pop-to-buffer
(cond ((stringp buf) (get-buffer-create buf))
((bufferp buf) buf)
(t (error "Invalid buffer %s" buf))))))
(advice-add #'org-switch-to-buffer-other-window :override #'doom*org-pop-to-buffer)
;; org-agenda
(setq org-agenda-window-setup 'other-window
org-agenda-restore-windows-after-quit nil)
;; Hide modeline in org-agenda
(add-hook 'org-agenda-finalize-hook #'doom-hide-modeline-mode)
(add-hook 'org-agenda-finalize-hook #'org-fit-window-to-buffer)
;; Don't monopolize frame!
(advice-add #'org-agenda :around #'doom*suppress-delete-other-windows)
;; ensure quit keybindings work propertly
(map! :map* org-agenda-mode-map
:m [escape] 'org-agenda-Quit
:m "ESC" 'org-agenda-Quit)))
(add-hook 'doom-init-hook #'doom|init-org-popups)
(provide 'core-popups)
;;; core-popups.el ends here

View file

@ -1,133 +1,128 @@
;;; core-projects.el -*- lexical-binding: t; -*-
(defvar doom-projectile-cache-limit 25000
"If any project cache surpasses this many files it is purged when quitting
Emacs.")
(defvar doom-projectile-cache-blacklist '("~" "/tmp" "/")
"Directories that should never be cached.")
(defvar doom-projectile-cache-purge-non-projects nil
"If non-nil, non-projects are purged from the cache on `kill-emacs-hook'.")
(defvar doom-projectile-fd-binary "fd"
"name of `fd-find' executable binary")
;;
;;; Packages
(def-package! projectile
:hook (doom-init . projectile-mode)
:after-call (after-find-file dired-before-readin-hook minibuffer-setup-hook)
:commands (projectile-project-root
projectile-project-name
projectile-project-p
projectile-add-known-project) ; TODO PR autoload upstream
:init
(setq projectile-cache-file (concat doom-cache-dir "projectile.cache")
projectile-enable-caching (not noninteractive)
projectile-indexing-method 'alien
projectile-known-projects-file (concat doom-cache-dir "projectile.projects")
projectile-require-project-root nil
projectile-require-project-root t
projectile-globally-ignored-files '(".DS_Store" "Icon " "TAGS")
projectile-globally-ignored-file-suffixes '(".elc" ".pyc" ".o"))
projectile-globally-ignored-file-suffixes '(".elc" ".pyc" ".o")
projectile-ignored-projects '("~/" "/tmp")
projectile-kill-buffers-filter 'kill-only-files
projectile-files-cache-expire 604800) ; expire after a week
:config
(add-hook 'dired-before-readin-hook #'projectile-track-known-projects-find-file-hook)
(add-hook 'find-file-hook #'doom|autoload-project-mode)
(projectile-mode +1)
(global-set-key [remap evil-jump-to-tag] #'projectile-find-tag)
(global-set-key [remap find-tag] #'projectile-find-tag)
;; a more generic project root file
(push ".project" projectile-project-root-files-bottom-up)
(push (abbreviate-file-name doom-local-dir) projectile-globally-ignored-directories)
(setq projectile-globally-ignored-directories
(append projectile-globally-ignored-directories
(list (abbreviate-file-name doom-local-dir) ".sync"))
projectile-other-file-alist
(append projectile-other-file-alist
'(("css" . ("scss" "sass" "less" "styl"))
("scss" . ("css"))
("sass" . ("css"))
("less" . ("css"))
("styl" . ("css")))))
;; Accidentally indexing big directories like $HOME or / will massively bloat
;; projectile's cache (into the hundreds of MBs). This purges those entries
;; when exiting Emacs to prevent slowdowns/freezing when cache files are
;; loaded or written to.
(defun doom|cleanup-project-cache ()
"Purge projectile cache entries that:
a) have too many files (see `doom-projectile-cache-limit'),
b) represent blacklisted directories that are too big, change too often or are
private. (see `doom-projectile-cache-blacklist'),
c) are not valid projectile projects."
(when (bound-and-true-p projectile-projects-cache)
(cl-loop with blacklist = (mapcar #'file-truename doom-projectile-cache-blacklist)
for proot in (hash-table-keys projectile-projects-cache)
for len = (length (gethash proot projectile-projects-cache))
if (or (>= len doom-projectile-cache-limit)
(member (substring proot 0 -1) blacklist)
(and doom-projectile-cache-purge-non-projects
(not (doom-project-p proot))))
do (doom-log "Removed %S from projectile cache" proot)
and do (remhash proot projectile-projects-cache)
and do (remhash proot projectile-projects-cache-time)
and do (remhash proot projectile-project-type-cache))
(projectile-serialize-cache)))
(add-hook 'kill-emacs-hook #'doom|cleanup-project-cache)
;; It breaks projectile's project root resolution if HOME is a project (e.g.
;; it's a git repo). In that case, we disable bottom-up root searching to
;; prevent issues. This makes project resolution a little slower and less
;; accurate in some cases.
(let ((default-directory "~"))
(when (cl-find-if #'projectile-file-exists-p
projectile-project-root-files-bottom-up)
(message "HOME appears to be a project. Disabling bottom-up root search.")
(setq projectile-project-root-files
(append projectile-project-root-files-bottom-up
projectile-project-root-files)
projectile-project-root-files-bottom-up nil)))
;; Projectile root-searching functions can cause an infinite loop on TRAMP
;; connections, so disable them.
(defun doom*projectile-locate-dominating-file (orig-fn &rest args)
;; TODO Is this still necessary?
(defun doom*projectile-locate-dominating-file (orig-fn file name)
"Don't traverse the file system if on a remote connection."
(unless (file-remote-p default-directory)
(apply orig-fn args)))
(when (and (stringp file)
(not (file-remote-p file)))
(funcall orig-fn file name)))
(advice-add #'projectile-locate-dominating-file :around #'doom*projectile-locate-dominating-file)
(defun doom*projectile-cache-current-file (orig-fun &rest args)
"Don't cache ignored files."
(unless (cl-loop for path in (projectile-ignored-directories)
if (string-prefix-p buffer-file-name (expand-file-name path))
return t)
(apply orig-fun args)))
(advice-add #'projectile-cache-current-file :around #'doom*projectile-cache-current-file))
;; If fd exists, use it for git and generic projects
;; fd is a rust program that is significantly faster. It also respects
;; .gitignore. This is recommended in the projectile docs
(when (executable-find doom-projectile-fd-binary)
(setq projectile-git-command (concat
doom-projectile-fd-binary
" . --type f -0 -H -E .git")
projectile-generic-command projectile-git-command)))
;;
;; Library
;;
(defun doom//reload-project ()
"Reload the project root cache."
(interactive)
(projectile-invalidate-cache nil)
(projectile-reset-cached-project-root)
(dolist (fn projectile-project-root-files-functions)
(remhash (format "%s-%s" fn default-directory) projectile-project-root-cache)))
(defun doom-project-p ()
"Whether or not this buffer is currently in a project or not."
(let ((projectile-require-project-root t))
(projectile-project-p)))
(defun doom-project-root ()
"Get the path to the root of your project.
If STRICT-P, return nil if no project was found, otherwise return
`default-directory'."
(let (projectile-require-project-root)
(projectile-project-root)))
(defalias 'doom-project-expand #'projectile-expand-root)
(defmacro doom-project-has! (files)
"Checks if the project has the specified FILES.
Paths are relative to the project root, unless they start with ./ or ../ (in
which case they're relative to `default-directory'). If they start with a slash,
they are absolute."
(doom--resolve-path-forms files (doom-project-root)))
(defun doom-project-find-file (dir)
"Fuzzy-find a file under DIR."
(let ((default-directory dir)
;; Necessary to isolate this search from the current project
projectile-project-name
projectile-require-project-root
projectile-cached-buffer-file-name
projectile-cached-project-root)
(call-interactively
;; completion modules may remap this command
(or (command-remapping #'projectile-find-file)
#'projectile-find-file))))
(defun doom-project-browse (dir)
"Traverse a file structure starting linearly from DIR."
(let ((default-directory dir))
(call-interactively
;; completion modules may remap this command
(or (command-remapping #'find-file)
#'find-file))))
;;
;; Projects
;;
(defvar-local doom-project nil
"Either the symbol or a list of project modes you want to enable. Available
for .dir-locals.el.")
;; Project-based minor modes
(defvar doom-project-hook nil
"Hook run when a project is enabled. The name of the project's mode and its
state are passed in.")
(defun doom|autoload-project-mode ()
"Auto-enable the project(s) listed in `doom-project'."
(when doom-project
(if (symbolp doom-project)
(funcall doom-project)
(cl-loop for mode in doom-project
unless (symbol-value mode)
do (funcall mode)))))
(defmacro def-project-mode! (name &rest plist)
(cl-defmacro def-project-mode! (name &key
modes
files
when
match
add-hooks
on-load
on-enter
on-exit)
"Define a project minor-mode named NAME (a symbol) and declare where and how
it is activated. Project modes allow you to configure 'sub-modes' for
major-modes that are specific to a specific folder, certain project structure,
framework or arbitrary context you define. These project modes can have their
own settings, keymaps, hooks, snippets, etc.
major-modes that are specific to a folder, project structure, framework or
whatever arbitrary context you define. These project modes can have their own
settings, keymaps, hooks, snippets, etc.
This creates NAME-hook and NAME-map as well.
@ -157,37 +152,26 @@ should be activated. If they are *all* true, NAME is activated.
:on-exit FORM -- FORM is run each time the mode is disabled.
Relevant: `doom-project-hook'."
(declare (indent 1) (doc-string 2))
(let ((doc-string (if (stringp (car plist))
(prog1 (car plist)
(setq plist (cdr plist)))
"A project minor mode."))
(modes (plist-get plist :modes))
(files (plist-get plist :files))
(when (plist-get plist :when))
(match (plist-get plist :match))
(hooks (plist-get plist :add-hooks))
(load-form (plist-get plist :on-load))
(enter-form (plist-get plist :on-enter))
(exit-form (plist-get plist :on-exit))
(init-var (intern (format "%s-init" name))))
(declare (indent 1))
(let ((init-var (intern (format "%s-init" name))))
`(progn
,(if load-form `(defvar ,init-var nil))
,(if on-load `(defvar ,init-var nil))
(define-minor-mode ,name
,doc-string
"A project minor mode generated by `def-project-mode!'."
:init-value nil
:lighter ""
:keymap (make-sparse-keymap)
(if (not ,name)
,exit-form
,on-exit
(run-hook-with-args 'doom-project-hook ',name ,name)
,(when load-form
,(when on-load
`(unless ,init-var
,load-form
,on-load
(setq ,init-var t)))
,enter-form))
,(when hooks
`(setq ,(intern (format "%s-hook" name)) ',hooks))
,on-enter))
,@(cl-loop for hook in add-hooks
collect `(add-hook ',(intern (format "%s-hook" name))
#',hook))
,(when (or modes match files when)
`(associate! ,name
:modes ,modes

View file

@ -1,87 +1,208 @@
;;; core-ui.el -*- lexical-binding: t; -*-
(defvar doom-fringe-size '4
"Default fringe width.")
;;
;;; Variables
(defvar doom-theme nil
"A symbol representing the color theme to load.")
"A symbol representing the Emacs theme to load at startup.
This is changed by `load-theme'.")
(defvar doom-font nil
"The default font to use. Expects a `font-spec'.")
"The default font to use.
Expects either a `font-spec', font object, an XFT font string or an XLFD font
string.
This affects the `default' and `fixed-pitch' faces.
Examples:
(setq doom-font (font-spec :family \"Fira Mono\" :size 12))
(setq doom-font \"Terminus (TTF):pixelsize=12:antialias=off\")")
(defvar doom-big-font nil
"The default large font to use when `doom-big-font-mode' is enabled. Expects a
`font-spec'.")
"The font to use when `doom-big-font-mode' is enabled. Expects either a
`font-spec' or a XFT font string. See `doom-font' for examples.")
(defvar doom-variable-pitch-font nil
"The default font to use for variable-pitch text. Expects a `font-spec'.")
"The font to use for variable-pitch text.
Expects either a `font-spec', font object, a XFT font string or XLFD string. See
`doom-font' for examples.
It is recommended you don't set specify a font-size, as to inherit `doom-font's
size.")
(defvar doom-serif-font nil
"The default font to use for the `fixed-pitch-serif' face.
Expects either a `font-spec', font object, a XFT font string or XLFD string. See
`doom-font' for examples.
It is recommended you don't set specify a font-size, as to inherit `doom-font's
size.")
(defvar doom-unicode-font nil
"Fallback font for unicode glyphs. Is ignored if :feature unicode is active.
Expects a `font-spec'.")
(defvar doom-major-mode-names
'((sh-mode . "sh")
(emacs-lisp-mode . "Elisp"))
"An alist mapping major modes symbols to strings (or functions that will
return a string). This changes the 'long' name of a major-mode, allowing for
shorter major mode name in the mode-line. See `doom|set-mode-name'.")
Expects either a `font-spec', font object, a XFT font string or XLFD string. See
`doom-font' for examples.
It is recommended you don't set specify a font-size, as to inherit `doom-font's
size.")
(defvar doom--prefer-theme-elc nil
"If non-nil, `load-theme' will prefer the compiled theme (unlike its default
behavior). Do not set this directly, this is let-bound in `doom|init-theme'.")
;; Hook(s)
;;
;;; Custom hooks
(defvar doom-init-ui-hook nil
"List of hooks to run when the theme and font is initialized (or reloaded with
`doom//reload-theme').")
"List of hooks to run when the UI has been initialized.")
(defvar doom-load-theme-hook nil
"Hook run after the theme is loaded with `load-theme' or reloaded with
`doom/reload-theme'.")
(defvar doom-switch-buffer-hook nil
"A list of hooks run after changing the current buffer.")
(defvar doom-switch-window-hook nil
"A list of hooks run after changing the focused windows.")
(defvar doom-switch-frame-hook nil
"A list of hooks run after changing the focused frame.")
(defvar doom-inhibit-switch-buffer-hooks nil
"Letvar for inhibiting `doom-switch-buffer-hook'. Do not set this directly.")
(defvar doom-inhibit-switch-window-hooks nil
"Letvar for inhibiting `doom-switch-window-hook'. Do not set this directly.")
(defvar doom-inhibit-switch-frame-hooks nil
"Letvar for inhibiting `doom-switch-frame-hook'. Do not set this directly.")
(defvar doom--last-window nil)
(defvar doom--last-frame nil)
(defun doom|run-switch-window-hooks ()
(unless (or doom-inhibit-switch-window-hooks
(eq doom--last-window (selected-window))
(minibufferp))
(let ((doom-inhibit-switch-window-hooks t))
(run-hooks 'doom-switch-window-hook)
(doom-log "Window switched to %s" (selected-window))
(setq doom--last-window (selected-window)))))
(defun doom|run-switch-frame-hooks (&rest _)
(unless (or doom-inhibit-switch-frame-hooks
(eq doom--last-frame (selected-frame))
(frame-parameter nil 'parent-frame))
(let ((doom-inhibit-switch-frame-hooks t))
(run-hooks 'doom-switch-frame-hook)
(doom-log "Frame switched to %s" (selected-frame))
(setq doom--last-frame (selected-frame)))))
(defun doom*run-switch-buffer-hooks (orig-fn buffer-or-name &rest args)
(if (or doom-inhibit-switch-buffer-hooks
(if (eq orig-fn 'switch-to-buffer)
(car args) ; norecord
(eq (get-buffer buffer-or-name) (current-buffer))))
(apply orig-fn buffer-or-name args)
(let ((doom-inhibit-switch-buffer-hooks t))
(doom-log "Buffer switched in %s" (selected-window))
(prog1 (apply orig-fn buffer-or-name args)
(run-hooks 'doom-switch-buffer-hook)))))
(defun doom*run-load-theme-hooks (theme &optional _no-confirm no-enable)
"Set up `doom-load-theme-hook' to run after `load-theme' is called."
(unless no-enable
(setq doom-theme theme)
(run-hooks 'doom-load-theme-hook)))
(defun doom|protect-fallback-buffer ()
"Don't kill the scratch buffer. Meant for `kill-buffer-query-functions'."
(not (eq (current-buffer) (doom-fallback-buffer))))
(defun doom|highlight-non-default-indentation ()
"Highlight whitespace that doesn't match your `indent-tabs-mode' setting.
e.g. If you indent with spaces by default, tabs will be highlighted. If you
indent with tabs, spaces at BOL are highlighted.
Does nothing if `whitespace-mode' is already active or the current buffer is
read-only or not file-visiting."
(unless (or (bound-and-true-p global-whitespace-mode)
(bound-and-true-p whitespace-mode)
(eq major-mode 'fundamental-mode)
buffer-read-only
(null buffer-file-name))
(require 'whitespace)
(set (make-local-variable 'whitespace-style)
(if (bound-and-true-p whitespace-newline-mode)
(cl-union (if indent-tabs-mode '(indentation) '(tabs tab-mark))
whitespace-style)
`(face ,@(if indent-tabs-mode '(indentation) '(tabs tab-mark))
trailing-lines tail)))
(whitespace-mode +1)))
;;
;;; General configuration
(setq-default
ansi-color-for-comint-mode t
bidi-display-reordering nil ; disable bidirectional text for tiny performance boost
blink-matching-paren nil ; don't blink--too distracting
compilation-always-kill t ; kill compilation process before starting another
compilation-ask-about-save nil ; save all buffers on `compile'
compilation-scroll-output 'first-error
confirm-nonexistent-file-or-buffer t
confirm-kill-emacs #'doom-quit-p ; custom confirmation when killing Emacs
cursor-in-non-selected-windows nil ; hide cursors in other windows
custom-theme-directory (expand-file-name "themes/" doom-private-dir)
display-line-numbers-width 3
enable-recursive-minibuffers nil
frame-inhibit-implied-resize t
frame-title-format '("%b Doom Emacs") ; simple name in frame title
;; remove continuation arrow on right fringe
fringe-indicator-alist (delq (assq 'continuation fringe-indicator-alist)
fringe-indicator-alist
(delq (assq 'continuation fringe-indicator-alist)
fringe-indicator-alist)
highlight-nonselected-windows nil
image-animate-loop t
indicate-buffer-boundaries nil
indicate-empty-lines nil
inhibit-compacting-font-caches t
max-mini-window-height 0.3
mode-line-default-help-echo nil ; disable mode-line mouseovers
mouse-yank-at-point t ; middle-click paste at point, not at click
ibuffer-use-other-window t
resize-mini-windows 'grow-only ; Minibuffer resizing
show-help-function nil ; hide :help-echo text
split-width-threshold 160 ; favor horizontal splits
uniquify-buffer-name-style 'forward
uniquify-buffer-name-style nil ; custom modeline will show file paths anyway
use-dialog-box nil ; always avoid GUI
visible-cursor nil
x-stretch-cursor nil
;; defer jit font locking slightly to [try to] improve Emacs performance
jit-lock-defer-time nil
jit-lock-stealth-nice 0.1
jit-lock-stealth-time 0.2
jit-lock-stealth-verbose nil
;; `pos-tip' defaults
pos-tip-internal-border-width 6
pos-tip-border-width 1
;; no beeping or blinking please
ring-bell-function #'ignore
visible-bell nil)
(fset #'yes-or-no-p #'y-or-n-p) ; y/n instead of yes/no
(defun doom-quit-p (&optional prompt)
"Return t if this session should be killed. Prompts the user for
confirmation."
(if (ignore-errors (doom-real-buffer-list))
(or (yes-or-no-p (format " %s" (or prompt "Quit Emacs?")))
(ignore (message "Aborted")))
t))
(setq confirm-kill-emacs nil)
(add-hook 'kill-emacs-query-functions #'doom-quit-p)
visible-bell nil
;; don't resize emacs in steps, it looks weird
window-resize-pixelwise t
frame-resize-pixelwise t)
;; y/n instead of yes/no
(fset #'yes-or-no-p #'y-or-n-p)
;; Truly silence startup message
(fset #'display-startup-echo-area-message #'ignore)
;; relegate tooltips to echo area only
(if (bound-and-true-p tooltip-mode) (tooltip-mode -1))
;; enabled by default; no thanks, too distracting
(blink-cursor-mode -1)
;; Handle ansi codes in compilation buffer
(add-hook 'compilation-filter-hook #'doom|apply-ansi-color-to-compilation-buffer)
;; show typed keystrokes in minibuffer
(defun doom|enable-ui-keystrokes () (setq echo-keystrokes 0.02))
(defun doom|disable-ui-keystrokes () (setq echo-keystrokes 0))
@ -89,192 +210,47 @@ confirmation."
;; ...but hide them while isearch is active
(add-hook 'isearch-mode-hook #'doom|disable-ui-keystrokes)
(add-hook 'isearch-mode-end-hook #'doom|enable-ui-keystrokes)
;; A minor mode for toggling the mode-line
(defvar-local doom--modeline-format nil
"The modeline format to use when `doom-hide-modeline-mode' is active. Don't
set this directly. Let-bind it instead.")
(defvar-local doom--old-modeline-format nil
"The old modeline format, so `doom-hide-modeline-mode' can revert when it's
disabled.")
(define-minor-mode doom-hide-modeline-mode
"Minor mode to hide the mode-line in the current buffer."
:init-value nil
:global nil
(if doom-hide-modeline-mode
(setq doom--old-modeline-format mode-line-format
mode-line-format doom--modeline-format)
(setq mode-line-format doom--old-modeline-format
doom--old-modeline-format nil))
(force-mode-line-update))
;; Ensure major-mode or theme changes don't overwrite these variables
(put 'doom--modeline-format 'permanent-local t)
(put 'doom--old-modeline-format 'permanent-local t)
(put 'doom-hide-modeline-mode 'permanent-local t)
(defun doom|hide-modeline-mode-reset ()
"Sometimes, a major-mode is activated after `doom-hide-modeline-mode' is
activated, thus disabling it (because changing major modes invokes
`kill-all-local-variables' and specifically seems to kill `mode-line-format's
local value, whether or not it's permanent-local. Therefore, we cycle
`doom-hide-modeline-mode' to fix this."
(when doom-hide-modeline-mode
(doom-hide-modeline-mode -1)
(doom-hide-modeline-mode +1)))
(add-hook 'after-change-major-mode-hook #'doom|hide-modeline-mode-reset)
;; no modeline in completion popups
(add-hook 'completion-list-mode-hook #'doom-hide-modeline-mode)
;; undo/redo changes to Emacs' window layout
(defvar winner-dont-bind-my-keys t) ; I'll bind keys myself
(autoload 'winner-mode "winner" nil t)
(add-hook 'doom-init-ui-hook #'winner-mode)
;; highlight matching delimiters
(setq show-paren-delay 0.1
show-paren-highlight-openparen t
show-paren-when-point-inside-paren t)
(add-hook 'doom-init-ui-hook #'show-paren-mode)
;;; More reliable inter-window border
;; The native border "consumes" a pixel of the fringe on righter-most splits,
;; `window-divider' does not. Available since Emacs 25.1.
(setq-default window-divider-default-places t
window-divider-default-bottom-width 0
window-divider-default-right-width 1)
(add-hook 'doom-init-ui-hook #'window-divider-mode)
;; like diminish, but for major-modes. [pedantry intensifies]
(defun doom|set-mode-name ()
"Set the major mode's `mode-name', as dictated by `doom-major-mode-names'."
(when-let* ((name (cdr (assq major-mode doom-major-mode-names))))
(setq mode-name
(cond ((functionp name) (funcall name))
((stringp name) name)
(t (error "'%s' isn't a valid name for %s" name major-mode))))))
(add-hook 'after-change-major-mode-hook #'doom|set-mode-name)
;;
;; Themes & fonts
;;
;; Getting themes to remain consistent across GUI Emacs, terminal Emacs and
;; daemon Emacs is hairy.
;;
;; + Running `doom|init-ui' directly sorts out the initial GUI frame.
;; + Attaching it to `after-make-frame-functions' sorts out daemon Emacs.
;; + Waiting for 0.1s in `doom|reload-ui-in-daemon' fixes daemon Emacs started
;; with `server-start' in an interactive session of Emacs AND in tty Emacs.
(defun doom|init-ui (&optional frame)
"Set the theme and load the font, in that order."
(when doom-theme
(load-theme doom-theme t))
(condition-case-unless-debug ex
(when (display-graphic-p)
(when (fontp doom-font)
(set-frame-font doom-font nil (if frame (list frame) t))
(set-face-attribute 'fixed-pitch frame :font doom-font))
;; Fallback to `doom-unicode-font' for Unicode characters
(when (fontp doom-unicode-font)
(set-fontset-font t 'unicode doom-unicode-font frame))
;; ...and for variable-pitch-mode:
(when (fontp doom-variable-pitch-font)
(set-face-attribute 'variable-pitch frame :font doom-variable-pitch-font)))
('error
(if (string-prefix-p "Font not available: " (error-message-string ex))
(lwarn 'doom-ui :warning
"Could not find the '%s' font on your system, falling back to system font"
(font-get (caddr ex) :family))
(lwarn 'doom-ui :error
"Unexpected error while initializing fonts: %s"
(error-message-string ex)))))
(run-hooks 'doom-init-ui-hook))
(defun doom|reload-ui-in-daemon (frame)
"Reload the theme (and font) in an daemon frame."
(when (or (daemonp) (not (display-graphic-p)))
(with-selected-frame frame
(run-with-timer 0.1 nil #'doom|init-ui))))
;; register UI init hooks
(add-hook 'doom-post-init-hook #'doom|init-ui)
(add-hook! 'after-make-frame-functions #'(doom|init-ui doom|reload-ui-in-daemon))
;;
;; Bootstrap
;;
;; Make `next-buffer', `other-buffer', etc. ignore unreal buffers.
(add-to-list 'default-frame-alist '(buffer-predicate . doom-buffer-frame-predicate))
;; Prevent the glimpse of un-styled Emacs by setting these early.
(add-to-list 'default-frame-alist '(tool-bar-lines . 0))
(add-to-list 'default-frame-alist '(menu-bar-lines . 0))
(add-to-list 'default-frame-alist '(vertical-scroll-bars))
;; prompts the user for confirmation when deleting a non-empty frame
(define-key global-map [remap delete-frame] #'doom/delete-frame)
;; simple name in frame title
(setq-default frame-title-format '("DOOM Emacs"))
;; auto-enabled in Emacs 25+; I'll do it myself
(global-eldoc-mode -1)
;; a good indicator that Emacs isn't frozen
(add-hook 'doom-post-init-hook #'blink-cursor-mode)
;; standardize default fringe width
(if (fboundp 'fringe-mode) (fringe-mode doom-fringe-size))
;; draw me like one of your French editors
(tooltip-mode -1) ; relegate tooltips to echo area only
(menu-bar-mode -1)
(if (fboundp 'tool-bar-mode) (tool-bar-mode -1))
(if (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))
(defun doom|no-fringes-in-minibuffer ()
"Disable fringes in the minibuffer window."
(set-window-fringes (minibuffer-window) 0 0 nil))
(add-hook! '(doom-post-init-hook minibuffer-setup-hook)
#'doom|no-fringes-in-minibuffer)
(defun doom|protect-visible-buffers ()
"Don't kill the current buffer if it is visible in another window (bury it
instead)."
(not (delq (selected-window)
(get-buffer-window-list nil nil t))))
(add-hook! doom-post-init
(add-hook 'kill-buffer-query-functions #'doom|protect-visible-buffers))
(global-set-key [remap delete-frame] #'doom/delete-frame)
;;
;; Plugins
;;
;;; Built-in packages
(def-package! all-the-icons
:commands (all-the-icons-octicon all-the-icons-faicon all-the-icons-fileicon
all-the-icons-wicon all-the-icons-material all-the-icons-alltheicon
all-the-icons-install-fonts)
;; Disable these because whitespace should be customized programmatically
;; (through `whitespace-style'), and not through these commands.
(put 'whitespace-toggle-options 'disabled t)
(put 'global-whitespace-toggle-options 'disabled t)
(def-package! ediff
:defer t
:init
(defun doom*disable-all-the-icons-in-tty (orig-fn &rest args)
(when (display-graphic-p)
(apply orig-fn args)))
;; all-the-icons doesn't work in the terminal, so we "disable" it.
(dolist (fn '(all-the-icons-octicon all-the-icons-material
all-the-icons-faicon all-the-icons-fileicon
all-the-icons-wicon all-the-icons-alltheicon))
(advice-add fn :around #'doom*disable-all-the-icons-in-tty)))
(setq ediff-diff-options "-w" ; turn off whitespace checking
ediff-split-window-function #'split-window-horizontally
ediff-window-setup-function #'ediff-setup-windows-plain)
:config
(defvar doom--ediff-saved-wconf nil)
;; Restore window config after quitting ediff
(defun doom|ediff-save-wconf ()
(setq doom--ediff-saved-wconf (current-window-configuration)))
(add-hook 'ediff-before-setup-hook #'doom|ediff-save-wconf)
(def-package! fringe-helper
:commands (fringe-helper-define fringe-helper-convert)
:init
(unless (fboundp 'define-fringe-bitmap)
;; doesn't exist in terminal Emacs; define it to prevent errors
(defun define-fringe-bitmap (&rest _))))
(defun doom|ediff-restore-wconf ()
(when (window-configuration-p doom--ediff-saved-wconf)
(set-window-configuration doom--ediff-saved-wconf)))
(add-hook 'ediff-quit-hook #'doom|ediff-restore-wconf 'append)
(add-hook 'ediff-suspend-hook #'doom|ediff-restore-wconf 'append))
(def-package! hideshow ; built-in
:commands (hs-minor-mode hs-toggle-hiding hs-already-hidden-p)
:config (setq hs-hide-comments-when-hiding-all nil))
(def-package! highlight-indentation
:commands (highlight-indentation-mode highlight-indentation-current-column-mode))
;; For modes with sub-par number fontification
(def-package! highlight-numbers :commands highlight-numbers-mode)
(def-package! hl-line
;; Highlights the current line
(def-package! hl-line ; built-in
:hook ((prog-mode text-mode conf-mode) . hl-line-mode)
:config
;; I don't need hl-line showing in other windows. This also offers a small
@ -282,98 +258,124 @@ instead)."
(setq hl-line-sticky-flag nil
global-hl-line-sticky-flag nil)
;; On Emacs 26+, when point is on the last line, hl-line highlights bleed into
;; the rest of the window after eob. This is the fix.
(when (boundp 'display-line-numbers)
(defun doom--line-range ()
(cons (line-beginning-position)
(cond ((save-excursion
(goto-char (line-end-position))
(and (eobp) (not (bolp))))
(1- (line-end-position)))
((or (eobp) (save-excursion (forward-line) (eobp)))
(line-end-position))
(t
(line-beginning-position 2)))))
(setq hl-line-range-function #'doom--line-range))
;; Disable `hl-line' in evil-visual mode (temporarily). `hl-line' can make the
;; selection region harder to see while in evil visual mode.
(after! evil
(defvar-local doom-buffer-hl-line-mode nil)
;; Disable `hl-line' in evil-visual mode (temporarily). `hl-line' can make
;; the selection region harder to see while in evil visual mode.
(defun doom|disable-hl-line ()
(when hl-line-mode
(setq doom-buffer-hl-line-mode t)
(hl-line-mode -1)))
(defun doom|enable-hl-line-maybe ()
(if doom-buffer-hl-line-mode (hl-line-mode +1)))
(add-hook 'evil-visual-state-entry-hook #'doom|disable-hl-line)
(add-hook 'evil-visual-state-exit-hook #'doom|enable-hl-line-maybe)))
;; Helps us distinguish stacked delimiter pairs. Especially in parentheses-drunk
;; languages like Lisp.
(def-package! rainbow-delimiters
:hook (lisp-mode . rainbow-delimiters-mode)
:config (setq rainbow-delimiters-max-face-count 3))
;; For a distractions-free-like UI, that dynamically resizes margets and can
;; center a buffer.
(def-package! visual-fill-column
:commands visual-fill-column-mode
(def-package! winner
;; undo/redo changes to Emacs' window layout
:after-call (after-find-file doom-switch-window-hook)
:preface (defvar winner-dont-bind-my-keys t)
:config (winner-mode +1)) ; I'll bind keys myself
(def-package! paren
;; highlight matching delimiters
:after-call (after-find-file doom-switch-buffer-hook)
:init
(defun doom|disable-show-paren-mode ()
"Turn off `show-paren-mode' buffer-locally."
(set (make-local-variable 'show-paren-mode) nil))
:config
(setq-default
visual-fill-column-center-text t
(setq show-paren-delay 0.1
show-paren-highlight-openparen t
show-paren-when-point-inside-paren t)
(show-paren-mode +1))
;; The native border "consumes" a pixel of the fringe on righter-most splits,
;; `window-divider' does not. Available since Emacs 25.1.
(setq-default window-divider-default-places t
window-divider-default-bottom-width 1
window-divider-default-right-width 1)
(add-hook 'doom-init-ui-hook #'window-divider-mode)
;; `whitespace-mode'
(setq whitespace-line-column nil
whitespace-style
'(face indentation tabs tab-mark spaces space-mark newline newline-mark
trailing lines-tail)
whitespace-display-mappings
'((tab-mark ?\t [? ?\t])
(newline-mark ?\n [ ?\n])
(space-mark ?\ [] [?.])))
;;
;;; Third party packages
;;;###package avy
(setq avy-all-windows nil
avy-background t)
(def-package! all-the-icons
:commands (all-the-icons-octicon all-the-icons-faicon all-the-icons-fileicon
all-the-icons-wicon all-the-icons-material all-the-icons-alltheicon)
:init
(defun doom*disable-all-the-icons-in-tty (orig-fn &rest args)
(if (display-graphic-p)
(apply orig-fn args)
""))
:config
;; all-the-icons doesn't work in the terminal, so we "disable" it.
(dolist (fn '(all-the-icons-octicon all-the-icons-material
all-the-icons-faicon all-the-icons-fileicon
all-the-icons-wicon all-the-icons-alltheicon))
(advice-add fn :around #'doom*disable-all-the-icons-in-tty)))
;;;###package hide-mode-line-mode
(add-hook 'completion-list-mode-hook #'hide-mode-line-mode)
(add-hook 'Man-mode-hook #'hide-mode-line-mode)
;; Better fontification of number literals in code
(def-package! highlight-numbers
:hook ((prog-mode conf-mode) . highlight-numbers-mode)
:config (setq highlight-numbers-generic-regexp "\\_<[[:digit:]]+\\(?:\\.[0-9]*\\)?\\_>"))
;;;###package highlight-escape-sequences
(def-package! highlight-escape-sequences
:hook ((prog-mode conf-mode) . highlight-escape-sequences-mode))
;;;###package rainbow-delimiters
;; Helps us distinguish stacked delimiter pairs, especially in parentheses-drunk
;; languages like Lisp.
(setq rainbow-delimiters-max-face-count 3)
;;;###package visual-fill-column
;; For a distractions-free-like UI, that dynamically resizes margins and can
;; center a buffer.
(setq visual-fill-column-center-text t
visual-fill-column-width
;; take Emacs 26 line numbers into account
(+ (if (boundp 'display-line-numbers) 6 0)
fill-column)))
(+ (if EMACS26+ 6 0) fill-column))
;;
;; Line numbers
;;
;;; Line numbers
(defvar doom-line-numbers-style t
"The style to use for the line number display.
;; line numbers in most modes
(add-hook! (prog-mode text-mode conf-mode) #'display-line-numbers-mode)
Accepts the same arguments as `display-line-numbers', which are:
(defun doom|enable-line-numbers () (display-line-numbers-mode +1))
(defun doom|disable-line-numbers () (display-line-numbers-mode -1))
nil No line numbers
t Ordinary line numbers
'relative Relative line numbers")
(defun doom|enable-line-numbers (&optional arg)
"Enables the display of line numbers, using `display-line-numbers' (in Emacs
26+) or `nlinum-mode'.
See `doom-line-numbers-style' to control the style of line numbers to display."
(cond ((boundp 'display-line-numbers)
(setq display-line-numbers
(pcase arg
(+1 doom-line-numbers-style)
(-1 nil)
(_ doom-line-numbers-style))))
((eq doom-line-numbers-style 'relative)
(if (= arg -1)
(nlinum-relative-off)
(nlinum-relative-on)))
((not (null doom-line-numbers-style))
(nlinum-mode (or arg +1)))))
(defun doom|disable-line-numbers ()
"Disable the display of line numbers."
(doom|enable-line-numbers -1))
(add-hook! (prog-mode text-mode conf-mode) #'doom|enable-line-numbers)
;; Emacs 26+ has native line number support.
;; `nlinum' is used for Emacs 25 users, as Emacs 26+ has native line numbers.
(def-package! nlinum
;; Line number column. A faster (or equivalent, in the worst case) line number
;; plugin than `linum-mode'.
(def-package! nlinum
:unless (boundp 'display-line-numbers)
:commands nlinum-mode
:unless EMACS26+
:defer t
:init
(defvar doom-line-number-lpad 4
"How much padding to place before line numbers.")
@ -418,9 +420,9 @@ character that looks like a space that `whitespace-mode' won't affect.")
(format-mode-line "%l")))))
(add-hook 'nlinum-mode-hook #'doom|init-nlinum-width))
;; Fixes disappearing line numbers in nlinum and other quirks
(def-package! nlinum-hl
:unless (boundp 'display-line-numbers)
;; Fixes disappearing line numbers in nlinum and other quirks
:unless EMACS26+
:after nlinum
:config
;; With `markdown-fontify-code-blocks-natively' enabled in `markdown-mode',
@ -432,79 +434,171 @@ character that looks like a space that `whitespace-mode' won't affect.")
(advice-add #'web-mode-fold-or-unfold :after #'nlinum-hl-do-generic-flush)
;; Changing fonts can leave nlinum line numbers in their original size; this
;; forces them to resize.
(advice-add #'set-frame-font :after #'nlinum-hl-flush-all-windows))
(add-hook 'after-setting-font-hook #'nlinum-hl-flush-all-windows))
(def-package! nlinum-relative
:unless (boundp 'display-line-numbers)
:commands nlinum-relative-mode
:unless EMACS26+
:defer t
:config
(after! evil (nlinum-relative-setup-evil)))
(setq nlinum-format " %d ")
(add-hook 'evil-mode-hook #'nlinum-relative-setup-evil))
;;
;; Modeline
;;; Theme & font
(defvar doom-last-window-system
(if (daemonp) 'daemon initial-window-system)
"The `window-system' of the last frame. If this doesn't match the current
frame's window-system, the theme will be reloaded.")
(defun doom|init-fonts ()
"Loads fonts.
Fonts are specified by `doom-font', `doom-variable-pitch-font',
`doom-serif-font' and `doom-unicode-font'."
(condition-case e
(progn
(cond (doom-font
(add-to-list
'default-frame-alist
(cons 'font
(cond ((stringp doom-font) doom-font)
((fontp doom-font) (font-xlfd-name doom-font))
((signal 'wrong-type-argument (list '(fontp stringp) doom-font)))))))
((display-graphic-p)
(setq doom-font (face-attribute 'default :font))))
(when doom-serif-font
(set-face-attribute 'fixed-pitch-serif nil :font doom-serif-font))
(when doom-variable-pitch-font
(set-face-attribute 'variable-pitch nil :font doom-variable-pitch-font))
;; Fallback to `doom-unicode-font' for Unicode characters
(when (fontp doom-unicode-font)
(set-fontset-font t nil doom-unicode-font nil 'append)))
((debug error)
(if (string-prefix-p "Font not available: " (error-message-string e))
(lwarn 'doom-ui :warning
"Could not find the '%s' font on your system, falling back to system font"
(font-get (caddr e) :family))
(signal 'doom-error e)))))
(defun doom|init-theme ()
"Load the theme specified by `doom-theme'."
(when (and doom-theme (not (memq doom-theme custom-enabled-themes)))
(let ((doom--prefer-theme-elc t))
(load-theme doom-theme t))))
(defun doom|reload-theme-maybe (_frame)
"Reloads the theme if the display device has changed."
(unless (cl-find doom-last-window-system (frame-list) :key #'framep-on-display)
(setq doom-last-window-system nil)
(doom|reload-theme-in-frame-maybe (selected-frame))))
(defun doom|reload-theme-in-frame-maybe (frame)
"Reloads the theme if the display device has changed.
Getting themes to remain consistent across GUI Emacs, terminal Emacs and daemon
Emacs is hairy. `doom|init-theme' sorts out the initial GUI frame. Attaching
`doom|reload-theme-in-frame-maybe' to `after-make-frame-functions' sorts out
daemon and emacsclient frames.
There will still be issues with simultaneous gui and terminal (emacsclient)
frames, however. There's always `doom/reload-theme' if you need it!"
(when (and doom-theme
(framep frame)
(not (eq doom-last-window-system (framep-on-display frame))))
(with-selected-frame frame
(load-theme doom-theme t))
(setq doom-last-window-system (framep-on-display frame))))
;;
;;; Bootstrap
(defmacro def-modeline-segment! (name &rest forms)
"Defines a modeline segment and byte compiles it."
(declare (indent defun) (doc-string 2))
(let ((sym (intern (format "doom-modeline-segment--%s" name))))
(defun doom|init-ui ()
"Initialize Doom's user interface by applying all its advice and hooks."
(run-hook-wrapped 'doom-init-ui-hook #'doom-try-run-hook)
(add-to-list 'kill-buffer-query-functions #'doom|protect-fallback-buffer nil 'eq)
(add-hook 'after-change-major-mode-hook #'doom|highlight-non-default-indentation)
;; Reload theme if the display device has changed
(add-hook 'after-make-frame-functions #'doom|reload-theme-in-frame-maybe)
(add-hook 'after-delete-frame-functions #'doom|reload-theme-maybe)
;; Initialize custom switch-{buffer,window,frame} hooks:
;; + `doom-switch-buffer-hook'
;; + `doom-switch-window-hook'
;; + `doom-switch-frame-hook'
(add-hook 'buffer-list-update-hook #'doom|run-switch-window-hooks)
(add-hook 'focus-in-hook #'doom|run-switch-frame-hooks)
(advice-add! '(switch-to-buffer display-buffer) :around #'doom*run-switch-buffer-hooks))
;; Apply `doom-theme'
(unless (daemonp)
(add-hook 'doom-init-ui-hook #'doom|init-theme))
;; Apply `doom-font' et co
(add-hook 'doom-after-init-modules-hook #'doom|init-fonts)
;; Setup `doom-load-theme-hook'
(advice-add #'load-theme :after #'doom*run-load-theme-hooks)
(add-hook 'window-setup-hook #'doom|init-ui)
;;
;;; Fixes/hacks
;; doesn't exist in terminal Emacs; we define it to prevent errors
(unless (fboundp 'define-fringe-bitmap)
(defun define-fringe-bitmap (&rest _)))
(defun doom*prefer-compiled-theme (orig-fn &rest args)
"Make `load-theme' prioritize the byte-compiled theme for a moderate boost in
startup (or theme switch) time, so long as `doom--prefer-theme-elc' is non-nil."
(if (or (null after-init-time)
doom--prefer-theme-elc)
(cl-letf* ((old-locate-file (symbol-function 'locate-file))
((symbol-function 'locate-file)
(lambda (filename path &optional _suffixes predicate)
(funcall old-locate-file filename path '("c" "") predicate))))
(apply orig-fn args))
(apply orig-fn args)))
(advice-add #'load-theme :around #'doom*prefer-compiled-theme)
(after! whitespace
(defun doom*disable-whitespace-mode-in-childframes (orig-fn)
"`whitespace-mode' inundates child frames with whitspace markers, so disable
it to fix all that visual noise."
(unless (frame-parameter nil 'parent-frame)
(funcall orig-fn)))
(add-function :around whitespace-enable-predicate #'doom*disable-whitespace-mode-in-childframes)
(defun doom|disable-whitespace-mode-in-childframes (frame)
"`whitespace-mode' inundates child frames with whitspace markers, so disable
it to fix all that visual noise."
(when (frame-parameter frame 'parent-frame)
(with-selected-frame frame
(setq-local whitespace-style nil)
frame)))
(add-hook 'after-make-frame-functions #'doom|disable-whitespace-mode-in-childframes))
;; Don't allow cursor to enter the prompt
(setq minibuffer-prompt-properties '(read-only t intangible t cursor-intangible t face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
;; Don't display messages in the minibuffer when using the minibuffer
(defmacro doom-silence-motion-key (command key)
(let ((key-command (intern (format "doom/silent-%s" command))))
`(progn
(defun ,sym () ,@forms)
,(unless (bound-and-true-p byte-compile-current-file)
`(let (byte-compile-warnings)
(byte-compile #',sym))))))
(defun ,key-command ()
(interactive)
(ignore-errors (call-interactively ',command)))
(define-key minibuffer-local-map (kbd ,key) #',key-command))))
(doom-silence-motion-key backward-delete-char "<backspace>")
(doom-silence-motion-key delete-char "<delete>")
(defsubst doom--prepare-modeline-segments (segments)
(cl-loop for seg in segments
if (stringp seg)
collect seg
else
collect (list (intern (format "doom-modeline-segment--%s" (symbol-name seg))))))
(defmacro def-modeline! (name lhs &optional rhs)
"Defines a modeline format and byte-compiles it. NAME is a symbol to identify
it (used by `doom-modeline' for retrieval). LHS and RHS are lists of symbols of
modeline segments defined with `def-modeline-segment!'.
Example:
(def-modeline! minimal
(bar matches \" \" buffer-info)
(media-info major-mode))
(doom-set-modeline 'minimal t)"
(let ((sym (intern (format "doom-modeline-format--%s" name)))
(lhs-forms (doom--prepare-modeline-segments lhs))
(rhs-forms (doom--prepare-modeline-segments rhs)))
`(progn
(defun ,sym ()
(let ((lhs (list ,@lhs-forms))
(rhs (list ,@rhs-forms)))
(let ((rhs-str (format-mode-line rhs)))
(list lhs
(propertize
" " 'display
`((space :align-to (- (+ right right-fringe right-margin)
,(+ 1 (string-width rhs-str))))))
rhs-str))))
,(unless (bound-and-true-p byte-compile-current-file)
`(let (byte-compile-warnings)
(byte-compile #',sym))))))
(defun doom-modeline (key)
"Returns a mode-line configuration associated with KEY (a symbol). Throws an
error if it doesn't exist."
(let ((fn (intern (format "doom-modeline-format--%s" key))))
(when (functionp fn)
`(:eval (,fn)))))
(defun doom-set-modeline (key &optional default)
"Set the modeline format. Does nothing if the modeline KEY doesn't exist. If
DEFAULT is non-nil, set the default mode-line for all buffers."
(when-let* ((modeline (doom-modeline key)))
(setf (if default
(default-value 'mode-line-format)
(buffer-local-value 'mode-line-format (current-buffer)))
modeline)))
;; Switch to `doom-fallback-buffer' if on last real buffer
(advice-add #'kill-this-buffer :around #'doom*switch-to-fallback-buffer-maybe)
(provide 'core-ui)
;;; core-ui.el ends here

View file

@ -1,38 +1,40 @@
;;; core.el --- the heart of the beast -*- lexical-binding: t; -*-
;;; Naming conventions:
;;
;; doom-... public variables or non-interactive functions
;; doom--... private anything (non-interactive), not safe for direct use
;; doom/... an interactive function; safe for M-x or keybinding
;; doom//... an interactive function for managing/maintaining Doom itself
;; doom:... an evil operator, motion or command
;; doom|... hook function
;; doom*... advising functions
;; doom@... a hydra command
;; ...! a macro or function that configures DOOM
;; =... an interactive command that starts an app module
;; %... functions used for in-snippet logic
;; +... Any of the above but part of a module, e.g. `+emacs-lisp|init-hook'
;;
;; Autoloaded functions are in core/autoload/*.el and modules/*/*/autoload.el or
;; modules/*/*/autoload/*.el.
(defvar doom-version "2.0.9"
"Current version of DOOM emacs.")
(eval-when-compile
(and (version< emacs-version "25.3")
(error "Detected Emacs %s. Doom only supports Emacs 25.3 and higher"
emacs-version)))
(defvar doom-debug-mode (or (getenv "DEBUG") init-file-debug)
"If non-nil, all doom functions will be verbose. Set DEBUG=1 in the command
line or use --debug-init to enable this.")
(defvar doom-emacs-dir (file-truename user-emacs-directory)
"The path to this emacs.d directory.")
;;
;;; Constants
(defconst doom-version "2.0.9"
"Current version of DOOM emacs.")
(defconst EMACS26+ (> emacs-major-version 25))
(defconst EMACS27+ (> emacs-major-version 26))
(defconst IS-MAC (eq system-type 'darwin))
(defconst IS-LINUX (eq system-type 'gnu/linux))
(defconst IS-WINDOWS (memq system-type '(cygwin windows-nt ms-dos)))
(defconst IS-BSD (or IS-MAC (eq system-type 'berkeley-unix)))
;;
(defvar doom-emacs-dir
(eval-when-compile (file-truename user-emacs-directory))
"The path to this emacs.d directory. Must end in a slash.")
(defvar doom-core-dir (concat doom-emacs-dir "core/")
"Where essential files are stored.")
"The root directory of core Doom files.")
(defvar doom-modules-dir (concat doom-emacs-dir "modules/")
"Where configuration modules are stored.")
"The root directory for Doom's modules.")
(defvar doom-local-dir (concat doom-emacs-dir ".local/")
"Root directory for local Emacs files. Use this as permanent storage for files
@ -53,152 +55,153 @@ Use this for files that change often, like cache files.")
(defvar doom-packages-dir (concat doom-local-dir "packages/")
"Where package.el and quelpa plugins (and their caches) are stored.")
(defvar doom-docs-dir (concat doom-emacs-dir "docs/")
"Where the Doom manual is stored.")
(defvar doom-private-dir
(or (getenv "DOOMDIR")
(let ((xdg-path
(expand-file-name "doom/"
(or (getenv "XDG_CONFIG_HOME")
"~/.config"))))
(if (file-directory-p xdg-path) xdg-path))
"~/.doom.d/")
"Where your private customizations are placed. Must end in a slash. Respects
XDG directory conventions if ~/.config/doom exists.")
(defvar doom-autoload-file (concat doom-local-dir "autoloads.el")
"Where `doom//reload-autoloads' will generate its autoloads file.")
"Where `doom-reload-doom-autoloads' will generate its core autoloads file.")
(defgroup doom nil
"DOOM Emacs, an Emacs configuration for a stubborn, shell-dwelling and
melodramatic ex-vimmer disappointed with the text-editor status quo."
:group 'emacs)
(defvar doom-package-autoload-file (concat doom-local-dir "autoloads.pkg.el")
"Where `doom-reload-package-autoloads' will generate its package.el autoloads
file.")
(defvar doom-env-file (concat doom-local-dir "env")
"The location of your env file, generated by `doom env refresh`.
This file contains environment variables scraped from your non and interactive
shell environment, and is loaded at startup (if it exist)s. This is helpful if
Emacs can't (easily) be launched from the correct shell session (particular for
MacOS users).")
;;;
;;
;;; Doom core variables
(defvar doom-init-p nil
"Non-nil if `doom-initialize' has run.")
(defvar doom-init-time nil
"The time it took, in seconds, for DOOM Emacs to initialize.")
(defvar doom-emacs-changed-p nil
"If non-nil, the running version of Emacs is different from the first time
Doom was setup, which can cause problems.")
(defvar doom-site-load-path (cons doom-core-dir load-path)
"The initial value of `load-path', before it was altered by
`doom-initialize'.")
(defvar doom-site-process-environment process-environment
"The initial value of `process-environment', before it was altered by
`doom-initialize'.")
(defvar doom-site-exec-path exec-path
"The initial value of `exec-path', before it was altered by
`doom-initialize'.")
(defvar doom-site-shell-file-name shell-file-name
"The initial value of `shell-file-name', before it was altered by
`doom-initialize'.")
(defvar doom--last-emacs-file (concat doom-local-dir "emacs-version.el"))
(defvar doom--last-emacs-version nil)
(defvar doom--refreshed-p nil)
(defvar doom--stage 'init)
;;
;;; Custom error types
(define-error 'doom-error "Error in Doom Emacs core")
(define-error 'doom-hook-error "Error in a Doom startup hook" 'doom-error)
(define-error 'doom-autoload-error "Error in an autoloads file" 'doom-error)
(define-error 'doom-module-error "Error in a Doom module" 'doom-error)
(define-error 'doom-private-error "Error in private config" 'doom-error)
(define-error 'doom-package-error "Error with packages" 'doom-error)
;;
;;; Custom hooks
(defvar doom-reload-hook nil
"A list of hooks to run when `doom/reload' is called.")
;;
;;; Emacs core configuration
;; UTF-8 as the default coding system
(when (fboundp 'set-charset-priority)
(set-charset-priority 'unicode)) ; pretty
(prefer-coding-system 'utf-8) ; pretty
(set-terminal-coding-system 'utf-8) ; pretty
(set-keyboard-coding-system 'utf-8) ; pretty
(set-selection-coding-system 'utf-8) ; perdy
(setq locale-coding-system 'utf-8) ; please
(setq-default buffer-file-coding-system 'utf-8) ; with sugar on top
(unless IS-WINDOWS
(setq selection-coding-system 'utf-8)) ; with sugar on top
(setq-default
ad-redefinition-action 'accept ; silence advised function warnings
apropos-do-all t ; make `apropos' more useful
compilation-always-kill t ; kill compilation process before starting another
compilation-ask-about-save nil ; save all buffers on `compile'
compilation-scroll-output t
confirm-nonexistent-file-or-buffer t
enable-recursive-minibuffers nil
debug-on-error (and (not noninteractive) doom-debug-mode)
auto-mode-case-fold nil
autoload-compute-prefixes nil
debug-on-error doom-debug-mode
jka-compr-verbose doom-debug-mode ; silence compression messages
ffap-machine-p-known 'reject ; don't ping things that look like domain names
find-file-visit-truename t ; resolve symlinks when opening files
idle-update-delay 2 ; update ui less often
load-prefer-newer (or noninteractive doom-debug-mode)
;; keep the point out of the minibuffer
minibuffer-prompt-properties '(read-only t point-entered minibuffer-avoid-prompt face minibuffer-prompt)
;; be quiet at startup; don't load or display anything unnecessary
inhibit-startup-message t
inhibit-startup-echo-area-message user-login-name
inhibit-default-init t
initial-major-mode 'fundamental-mode
initial-scratch-message nil
;; History & backup settings (save nothing, that's what git is for)
auto-save-default nil
create-lockfiles nil
history-length 500
make-backup-files nil
make-backup-files nil ; don't create backup~ files
;; byte compilation
byte-compile-verbose doom-debug-mode
byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local)
;; security
gnutls-verify-error (not (getenv "INSECURE")) ; you shouldn't use this
tls-checktrust gnutls-verify-error
tls-program (list "gnutls-cli --x509cafile %t -p %p %h"
;; compatibility fallbacks
"gnutls-cli -p %p %h"
"openssl s_client -connect %h:%p -no_ssl2 -no_ssl3 -ign_eof")
;; Don't store authinfo in plain text!
auth-sources (list (expand-file-name "authinfo.gpg" doom-etc-dir)
"~/.authinfo.gpg")
;; files
abbrev-file-name (concat doom-local-dir "abbrev.el")
async-byte-compile-log-file (concat doom-etc-dir "async-bytecomp.log")
auto-save-list-file-name (concat doom-cache-dir "autosave")
backup-directory-alist (list (cons "." (concat doom-cache-dir "backup/")))
desktop-dirname (concat doom-etc-dir "desktop")
desktop-base-file-name "autosave"
desktop-base-lock-name "autosave-lock"
pcache-directory (concat doom-cache-dir "pcache/")
mc/list-file (concat doom-etc-dir "mc-lists.el")
request-storage-directory (concat doom-cache-dir "request")
server-auth-dir (concat doom-cache-dir "server/")
shared-game-score-directory (concat doom-etc-dir "shared-game-score/")
tramp-auto-save-directory (concat doom-cache-dir "tramp-auto-save/")
tramp-backup-directory-alist backup-directory-alist
tramp-persistency-file-name (concat doom-cache-dir "tramp-persistency.el")
url-cache-directory (concat doom-cache-dir "url/")
url-configuration-directory (concat doom-etc-dir "url/"))
url-configuration-directory (concat doom-etc-dir "url/")
gamegrid-user-score-file-directory (concat doom-etc-dir "games/"))
;; move custom defs out of init.el
(setq custom-file (concat doom-etc-dir "custom.el"))
(load custom-file t t)
;; be quiet at startup; don't load or display anything unnecessary
(unless noninteractive
(advice-add #'display-startup-echo-area-message :override #'ignore)
(setq inhibit-startup-message t
inhibit-startup-echo-area-message user-login-name
inhibit-default-init t
initial-major-mode 'fundamental-mode
initial-scratch-message nil
mode-line-format nil))
;; Custom init hooks; clearer than `after-init-hook', `emacs-startup-hook', and
;; `window-setup-hook'.
(defvar doom-init-hook nil
"A list of hooks run when DOOM is initialized, before `doom-post-init-hook'.")
(defvar doom-post-init-hook nil
"A list of hooks run after DOOM initialization is complete, and after
`doom-init-hook'.")
(defun doom-try-run-hook (fn hook)
"Runs a hook wrapped in a `condition-case-unless-debug' block; its objective
is to include more information in the error message, without sacrificing your
ability to invoke the debugger in debug mode."
(condition-case-unless-debug ex
(if noninteractive
(quiet! (funcall fn))
(funcall fn))
('error
(lwarn hook :error
"%s in '%s' -> %s"
(car ex) fn (error-message-string ex))))
nil)
;;;
;; Initialize
(eval-and-compile
(defvar doom--file-name-handler-alist file-name-handler-alist)
(unless (or after-init-time noninteractive)
;; One of the contributors to long startup times is the garbage collector,
;; so we up its memory threshold, temporarily. It is reset later in
;; `doom|finalize'.
(setq gc-cons-threshold 402653184
gc-cons-percentage 0.6
file-name-handler-alist nil))
(require 'cl-lib)
(load (concat doom-core-dir "core-packages") nil t)
(setq load-path (eval-when-compile (doom-initialize t)
(doom-initialize-load-path t))
doom--package-load-path (eval-when-compile doom--package-load-path))
(load! core-lib)
(load! core-os) ; consistent behavior across OSes
(condition-case-unless-debug ex
(require 'autoloads doom-autoload-file t)
('error
(lwarn 'doom-autoloads :warning
"%s in autoloads.el -> %s"
(car ex) (error-message-string ex))))
(unless noninteractive
(load! core-ui) ; draw me like one of your French editors
(load! core-popups) ; taming sudden yet inevitable windows
(load! core-editor) ; baseline configuration for text editing
(load! core-projects) ; making Emacs project-aware
(load! core-keybinds)) ; centralized keybind system + which-key
(defun doom|finalize ()
"Run `doom-init-hook', `doom-post-init-hook' and reset `gc-cons-threshold',
`gc-cons-percentage' and `file-name-handler-alist'."
(unless (or (not after-init-time) noninteractive)
(dolist (hook '(doom-init-hook doom-post-init-hook))
(run-hook-wrapped hook #'doom-try-run-hook hook)))
;; If you forget to reset this, you'll get stuttering and random freezes!
(setq gc-cons-threshold 16777216
gc-cons-percentage 0.1
file-name-handler-alist doom--file-name-handler-alist)
t)
(add-hook! '(emacs-startup-hook doom-reload-hook)
#'doom|finalize))
;;
;; Emacs fixes/hacks
;;
;; Automatic minor modes
(defvar doom-auto-minor-mode-alist '()
"Alist mapping filename patterns to corresponding minor mode functions, like
`auto-mode-alist'. All elements of this alist are checked, meaning you can
@ -214,7 +217,7 @@ enable multiple minor modes for the same regexp.")
(setq name (file-name-sans-versions name))
;; Remove remote file name identification.
(when (and (stringp remote-id)
(string-match-p (regexp-quote remote-id) name))
(string-match (regexp-quote remote-id) name))
(setq name (substring name (match-end 0))))
(while (and alist (caar alist) (cdar alist))
(if (string-match-p (caar alist) name)
@ -222,24 +225,291 @@ enable multiple minor modes for the same regexp.")
(setq alist (cdr alist))))))
(add-hook 'find-file-hook #'doom|enable-minor-mode-maybe)
(defun doom*set-indirect-buffer-filename (orig-fn base-buffer name &optional clone)
"In indirect buffers, `buffer-file-name' is nil, which can cause problems
with functions that require it (like modeline segments)."
(let ((file-name (buffer-file-name base-buffer))
(buffer (funcall orig-fn base-buffer name clone)))
(when (and file-name buffer)
(with-current-buffer buffer
(unless buffer-file-name
(setq buffer-file-name file-name
buffer-file-truename (file-truename file-name)))))
buffer))
(advice-add #'make-indirect-buffer :around #'doom*set-indirect-buffer-filename)
(defun doom*symbol-file (orig-fn symbol &optional type)
"If a `doom-file' symbol property exists on SYMBOL, use that instead of the
original value of `symbol-file'."
(or (if (symbolp symbol) (get symbol 'doom-file))
(funcall orig-fn symbol type)))
(advice-add #'symbol-file :around #'doom*symbol-file)
(defun doom*no-authinfo-for-tramp (orig-fn &rest args)
"Don't look into .authinfo for local sudo TRAMP buffers."
(let ((auth-sources (if (equal tramp-current-method "sudo") nil auth-sources)))
(apply orig-fn args)))
(advice-add #'tramp-read-passwd :around #'doom*no-authinfo-for-tramp)
;; To speed up minibuffer commands (like helm and ivy), defer garbage collection
;; when the minibuffer is active. It may mean a pause when finished, but that's
;; acceptable instead of pauses during.
(defun doom|defer-garbage-collection ()
(setq gc-cons-threshold doom-gc-cons-upper-limit))
(defun doom|restore-garbage-collection ()
(setq gc-cons-threshold doom-gc-cons-threshold))
(add-hook 'minibuffer-setup-hook #'doom|defer-garbage-collection)
(add-hook 'minibuffer-exit-hook #'doom|restore-garbage-collection)
;; File+dir local variables are initialized after the major mode and its hooks
;; have run. If you want hook functions to be aware of these customizations, add
;; them to MODE-local-vars-hook instead.
(defun doom|run-local-var-hooks ()
"Run MODE-local-vars-hook after local variables are initialized."
(run-hook-wrapped (intern-soft (format "%s-local-vars-hook" major-mode))
#'doom-try-run-hook))
(add-hook 'hack-local-variables-hook #'doom|run-local-var-hooks)
(defun doom|run-local-var-hooks-if-necessary ()
"If `enable-local-variables' is disabled, then `hack-local-variables-hook' is
never triggered."
(unless enable-local-variables
(doom|run-local-var-hooks)))
(add-hook 'after-change-major-mode-hook #'doom|run-local-var-hooks-if-necessary)
;;
;;; Incremental lazy-loading
(defvar doom-incremental-packages '(t)
"A list of packages to load incrementally after startup. Any large packages
here may cause noticable pauses, so it's recommended you break them up into
sub-packages. For example, `org' is comprised of many packages, and can be broken up into:
(doom-load-packages-incrementally
'(calendar find-func format-spec org-macs org-compat
org-faces org-entities org-list org-pcomplete org-src
org-footnote org-macro ob org org-clock org-agenda
org-capture))
This is already done by the lang/org module, however.
If you want to disable incremental loading altogether, either remove
`doom|load-packages-incrementally' from `emacs-startup-hook' or set
`doom-incremental-first-idle-timer' to nil.")
(defvar doom-incremental-first-idle-timer 2
"How long (in idle seconds) until incremental loading starts.
Set this to nil to disable incremental loading.")
(defvar doom-incremental-idle-timer 1.5
"How long (in idle seconds) in between incrementally loading packages.")
(defun doom-load-packages-incrementally (packages &optional now)
"Registers PACKAGES to be loaded incrementally.
If NOW is non-nil, load PACKAGES incrementally, in `doom-incremental-idle-timer'
intervals."
(if (not now)
(nconc doom-incremental-packages packages)
(when packages
(let ((gc-cons-threshold doom-gc-cons-upper-limit)
file-name-handler-alist)
(let* ((reqs (cl-delete-if #'featurep packages))
(req (ignore-errors (pop reqs))))
(when req
(doom-log "Incrementally loading %s" req)
(condition-case e
(require req nil t)
((error debug)
(message "Failed to load '%s' package incrementally, because: %s"
req e)))
(if reqs
(run-with-idle-timer doom-incremental-idle-timer
nil #'doom-load-packages-incrementally
reqs t)
(doom-log "Finished incremental loading"))))))))
(defun doom|load-packages-incrementally ()
"Begin incrementally loading packages in `doom-incremental-packages'.
If this is a daemon session, load them all immediately instead."
(if (daemonp)
(mapc #'require (cdr doom-incremental-packages))
(when (integerp doom-incremental-first-idle-timer)
(run-with-idle-timer doom-incremental-first-idle-timer
nil #'doom-load-packages-incrementally
(cdr doom-incremental-packages) t))))
(add-hook 'window-setup-hook #'doom|load-packages-incrementally)
;;
;;; Bootstrap helpers
(defun doom-try-run-hook (hook)
"Run HOOK (a hook function), but handle errors better, to make debugging
issues easier.
Meant to be used with `run-hook-wrapped'."
(doom-log "Running doom hook: %s" hook)
(condition-case e
(funcall hook)
((debug error)
(signal 'doom-hook-error (list hook e))))
;; return nil so `run-hook-wrapped' won't short circuit
nil)
(defun doom-ensure-same-emacs-version-p ()
"Check if the running version of Emacs has changed and set
`doom-emacs-changed-p' if it has."
(if (load doom--last-emacs-file 'noerror 'nomessage 'nosuffix)
(setq doom-emacs-changed-p
(not (equal emacs-version doom--last-emacs-version)))
(with-temp-file doom--last-emacs-file
(princ `(setq doom--last-emacs-version ,(prin1-to-string emacs-version))
(current-buffer))))
(cond ((not doom-emacs-changed-p))
((y-or-n-p
(format
(concat "Your version of Emacs has changed from %s to %s, which may cause incompatibility\n"
"issues. If you run into errors, run `bin/doom compile :plugins` or reinstall your\n"
"plugins to resolve them.\n\n"
"Continue?")
doom--last-emacs-version
emacs-version))
(delete-file doom--last-emacs-file))
(noninteractive (error "Aborting"))
((kill-emacs))))
(defun doom-ensure-core-directories-exist ()
"Make sure all Doom's essential local directories (in and including
`doom-local-dir') exist."
(dolist (dir (list doom-local-dir doom-etc-dir doom-cache-dir doom-packages-dir))
(unless (file-directory-p dir)
(make-directory dir t))))
(defun doom|display-benchmark (&optional return-p)
"Display a benchmark, showing number of packages and modules, and how quickly
they were loaded at startup.
If RETURN-P, return the message as a string instead of displaying it."
(funcall (if return-p #'format #'message)
"Doom loaded %s packages across %d modules in %.03fs"
(length package-activated-list)
(if doom-modules (hash-table-count doom-modules) 0)
(or doom-init-time
(setq doom-init-time (float-time (time-subtract (current-time) before-init-time))))))
(defun doom|run-all-startup-hooks ()
"Run all startup Emacs hooks. Meant to be executed after starting Emacs with
-q or -Q, for example:
emacs -Q -l init.el -f doom|run-all-startup-hooks"
(run-hook-wrapped 'after-init-hook #'doom-try-run-hook)
(setq after-init-time (current-time))
(dolist (hook (list 'delayed-warnings-hook
'emacs-startup-hook 'term-setup-hook
'window-setup-hook))
(run-hook-wrapped hook #'doom-try-run-hook)))
(defun doom-initialize-autoloads (file)
"Tries to load FILE (an autoloads file). Return t on success, throws an error
in interactive sessions, nil otherwise (but logs a warning)."
(condition-case e
(load (file-name-sans-extension file) 'noerror 'nomessage)
((debug error)
(if noninteractive
(message "Autoload file warning: %s -> %s" (car e) (error-message-string e))
(signal 'doom-autoload-error (list (file-name-nondirectory file) e))))))
(defun doom-initialize (&optional force-p)
"Bootstrap Doom, if it hasn't already (or if FORCE-P is non-nil).
The bootstrap process involves making sure 1) the essential directories exist,
2) the core packages are installed, 3) `doom-autoload-file' and
`doom-package-autoload-file' exist and have been loaded, and 4) Doom's core
files are loaded.
If the cache exists, much of this function isn't run, which substantially
reduces startup time.
The overall load order of Doom is as follows:
~/.emacs.d/init.el
~/.emacs.d/core/core.el
~/.doom.d/init.el
Module init.el files
`doom-before-init-modules-hook'
Module config.el files
~/.doom.d/config.el
`doom-init-modules-hook'
`after-init-hook'
`emacs-startup-hook'
`doom-init-ui-hook'
`window-setup-hook'
Module load order is determined by your `doom!' block. See `doom-modules-dirs'
for a list of all recognized module trees. Order defines precedence (from most
to least)."
(when (or force-p (not doom-init-p))
(setq doom-init-p t) ; Prevent infinite recursion
;; Reset as much state as possible
(setq exec-path doom-site-exec-path
load-path doom-site-load-path
process-environment doom-site-process-environment
shell-file-name doom-site-shell-file-name)
;; `doom-autoload-file' tells Emacs where to load all its autoloaded
;; functions from. This includes everything in core/autoload/*.el and all
;; the autoload files in your enabled modules.
(when (or force-p (not (doom-initialize-autoloads doom-autoload-file)))
(doom-ensure-core-directories-exist)
(doom-ensure-same-emacs-version-p)
(require 'core-packages)
(doom-ensure-packages-initialized force-p)
(doom-ensure-core-packages)
(unless (or force-p noninteractive)
(user-error "Your doom autoloads are missing! Run `bin/doom refresh' to regenerate them")))
;; Loads `doom-package-autoload-file', which loads a concatenated package
;; autoloads file and caches `load-path', `auto-mode-alist',
;; `Info-directory-list', `doom-disabled-packages' and
;; `package-activated-list'. A big reduction in startup time.
(let (command-switch-alist)
(unless (or force-p
(doom-initialize-autoloads doom-package-autoload-file)
noninteractive)
(user-error "Your package autoloads are missing! Run `bin/doom refresh' to regenerate them")))
;; Load shell environment
(when (and (not noninteractive)
(file-readable-p doom-env-file)
(require 'load-env-vars nil t))
(load-env-vars doom-env-file)
(setq exec-path (append (split-string (getenv "PATH") ":")
(list exec-directory))
shell-file-name (or (getenv "SHELL")
shell-file-name))))
(require 'core-lib)
(require 'core-modules)
(require 'core-os)
(if noninteractive
(require 'core-cli)
(add-hook 'window-setup-hook #'doom|display-benchmark)
(require 'core-keybinds)
(require 'core-ui)
(require 'core-projects)
(require 'core-editor)))
;;
;;; Bootstrap Doom
(eval-and-compile
(require 'subr-x)
(require 'cl-lib)
(unless EMACS26+
(with-no-warnings
;; if-let and when-let were moved to (if|when)-let* in Emacs 26+ so we
;; alias them for 25 users.
(defalias 'if-let* #'if-let)
(defalias 'when-let* #'when-let))))
(add-to-list 'load-path doom-core-dir)
(doom-initialize noninteractive)
(unless noninteractive
(doom-initialize-modules))
(after! package
(require 'core-packages)
(doom-initialize-packages))
(provide 'core)
;;; core.el ends here

37
core/doctor.el Normal file
View file

@ -0,0 +1,37 @@
;;; core/doctor.el -*- lexical-binding: t; -*-
(defun file-size (file &optional dir)
(setq file (expand-file-name file dir))
(when (file-exists-p file)
(/ (nth 7 (file-attributes file))
1024.0)))
;; Check for oversized problem files in cache that may cause unusual/tremendous
;; delays or freezing. This shouldn't happen often.
(dolist (file (list "savehist"
"projectile.cache"))
(let* ((path (expand-file-name file doom-cache-dir))
(size (file-size path)))
(when (and (numberp size) (> size 2000))
(warn! "%s is too large (%.02fmb). This may cause freezes or odd startup delays"
(file-relative-name path doom-core-dir)
(/ size 1024))
(explain! "Consider deleting it from your system (manually)"))))
(when! (not (executable-find doom-projectile-fd-binary))
(warn! "Couldn't find the `fd' binary; project file searches will be slightly slower"))
(let ((default-directory "~"))
(require 'projectile)
(when! (cl-find-if #'projectile-file-exists-p projectile-project-root-files-bottom-up)
(warn! "Your $HOME is recognized as a project root")
(explain! "Doom will disable bottom-up root search, which may reduce the accuracy of project\n"
"detection.")))
;; There should only be one
(when! (and (file-equal-p doom-private-dir "~/.config/doom")
(file-directory-p "~/.doom.d"))
(warn! "Both %S and '~/.doom.d' exist on your system"
(abbreviate-file-name doom-private-dir))
(explain! "Doom will only load one of these (~/.config/doom takes precedence). Since\n"
"it is rarely intentional that you have both, ~/.doom.d should be removed."))

View file

@ -1,44 +1,51 @@
;; -*- no-byte-compile: t; -*-
;;; core/packages.el
;; core.el
(package! dotenv-mode)
;; core-os.el
;; In case this config is shared across multiple computers (like mine is), let's
;; protect these from autoremoval.
(package! exec-path-from-shell :ignore (not IS-MAC))
(package! osx-clipboard :ignore (not IS-MAC))
(if (not IS-MAC)
(package! xclip)
(package! osx-clipboard)
(package! ns-auto-titlebar))
;; core-ui.el
(package! all-the-icons)
(package! fringe-helper)
(package! highlight-indentation)
(package! hide-mode-line)
(package! highlight-numbers)
(unless (boundp 'display-line-numbers)
(package! highlight-escape-sequences
:recipe (:fetcher github :repo "hlissner/highlight-escape-sequences"))
(unless (locate-library "display-line-numbers")
(package! nlinum)
(package! nlinum-hl)
(package! nlinum-relative))
(package! rainbow-delimiters)
(package! visual-fill-column)
;; core-popups.el
(package! shackle)
(package! restart-emacs)
;; core-editor.el
(package! ace-link)
(package! ace-window)
(package! avy)
(package! command-log-mode)
(package! editorconfig)
(package! expand-region)
(package! help-fns+)
(package! dtrt-indent)
(package! helpful)
(package! pcre2el)
(package! smart-forward)
(package! smartparens)
(package! undo-tree)
(package! wgrep)
(package! ws-butler)
;; core-projects.el
(package! projectile)
;; core-keybinds.el
(package! general)
(package! which-key)
(package! hydra)
;; autoload/debug.el
(package! esup)
;; autoload/test.el
(package! buttercup)

29
core/templates/BUG_REPORT Normal file
View file

@ -0,0 +1,29 @@
Please read through the following before you submit your issue.
+ [ ] Running `make` (then restarting Emacs) did not fix my issue
+ [ ] If I have byte-compiled, I've tried recompiling with `make compile`
+ [ ] If I changed the version of Emacs installed, I've recompiled by plugins
with `make compile-elpa`
+ [ ] I ran `make doctor` and it produced no leads
+ [ ] My issue cannot be found [on the wiki](/docs/troubleshoot.org)
+ [ ] I filled out the four fields in the template below
-------------------------------------------------------------------
### Observed behavior
<!-- What happened -->
### Expected behavior
<!-- What *should* have happened -->
### Steps to reproduce
<!-- Tell us how to reproduce the issue in steps -->
### Extra details
<!-- Include backtraces & screenshots if possible -->
-------------------------------------------------------------------

View file

@ -0,0 +1,30 @@
Before you doom yourself, there are a few things you should know:
1. If you use GUI Emacs, run `M-x all-the-icons-install-fonts` so you don't get
weird symbols all over the place.
2. Whenever you edit ~/.doom.d/init.el or modify modules, run:
bin/doom refresh
This will ensure all needed packages are installed, all orphaned packages are
removed, and your autoloads files are up to date. This is important! If you
forget to do this you will get errors!
3. If something inexplicably goes wrong, it's a good idea to try:
bin/doom doctor
This will diagnose common issues with your environment and setup, and may
give you clues about what is wrong.
4. To update doom, run
bin/doom upgrade
Doing it any other way will require you run `bin/doom refresh` otherwise,
5. Check out `bin/doom help` to see what else it can do (it is recommended you
add ~/.emacs.d/bin to your PATH).
Have fun!

View file

@ -0,0 +1,12 @@
;; Welcome to the vanilla sandbox!
;;
;; This is a test bed for running Emacs Lisp in an instance of Emacs with varying
;; amounts of Doom loaded:
;;
;; a) vanilla Emacs (nothing loaded) \\[doom--run-vanilla-emacs]
;; b) vanilla Doom (only Doom core) \\[doom--run-vanilla-doom]
;; c) Doom + modules - your private config \\[doom--run-vanilla-doom+]
;; d) Doom (normal) \\[doom--run-full-doom]
;;
;; This is done without sacrificing access to installed packages. Use the sandbox
;; to reproduce bugs and determine if Doom is to blame.

View file

@ -1,135 +0,0 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/autoload-buffers.el
(defmacro with-temp-buffers!! (buffer-args &rest body)
(declare (indent defun))
(let (buffers)
(dolist (bsym buffer-args)
(push `(,bsym (get-buffer-create ,(symbol-name bsym)))
buffers))
`(cl-flet ((buffer-list
(lambda ()
(cl-remove-if-not #'buffer-live-p (list ,@(reverse (mapcar #'car buffers)))))))
(let* (persp-mode
,@buffers)
,@body
(mapc #'kill-buffer (buffer-list))))))
;;
(def-test! get-buffers
(with-temp-buffers!! (a b c)
(should (cl-every #'buffer-live-p (buffer-list)))
(should (equal (buffer-list) (list a b c)))
(dolist (buf (list (cons a doom-emacs-dir)
(cons b doom-emacs-dir)
(cons c "/tmp/")))
(with-current-buffer (car buf)
(setq-local default-directory (cdr buf))))
(projectile-mode +1)
(with-current-buffer a
;; should produce all buffers
(let ((buffers (doom-buffer-list)))
(should (cl-every (lambda (x) (memq x buffers)) (list a b c))))
;; should produce only project buffers
(let ((buffers (doom-project-buffer-list)))
(should (cl-every (lambda (x) (memq x buffers)) (list a b)))
(should-not (memq c buffers))))
;; If no project is available, just get all buffers
(with-current-buffer c
(let ((buffers (doom-project-buffer-list)))
(should (cl-every (lambda (x) (memq x buffers)) (list a b c)))))
(projectile-mode -1)))
(def-test! real-buffers
(let (doom-real-buffer-functions)
(with-temp-buffers!! (a b c d)
(dolist (buf (list a b))
(with-current-buffer buf
(setq-local buffer-file-name "x")))
(with-current-buffer c
(rename-buffer "*C*"))
(with-current-buffer d
(doom-popup-mode +1))
(should (doom-real-buffer-p a))
(should (doom-real-buffer-p b))
(should-not (doom-real-buffer-p c))
(should-not (doom-real-buffer-p d))
(let ((buffers (doom-real-buffer-list)))
(should (= (length buffers) 2))
(should (cl-every (lambda (x) (memq x buffers)) (list a b)))
(should (cl-notany (lambda (x) (memq x buffers)) (list c d)))))))
;; `doom-visible-windows'
;; `doom-visible-buffers'
;; `doom-buried-buffers'
(def-test! visible-buffers-and-windows
(with-temp-buffers!! (a b c d)
(switch-to-buffer a)
(should (eq (current-buffer) a))
(should (eq (selected-window) (get-buffer-window a)))
(split-window nil 1)
(switch-to-buffer b)
(should (eq (current-buffer) b))
(should (eq (selected-window) (get-buffer-window b)))
(should (cl-intersection (list a b) (doom-visible-buffers)))
(should (cl-intersection (list c d) (doom-buried-buffers)))
(should (cl-intersection (mapcar #'get-buffer-window (list a b))
(doom-visible-windows)))))
;; `doom-matching-buffers'
(def-test! matching-buffers
(with-temp-buffers!! (a b c)
(let ((buffers (doom-matching-buffers "^[ac]$")))
(should (= 2 (length buffers)))
(should (cl-every #'bufferp buffers))
(should (cl-every (lambda (x) (memq x buffers)) (list a c)))
(should (equal buffers (doom-matching-buffers "^[ac]$"))))))
;; `doom-buffers-in-mode'
(def-test! buffers-in-mode
(with-temp-buffers!! (a b c d e)
(dolist (buf (list a b))
(with-current-buffer buf
(emacs-lisp-mode)))
(dolist (buf (list c d e))
(with-current-buffer buf
(text-mode)))
(let ((el-buffers (doom-buffers-in-mode 'emacs-lisp-mode))
(txt-buffers (doom-buffers-in-mode 'text-mode)))
(should (cl-every #'buffer-live-p (append el-buffers txt-buffers)))
(should (= 2 (length el-buffers)))
(should (= 3 (length txt-buffers))))))
;; `doom-kill-buffer'
(def-test! kill-buffer
(with-temp-buffers!! (a b)
(doom-kill-buffer a)
(should-not (buffer-live-p a))
;; modified buffer
(with-current-buffer b
(set-buffer-modified-p t))
(doom-kill-buffer b t)
(should-not (buffer-live-p a))))
;; `doom--cycle-real-buffers'
(def-test! kill-buffer-then-show-real-buffer
(with-temp-buffers!! (a b c d)
(dolist (buf (list a b d))
(with-current-buffer buf
(setq-local buffer-file-name "x")))
(should (cl-every #'buffer-live-p (buffer-list)))
(switch-to-buffer a)
(should (eq (current-buffer) a))
(should (eq (selected-window) (get-buffer-window a)))
(should (doom-kill-buffer a))
;; eventually end up in the fallback buffer
(let ((fallback (doom-fallback-buffer)))
(while (not (eq (current-buffer) fallback))
(should (doom-real-buffer-p))
(doom-kill-buffer))
(should (eq (current-buffer) fallback)))))
;; TODO doom/kill-all-buffers
;; TODO doom/kill-other-buffers
;; TODO doom/kill-matching-buffers
;; TODO doom/cleanup-session

View file

@ -1,17 +0,0 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/autoload-debug.el
(def-test! what-face
(insert (propertize "Hello " 'face 'font-lock-keyword-face))
(insert "world")
(should (equal (doom/what-face (point-min)) '((font-lock-keyword-face) ())))
(should-not (doom/what-face (point-max))))
(def-test! what-face-overlays
(insert "Hello world")
(let ((ov (make-overlay 1 6)))
(overlay-put ov 'face 'font-lock-keyword-face))
(should (equal (doom/what-face (point-min)) '(() (font-lock-keyword-face))))
(should-not (doom/what-face (point-max))))

View file

@ -1,41 +0,0 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/autoload-message.el
;; ansi messages
(def-test! ansi-format
(let ((noninteractive t))
(should (equal (format! "Hello %s" "World")
"Hello World"))
(should (equal (format! (red "Hello %s" "World"))
"Hello World"))
(should (equal (format! (green "Hello %s" "World"))
(format "\e[%dm%s\e[0m"
(cdr (assq 'green doom-message-fg))
"Hello World")))
(should (equal (format! (on-red "Hello %s" "World"))
(format "\e[%dm%s\e[0m"
(cdr (assq 'on-red doom-message-bg))
"Hello World")))
(should (equal (format! (bold "Hello %s" "World"))
(format "\e[%dm%s\e[0m"
(cdr (assq 'bold doom-message-fx))
"Hello World")))))
(def-test! ansi-format-nested
(let ((noninteractive t))
(should (equal (format! (bold (red "Hello %s" "World")))
(format "\e[%dm%s\e[0m" 1
(format "\e[%dm%s\e[0m" 31 "Hello World"))))
(should (equal (format! (on-red (bold "Hello %s" "World")))
(format "\e[%dm%s\e[0m" 41
(format "\e[%dm%s\e[0m" 1 "Hello World"))))
(should (equal (format! (dark (white "Hello %s" "World")))
(format "\e[%dm%s\e[0m" 2
(format "\e[%dm%s\e[0m" 37 "Hello World"))))))
(def-test! ansi-format-apply
(let ((noninteractive t))
(should (equal (format! (color 'red "Hello %s" "World"))
(format! (red "Hello %s" "World"))))
(should (equal (format! (color (if nil 'red 'blue) "Hello %s" "World"))
(format! (blue "Hello %s" "World"))))))

View file

@ -1,70 +0,0 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/autoload-package.el
(defun -pkg (name version &optional reqs)
(package-desc-create :name name :version version :reqs reqs))
(defmacro with-packages!! (packages package-descs &rest body)
`(let* ((doom-packages-dir ,(expand-file-name "packages/" (file-name-directory load-file-name)))
(package-user-dir ,(expand-file-name "elpa" doom-packages-dir))
(quelpa-dir ,(expand-file-name "quelpa" doom-packages-dir)))
;; (make-directory doom-packages-dir t)
(let ((doom-packages ,packages)
(package-alist ,package-descs)
doom-core-packages)
(cl-letf (((symbol-function 'doom-initialize-packages) (lambda (&rest _))))
,@body))
;; (delete-directory doom-packages-dir t)
))
;;
;; Tests
;;
(def-test! backend-detection
(let ((package-alist `((doom-dummy ,(-pkg 'doom-dummy '(20160405 1234)))))
(quelpa-cache '((doom-quelpa-dummy :fetcher github :repo "hlissner/does-not-exist")))
(quelpa-initialized-p t))
(should (eq (doom-package-backend 'doom-dummy) 'elpa))
(should (eq (doom-package-backend 'doom-quelpa-dummy) 'quelpa))
(should (eq (doom-package-backend 'org) 'emacs))))
(def-test! elpa-outdated-detection
(let* ((doom--last-refresh (current-time))
(package-alist
`((doom-dummy ,(-pkg 'doom-dummy '(20160405 1234)))))
(package-archive-contents
`((doom-dummy ,(-pkg 'doom-dummy '(20170405 1234))))))
(cl-letf (((symbol-function 'package-refresh-contents) (lambda (&rest _))))
(should (equal (doom-package-outdated-p 'doom-dummy)
'(doom-dummy (20160405 1234) (20170405 1234)))))))
;; TODO quelpa-outdated-detection
(def-test! get-packages
(let ((quelpa-initialized-p t))
(with-packages!!
'((doom-dummy))
'((doom-dummy nil)
(doom-dummy-unwanted nil)
(doom-dummy-dep nil))
(should (equal (doom-get-packages) '((doom-dummy)))))))
(def-test! orphaned-packages
"Test `doom-get-orphaned-packages', which gets a list of packages that are
no longer enabled or depended on."
(with-packages!!
'((doom-dummy))
`((doom-dummy ,(-pkg 'doom-dummy '(20160405 1234) '((doom-dummy-dep (1 0)))))
(doom-dummy-unwanted ,(-pkg 'doom-dummy-unwanted '(20160601 1234)))
(doom-dummy-dep ,(-pkg 'doom-dummy-dep '(20160301 1234))))
(should (equal (doom-get-orphaned-packages) '(doom-dummy-unwanted)))))
(def-test! missing-packages
"Test `doom-get-missing-packages, which gets a list of enabled packages that
aren't installed."
(with-packages!!
'((doom-dummy) (doom-dummy-installed))
`((doom-dummy-installed ,(-pkg 'doom-dummy-installed '(20160405 1234))))
(should (equal (doom-get-missing-packages) '((doom-dummy))))))

View file

@ -1,159 +0,0 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/core-lib.el
;; --- Helpers ----------------------------
;; `doom--resolve-path-forms'
(def-test! resolve-path-forms
(should
(equal (doom--resolve-path-forms '(and "fileA" "fileB"))
'(and (file-exists-p (expand-file-name "fileA" (doom-project-root)))
(file-exists-p (expand-file-name "fileB" (doom-project-root)))))))
;; `doom--resolve-hook-forms'
(def-test! resolve-hook-forms
(should (equal (doom--resolve-hook-forms '(js2-mode haskell-mode))
'(js2-mode-hook haskell-mode-hook)))
(should (equal (doom--resolve-hook-forms '(quote (js2-mode-hook haskell-mode-hook)))
'(js2-mode-hook haskell-mode-hook))))
;; `doom-unquote'
(def-test! unquote
(should (equal (doom-unquote '(quote (a b c))) '(a b c)))
;; nested
(should (equal (doom-unquote '(quote (quote (a b c)))) '(a b c)))
;; sub-quote
(should (equal (doom-unquote '(quote (a (quote b) c))) '(a (quote b) c)))
;; function
(should (equal (doom-unquote '(function a)) 'a)))
;; `doom-enlist'
(def-test! enlist
(should (equal (doom-enlist 'a) '(a)))
(should (equal (doom-enlist '(a)) '(a))))
;; `doom-resolve-vim-path'
(def-test! resolve-vim-path
(cl-flet ((do-it #'doom-resolve-vim-path))
;; file modifiers
(let ((buffer-file-name "~/.emacs.d/test/modules/feature/test-evil.el")
(default-directory "~/.emacs.d/test/modules/"))
(should (equal (do-it "%") "feature/test-evil.el"))
(should (equal (do-it "%:r") "feature/test-evil"))
(should (equal (do-it "%:r.elc") "feature/test-evil.elc"))
(should (equal (do-it "%:e") "el"))
(should (equal (do-it "%:p") (expand-file-name buffer-file-name)))
(should (equal (do-it "%:h") "feature"))
(should (equal (do-it "%:t") "test-evil.el"))
(should (equal (do-it "%:.") "feature/test-evil.el"))
(should (equal (do-it "%:~") "~/.emacs.d/test/modules/feature/test-evil.el"))
(should (equal (file-truename (do-it "%:p"))
(file-truename buffer-file-name))))
;; nested file modifiers
(let ((buffer-file-name "~/vim/src/version.c")
(default-directory "~/vim/"))
(should (equal (do-it "%:p") (expand-file-name "~/vim/src/version.c")))
(should (equal (do-it "%:p:.") "src/version.c"))
(should (equal (do-it "%:p:~") "~/vim/src/version.c"))
(should (equal (do-it "%:h") "src"))
(should (equal (do-it "%:p:h") (expand-file-name "~/vim/src")))
(should (equal (do-it "%:p:h:h") (expand-file-name "~/vim")))
(should (equal (do-it "%:t") "version.c"))
(should (equal (do-it "%:p:t") "version.c"))
(should (equal (do-it "%:r") "src/version"))
(should (equal (do-it "%:p:r") (expand-file-name "~/vim/src/version")))
(should (equal (do-it "%:t:r") "version")))
;; empty file modifiers
(let (buffer-file-name default-directory)
(should (equal (do-it "%") ""))
(should (equal (do-it "%:r") ""))
(should (equal (do-it "%:e") ""))
(should (equal (do-it "%:h") ""))
(should (equal (do-it "%:t") ""))
(should (equal (do-it "%:.") ""))
(should (equal (do-it "%:~") ""))
(should (equal (do-it "%:P") "")))))
;; --- Macros -----------------------------
;; `add-hook!'
(def-test! add-one-to-one-hook
(let (hooks)
(add-hook! 'hooks 'a-hook)
(should (equal hooks '(a-hook)))))
(def-test! add-many-to-one-hook
(let (hooks)
(add-hook! 'hooks '(hook-a hook-b hook-c))
(should (equal hooks '(hook-c hook-b hook-a)))))
(def-test! add-one-to-many-hooks
(let (hooks-a hooks-b hooks-c)
(add-hook! '(hooks-a hooks-b hooks-c) 'a-hook)
(should (equal hooks-a '(a-hook)))
(should (equal hooks-b '(a-hook)))
(should (equal hooks-c '(a-hook)))))
(def-test! add-many-to-many-hooks
(let (hooks-a hooks-b hooks-c)
(add-hook! '(hooks-a hooks-b hooks-c) '(hook-a hook-b hook-c))
(should (equal hooks-a '(hook-c hook-b hook-a)))
(should (equal hooks-b '(hook-c hook-b hook-a)))
(should (equal hooks-c '(hook-c hook-b hook-a)))))
(def-test! add-non-literal-hooks
(let (some-mode-hook)
(add-hook! some-mode 'a-hook)
(should (equal some-mode-hook '(a-hook)))))
;; `remove-hook!'
(def-test! remove-hooks
(let ((hooks-a '(hook-c hook-b hook-a))
(hooks-b '(hook-c hook-b hook-a))
(hooks-c '(hook-c hook-b hook-a)))
(remove-hook! '(hooks-a hooks-b hooks-c) '(hook-a hook-b hook-c))
(should (null hooks-a))
(should (null hooks-b))
(should (null hooks-c))))
(def-test! remove-hook-forms
(let (hooks)
(add-hook! 'hooks (message "Hello world"))
(should hooks)
(remove-hook! 'hooks (message "Hello world"))
(should (null hooks))))
;; `add-transient-hook!'
(def-test! transient-hooks
(let (hooks value)
(add-transient-hook! 'hooks (setq value t))
(run-hooks 'hooks)
(should (eq value t))
(should (null hooks))))
(def-test! transient-function
(let (value)
(add-transient-hook! #'ignore (setq value (not value)))
(ignore t)
(should (eq value t))
;; repeat to ensure it was only run once
(ignore t)
(should (eq value t))))
;; TODO `associate!'
;; --- Settings ---------------------------
(def-test! set
(eval-and-compile
(let (doom-settings)
(def-setting! :-test-setting (x) `(setq result ,x))
(should (assq :-test-setting doom-settings))
(let ((inhibit-message t)
result)
(set! :-test-setting t)
(should result)
(set! :non-existant-setting (error "This shouldn't trigger"))))))

View file

@ -1,43 +0,0 @@
;; -*- no-byte-compile: t; -*-
;;; ../core/test/core-projects.el
(require 'projectile)
;;
;; `doom-project-p'
(def-test! project-p
:minor-mode projectile-mode
(let ((default-directory doom-emacs-dir))
(should (doom-project-p)))
(let ((default-directory (expand-file-name "~")))
(should-not (doom-project-p))))
;; `doom-project-p'
(def-test! project-root
:minor-mode projectile-mode
;; Should resolve to project root
(let ((default-directory doom-core-dir))
(should (equal (doom-project-root) doom-emacs-dir)))
;; Should resolve to `default-directory' if not a project
(let ((default-directory (expand-file-name "~")))
(should (equal (doom-project-root) default-directory))))
;; `doom-project-expand'
(def-test! project-expand
:minor-mode projectile-mode
(let ((default-directory doom-core-dir))
(should (equal (doom-project-expand "init.el")
(expand-file-name "init.el" (doom-project-root))))))
;; `doom-project-has!'
(def-test! project-has!
:minor-mode projectile-mode
(let ((default-directory doom-core-dir))
;; Resolve from project root
(should (doom-project-has! "init.el"))
;; Chained file checks
(should (doom-project-has! (and "init.el" "LICENSE")))
(should (doom-project-has! (or "init.el" "does-not-exist")))
(should (doom-project-has! (and "init.el" (or "LICENSE" "does-not-exist"))))
;; Should resolve relative paths from `default-directory'
(should (doom-project-has! (and "./core.el" "../init.el")))))

View file

@ -1,45 +0,0 @@
;; -*- no-byte-compile: t; -*-
;;; ../core/test/core-ui.el
(defmacro with-temp-windows!! (&rest body)
(declare (indent defun))
`(progn
(delete-other-windows)
(cl-flet ((split-window (symbol-function #'split-window-horizontally)))
(let ((a (get-buffer-create "a"))
(b (get-buffer-create "b"))
(split-width-threshold 0)
(window-min-width 0))
,@body))))
;;
(def-test! set-mode-name
(let ((doom-major-mode-names '((text-mode . "abc")
(lisp-mode . (lambda () "xyz"))
(js-mode . t))))
(text-mode)
(should (equal mode-name "abc"))
(lisp-mode)
(should (equal mode-name "xyz"))
(should-error (js-mode))))
(def-test! protect-visible-buffers
(with-temp-windows!!
(let ((kill-buffer-query-functions '(doom|protect-visible-buffers)))
(switch-to-buffer a) (split-window)
(switch-to-buffer b) (split-window)
(switch-to-buffer a)
(should-not (kill-buffer))
(select-window (get-buffer-window b))
(should (kill-buffer)))))
(def-test! *quit-window
(with-temp-windows!!
(let (kill-buffer-query-functions)
(switch-to-buffer a) (split-window)
(switch-to-buffer b)
(save-window-excursion
(quit-window t)
(should (buffer-live-p b)))
(quit-window)
(should-not (buffer-live-p b)))))

View file

@ -0,0 +1,115 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/test-autoload-buffers.el
(require 'core-projects)
(load! "autoload/buffers" doom-core-dir)
;;
(describe "core/autoload/buffers"
:var (a b c d)
(before-all
(spy-on 'buffer-list :and-call-fake
(lambda (&optional _)
(cl-remove-if-not #'buffer-live-p (list a b c d)))))
(before-each
(delete-other-windows)
(setq a (switch-to-buffer (get-buffer-create "a"))
b (get-buffer-create "b")
c (get-buffer-create "c")
d (get-buffer-create "d")))
(after-each
(kill-buffer a)
(kill-buffer b)
(kill-buffer c)
(kill-buffer d))
(describe "buffer-list"
(it "should only see four buffers"
(expect (doom-buffer-list) :to-have-same-items-as (list a b c d))))
(describe "project-buffer-list"
:var (projectile-projects-cache-time projectile-projects-cache)
(before-all (require 'projectile))
(after-all (unload-feature 'projectile t))
(before-each
(with-current-buffer a (setq default-directory doom-emacs-dir))
(with-current-buffer b (setq default-directory doom-emacs-dir))
(with-current-buffer c (setq default-directory "/tmp/"))
(with-current-buffer d (setq default-directory "~"))
(projectile-mode +1))
(after-each
(projectile-mode -1))
(it "returns buffers in the same project"
(with-current-buffer a
(expect (doom-project-buffer-list)
:to-have-same-items-as (list a b))))
(it "returns all buffers if not in a project"
(with-current-buffer c
(expect (doom-project-buffer-list)
:to-have-same-items-as (buffer-list)))))
(describe "fallback-buffer"
(it "returns a live buffer"
(expect (buffer-live-p (doom-fallback-buffer)))))
(describe "real buffers"
(before-each
(doom-set-buffer-real a t)
(with-current-buffer b (setq buffer-file-name "x"))
(with-current-buffer c (rename-buffer "*C*")))
(describe "real-buffer-p"
(it "returns t for buffers manually marked real"
(expect (doom-real-buffer-p a)))
(it "returns t for file-visiting buffers"
(expect (doom-real-buffer-p b)))
(it "returns nil for temporary buffers"
(expect (doom-real-buffer-p c) :to-be nil)
(expect (doom-real-buffer-p d) :to-be nil)))
(describe "real-buffer-list"
(it "returns only real buffers"
(expect (doom-real-buffer-list) :to-have-same-items-as (list a b)))))
(describe "buffer/window management"
(describe "buffer search methods"
(before-each
(with-current-buffer a (lisp-mode))
(with-current-buffer b (text-mode))
(with-current-buffer c (text-mode))
(split-window)
(switch-to-buffer b))
(it "can match buffers by regexp"
(expect (doom-matching-buffers "^[ac]$") :to-have-same-items-as (list a c)))
(it "can match buffers by major-mode"
(expect (doom-buffers-in-mode 'text-mode) :to-have-same-items-as (list b c)))
(it "can find all buried buffers"
(expect (doom-buried-buffers)
:to-have-same-items-as (list c d)))
(it "can find all visible buffers"
(expect (doom-visible-buffers)
:to-have-same-items-as (list a b)))
(it "can find all visible windows"
(expect (doom-visible-windows)
:to-have-same-items-as
(mapcar #'get-buffer-window (list a b)))))
(describe "kill-buffer-and-windows"
(before-each
(split-window) (switch-to-buffer b)
(split-window) (switch-to-buffer a))
(it "kills the selected buffers and all its windows"
(doom-kill-buffer-and-windows a)
(expect (buffer-live-p a) :to-be nil)
(expect (length (doom-visible-windows)) :to-be 1)))
;; TODO
(describe "kill-all-buffers")
(describe "kill-other-buffers")
(describe "kill-matching-buffers")
(describe "cleanup-session")))

View file

@ -0,0 +1,54 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/test-autoload-files.el
;;;
(require 'core-projects)
(require 'projectile)
(describe "core/autoload/files"
:var (src dest projectile-projects-cache-time projectile-projects-cache)
(before-each
(setq src (make-temp-file "test-src")
existing (make-temp-file "test-existing")
dest (expand-file-name "test-dest" temporary-file-directory))
(quiet! (find-file-literally src))
(spy-on 'y-or-n-p :and-return-value nil)
(projectile-mode +1))
(after-each
(projectile-mode -1)
(switch-to-buffer (doom-fallback-buffer))
(ignore-errors (delete-file src))
(ignore-errors (delete-file existing))
(ignore-errors (delete-file dest)))
(describe "move-this-file"
(it "won't move to itself"
(expect (quiet! (doom/move-this-file src)) :to-throw))
(it "will move to another file"
(expect (quiet! (doom/move-this-file dest t)))
(expect (file-exists-p dest))
(expect (file-exists-p src) :to-be nil))
(it "will prompt if overwriting a file"
(quiet! (doom/move-this-file existing))
(expect 'y-or-n-p :to-have-been-called-times 1)
(expect (file-exists-p src))))
(describe "copy-this-file"
(it "refuses to copy to itself"
(expect (quiet! (doom/copy-this-file src)) :to-throw))
(it "copies to another file"
(expect (quiet! (doom/copy-this-file dest t)))
(expect (file-exists-p! src dest)))
(it "prompts if overwriting a file"
(quiet! (doom/copy-this-file existing))
(expect 'y-or-n-p :to-have-been-called-times 1)))
(describe "delete-this-file"
(it "fails gracefully on non-existent files"
(expect (quiet! (doom/delete-this-file dest)) :to-throw))
(it "deletes existing files"
(quiet! (doom/delete-this-file existing t))
(expect (file-exists-p existing) :to-be nil))
(it "prompts to delete any existing file"
(quiet! (doom/delete-this-file existing))
(expect 'y-or-n-p :to-have-been-called-times 1))))

View file

@ -0,0 +1,10 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/test-autoload-help.el
;; (load! "autoload/help" doom-core-dir)
;;
;; (describe "core/autoload/help"
;; :var (a)
;; (before-each (setq a (switch-to-buffer (get-buffer-create "a"))))
;; (after-each (kill-buffer a)))

View file

@ -0,0 +1,37 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/test-autoload-message.el
(describe "core/autoload/message"
(describe "format!"
:var (noninteractive)
(before-all (setq noninteractive t))
(it "should be a drop-in replacement for `format'"
(expect (format! "Hello %s" "World")
:to-equal "Hello World"))
(it "supports ansi coloring in noninteractive sessions"
(expect (format! (red "Hello %s") "World")
:to-equal "Hello World"))
(it "supports faces in interactive sessions"
(let (noninteractive)
(expect (get-text-property 0 'face (format! (red "Hello %s") "World"))
:to-equal (list :foreground (face-foreground 'term-color-red)))))
(it "supports nested color specs"
(expect (format! (bold (red "Hello %s")) "World")
:to-equal (format "\e[%dm%s\e[0m" 1
(format "\e[%dm%s\e[0m" 31 "Hello World")))
(expect (format! (on-red (bold "Hello %s")) "World")
:to-equal (format "\e[%dm%s\e[0m" 41
(format "\e[%dm%s\e[0m" 1 "Hello World")))
(expect (format! (dark (white "Hello %s")) "World")
:to-equal (format "\e[%dm%s\e[0m" 2
(format "\e[%dm%s\e[0m" 37 "Hello World"))))
(it "supports dynamic color apply syntax"
(expect (format! (color 'red "Hello %s") "World")
:to-equal (format! (red "Hello %s") "World"))
(expect (format! (color (if nil 'red 'blue) "Hello %s") "World")
:to-equal (format! (blue "Hello %s") "World")))))

View file

@ -0,0 +1,138 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/test-autoload-package.el
(describe "core/autoload/packages"
:var (package-alist
package-archive-contents
package-selected-packages
doom-packages
quelpa-cache
quelpa-initialized-p
doom-packages-dir
doom-core-packages
package-user-dir
quelpa-dir
pkg)
(before-all
(fset 'pkg
(lambda (name version &optional reqs)
(package-desc-create
:name name :version version :reqs reqs
:dir (expand-file-name (format "%s/" name) package-user-dir))))
(require 'package)
(require 'quelpa)
(setq doom-packages-dir (expand-file-name "packages/" (file-name-directory load-file-name))
package-user-dir (expand-file-name "elpa" doom-packages-dir)
quelpa-dir (expand-file-name "quelpa" doom-packages-dir)
quelpa-initialized-p t
doom-core-packages nil)
(spy-on #'package--user-installed-p :and-call-fake (lambda (_p) t))
(spy-on #'doom-initialize-packages :and-call-fake (lambda (&optional _)))
(spy-on #'package-refresh-contents :and-call-fake (lambda (&optional _)))
(spy-on #'quelpa-checkout :and-call-fake
(lambda (rcp _dir)
(when (eq (car rcp) 'doom-quelpa-dummy)
"20170405.1234"))))
(after-all
(unload-feature 'package t)
(unload-feature 'quelpa t))
(before-each
(setq package-alist
`((doom-dummy ,(pkg 'doom-dummy '(20160405 1234)))
(doom-uptodate-dummy ,(pkg 'doom-uptodate-dummy '(20160605 1234)))
(doom-unwanted-dummy ,(pkg 'doom-unwanted-dummy '(20160605 1234)))
(doom-quelpa-dummy ,(pkg 'doom-quelpa-dummy '(20160405 1234)))
(doom-noquelpa-dummy ,(pkg 'doom-noquelpa-dummy '(20160405 1234))))
package-archive-contents
`((doom-dummy ,(pkg 'doom-dummy '(20170405 1234)))
(doom-uptodate-dummy ,(pkg 'doom-uptodate-dummy '(20160605 1234))))
doom-packages
'((doom-dummy)
(doom-uptodate-dummy)
(doom-missing-dummy)
(doom-noquelpa-dummy)
(doom-disabled-dummy :disable t)
(doom-private-dummy :modules ((:private)))
(doom-disabled-private-dummy :modules ((:private)) :disable t)
(doom-quelpa-dummy :recipe (doom-quelpa-dummy :fetcher github :repo "hlissner/does-not-exist")))
quelpa-cache
'((doom-quelpa-dummy :fetcher github :repo "hlissner/does-not-exist")
(doom-noquelpa-dummy :fetcher github :repo "hlissner/does-not-exist-3")
(doom-new-quelpa-dummy :fetcher github :repo "hlissner/does-not-exist-2"))
package-selected-packages (mapcar #'car doom-packages)))
(describe "package-backend"
(it "determines the correct backend of a package"
(expect (doom-package-backend 'doom-dummy) :to-be 'elpa)
(expect (doom-package-backend 'doom-quelpa-dummy) :to-be 'quelpa)
(expect (doom-package-backend 'org) :to-be 'emacs))
(it "errors out if package isn't installed"
(expect (doom-package-backend 'xyz) :to-throw)))
(describe "package-outdated-p (elpa)"
(it "detects outdated ELPA packages and returns both versions"
(expect (doom-package-outdated-p 'doom-dummy)
:to-equal '(doom-dummy (20160405 1234) (20170405 1234))))
(it "ignores up-to-date ELPA packages"
(expect (doom-package-outdated-p 'doom-uptodate-dummy) :to-be nil))
(it "detects outdated QUELPA packages and returns both versions"
(expect (doom-package-outdated-p 'doom-quelpa-dummy)
:to-equal '(doom-quelpa-dummy (20160405 1234) (20170405 1234))))
(it "ignores up-to-date QUELPA packages"
(expect (doom-package-outdated-p 'doom-uptodate-dummy) :to-be nil))
(it "returns nil if package isn't installed"
(expect (doom-package-outdated-p 'xyz) :to-be nil)))
(describe "get-packages"
(before-all
;; In addition to `package-installed-p', `doom-package-installed-p' does
;; file existence checks which won't work here, so we simplify it
(spy-on #'doom-package-installed-p :and-call-fake #'package-installed-p))
(it "returns all packages"
(expect (mapcar #'car (doom-find-packages))
:to-have-same-items-as
(mapcar #'car doom-packages)))
(it "returns only disabled packages"
(expect (mapcar #'car (doom-find-packages :disabled t))
:to-have-same-items-as
'(doom-disabled-dummy doom-disabled-private-dummy)))
(it "returns only non-disabled packages"
(expect (mapcar #'car (doom-find-packages :disabled nil))
:to-have-same-items-as
'(doom-dummy doom-uptodate-dummy doom-quelpa-dummy doom-missing-dummy doom-noquelpa-dummy doom-private-dummy)))
(it "returns only installed packages"
(expect (mapcar #'car (doom-find-packages :disabled nil :installed t))
:to-have-same-items-as
'(doom-dummy doom-uptodate-dummy doom-quelpa-dummy doom-noquelpa-dummy)))
(it "returns only non-installed packages"
(expect (mapcar #'car (doom-find-packages :disabled nil :installed nil))
:to-have-same-items-as
'(doom-missing-dummy doom-private-dummy)))
(it "returns only private packages"
(expect (mapcar #'car (doom-find-packages :private t))
:to-have-same-items-as
'(doom-private-dummy doom-disabled-private-dummy)))
(it "returns only disabled and private packages"
(expect (mapcar #'car (doom-find-packages :disabled t :private t))
:to-have-same-items-as
'(doom-disabled-private-dummy))))
(describe "get-orphaned-packages"
(it "returns orphaned packages"
(expect (doom-get-orphaned-packages) :to-contain 'doom-unwanted-dummy))
(it "returns packages that have changed backends"
(expect (doom-get-orphaned-packages) :to-contain 'doom-noquelpa-dummy)))
(describe "get-missing-packages"
(it "returns packages that haven't been installed"
(expect (mapcar #'car (doom-get-missing-packages))
:to-contain 'doom-missing-dummy))
(it "returns packages that have changed backends"
(expect (mapcar #'car (doom-get-missing-packages))
:to-contain 'doom-noquelpa-dummy))))

View file

@ -0,0 +1,261 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/test-core-keybinds.el
(require 'core-keybinds)
(buttercup-define-matcher :to-expand-into (src result)
(let ((src (funcall src))
(result (funcall result)))
(or (equal (macroexpand-1 src) result)
(error "'%s' expanded into '%s' instead of '%s'"
src (macroexpand-1 src) result))))
(describe "core/keybinds"
(describe "map!"
:var (doom--map-evil-p doom-map-states)
(before-each
(setq doom--map-evil-p t
doom-map-states '((:n . normal)
(:v . visual)
(:i . insert)
(:e . emacs)
(:o . operator)
(:m . motion)
(:r . replace))))
(describe "Single keybinds"
(it "binds a global key"
(expect '(map! "C-." #'a) :to-expand-into '(general-define-key "C-." #'a)))
(it "binds a key in one evil state"
(dolist (state doom-map-states)
(expect `(map! ,(car state) "C-." #'a)
:to-expand-into
`(general-define-key :states ',(cdr state) "C-." #'a))))
(it "binds a key in multiple evil states"
(expect `(map! :nvi "C-." #'a)
:to-expand-into
'(progn (general-define-key :states 'insert "C-." #'a)
(general-define-key :states 'visual "C-." #'a)
(general-define-key :states 'normal "C-." #'a))))
(it "binds evil keybinds together with global keybinds"
(expect '(map! :ng "C-." #'a)
:to-expand-into
'(progn
(general-define-key :states 'normal "C-." #'a)
(general-define-key "C-." #'a)))))
(describe "Multiple keybinds"
(it "binds global keys and preserves order"
(expect '(map! "C-." #'a "C-," #'b "C-/" #'c)
:to-expand-into
'(general-define-key "C-." #'a "C-," #'b "C-/" #'c)))
(it "binds multiple keybinds in an evil state and preserve order"
(dolist (state doom-map-states)
(expect `(map! ,(car state) "a" #'a
,(car state) "b" #'b
,(car state) "c" #'c)
:to-expand-into
`(general-define-key :states ',(cdr state)
"a" #'a
"b" #'b
"c" #'c))))
(it "binds multiple keybinds in different evil states"
(expect `(map! :n "a" #'a
:n "b" #'b
:n "e" #'e
:v "c" #'c
:i "d" #'d)
:to-expand-into
`(progn (general-define-key :states 'insert "d" #'d)
(general-define-key :states 'visual "c" #'c)
(general-define-key :states 'normal "a" #'a "b" #'b "e" #'e))))
(it "groups multi-state keybinds while preserving same-group key order"
(expect `(map! :n "a" #'a
:v "c" #'c
:n "b" #'b
:i "d" #'d
:n "e" #'e)
:to-expand-into
`(progn (general-define-key :states 'insert "d" #'d)
(general-define-key :states 'visual "c" #'c)
(general-define-key :states 'normal "a" #'a "b" #'b "e" #'e))))
(it "binds multiple keybinds in multiple evil states"
(expect `(map! :nvi "a" #'a
:nvi "b" #'b
:nvi "c" #'c)
:to-expand-into
'(progn (general-define-key :states 'insert "a" #'a "b" #'b "c" #'c)
(general-define-key :states 'visual "a" #'a "b" #'b "c" #'c)
(general-define-key :states 'normal "a" #'a "b" #'b "c" #'c)))))
(describe "Nested keybinds"
(it "binds global keys"
(expect '(map! "C-." #'a
("C-a" #'b)
("C-x" #'c))
:to-expand-into
'(progn (general-define-key "C-." #'a)
(general-define-key "C-a" #'b)
(general-define-key "C-x" #'c))))
(it "binds nested evil keybinds"
(expect '(map! :n "C-." #'a
(:n "C-a" #'b)
(:n "C-x" #'c))
:to-expand-into
'(progn (general-define-key :states 'normal "C-." #'a)
(general-define-key :states 'normal "C-a" #'b)
(general-define-key :states 'normal "C-x" #'c))))
(it "binds global keybinds in between evil keybinds"
(expect '(map! :n "a" #'a
"b" #'b
:n "c" #'c)
:to-expand-into
'(progn (general-define-key "b" #'b)
(general-define-key :states 'normal "a" #'a "c" #'c)))))
;;
(describe "Properties"
(describe ":after"
(it "wraps `general-define-key' in a `after!' block"
(dolist (form '((map! :after helm "a" #'a "b" #'b)
(map! (:after helm "a" #'a "b" #'b))))
(expect form :to-expand-into '(after! helm (general-define-key "a" #'a "b" #'b))))
(expect '(map! "a" #'a (:after helm "b" #'b "c" #'c))
:to-expand-into
'(progn
(general-define-key "a" #'a)
(after! helm
(general-define-key "b" #'b "c" #'c))))
(expect '(map! (:after helm "b" #'b "c" #'c) "a" #'a)
:to-expand-into
'(progn
(after! helm
(general-define-key "b" #'b "c" #'c))
(general-define-key "a" #'a))))
(it "nests `after!' blocks"
(expect '(map! :after x "a" #'a
(:after y "b" #'b
(:after z "c" #'c)))
:to-expand-into
'(after! x
(progn
(general-define-key "a" #'a)
(after! y
(progn
(general-define-key "b" #'b)
(after! z
(general-define-key "c" #'c))))))))
(it "nests `after!' blocks in other nested blocks"
(expect '(map! :after x "a" #'a
(:when t "b" #'b
(:after z "c" #'c)))
:to-expand-into
'(after! x
(progn
(general-define-key "a" #'a)
(when t
(progn
(general-define-key "b" #'b)
(after! z (general-define-key "c" #'c)))))))))
(describe ":desc"
(it "add a :which-key property to a keybind's DEF"
(expect '(map! :desc "A" "a" #'a)
:to-expand-into
`(general-define-key "a" (list :def #'a :which-key "A")))))
(describe ":when/:unless"
(it "wraps keys in a conditional block"
(dolist (prop '(:when :unless))
(let ((prop-fn (intern (doom-keyword-name prop))))
(expect `(map! ,prop t "a" #'a "b" #'b)
:to-expand-into
`(,prop-fn t (general-define-key "a" #'a "b" #'b)))
(expect `(map! (,prop t "a" #'a "b" #'b))
:to-expand-into
`(,prop-fn t (general-define-key "a" #'a "b" #'b))))))
(it "nests conditional blocks"
(expect '(map! (:when t "a" #'a (:when t "b" #'b)))
:to-expand-into
'(when t
(progn (general-define-key "a" #'a)
(when t (general-define-key "b" #'b)))))))
(describe ":leader"
(it "uses leader definer"
(expect '(map! :leader "a" #'a "b" #'b)
:to-expand-into
'(doom--define-leader-key "a" #'a "b" #'b)))
(it "it persists for nested keys"
(expect '(map! :leader "a" #'a ("b" #'b))
:to-expand-into
'(progn (doom--define-leader-key "a" #'a)
(doom--define-leader-key "b" #'b)))))
(describe ":localleader"
(it "uses localleader definer"
(expect '(map! :localleader "a" #'a "b" #'b)
:to-expand-into
'(define-localleader-key! "a" #'a "b" #'b)))
(it "it persists for nested keys"
(expect '(map! :localleader "a" #'a ("b" #'b))
:to-expand-into
'(progn (define-localleader-key! "a" #'a)
(define-localleader-key! "b" #'b)))))
(describe ":map/:keymap"
(it "specifies a single keymap for keys"
(expect '(map! :map emacs-lisp-mode-map "a" #'a)
:to-expand-into
'(general-define-key :keymaps '(emacs-lisp-mode-map) "a" #'a)))
(it "specifies multiple keymap for keys"
(expect '(map! :map (lisp-mode-map emacs-lisp-mode-map) "a" #'a)
:to-expand-into
'(general-define-key :keymaps '(lisp-mode-map emacs-lisp-mode-map) "a" #'a))))
(describe ":mode"
(it "appends -map to MODE"
(expect '(map! :mode emacs-lisp-mode "a" #'a)
:to-expand-into
'(general-define-key :keymaps '(emacs-lisp-mode-map) "a" #'a))))
(describe ":prefix"
(it "specifies a prefix for all keys"
(expect '(map! :prefix "a" "x" #'x "y" #'y "z" #'z)
:to-expand-into
'(general-define-key :prefix "a" "x" #'x "y" #'y "z" #'z)))
(it "overwrites previous inline :prefix properties"
(expect '(map! :prefix "a" "x" #'x "y" #'y :prefix "b" "z" #'z)
:to-expand-into
'(progn (general-define-key :prefix "a" "x" #'x "y" #'y)
(general-define-key :prefix "b" "z" #'z))))
(it "accumulates keys when nested"
(expect '(map! (:prefix "a" "x" #'x (:prefix "b" "x" #'x)))
:to-expand-into
`(progn (general-define-key :prefix "a" "x" #'x)
(general-define-key :prefix (general--concat nil "a" "b")
"x" #'x)))))
(describe ":textobj"
(it "defines keys in evil-{inner,outer}-text-objects-map"
(expect '(map! :textobj "a" #'inner #'outer)
:to-expand-into
'(map! (:map evil-inner-text-objects-map "a" #'inner)
(:map evil-outer-text-objects-map "a" #'outer))))))))

View file

@ -0,0 +1,92 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/test-core-lib.el
(require 'core-lib)
(describe "core/lib"
;; --- Helpers ----------------------------
(describe "doom-unquote"
(it "unquotes a quoted form"
(expect (doom-unquote '(quote hello)) :to-be 'hello))
(it "unquotes nested-quoted forms"
(expect (doom-unquote '(quote (quote (a b c)))) :to-equal '(a b c)))
(it "unquotes function-quoted forms"
(expect (doom-unquote '(function a)) :to-be 'a))
(it "does nothing to unquoted forms"
(expect (doom-unquote 'hello) :to-be 'hello)))
(describe "doom-enlist"
(it "creates a list out of non-lists"
(expect (doom-enlist 'a) :to-equal '(a)))
(it "does nothing to lists"
(expect (doom-enlist '(a)) :to-equal '(a))))
;; --- Macros -----------------------------
(describe "hooks"
(describe "add-hook!"
:var (fake-mode-hook other-mode-hook some-mode-hook)
(before-each
(setq fake-mode-hook '(first-hook)
other-mode-hook nil
some-mode-hook '(first-hook second-hook)))
(it "adds one-to-one hook"
(add-hook! fake-mode #'hook-2)
(add-hook! 'fake-mode-hook #'hook-1)
(expect fake-mode-hook :to-equal '(hook-1 hook-2 first-hook)))
(it "adds many-to-one hook"
(add-hook! (fake-mode other-mode some-mode) #'hook-2)
(add-hook! '(fake-mode-hook other-mode-hook some-mode-hook) #'hook-1)
(add-hook! :append (fake-mode other-mode some-mode) #'last-hook)
(expect fake-mode-hook :to-equal '(hook-1 hook-2 first-hook last-hook))
(expect other-mode-hook :to-equal '(hook-1 hook-2 last-hook))
(expect some-mode-hook :to-equal '(hook-1 hook-2 first-hook second-hook last-hook)))
(it "adds many-to-many hooks and preserve provided order"
(add-hook! (fake-mode other-mode some-mode) #'(hook-3 hook-4))
(add-hook! '(fake-mode-hook other-mode-hook some-mode-hook) #'(hook-1 hook-2))
(add-hook! :append '(fake-mode-hook other-mode-hook some-mode-hook) #'(last-hook-1 last-hook-2))
(expect fake-mode-hook :to-equal '(hook-1 hook-2 hook-3 hook-4 first-hook last-hook-1 last-hook-2))
(expect other-mode-hook :to-equal '(hook-1 hook-2 hook-3 hook-4 last-hook-1 last-hook-2))
(expect some-mode-hook :to-equal '(hook-1 hook-2 hook-3 hook-4 first-hook second-hook last-hook-1 last-hook-2)))
(it "adds implicit lambda to one hook"
(add-hook! fake-mode (progn))
(add-hook! 'other-mode-hook (ignore))
(add-hook! :append 'some-mode-hook (ignore))
(expect (caar fake-mode-hook) :to-be 'lambda)
(expect (caar other-mode-hook) :to-be 'lambda)
(expect (caar (last other-mode-hook)) :to-be 'lambda)))
(describe "remove-hook!"
:var (fake-mode-hook)
(before-each
(setq fake-mode-hook '(first-hook second-hook third-hook fourth-hook)))
(it "removes one hook"
(remove-hook! fake-mode #'third-hook)
(remove-hook! 'fake-mode-hook #'second-hook)
(expect fake-mode-hook :to-equal '(first-hook fourth-hook)))
(it "removes multiple hooks"
(remove-hook! fake-mode #'(first-hook third-hook))
(remove-hook! 'fake-mode-hook #'(second-hook fourth-hook))
(expect fake-mode-hook :to-be nil))))
(describe "add-transient-hook!"
(it "adds a transient function to hooks"
(let (hooks value)
(add-transient-hook! 'hooks (setq value t))
(run-hooks 'hooks)
(expect value)
(expect hooks :to-be nil)))
(it "advises a function with a transient advisor"
(let (value)
(add-transient-hook! #'ignore (setq value (not value)))
(ignore t)
(expect value)
;; repeat to ensure it was only run once
(ignore t)
(expect value))))
(xdescribe "associate!")) ; TODO

View file

@ -0,0 +1,6 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/test-core-modules.el
;; (require 'core-modules)
(describe "core-modules")

View file

@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/test-core-packages.el
(describe "core-packages")

View file

@ -0,0 +1,35 @@
;; -*- no-byte-compile: t; -*-
;;; ../core/test/test-core-projects.el
(require 'core-projects)
(require 'projectile)
(describe "core/projects"
(before-each (projectile-mode +1))
(after-each (projectile-mode -1))
(describe "project-p"
(it "Should detect when in a valid project"
(expect (doom-project-p doom-emacs-dir)))
(it "Should detect when not in a valid project"
(expect (doom-project-p (expand-file-name "~")) :to-be nil)))
(describe "project-root"
(it "should resolve to the project's root"
(expect (doom-project-root doom-core-dir) :to-equal doom-emacs-dir))
(it "should return nil if not in a project"
(expect (doom-project-root (expand-file-name "~")) :to-be nil)))
(describe "project-expand"
(it "expands to a path relative to the project root"
(expect (doom-project-expand "init.el" doom-core-dir)
:to-equal (expand-file-name "init.el" (doom-project-root doom-core-dir)))))
(describe "project-file-exists-p!"
(let ((default-directory doom-core-dir))
;; Resolve from project root
(expect (project-file-exists-p! "init.el"))
;; Chained file checks
(expect (project-file-exists-p! (and "init.el" "LICENSE")))
(expect (project-file-exists-p! (or "init.el" "does-not-exist")))
(expect (project-file-exists-p! (and "init.el" (or "LICENSE" "does-not-exist")))))))

17
core/test/test-core-ui.el Normal file
View file

@ -0,0 +1,17 @@
;; -*- no-byte-compile: t; -*-
;;; ../core/test/test-core-ui.el
(require 'core-ui)
(describe "core/ui"
(describe "doom|protect-fallback-buffer"
:var (kill-buffer-query-functions a b)
(before-all
(setq kill-buffer-query-functions '(doom|protect-fallback-buffer)))
(it "should kill other buffers"
(expect (kill-buffer (get-buffer-create "a"))))
(it "shouldn't kill the fallback buffer"
(expect (not (kill-buffer (doom-fallback-buffer)))))))

117
core/test/test-core.el Normal file
View file

@ -0,0 +1,117 @@
;; -*- no-byte-compile: t; -*-
;;; core/test/test-core.el
(describe "core"
(xdescribe "initialize"
:var (doom-init-p doom-init-modules-p doom-private-dir)
(before-each
(setq doom-init-p nil
doom-init-modules-p nil
doom-private-dir doom-emacs-dir)
(spy-on 'require)
(spy-on 'load)
(spy-on 'doom-reload-doom-autoloads)
(spy-on 'doom-reload-package-autoloads)
(spy-on 'doom-initialize-autoloads)
(spy-on 'doom-ensure-core-directories)
(spy-on 'doom-ensure-core-packages)
(spy-on 'doom-ensure-packages-initialized)
(spy-on 'doom-ensure-same-emacs-version-p))
(describe "in interactive session"
:var (noninteractive)
(before-each (setq noninteractive t))
(it "initializes once, unless forced")
(it "does not initialize on consecutive invokations")
(it "loads all core libraries" )
(it "loads autoloads file" )
(it "does not load autoloads file if forced" )
(it "regenerates missing autoloads" ))
(describe "in non-interactive session"
:var (noninteractive)
(before-each (setq noninteractive nil))
(it "initializes once, unless forced")
(it "does not initialize on consecutive invokations")
(it "does not load all core libraries" )
(it "loads autoloads file" )
(it "does not load autoloads file if forced" )
(it "does not regenerate missing autoloads" )))
(xdescribe "initialize-packages"
(before-each (spy-on 'quelpa-setup-p))
(it "initializes package.el once, unless forced" )
(it "initializes quelpa once, unless forced" )
(it "initializes doom-packages once, unless forced" ))
(xdescribe "initialize-modules"
(it "loads private init.el once, unless forced" ))
(xdescribe "initialize-autoloads"
(it "loads autoloads file" )
(it "ignores autoloads file if cleared" ))
(describe "custom hooks"
(describe "switch hooks"
:var (before-hook after-hook a b)
(before-each
(setq a (switch-to-buffer (get-buffer-create "a"))
b (get-buffer-create "b"))
(spy-on 'hook)
(add-hook 'buffer-list-update-hook #'doom|run-switch-window-hooks)
(add-hook 'focus-in-hook #'doom|run-switch-frame-hooks)
(advice-add! '(switch-to-buffer display-buffer) :around #'doom*run-switch-buffer-hooks))
(after-each
(remove-hook 'buffer-list-update-hook #'doom|run-switch-window-hooks)
(remove-hook 'focus-in-hook #'doom|run-switch-frame-hooks)
(advice-remove! '(switch-to-buffer display-buffer) #'doom*run-switch-buffer-hooks)
(kill-buffer a)
(kill-buffer b))
(describe "switch-buffer"
:var (doom-switch-buffer-hook)
(before-each
(setq doom-switch-buffer-hook '(hook)))
(after-each
(setq doom-switch-buffer-hook nil))
(it "should trigger when switching buffers"
(switch-to-buffer b)
(switch-to-buffer a)
(switch-to-buffer b)
(expect 'hook :to-have-been-called-times 3))
(it "should trigger only once on the same buffer"
(switch-to-buffer b)
(switch-to-buffer b)
(switch-to-buffer a)
(expect 'hook :to-have-been-called-times 2)))
(describe "switch-window"
:var (doom-switch-window-hook x y)
(before-each
(delete-other-windows)
(setq x (get-buffer-window a)
y (save-selected-window (split-window)))
(with-selected-window y
(switch-to-buffer b))
(select-window x)
(spy-calls-reset 'hook)
(setq doom-switch-window-hook '(hook)))
(it "should trigger when switching windows"
(select-window y)
(select-window x)
(select-window y)
(expect 'hook :to-have-been-called-times 3))
(it "should trigger only once on the same window"
(select-window y)
(select-window y)
(select-window x)
(expect 'hook :to-have-been-called-times 2))))))

30
docs/ISSUE_TEMPLATE.md Normal file
View file

@ -0,0 +1,30 @@
+ [ ] I've checked for duplicates of this issue.
+ [ ] `bin/doom clean` and `bin/doom refresh` (then restarting Emacs) did not
fix my issue.
+ [ ] I ran `make doctor` and it produced no leads.
+ [ ] My issue cannot be found [in the FAQ](/../../wiki/FAQ)
+ [ ] I filled out the four fields in the template below.
+ [ ] I have deleted this checklist and message.
### Observed behavior
(Describe what happened)
### Expected behavior
(Describe what you expected to happen)
### Steps to reproduce
1. Select these example steps,
2. Delete them,
3. And replace them with precise steps to reproduce your issue.
4. Fill in "system information" below.
### System information
<details>
<summary>Click to expand</summary>
Replace this line with the output of `M-x doom/info` OR `~/.emacs.d/bin/doom info`
</details>

View file

@ -1,5 +1,5 @@
Thank you for contributing to Doom!
Before you submit this PR, please make sure your PR is targeted at develop, not
Before you submit this PR, please make sure it is targeted at develop, not
master (unless this is a fix for a critical error). Then replace this message
with a description of your changes.

20
early-init.el Normal file
View file

@ -0,0 +1,20 @@
;;; early-init.el -*- lexical-binding: t; -*-
;; Emacs HEAD (27+) introduces early-init.el, which is run before init.el,
;; before package and UI initialization happens.
;; Defer garbage collection further back in the startup process
(setq gc-cons-threshold 268435456)
;; Package initialize occurs automatically, before `user-init-file' is
;; loaded, but after `early-init-file'. Doom handles package
;; initialization, so we must prevent Emacs from doing it early!
(setq package-enable-at-startup nil)
;; Prevent the glimpse of un-styled Emacs by setting these early.
(add-to-list 'default-frame-alist '(tool-bar-lines . 0))
(add-to-list 'default-frame-alist '(menu-bar-lines . 0))
(add-to-list 'default-frame-alist '(vertical-scroll-bars))
;; One less file to load at startup
(setq site-run-file nil)

73
init.el Normal file
View file

@ -0,0 +1,73 @@
;;; init.el -*- lexical-binding: t; -*-
;;
;; Author: Henrik Lissner <henrik@lissner.net>
;; URL: https://github.com/hlissner/doom-emacs
;;
;; ================= =============== =============== ======== ========
;; \\ . . . . . . .\\ //. . . . . . .\\ //. . . . . . .\\ \\. . .\\// . . //
;; ||. . ._____. . .|| ||. . ._____. . .|| ||. . ._____. . .|| || . . .\/ . . .||
;; || . .|| ||. . || || . .|| ||. . || || . .|| ||. . || ||. . . . . . . ||
;; ||. . || || . .|| ||. . || || . .|| ||. . || || . .|| || . | . . . . .||
;; || . .|| ||. _-|| ||-_ .|| ||. . || || . .|| ||. _-|| ||-_.|\ . . . . ||
;; ||. . || ||-' || || `-|| || . .|| ||. . || ||-' || || `|\_ . .|. .||
;; || . _|| || || || || ||_ . || || . _|| || || || |\ `-_/| . ||
;; ||_-' || .|/ || || \|. || `-_|| ||_-' || .|/ || || | \ / |-_.||
;; || ||_-' || || `-_|| || || ||_-' || || | \ / | `||
;; || `' || || `' || || `' || || | \ / | ||
;; || .===' `===. .==='.`===. .===' /==. | \/ | ||
;; || .==' \_|-_ `===. .===' _|_ `===. .===' _-|/ `== \/ | ||
;; || .==' _-' `-_ `=' _-' `-_ `=' _-' `-_ /| \/ | ||
;; || .==' _-' '-__\._-' '-_./__-' `' |. /| | ||
;; ||.==' _-' `' | /==.||
;; ==' _-' \/ `==
;; \ _-' `-_ /
;; `'' ``'
;;
;; These demons are not part of GNU Emacs.
;;
;;; License: MIT
(defvar doom-gc-cons-threshold 16777216 ; 16mb
"The default value to use for `gc-cons-threshold'. If you experience freezing,
decrease this. If you experience stuttering, increase this.")
(defvar doom-gc-cons-upper-limit 268435456 ; 256mb
"The temporary value for `gc-cons-threshold' to defer it.")
(defvar doom--file-name-handler-alist file-name-handler-alist)
(defun doom|restore-startup-optimizations ()
"Resets garbage collection settings to reasonable defaults (a large
`gc-cons-threshold' can cause random freezes otherwise) and resets
`file-name-handler-alist'."
(setq file-name-handler-alist doom--file-name-handler-alist)
;; Do this on idle timer to defer a possible GC pause that could result; also
;; allows deferred packages to take advantage of these optimizations.
(run-with-idle-timer
3 nil (lambda () (setq-default gc-cons-threshold doom-gc-cons-threshold))))
(if (or after-init-time noninteractive)
(setq gc-cons-threshold doom-gc-cons-threshold)
;; A big contributor to startup times is garbage collection. We up the gc
;; threshold to temporarily prevent it from running, then reset it later in
;; `doom|restore-startup-optimizations'.
(setq gc-cons-threshold doom-gc-cons-upper-limit)
;; This is consulted on every `require', `load' and various path/io functions.
;; You get a minor speed up by nooping this.
(setq file-name-handler-alist nil)
;; Not restoring these to their defaults will cause stuttering/freezes.
(add-hook 'after-init-hook #'doom|restore-startup-optimizations))
;; Ensure Doom is running out of this file's directory
(setq user-emacs-directory (file-name-directory load-file-name))
;; In noninteractive sessions, prioritize non-byte-compiled source files to
;; prevent stale, byte-compiled code from running. However, if you're getting
;; recursive load errors, it may help to set this to nil.
(setq load-prefer-newer noninteractive)
;; Let 'er rip!
(require 'core (concat user-emacs-directory "core/core"))

View file

@ -1,139 +1,172 @@
;;; init.el -*- lexical-binding: t; -*-
;;
;; Author: Henrik Lissner <henrik@lissner.net>
;; URL: https://github.com/hlissner/.emacs.d
;;
;; ================= =============== =============== ======== ========
;; \\ . . . . . . .\\ //. . . . . . .\\ //. . . . . . .\\ \\. . .\\// . . //
;; ||. . ._____. . .|| ||. . ._____. . .|| ||. . ._____. . .|| || . . .\/ . . .||
;; || . .|| ||. . || || . .|| ||. . || || . .|| ||. . || ||. . . . . . . ||
;; ||. . || || . .|| ||. . || || . .|| ||. . || || . .|| || . | . . . . .||
;; || . .|| ||. _-|| ||-_ .|| ||. . || || . .|| ||. _-|| ||-_.|\ . . . . ||
;; ||. . || ||-' || || `-|| || . .|| ||. . || ||-' || || `|\_ . .|. .||
;; || . _|| || || || || ||_ . || || . _|| || || || |\ `-_/| . ||
;; ||_-' || .|/ || || \|. || `-_|| ||_-' || .|/ || || | \ / |-_.||
;; || ||_-' || || `-_|| || || ||_-' || || | \ / | `||
;; || `' || || `' || || `' || || | \ / | ||
;; || .===' `===. .==='.`===. .===' /==. | \/ | ||
;; || .==' \_|-_ `===. .===' _|_ `===. .===' _-|/ `== \/ | ||
;; || .==' _-' `-_ `=' _-' `-_ `=' _-' `-_ /| \/ | ||
;; || .==' _-' '-__\._-' '-_./__-' `' |. /| | ||
;; ||.==' _-' `' | /==.||
;; ==' _-' \/ `==
;; \ _-' `-_ /
;; `'' ``'
;;
;; These demons are not part of GNU Emacs.
;;
;;; License: MIT
(require 'core (concat user-emacs-directory "core/core"))
;; Copy this file to ~/.doom.d/init.el or ~/.config/doom/init.el ('doom
;; quickstart' will do this for you). The `doom!' block below controls what
;; modules are enabled and in what order they will be loaded. Remember to run
;; 'doom refresh' after modifying it.
;;
;; More information about these modules (and what flags they support) can be
;; found in modules/README.org.
(doom! :feature
;debugger ; FIXME stepping through code, to help you add bugs
;;debugger ; FIXME stepping through code, to help you add bugs
eval ; run code, run (also, repls)
evil ; come to the dark side, we have cookies
(evil +everywhere); come to the dark side, we have cookies
file-templates ; auto-snippets for empty files
jump ; helping you get around
services ; TODO managing external services & code builders
(lookup ; helps you navigate your code and documentation
+docsets) ; ...or in Dash docsets locally
snippets ; my elves. They type so I don't have to
spellcheck ; tasing you for misspelling mispelling
syntax-checker ; tasing you for every semicolon you forget
version-control ; remember, remember that commit in November
workspaces ; tab emulation, persistence & separate workspaces
:completion
company ; the ultimate code completion backend
;;helm ; the *other* search engine for love and life
;;ido ; the other *other* search engine...
ivy ; a search engine for love and life
;helm ; the *other* search engine for love and life
;ido ; the other *other* search engine...
:ui
;;deft ; notational velocity for Emacs
doom ; what makes DOOM look the way it does
doom-dashboard ; a nifty splash screen for Emacs
doom-modeline ; a snazzy Atom-inspired mode-line
doom-quit ; DOOM quit-message prompts when you quit Emacs
;;fill-column ; a `fill-column' indicator
hl-todo ; highlight TODO/FIXME/NOTE tags
;;indent-guides ; highlighted indent columns
modeline ; snazzy, Atom-inspired modeline, plus API
nav-flash ; blink the current line after jumping
evil-goggles ; display visual hints when editing in evil
;unicode ; extended unicode support for various languages
;tabbar ; FIXME an (incomplete) tab bar for Emacs
;;neotree ; a project drawer, like NERDTree for vim
ophints ; highlight the region an operation acts on
(popup ; tame sudden yet inevitable temporary windows
+all ; catch all popups that start with an asterix
+defaults) ; default popup rules
;;pretty-code ; replace bits of code with pretty symbols
;;tabbar ; FIXME an (incomplete) tab bar for Emacs
treemacs ; a project drawer, like neotree but cooler
;;unicode ; extended unicode support for various languages
vc-gutter ; vcs diff in the fringe
vi-tilde-fringe ; fringe tildes to mark beyond EOB
(window-select +ace-window) ; visually switch windows
window-select ; visually switch windows
:editor
fold ; (nigh) universal code folding
;;(format +onsave) ; automated prettiness
;;lispy ; vim for lisp, for people who dont like vim
multiple-cursors ; editing in many places at once
;;parinfer ; turn lisp into python, sort of
rotate-text ; cycle region at point between text candidates
:emacs
(dired ; making dired pretty [functional]
;;+ranger ; bringing the goodness of ranger to dired
;;+icons ; colorful icons for dired-mode
)
electric ; smarter, keyword-based electric-indent
;;eshell ; a consistent, cross-platform shell (WIP)
imenu ; an imenu sidebar and searchable code index
;;term ; terminals in Emacs
vc ; version-control and Emacs, sitting in a tree
:tools
dired ; making dired pretty [functional]
electric-indent ; smarter, keyword-based electric-indent
eshell ; a consistent, cross-platform shell (WIP)
gist ; interacting with github gists
imenu ; an imenu sidebar and searchable code index
impatient-mode ; show off code over HTTP
;macos ; MacOS-specific commands
make ; run make tasks from Emacs
neotree ; a project drawer, like NERDTree for vim
password-store ; password manager for nerds
rotate-text ; cycle region at point between text candidates
term ; terminals in Emacs
tmux ; an API for interacting with tmux
upload ; map local to remote projects via ssh/ftp
;;ansible
;;direnv
;;docker
;;editorconfig ; let someone else argue about tabs vs spaces
;;ein ; tame Jupyter notebooks with emacs
flycheck ; tasing you for every semicolon you forget
;;flyspell ; tasing you for misspelling mispelling
;;gist ; interacting with github gists
;;lsp
;;macos ; MacOS-specific commands
magit ; a git porcelain for Emacs
;;make ; run make tasks from Emacs
;;password-store ; password manager for nerds
;;pdf ; pdf enhancements
;;prodigy ; FIXME managing external services & code builders
;;rgb ; creating color strings
;;terraform ; infrastructure as code
;;tmux ; an API for interacting with tmux
;;upload ; map local to remote projects via ssh/ftp
;;wakatime
;;vterm ; another terminals in Emacs
:lang
assembly ; assembly for fun or debugging
cc ; C/C++/Obj-C madness
crystal ; ruby at the speed of c
clojure ; java with a lisp
csharp ; unity, .NET, and mono shenanigans
;;agda ; types of types of types of types...
;;assembly ; assembly for fun or debugging
;;(cc +irony +rtags); C/C++/Obj-C madness
;;clojure ; java with a lisp
;;common-lisp ; if you've seen one lisp, you've seen them all
;;coq ; proofs-as-programs
;;crystal ; ruby at the speed of c
;;csharp ; unity, .NET, and mono shenanigans
data ; config/data formats
elixir ; erlang done right
elm ; care for a cup of TEA?
;;erlang ; an elegant language for a more civilized age
;;elixir ; erlang done right
;;elm ; care for a cup of TEA?
emacs-lisp ; drown in parentheses
go ; the hipster dialect
(haskell +intero) ; a language that's lazier than I am
hy ; readability of scheme w/ speed of python
(java +meghanada) ; the poster child for carpal tunnel syndrome
javascript ; all(hope(abandon(ye(who(enter(here))))))
julia ; a better, faster MATLAB
latex ; writing papers in Emacs has never been so fun
ledger ; an accounting system in Emacs
lua ; one-based indices? one-based indices
;;ess ; emacs speaks statistics
;;go ; the hipster dialect
;;(haskell +intero) ; a language that's lazier than I am
;;hy ; readability of scheme w/ speed of python
;;idris ;
;;(java +meghanada) ; the poster child for carpal tunnel syndrome
;;javascript ; all(hope(abandon(ye(who(enter(here))))))
;;julia ; a better, faster MATLAB
;;kotlin ; a better, slicker Java(Script)
;;latex ; writing papers in Emacs has never been so fun
;;ledger ; an accounting system in Emacs
;;lua ; one-based indices? one-based indices
markdown ; writing docs for people to ignore
ocaml ; an objective camel
;;nim ; python + lisp at the speed of c
;;nix ; I hereby declare "nix geht mehr!"
;;ocaml ; an objective camel
(org ; organize your plain life in plain text
+attach ; custom attachment system
+babel ; running code in org
+capture ; org-capture in and outside of Emacs
+export ; centralized export system + more backends
+export ; Exporting org to whatever you want
+habit ; Keep track of your habits
+present ; Emacs for presentations
;; TODO +publish
)
perl ; write code no one else can comprehend
php ; make php less awful to work with
plantuml ; diagrams for confusing people more
purescript ; javascript, but functional
python ; beautiful is better than ugly
rest ; Emacs as a REST client
ruby ; 1.step do {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
rust ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
scala ; java, but good
sh ; she sells (ba|z)sh shells on the C xor
swift ; who asked for emoji variables?
typescript ; javascript, but better
web ; the tubes
+protocol) ; Support for org-protocol:// links
;;perl ; write code no one else can comprehend
;;php ; perl's insecure younger brother
;;plantuml ; diagrams for confusing people more
;;purescript ; javascript, but functional
;;python ; beautiful is better than ugly
;;qt ; the 'cutest' gui framework ever
;;racket ; a DSL for DSLs
;;rest ; Emacs as a REST client
;;ruby ; 1.step do {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
;;rust ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
;;scala ; java, but good
(sh +fish) ; she sells (ba|z|fi)sh shells on the C xor
;;solidity ; do you need a blockchain? No.
;;swift ; who asked for emoji variables?
;;terra ; Earth and Moon in alignment for performance.
;;web ; the tubes
;;vala ; GObjective-C
;; Applications are complex and opinionated modules that transform Emacs
;; toward a specific purpose. They may have additional dependencies and
;; should be loaded late.
:app
;email ; emacs as an email client
;irc ; how neckbeards socialize
;rss ; emacs as an RSS reader
;twitter ; twitter client https://twitter.com/vnought
;write ; emacs as a word processor (latex + org + markdown)
;;(email +gmail) ; emacs as an email client
;;irc ; how neckbeards socialize
;;(rss +org) ; emacs as an RSS reader
;;twitter ; twitter client https://twitter.com/vnought
;;(write ; emacs as a word processor (latex + org + markdown)
;; +wordnut ; wordnet (wn) search
;; +langtool) ; a proofreader (grammar/style check) for Emacs
;; Private modules are where you place your personal configuration files.
;; By default, they are not tracked. There is one module included here,
;; the defaults module. It contains a Spacemacs-inspired keybinding
;; scheme and additional ex commands for evil-mode. Use it as a reference
;; for your own.
:private default)
:collab
;;floobits ; peer programming for a price
;;impatient-mode ; show off code over HTTP
:config
;; For literate config users. This will tangle+compile a config.org
;; literate config in your `doom-private-dir' whenever it changes.
;;literate
;; The default module sets reasonable defaults for Emacs. It also
;; provides a Spacemacs-inspired keybinding scheme and a smartparens
;; config. Use it as a reference for your own modules.
(default +bindings +smartparens))

View file

@ -1,23 +1,15 @@
;;; init.test.el -- for automated unit tests -*- lexical-binding: t; -*-
(require 'core (concat user-emacs-directory "core/core"))
(doom! :feature
evil
workspaces
:completion
company
:ui
doom-dashboard
popup
:tools
password-store
:lang
org
web
:private
hlissner)
web)

174
modules/README.org Normal file
View file

@ -0,0 +1,174 @@
#+TITLE: Doom Modules
* Table of Contents :TOC:noexport:
- [[#feature][:feature]]
- [[#completion][:completion]]
- [[#ui][:ui]]
- [[#editor][:editor]]
- [[#emacs][:emacs]]
- [[#tools][:tools]]
- [[#lang][:lang]]
- [[#app][:app]]
- [[#collab][:collab]]
- [[#config][:config]]
* :feature
Broad modules that bring essential IDE functionality to Emacs.
+ debugger: A (nigh-)universal debugger in Emacs
+ [[file:feature/eval/README.org][eval]]: REPL & code evaluation support for a variety of languages
+ [[file:feature/evil/README.org][evil]] =+everywhere=: Vim in Emacs
+ [[file:feature/file-templates/README.org][file-templates]]: Auto-inserted templates in blank new files
+ [[file:feature/lookup/README.org][lookup]] =+docsets=: Universal jump-to & documentation lookup backend
+ [[file:feature/snippets/README.org][snippets]]: A templating system for Emacs for lazy typers (aka programmers)
+ [[file:feature/workspaces/README.org][workspaces]]: Isolated workspaces
* :completion
Swappable completion modules for quickly narrowing down lists of candidates.
+ [[file:completion/company/README.org][company]] =+auto +childframe=: The ultimate code completion backend
+ helm =+fuzzy +childframe=: *Another* search engine for love and life
+ ido: The /other/ *other* search engine for love and life
+ [[file:completion/ivy/README.org][ivy]] =+fuzzy +childframe=: /The/ search engine for love and life
* :ui
Aesthetic modules that affect the Emacs interface or user experience.
+ [[file:ui/deft/README.org][deft]]:
+ [[file:ui/doom/README.org][doom]]:
+ [[file:ui/doom-dashboard/README.org][doom-dashboard]]:
+ [[file:ui/doom-quit/README.org][doom-quit]]:
+ fill-column:
+ [[file:ui/hl-todo/README.org][hl-todo]]:
+ indent-guides:
+ [[file:ui/modeline/README.org][modeline]]:
+ [[file:ui/nav-flash/README.org][nav-flash]]:
+ [[file:ui/neotree/README.org][neotree]]:
+ [[file:ui/ophints/README.org][ophints]]:
+ [[file:ui/popup/README.org][popup]] =+all +defaults=: Makes temporary/disposable windows less intrusive
+ pretty-code:
+ [[file:ui/tabbar/README.org][tabbar]]:
+ treemacs:
+ [[file:ui/unicode/README.org][unicode]]:
+ vc-gutter:
+ vi-tilde-fringe:
+ [[file:ui/window-select/README.org][window-select]]:
* :editor
Modules that affect and augment your ability to write and edit text.
+ [[file:editor/fold/README.org][fold]]: universal code folding
+ [[file:editor/format/README.org][format]] =+onsave=:
+ [[file:editor/lispy/README.org][lispy]]:
+ multiple-cursors:
+ [[file:editor/parinfer/README.org][parinfer]]:
+ rotate-text:
* :emacs
Modules that reconfigure packages or features built into Emacs
+ dired =+ranger +icons=:
+ electric:
+ eshell:
+ imenu:
+ term:
+ vc:
* :tools
Small modules that give Emacs access to external tools & services.
+ ansible:
+ docker:
+ [[file:tools/editorconfig/README.org][editorconfig]]:
+ [[file:tools/ein/README.org][ein]]:
+ flycheck: Live error/warning highlights
+ flyspell: Spell checking
+ gist:
+ [[file:tools/lsp/README.org][lsp]]:
+ macos:
+ magit:
+ make:
+ password-store:
+ pdf:
+ prodigy:
+ rgb:
+ terraform:
+ tmux:
+ upload:
+ [[file:tools/wakatime/README.org][wakatime]]:
+ vterm:
* :lang
Modules that bring support for a language or group of languages to Emacs.
+ agda:
+ assembly:
+ [[file:lang/cc/README.org][cc]] =+lsp=:
+ clojure:
+ common-lisp:
+ [[file:lang/coq/README.org][coq]]:
+ crystal:
+ [[file:lang/csharp/README.org][csharp]]:
+ data:
+ erlang:
+ elixir:
+ elm:
+ emacs-lisp:
+ [[file:lang/ess/README.org][ess]]:
+ [[file:lang/go/README.org][go]] =+lsp=:
+ [[file:lang/haskell/README.org][haskell]] =+intero +dante=:
+ hy:
+ [[file:lang/idris/README.org][idris]]:
+ java =+meghanada=:
+ [[file:lang/javascript/README.org][javascript]] =+lsp=:
+ julia:
+ kotlin:
+ [[file:lang/latex/README.org][latex]]:
+ ledger:
+ lua:
+ markdown:
+ [[file:lang/nim/README.org][nim]]:
+ nix:
+ [[file:lang/ocaml/README.org][ocaml]] =+lsp=:
+ [[file:lang/org/README.org][org]] =+attach +babel +capture +export +present +ipython=:
+ [[file:lang/perl/README.org][perl]]:
+ [[file:lang/php/README.org][php]] =+lsp=:
+ plantuml:
+ purescript:
+ python =+lsp=:
+ qt:
+ racket:
+ [[file:lang/rest/README.org][rest]]:
+ ruby =+lsp=:
+ [[file:lang/rust/README.org][rust]] =+lsp=:
+ scala:
+ [[file:lang/sh/README.org][sh]] =+fish +lsp=:
+ [[file:lang/solidity/README.org][solidity]]:
+ swift:
+ terra:
+ web =+lsp=:
+ vala:
* :app
Large, opinionated modules that transform and take over Emacs, i.e.
Doom-specific porcelains.
+ calendar:
+ [[file:app/email/README.org][email]] =+gmail=:
+ [[file:app/irc/README.org][irc]]:
+ rss =+org=:
+ twitter:
+ [[file:app/write/README.org][write]] =+wordnut +langtool=:
* :collab
Modules that enable collaborative programming over the internet.
+ floobits:
+ impatient-mode:
* :config
Modules that configure Emacs one way or another, or focus on making it easier
for you to customize it yourself.
+ literate:
+ [[file:config/default/README.org][default]] =+bindings +smartparens=:

View file

@ -0,0 +1,61 @@
;;; app/calendar/autoload.el -*- lexical-binding: t; -*-
(defvar +calendar--wconf nil)
(defun +calendar--init ()
(if-let* ((win (cl-loop for win in (doom-visible-windows)
if (string-match-p "^\\*cfw:" (buffer-name (window-buffer win)))
return win)))
(select-window win)
(call-interactively +calendar-open-function)))
;;;###autoload
(defun =calendar ()
"Activate (or switch to) `calendar' in its workspace."
(interactive)
(if (featurep! :feature workspaces)
(progn
(+workspace-switch "Calendar" t)
(doom/switch-to-scratch-buffer)
(+calendar--init)
(+workspace/display))
(setq +calendar--wconf (current-window-configuration))
(delete-other-windows)
(switch-to-buffer (doom-fallback-buffer))
(+calendar--init)))
;;;###autoload
(defun +calendar/quit ()
"TODO"
(interactive)
(if (featurep! :feature workspaces)
(+workspace/delete "Calendar")
(doom-kill-matching-buffers "^\\*cfw:")
(set-window-configuration +calendar--wconf)
(setq +calendar--wconf nil)))
;;;###autoload
(defun +calendar/open-calendar ()
"TODO"
(interactive)
(cfw:open-calendar-buffer
;; :custom-map cfw:my-cal-map
:contents-sources
(list
(cfw:org-create-source (doom-color 'fg)) ; orgmode source
)))
;;;###autoload
(defun +calendar*cfw:render-button (title command &optional state)
"render-button
TITLE
COMMAND
STATE"
(let ((text (concat " " title " "))
(keymap (make-sparse-keymap)))
(cfw:rt text (if state 'cfw:face-toolbar-button-on
'cfw:face-toolbar-button-off))
(define-key keymap [mouse-1] command)
(cfw:tp text 'keymap keymap)
(cfw:tp text 'mouse-face 'highlight)
text))

View file

@ -0,0 +1,56 @@
;;; app/calendar/config.el -*- lexical-binding: t; -*-
(defvar +calendar-org-gcal-secret-file
(expand-file-name "private/org/secret.el" doom-modules-dir)
"TODO")
(defvar +calendar-open-function #'+calendar/open-calendar
"TODO")
;;
;; Packages
(def-package! calfw
:commands (cfw:open-calendar-buffer)
:config
;; better frame for calendar
(setq cfw:face-item-separator-color nil
cfw:render-line-breaker 'cfw:render-line-breaker-none
cfw:fchar-junction ?╋
cfw:fchar-vertical-line ?┃
cfw:fchar-horizontal-line ?━
cfw:fchar-left-junction ?┣
cfw:fchar-right-junction ?┫
cfw:fchar-top-junction ?┯
cfw:fchar-top-left-corner ?┏
cfw:fchar-top-right-corner ?┓)
(define-key cfw:calendar-mode-map "q" #'+calendar/quit)
(add-hook 'cfw:calendar-mode-hook #'doom|mark-buffer-as-real)
(add-hook 'cfw:calendar-mode-hook 'hide-mode-line-mode)
(advice-add #'cfw:render-button :override #'+calendar*cfw:render-button))
(def-package! calfw-org
:commands (cfw:open-org-calendar
cfw:org-create-source
cfw:open-org-calendar-withkevin
my-open-calendar))
(def-package! org-gcal
:commands (org-gcal-sync
org-gcal-fetch
org-gcal-post-at-point
org-gcal-delete-at-point)
:config
(load-file +calendar-org-gcal-secret-file)
;; hack to avoid the deferred.el error
(defun org-gcal--notify (title mes)
(message "org-gcal::%s - %s" title mes)))
;; (def-package! alert)

View file

@ -0,0 +1,6 @@
;; -*- no-byte-compile: t; -*-
;;; app/calendar/packages.el
(package! calfw)
(package! calfw-org)
(package! org-gcal)

View file

@ -0,0 +1,71 @@
#+TITLE: `=Calendar App`
* Setup sync between google calendar and org file
:PROPERTIES:
:ID: 5E190E8A-CA26-4679-B5F8-BF9CFD289271
:END:
- Checkout https://github.com/myuhe/org-gcal.el, put the following content in a file ~secret.el~ and set the variable `+calendar-org-gcal-secret-file` to the path of that file.
#+BEGIN_SRC emacs-lisp
(setq org-gcal-client-id "your-id-foo.apps.googleusercontent.com"
org-gcal-client-secret "your-secret"
org-gcal-file-alist '(("your-mail@gmail.com" . "~/schedule.org")
("another-mail@gmail.com" . "~/task.org")))
#+END_SRC
* Doom faces
:PROPERTIES:
:ID: 8223894E-EA68-4259-A2EA-AF7E3653C610
:END:
I'm using the following setting:
#+BEGIN_SRC emacs-lisp
;; calfw
(cfw:face-title :foreground blue :bold bold :height 2.0 :inherit 'variable-pitch)
(cfw:face-header :foreground (doom-blend blue bg 0.8) :bold bold)
(cfw:face-sunday :foreground (doom-blend red bg 0.8) :bold bold)
(cfw:face-saturday :foreground (doom-blend red bg 0.8) :bold bold)
(cfw:face-holiday :foreground nil :background bg-alt :bold bold)
(cfw:face-grid :foreground vertical-bar)
(cfw:face-periods :foreground yellow)
(cfw:face-toolbar :foreground nil :background nil)
(cfw:face-toolbar-button-off :foreground base6 :bold bold :inherit 'variable-pitch)
(cfw:face-toolbar-button-on :foreground blue :bold bold :inherit 'variable-pitch)
(cfw:face-default-content :foreground fg)
(cfw:face-day-title :foreground fg :bold bold)
(cfw:face-today-title :foreground bg :background blue :bold bold)
(cfw:face-default-day :bold bold)
(cfw:face-today :foreground nil :background nil :bold bold)
(cfw:face-annotation :foreground violet)
(cfw:face-disable :foreground grey)
(cfw:face-select :background region)
#+END_SRC
* Adjust calendar to be included
:PROPERTIES:
:ID: D734975C-4B49-4F66-A088-AB2707A77537
:END:
Checkout example from https://github.com/kiwanami/emacs-calfw
#+BEGIN_SRC emacs-lisp
(defun my-open-calendar ()
(interactive)
(cfw:open-calendar-buffer
:contents-sources
(list
(cfw:org-create-source "Green") ; orgmode source
(cfw:howm-create-source "Blue") ; howm source
(cfw:cal-create-source "Orange") ; diary source
(cfw:ical-create-source "Moon" "~/moon.ics" "Gray") ; ICS source1
(cfw:ical-create-source "gcal" "https://..../basic.ics" "IndianRed") ; google calendar ICS
)))
#+END_SRC
Specifically, if you want to adjust the org files to be included, use a ~let~ binding to set the ~org-agenda-files~ like below:
#+BEGIN_SRC emacs-lisp
;;;###autoload
(defun cfw:open-org-calendar-with-cal1 ()
(interactive)
(let ((org-agenda-files '("/path/to/org/" "/path/to/cal1.org")))
(call-interactively '+calendar/open-calendar)))
;;;###autoload
(defun cfw:open-org-calendar-with-cal2 ()
(interactive)
(let ((org-agenda-files '("/path/to/org/" "/path/to/cal2.org")))
(call-interactively '+calendar/open-calendar)))
#+END_SRC

View file

@ -0,0 +1,45 @@
;;; app/email/+gmail.el -*- lexical-binding: t; -*-
(after! mu4e
;; don't save message to Sent Messages, Gmail/IMAP takes care of this
(setq mu4e-sent-messages-behavior 'delete
;; don't need to run cleanup after indexing for gmail
mu4e-index-cleanup nil
;; because gmail uses labels as folders we can use lazy check since
;; messages don't really "move"
mu4e-index-lazy-check t)
;; In my workflow, emails won't be moved at all. Only their flags/labels are
;; changed. Se we redefine the trash and refile marks not to do any moving.
;; However, the real magic happens in `+email|gmail-fix-flags'.
;;
;; Gmail will handle the rest.
(defun +email--mark-seen (docid msg target)
(mu4e~proc-move docid (mu4e~mark-check-target target) "+S-u-N"))
(delq (assq 'delete mu4e-marks) mu4e-marks)
(setf (alist-get 'trash mu4e-marks)
(list :char '("d" . "")
:prompt "dtrash"
:dyn-target (lambda (_target msg) (mu4e-get-trash-folder msg))
:action #'+email--mark-seen)
;; Refile will be my "archive" function.
(alist-get 'refile mu4e-marks)
(list :char '("d" . "")
:prompt "dtrash"
:dyn-target (lambda (_target msg) (mu4e-get-trash-folder msg))
:action #'+email--mark-seen))
;; This hook correctly modifies gmail flags on emails when they are marked.
;; Without it, refiling (archiving), trashing, and flagging (starring) email
;; won't properly result in the corresponding gmail action, since the marks
;; are ineffectual otherwise.
(defun +email|gmail-fix-flags (mark msg)
(pcase mark
(`trash (mu4e-action-retag-message msg "-\\Inbox,+\\Trash,-\\Draft"))
(`refile (mu4e-action-retag-message msg "-\\Inbox"))
(`flag (mu4e-action-retag-message msg "+\\Starred"))
(`unflag (mu4e-action-retag-message msg "-\\Starred"))))
(add-hook 'mu4e-mark-execute-pre-hook #'+email|gmail-fix-flags))

View file

@ -1,42 +1,75 @@
#+TITLE: :app email
#+TITLE: app/email
#+DATE: April 8, 2017
#+SINCE: v2.0
#+STARTUP: inlineimages
* Table of Contents :TOC:
- [[Description][Description]]
- [[Module Flags][Module Flags]]
- [[Plugins][Plugins]]
- [[Prerequisites][Prerequisites]]
- [[MacOS][MacOS]]
- [[Arch Linux][Arch Linux]]
- [[Features][Features]]
- [[Configuration][Configuration]]
- [[offlineimap][offlineimap]]
- [[mbsync][mbsync]]
* Description
This module makes Emacs an email client, using ~mu4e~.
#+begin_quote
I want to live in Emacs, but as we all know, living is incomplete without email. So I prayed to the text editor gods and they (I) answered. Emacs+evil's editing combined with org-mode for writing emails? /Yes please./
I want to live in Emacs, but as we all know, living is incomplete without email.
So I prayed to the text editor gods and they (I) answered. Emacs+evil's editing
combined with org-mode for writing emails? /Yes please./
It uses ~mu4e~ to read my email, but depends on ~offlineimap~ (to sync my email via IMAP) and ~mu~ (to index my mail into a format ~mu4e~ can understand).
WARNING: my config is gmail/gsuite oriented, and since Google has its own opinions on the IMAP standard, it is unlikely to translate to other hosts.
It uses ~mu4e~ to read my email, but depends on ~offlineimap~ (to sync my email
via IMAP) and ~mu~ (to index my mail into a format ~mu4e~ can understand).
#+end_quote
* Table of Contents :TOC:
- [[#install][Install]]
- [[#macos][MacOS]]
- [[#arch-linux][Arch Linux]]
- [[#dependencies][Dependencies]]
** Module Flags
+ ~+gmail~ Enables gmail-specific configuration.
* Install
** Plugins
+ [[https://github.com/agpchil/mu4e-maildirs-extension][mu4e-maildirs-extension]]
* Prerequisites
This module requires:
+ ~offlineimap~ (to sync mail with)
+ Either ~mbsync~ (default) or ~offlineimap~ (to sync mail with)
+ ~mu~ (to index your downloaded messages)
** MacOS
#+BEGIN_SRC sh :tangle (if (doom-system-os 'macos) "yes")
#+BEGIN_SRC sh
brew install mu --with-emacs
# And one of the following
brew install isync # mbsync
brew install offlineimap
#+END_SRC
** Arch Linux
#+BEGIN_SRC sh :dir /sudo:: :tangle (if (doom-system-os 'arch) "yes")
sudo pacman --noconfirm --needed -S offlineimap mu
#+BEGIN_SRC sh
sudo pacman --noconfirm --needed -S mu
# And one of the following
sudo pacman -S isync # mbsync
sudo pacman -S offlineimap
#+END_SRC
* Dependencies
You need to do the following:
* TODO Features
1. Write a ~\~/.offlineimaprc~. Mine can be found [[https://github.com/hlissner/dotfiles/tree/master/shell/+mu][in my dotfiles repository]]. It is configured to download mail to ~\~/.mail~. I use [[https://www.passwordstore.org/][unix pass]] to securely store my login credentials.
* Configuration
** offlineimap
This module uses =mbsync= by default. To change this, change ~+email-backend~:
#+BEGIN_SRC emacs-lisp
(setq +email-backend 'offlineimap)
#+END_SRC
Then you must set up offlineimap and index your mail:
1. Write a ~\~/.offlineimaprc~. Mine can be found [[https://github.com/hlissner/dotfiles/tree/master/shell/mu][in my dotfiles repository]]. It
is configured to download mail to ~\~/.mail~. I use [[https://www.passwordstore.org/][unix pass]] to securely
store my login credentials.
2. Download your email: ~offlineimap -o~ (may take a while)
3. Index it with mu: ~mu index --maildir ~/.mail~
@ -44,7 +77,7 @@ Then configure Emacs to use your email address:
#+BEGIN_SRC emacs-lisp :tangle no
;; Each path is relative to `+email-mu4e-mail-path', which is ~/.mail by default
(set! :email "Lissner.net"
(set-email-account! "Lissner.net"
'((mu4e-sent-folder . "/Lissner.net/Sent Mail")
(mu4e-drafts-folder . "/Lissner.net/Drafts")
(mu4e-trash-folder . "/Lissner.net/Trash")
@ -55,3 +88,4 @@ Then configure Emacs to use your email address:
t)
#+END_SRC
** TODO mbsync

View file

@ -1,10 +1,62 @@
;;; app/email/autoload/email.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-email-account! (label letvars &optional default-p)
"Registers an email address for mu4e. The LABEL is a string. LETVARS are a
list of cons cells (VARIABLE . VALUE) -- you may want to modify:
+ `user-full-name' (this or the global `user-full-name' is required)
+ `user-mail-address' (required)
+ `smtpmail-smtp-user' (required for sending mail from Emacs)
OPTIONAL:
+ `mu4e-sent-folder'
+ `mu4e-drafts-folder'
+ `mu4e-trash-folder'
+ `mu4e-refile-folder'
+ `mu4e-compose-signature'
DEFAULT-P is a boolean. If non-nil, it marks that email account as the
default/fallback account."
(after! mu4e
(when-let* ((address (cdr (assq 'user-mail-address letvars))))
(add-to-list 'mu4e-user-mail-address-list address))
(setq mu4e-contexts
(cl-loop for context in mu4e-contexts
unless (string= (mu4e-context-name context) label)
collect context))
(let ((context (make-mu4e-context
:name label
:enter-func (lambda () (mu4e-message "Switched to %s" label))
:leave-func #'mu4e-clear-caches
:match-func
(lambda (msg)
(when msg
(string-prefix-p (format "/%s" label)
(mu4e-message-field msg :maildir))))
:vars letvars)))
(push context mu4e-contexts)
(when default-p
(setq-default mu4e-context-current context))
context)))
(defvar +email-workspace-name "*mu4e*"
"TODO")
(add-hook 'mu4e-main-mode-hook #'+email|init)
;;;###autoload
(defun =email ()
"Start email client."
(interactive)
(call-interactively #'mu4e))
(require 'mu4e)
(+workspace-switch +email-workspace-name t)
(mu4e~start 'mu4e~main-view)
;; (save-selected-window
;; (prolusion-mail-show))
)
;;;###autoload
(defun +email/compose ()
@ -13,3 +65,15 @@
;; TODO Interactively select email account
(call-interactively #'mu4e-compose-new))
;;
;; Hooks
(defun +email|init ()
(add-hook 'kill-buffer-hook #'+email|kill-mu4e nil t))
(defun +email|kill-mu4e ()
;; (prolusion-mail-hide)
(when (+workspace-exists-p +email-workspace-name)
(+workspace/delete +email-workspace-name)))

View file

@ -4,88 +4,52 @@
;; to give me the ability to read, search, write and send my email. It does so
;; with `mu4e', and requires `offlineimap' and `mu' to be installed.
(defvar +email-mu4e-mail-path "~/.mail"
"The directory path of mu's maildir.")
(defvar +email-backend 'mbsync
"Which backend to use. Can either be offlineimap, mbsync or nil (manual).")
;;
;; Config
;;
;; Packages
(def-setting! :email (label letvars &optional default-p)
"Registers an email address for mu4e. The LABEL is a string. LETVARS are a
list of cons cells (VARIABLE . VALUE) -- you may want to modify:
(add-to-list 'auto-mode-alist '("\\.\\(?:offlineimap\\|mbsync\\)rc\\'" . conf-mode))
+ `user-full-name' (this or the global `user-full-name' is required)
+ `user-mail-address' (required)
+ `smtpmail-smtp-user' (required for sending mail from Emacs)
OPTIONAL:
+ `mu4e-sent-folder'
+ `mu4e-drafts-folder'
+ `mu4e-trash-folder'
+ `mu4e-refile-folder'
+ `mu4e-compose-signature'
DEFAULT-P is a boolean. If non-nil, it marks that email account as the
default/fallback account."
`(after! mu4e
(let ((account-vars ,letvars))
(when-let* ((address (cdr (assq 'user-mail-address account-vars))))
(cl-pushnew address mu4e-user-mail-address-list :test #'equal))
(let ((context (make-mu4e-context
:name ,label
:enter-func (lambda () (mu4e-message "Switched to %s" ,label))
:leave-func (lambda () (mu4e-clear-caches))
:match-func
(lambda (msg)
(when msg
(string-prefix-p (format "/%s" ,label)
(mu4e-message-field msg :maildir))))
:vars ,letvars)))
(push context mu4e-contexts)
,(when default-p
`(setq-default mu4e-context-current context))))))
;;
;; Plugins
;;
(def-package! mu4e
:commands (mu4e mu4e-compose-new)
:init
(provide 'html2text) ; disable obsolete package
(setq mu4e-maildir "~/.mail"
mu4e-attachment-dir "~/.mail/.attachments"
mu4e-user-mail-address-list nil)
:config
(setq mu4e-maildir +email-mu4e-mail-path
mu4e-attachment-dir "~/Downloads"
mu4e-user-mail-address-list nil
mu4e-update-interval nil
(pcase +email-backend
(`mbsync
(setq mu4e-get-mail-command "mbsync -a"
mu4e-change-filenames-when-moving t))
(`offlineimap
(setq mu4e-get-mail-command "offlineimap -o -q")))
(setq mu4e-update-interval nil
mu4e-compose-format-flowed t ; visual-line-mode + auto-fill upon sending
mu4e-view-show-addresses t
mu4e-sent-messages-behavior 'sent
mu4e-hide-index-messages t
;; try to show images
mu4e-view-show-images t
mu4e-view-image-max-width 800
;; don't save message to Sent Messages, Gmail/IMAP takes care of this
mu4e-sent-messages-behavior 'delete
;; allow for updating mail using 'U' in the main view:
;; for mbsync
;; mu4e-headers-skip-duplicates t
;; mu4e-change-filenames-when-moving nil
;; mu4e-get-mail-command "mbsync -a"
;; for offlineimap
mu4e-get-mail-command "offlineimap -o -q"
;; configuration for sending mail
message-send-mail-function #'smtpmail-send-it
smtpmail-stream-type 'starttls
message-kill-buffer-on-exit t ; close after sending
;; start with the first (default) context;
mu4e-context-policy 'pick-first
;; compose with the current context, or ask
mu4e-compose-context-policy 'ask-if-none
;; use helm/ivy
mu4e-completing-read-function (cond ((featurep! :completion ivy) #'ivy-completing-read)
mu4e-completing-read-function
(cond ((featurep! :completion ivy) #'ivy-completing-read)
((featurep! :completion helm) #'completing-read)
(t #'ido-completing-read))
;; close message after sending it
message-kill-buffer-on-exit t
;; no need to ask
mu4e-confirm-quit nil
;; remove 'lists' column
@ -94,14 +58,28 @@ default/fallback account."
(:human-date . 12)
(:flags . 4)
(:from . 25)
(:subject))
mu4e-bookmarks `(("\\\\Inbox" "Inbox" ?i)
("\\\\Draft" "Drafts" ?d)
("flag:unread AND \\\\Inbox" "Unread messages" ?u)
("flag:flagged" "Starred messages" ?s)
("date:today..now" "Today's messages" ?t)
("date:7d..now" "Last 7 days" ?w)
("mime:image/*" "Messages with images" ?p)))
(:subject)))
;; set mail user agent
(setq mail-user-agent 'mu4e-user-agent)
;; Use fancy icons
(setq mu4e-headers-has-child-prefix '("+" . "")
mu4e-headers-empty-parent-prefix '("-" . "")
mu4e-headers-first-child-prefix '("\\" . "")
mu4e-headers-duplicate-prefix '("=" . "")
mu4e-headers-default-prefix '("|" . "")
mu4e-headers-draft-mark '("D" . "")
mu4e-headers-flagged-mark '("F" . "")
mu4e-headers-new-mark '("N" . "")
mu4e-headers-passed-mark '("P" . "")
mu4e-headers-replied-mark '("R" . "")
mu4e-headers-seen-mark '("S" . "")
mu4e-headers-trashed-mark '("T" . "")
mu4e-headers-attach-mark '("a" . "")
mu4e-headers-encrypted-mark '("x" . "")
mu4e-headers-signed-mark '("s" . "")
mu4e-headers-unread-mark '("u" . ""))
;; Add a column to display what email account the email belongs to.
(add-to-list 'mu4e-header-info-custom
@ -114,152 +92,33 @@ default/fallback account."
(let ((maildir (mu4e-message-field msg :maildir)))
(format "%s" (substring maildir 1 (string-match-p "/" maildir 1)))))))
;; In my workflow, emails won't be moved at all. Only their flags/labels are
;; changed. Se we redefine the trash and refile marks not to do any moving.
;; However, the real magic happens in `+email|gmail-fix-flags'.
;;
;; Gmail will handle the rest.
(setq mu4e-marks (assq-delete-all 'delete mu4e-marks))
(setq mu4e-marks (assq-delete-all 'trash mu4e-marks))
(push '(trash :char ("d" . "")
:prompt "dtrash"
:dyn-target (lambda (target msg) (mu4e-get-trash-folder msg))
:action
(lambda (docid msg target)
(mu4e~proc-move docid (mu4e~mark-check-target target) "+S-u-N")))
mu4e-marks)
;; Refile will be my "archive" function.
(setq mu4e-marks (assq-delete-all 'refile mu4e-marks))
(push '(refile :char ("r" . "")
:prompt "refile"
:show-target (lambda (target) "archive")
:action
(lambda (docid msg target)
(mu4e~proc-move docid (mu4e~mark-check-target target) "+S-u-N")))
mu4e-marks)
;; This hook correctly modifies gmail flags on emails when they are marked.
;; Without it, refiling (archiving), trashing, and flagging (starring) email
;; won't properly result in the corresponding gmail action, since the marks
;; are ineffectual otherwise.
(defun +email|gmail-fix-flags (mark msg)
(cond ((eq mark 'trash) (mu4e-action-retag-message msg "-\\Inbox,+\\Trash,-\\Draft"))
((eq mark 'refile) (mu4e-action-retag-message msg "-\\Inbox"))
((eq mark 'flag) (mu4e-action-retag-message msg "+\\Starred"))
((eq mark 'unflag) (mu4e-action-retag-message msg "-\\Starred"))))
(add-hook 'mu4e-mark-execute-pre-hook #'+email|gmail-fix-flags)
;; Refresh the current view after marks are executed
(defun +email*refresh (&rest _) (mu4e-headers-rerun-search))
(advice-add #'mu4e-mark-execute-all :after #'+email*refresh)
(when (featurep! :feature spellcheck)
(when (featurep! :tools flyspell)
(add-hook 'mu4e-compose-mode-hook #'flyspell-mode))
;; Wrap text in messages
(add-hook! 'mu4e-view-mode-hook
(setq-local truncate-lines nil))
(setq-hook! 'mu4e-view-mode-hook truncate-lines nil)
(when (fboundp 'imagemagick-register-types)
(imagemagick-register-types))
(after! evil
(cl-loop for str in '((mu4e-main-mode . normal)
(mu4e-view-mode . normal)
(mu4e-headers-mode . normal)
(mu4e-compose-mode . normal)
(mu4e~update-mail-mode . normal))
do (evil-set-initial-state (car str) (cdr str)))
(setq mu4e-view-mode-map (make-sparse-keymap)
;; mu4e-compose-mode-map (make-sparse-keymap)
mu4e-headers-mode-map (make-sparse-keymap)
mu4e-main-mode-map (make-sparse-keymap))
(map! (:map (mu4e-main-mode-map mu4e-view-mode-map)
:leader
:n "," #'mu4e-context-switch
:n "." #'mu4e-headers-search-bookmark
:n ">" #'mu4e-headers-search-bookmark-edit
:n "/" #'mu4e~headers-jump-to-maildir)
(:map (mu4e-headers-mode-map mu4e-view-mode-map)
:localleader
:n "f" #'mu4e-compose-forward
:n "r" #'mu4e-compose-reply
:n "c" #'mu4e-compose-new
:n "e" #'mu4e-compose-edit)
(:map mu4e-main-mode-map
:n "q" #'mu4e-quit
:n "u" #'mu4e-update-index
:n "U" #'mu4e-update-mail-and-index
:n "J" #'mu4e~headers-jump-to-maildir
:n "c" #'+email/compose
:n "b" #'mu4e-headers-search-bookmark)
(:map mu4e-headers-mode-map
:n "q" #'mu4e~headers-quit-buffer
:n "r" #'mu4e-compose-reply
:n "c" #'mu4e-compose-edit
:n "s" #'mu4e-headers-search-edit
:n "S" #'mu4e-headers-search-narrow
:n "RET" #'mu4e-headers-view-message
:n "u" #'mu4e-headers-mark-for-unmark
:n "U" #'mu4e-mark-unmark-all
:n "v" #'evil-visual-line
:nv "d" #'+email/mark
:nv "=" #'+email/mark
:nv "-" #'+email/mark
:nv "+" #'+email/mark
:nv "!" #'+email/mark
:nv "?" #'+email/mark
:nv "r" #'+email/mark
:nv "m" #'+email/mark
:n "x" #'mu4e-mark-execute-all
:n "]]" #'mu4e-headers-next-unread
:n "[[" #'mu4e-headers-prev-unread
(:localleader
:n "s" 'mu4e-headers-change-sorting
:n "t" 'mu4e-headers-toggle-threading
:n "r" 'mu4e-headers-toggle-include-related
:n "%" #'mu4e-headers-mark-pattern
:n "t" #'mu4e-headers-mark-subthread
:n "T" #'mu4e-headers-mark-thread))
(:map mu4e-view-mode-map
:n "q" #'mu4e~view-quit-buffer
:n "r" #'mu4e-compose-reply
:n "c" #'mu4e-compose-edit
:n "o" #'ace-link-mu4e
:n "<M-Left>" #'mu4e-view-headers-prev
:n "<M-Right>" #'mu4e-view-headers-next
:n "[m" #'mu4e-view-headers-prev
:n "]m" #'mu4e-view-headers-next
:n "[u" #'mu4e-view-headers-prev-unread
:n "]u" #'mu4e-view-headers-next-unread
(:localleader
:n "%" #'mu4e-view-mark-pattern
:n "t" #'mu4e-view-mark-subthread
:n "T" #'mu4e-view-mark-thread
:n "d" #'mu4e-view-mark-for-trash
:n "r" #'mu4e-view-mark-for-refile
:n "m" #'mu4e-view-mark-for-move))
(:map mu4e~update-mail-mode-map
:n "q" #'mu4e-interrupt-update-mail))))
(set-evil-initial-state!
'(mu4e-main-mode
mu4e-view-mode
mu4e-headers-mode
mu4e-compose-mode
mu4e~update-mail-mode)
'normal))
(def-package! mu4e-maildirs-extension
:after mu4e
:config (mu4e-maildirs-extension-load))
:config
(mu4e-maildirs-extension)
(setq mu4e-maildirs-extension-title nil))
(def-package! org-mu4e
@ -273,3 +132,8 @@ default/fallback account."
(add-hook! 'message-send-hook
(setq-local org-mu4e-convert-to-html nil)))
;;
;; Sub-modules
(if (featurep! +gmail) (load! "+gmail"))

View file

@ -1,21 +1,64 @@
#+TITLE: :app irc
This module turns adds an IRC client to Emacs ([[https://github.com/jorgenschaefer/circe][~circe~)]] with native notifications ([[https://github.com/eqyiel/circe-notifications][circe-notifications]]).
#+TITLE: app/irc
#+DATE: June 11, 2017
#+SINCE: v2.0.3
#+STARTUP: inlineimages
* Table of Contents :TOC:
- [[#dependencies][Dependencies]]
- [[#configure][Configure]]
- [[#pass-the-unix-password-manager][Pass: the unix password manager]]
- [[#emacs-auth-source-api][Emacs' auth-source API]]
- [[Description][Description]]
- [[Module Flags][Module Flags]]
- [[Plugins][Plugins]]
- [[Dependencies][Dependencies]]
- [[Prerequisites][Prerequisites]]
- [[Features][Features]]
- [[An IRC Client in Emacs][An IRC Client in Emacs]]
- [[Configuration][Configuration]]
- [[Pass: the unix password manager][Pass: the unix password manager]]
- [[Emacs' auth-source API][Emacs' auth-source API]]
- [[Troubleshooting][Troubleshooting]]
* Description
This module turns adds an IRC client to Emacs with OS notifications.
** Module Flags
This module provides no flags.
** Plugins
+ [[https://github.com/jorgenschaefer/circe][circe]]
+ [[https://github.com/eqyiel/circe-notifications][circe-notifications]]
* Dependencies
This module has no dependencies, besides =gnutls-cli= or =openssl= for secure connections.
This module requires =gnutls-cli= or =openssl= for secure connections.
* Configure
Use the ~:irc~ setting to configure IRC servers. Its second argument (a plist) takes the same arguments as ~circe-network-options~.
* Prerequisites
This module has no direct prerequisites.
* Features
** An IRC Client in Emacs
To connect to IRC you can invoke the ~=irc~ function using =M-x= or your own
custom keybinding.
| command | description |
|---------+-------------------------------------------|
| ~=irc~ | Connect to IRC and all configured servers |
When in a circe buffer these keybindings will be available.
| command | key | description |
|-----------------------------+-----------+----------------------------------------------|
| ~+irc/tracking-next-buffer~ | =SPC m a= | Switch to the next active buffer |
| ~circe-command-JOIN~ | =SPC m j= | Join a channel |
| ~+irc/send-message~ | =SPC m m= | Send a private message |
| ~circe-command-NAMES~ | =SPC m n= | List the names of the current channel |
| ~circe-command-PART~ | =SPC m p= | Part the current channel |
| ~+irc/quit~ | =SPC m Q= | Kill the current circe session and workgroup |
| ~circe-reconnect~ | =SPC m R= | Reconnect the current server |
* Configuration
Use ~set-irc-server!~ to configure IRC servers. Its second argument (a plist)
takes the same arguments as ~circe-network-options~.
#+BEGIN_SRC emacs-lisp :tangle no
(set! :irc "chat.freenode.net"
(set-irc-server! "chat.freenode.net"
`(:tls t
:nick "doom"
:sasl-username "myusername"
@ -23,15 +66,19 @@ Use the ~:irc~ setting to configure IRC servers. Its second argument (a plist) t
:channels ("#emacs")))
#+END_SRC
*It is a obviously a bad idea to store auth-details in plaintext,* so here are some ways to avoid that:
*It is a obviously a bad idea to store auth-details in plaintext,* so here are
some ways to avoid that:
** Pass: the unix password manager
[[https://www.passwordstore.org/][Pass]] is my tool of choice. I use it to manage my passwords. If you activate the [[/modules/tools/password-store/README.org][:tools password-store]] module you get an elisp API through which to access your password store.
[[https://www.passwordstore.org/][Pass]] is my tool of choice. I use it to manage my passwords. If you activate the
[[../../../modules/tools/password-store/README.org][:tools password-store]] module you get an elisp API through which to access your
password store.
~:irc~'s plist can use functions instead of strings. ~+pass-get-user~ and ~+pass-get-secret~ can help here:
~set-irc-server!~ accepts a plist can use functions instead of strings.
~+pass-get-user~ and ~+pass-get-secret~ can help here:
#+BEGIN_SRC emacs-lisp :tangle no
(set! :irc "chat.freenode.net"
(set-irc-server! "chat.freenode.net"
`(:tls t
:nick "doom"
:sasl-username ,(+pass-get-user "irc/freenode.net")
@ -39,11 +86,13 @@ Use the ~:irc~ setting to configure IRC servers. Its second argument (a plist) t
:channels ("#emacs")))
#+END_SRC
But wait, there's more! This stores your password in a public variable which could be accessed or appear in backtraces. Not good! So we go a step further:
But wait, there's more! This stores your password in a public variable which
could be accessed or appear in backtraces. Not good! So we go a step further:
#+BEGIN_SRC emacs-lisp :tangle no
(set! :irc "chat.freenode.net"
(set-irc-server! "chat.freenode.net"
`(:tls t
:port 6697
:nick "doom"
:sasl-username ,(+pass-get-user "irc/freenode.net")
:sasl-password (lambda (&rest _) (+pass-get-secret "irc/freenode.net"))
@ -52,8 +101,18 @@ But wait, there's more! This stores your password in a public variable which cou
And you're good to go!
Note that =+pass-get-user= tries to find your username by looking for the fields
listed in =+pass-user-fields= (by default =login=, =user==, =username== and
=email=)=). An example configuration looks like
#+BEGIN_SRC txt :tangle no
mysecretpassword
username: myusername
#+END_SRC
** Emacs' auth-source API
~auth-source~ is built into Emacs. As suggested [[https://github.com/jorgenschaefer/circe/wiki/Configuration#safer-password-management][in the circe wiki]], you can store (and retrieve) encrypted passwords with it.
~auth-source~ is built into Emacs. As suggested [[https://github.com/jorgenschaefer/circe/wiki/Configuration#safer-password-management][in the circe wiki]], you can store
(and retrieve) encrypted passwords with it.
#+BEGIN_SRC emacs-lisp :tangle no
(setq auth-sources '("~/.authinfo.gpg"))
@ -71,10 +130,12 @@ And you're good to go!
(defun my-nickserv-password (server)
(my-fetch-password :user "forcer" :host "irc.freenode.net"))
(set! :irc "chat.freenode.net"
(set-irc-server! "chat.freenode.net"
'(:tls t
:port 6697
:nick "doom"
:sasl-password my-nickserver-password
:channels ("#emacs")))
#+END_SRC
* TODO Troubleshooting

View file

@ -20,11 +20,17 @@
If INHIBIT-WORKSPACE (the universal argument) is non-nil, don't spawn a new
workspace for it."
(interactive "P")
(if (+workspace-exists-p +irc--workspace-name)
(+workspace-switch +irc--workspace-name)
(and (+irc-setup-wconf inhibit-workspace)
(cond ((and (featurep! :feature workspaces)
(+workspace-exists-p +irc--workspace-name))
(+workspace-switch +irc--workspace-name))
((not (+irc-setup-wconf inhibit-workspace))
(user-error "Couldn't start up a workspace for IRC")))
(if (doom-buffers-in-mode 'circe-mode (buffer-list) t)
(message "Circe buffers are already open")
(if circe-network-options
(cl-loop for network in circe-network-options
collect (circe (car network))))))
collect (circe (car network)))
(call-interactively #'circe))))
;;;###autoload
(defun +irc/connect (&optional inhibit-workspace)
@ -36,6 +42,12 @@ workspace for it."
(and (+irc-setup-wconf inhibit-workspace)
(call-interactively #'circe)))
;;;###autoload
(defun +irc/send-message (who what)
"Send WHO a message containing WHAT."
(interactive "sWho: \nsWhat: ")
(circe-command-MSG who what))
;;;###autoload
(defun +irc/quit ()
"Kill current circe session and workgroup."
@ -78,3 +90,10 @@ argument) is non-nil only show channels in current server."
(defun +irc--ivy-switch-to-buffer-action (buffer)
(when (stringp buffer)
(ivy--switch-buffer-action (string-trim-left buffer))))
;;;###autoload
(defun +irc/tracking-next-buffer ()
"Dissables switching to an unread buffer unless in the irc workspace."
(interactive)
(when (derived-mode-p 'circe-mode)
(tracking-next-buffer)))

View file

@ -0,0 +1,9 @@
;;; app/irc/autoload/settings.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-irc-server! (server letvars)
"Registers an irc SERVER for circe.
See `circe-network-options' for details."
(after! circe
(push (cons server letvars) circe-network-options)))

View file

@ -1,7 +1,9 @@
;;; app/irc/config.el -*- lexical-binding: t; -*-
(defvar +irc-left-padding 13
"TODO")
"By how much spaces the left hand side of the line should be padded.
Below a value of 12 this may result in uneven alignment between the various
types of messages.")
(defvar +irc-truncate-nick-char ?…
"Character to displayed when nick > `+irc-left-padding' in length.")
@ -11,27 +13,29 @@
"If these commands are called pre prompt the buffer will scroll to `point-max'.")
(defvar +irc-disconnect-hook nil
"TODO")
"Runs each hook when circe noticies the connection has been disconnected.
Useful for scenarios where an instant reconnect will not be successful.")
(defvar +irc-bot-list '("fsbot" "rudybot")
"TODO")
"Nicks listed have `circe-fool-face' applied and will not be tracked.")
(defvar +irc-time-stamp-format "%H:%M"
"TODO")
"The format of time stamps.
See `format-time-string' for a full description of available
formatting directives. ")
(defvar +irc-notifications-watch-strings nil
"TODO")
"A list of strings which can trigger a notification. You don't need to put
your nick here.
See `circe-notifications-watch-strings'.")
(defvar +irc-defer-notifications nil
"How long to defer enabling notifications, in seconds (e.g. 5min = 300).
Useful for ZNC users who want to avoid the deluge of notifications during buffer
playback.")
(def-setting! :irc (server letvars)
"Registers an irc server for circe."
`(after! circe
(push (cons ,server ,letvars) circe-network-options)))
(defvar +irc--defer-timer nil)
(defsubst +irc--pad (left right)
@ -40,8 +44,7 @@ playback.")
;;
;; Plugins
;;
;; Packages
(def-package! circe
:commands (circe circe-server-buffers)
@ -56,6 +59,10 @@ playback.")
circe-format-self-say circe-format-say
circe-format-action (format "{nick:+%ss} * {body}" +irc-left-padding)
circe-format-self-action circe-format-action
circe-format-server-notice
(let ((left "-Server-")) (concat (make-string (- +irc-left-padding (length left)) ? )
(concat left " _ {body}")))
circe-format-notice (format "{nick:%ss} _ {body}" +irc-left-padding)
circe-format-server-topic
(+irc--pad "Topic" "{userhost}: {topic-diff}")
circe-format-server-join-in-channel
@ -70,6 +77,8 @@ playback.")
(+irc--pad "Quit" "{nick} ({userhost}) left {channel}: {reason}]")
circe-format-server-rejoin
(+irc--pad "Re-join" "{nick} ({userhost}), left {departuredelta} ago")
circe-format-server-netmerge
(+irc--pad "Netmerge" "{split}, split {ago} ago (Use /WL to see who's still missing)")
circe-format-server-nick-change
(+irc--pad "Nick" "{old-nick} ({userhost}) is now known as {new-nick}")
circe-format-server-nick-change-self
@ -83,8 +92,9 @@ playback.")
(add-hook 'circe-channel-mode-hook #'turn-on-visual-line-mode)
;; Let `+irc/quit' and `circe' handle buffer cleanup
(map! :map circe-mode-map [remap doom/kill-this-buffer] #'bury-buffer)
(defun +irc*circe-disconnect-hook (&rest _)
(run-hooks '+irc-disconnect-hook))
(advice-add 'circe--irc-conn-disconnected :after #'+irc*circe-disconnect-hook)
(defun +irc*circe-truncate-nicks ()
"Truncate long nicknames in chat output non-destructively."
@ -98,15 +108,48 @@ playback.")
+irc-truncate-nick-char)))))
(add-hook 'lui-pre-output-hook #'+irc*circe-truncate-nicks)
(defun +circe-buffer-p (buf)
"Return non-nil if BUF is a `circe-mode' buffer."
(with-current-buffer buf
(and (derived-mode-p 'circe-mode)
(eq (safe-persp-name (get-current-persp))
+irc--workspace-name))))
(add-hook 'doom-real-buffer-functions #'+circe-buffer-p)
(defun +irc|circe-message-option-bot (nick &rest ignored)
"Fontify known bots and mark them to not be tracked."
(when (member nick +irc-bot-list)
'((text-properties . (face circe-fool-face lui-do-not-track t)))))
(add-hook 'circe-message-option-functions #'+irc|circe-message-option-bot)
(after! solaire-mode
;; distinguish chat/channel buffers from server buffers.
(add-hook 'circe-chat-mode-hook #'solaire-mode)))
(defun +irc|add-circe-buffer-to-persp ()
(let ((persp (get-current-persp))
(buf (current-buffer)))
;; Add a new circe buffer to irc workspace when we're in another workspace
(unless (eq (safe-persp-name persp) +irc--workspace-name)
;; Add new circe buffers to the persp containing circe buffers
(persp-add-buffer buf (persp-get-by-name +irc--workspace-name))
;; Remove new buffer from accidental workspace
(persp-remove-buffer buf persp))))
(add-hook 'circe-mode-hook #'+irc|add-circe-buffer-to-persp)
;; Let `+irc/quit' and `circe' handle buffer cleanup
(define-key circe-mode-map [remap kill-buffer] #'bury-buffer)
;; Fail gracefully if not in a circe buffer
(global-set-key [remap tracking-next-buffer] #'+irc/tracking-next-buffer)
(map! :localleader
(:map circe-mode-map
"a" #'tracking-next-buffer
"j" #'circe-command-JOIN
"m" #'+irc/send-message
"p" #'circe-command-PART
"Q" #'+irc/quit
"R" #'circe-reconnect
(:when (featurep! :completion ivy)
"c" #'+irc/ivy-jump-to-channel))
(:map circe-channel-mode-map
"n" #'circe-command-NAMES)))
(def-package! circe-color-nicks
@ -144,10 +187,11 @@ playback.")
(def-package! lui
:commands lui-mode
:config
(map! :map lui-mode-map "C-u" #'lui-kill-to-beginning-of-line)
(when (featurep! :feature spellcheck)
(setq lui-flyspell-p t
lui-fill-type nil))
(define-key lui-mode-map "\C-u" #'lui-kill-to-beginning-of-line)
(setq lui-fill-type nil)
(when (featurep! :tools flyspell)
(setq lui-flyspell-p t))
(after! evil
(defun +irc|evil-insert ()
@ -182,6 +226,9 @@ Courtesy of esh-mode.el"
(add-hook! 'lui-mode-hook
(add-hook 'pre-command-hook #'+irc|preinput-scroll-to-bottom nil t))
;; enable a horizontal line marking the last read message
(add-hook! 'lui-mode-hook #'enable-lui-track-bar)
(defun +irc|init-lui-margins ()
(setq lui-time-stamp-position 'right-margin
lui-time-stamp-format +irc-time-stamp-format

View file

@ -0,0 +1,189 @@
;;; app/notmuch/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defun =notmuch ()
"Activate (or switch to) `notmuch' in its workspace."
(interactive)
(unless (featurep! :feature workspaces)
(user-error ":feature workspaces is required, but disabled"))
(condition-case-unless-debug e
(progn
(+workspace-switch "*MAIL*" t)
(if-let* ((buf (cl-find-if (lambda (it) (string-match-p "^\\*notmuch" (buffer-name (window-buffer it))))
(doom-visible-windows))))
(select-window (get-buffer-window buf))
(notmuch-search "tag:inbox"))
(+workspace/display))
('error
(+notmuch/quit)
(signal (car e) (cdr e)))))
;;
;; Commands
;;;###autoload
(defun +notmuch/quit ()
(interactive)
;; (+popup/close (get-buffer-window "*notmuch-hello*"))
(doom-kill-matching-buffers "^\\*notmuch")
(+workspace/delete "*MAIL*"))
;;;###autoload
(defun +notmuch/update ()
(interactive)
(start-process-shell-command
"notmuch update" nil
(pcase +notmuch-sync-backend
(`gmi
(concat "cd " +notmuch-mail-folder " && gmi push && gmi pull && notmuch new && afew -a -t"))
(`mbsync
"mbsync -a && notmuch new && afew -a -t")
(`mbsync-xdg
"mbsync -c \"$XDG_CONFIG_HOME\"/isync/mbsyncrc -a && notmuch new && afew -a -t")
(`offlineimap
"offlineimap && notmuch new && afew -a -t"))))
;;;###autoload
(defun +notmuch/search-delete ()
(interactive)
(notmuch-search-add-tag (list "+trash" "-inbox" "-unread"))
(notmuch-tree-next-message))
;;;###autoload
(defun +notmuch/tree-delete ()
(interactive)
(notmuch-tree-add-tag (list "+trash" "-inbox" "-unread"))
(notmuch-tree-next-message))
;;;###autoload
(defun +notmuch/search-spam ()
(interactive)
(notmuch-search-add-tag (list "+spam" "-inbox" "-unread"))
(notmuch-search-next-thread))
;;;###autoload
(defun +notmuch/tree-spam ()
(interactive)
(notmuch-tree-add-tag (list "+spam" "-inbox" "-unread"))
(notmuch-tree-next-message))
;;;###autoload
(defun +notmuch/open-message-with-mail-app-notmuch-tree ()
(interactive)
(let* ((msg-path (car (plist-get (notmuch-tree-get-message-properties) :filename)))
(temp (make-temp-file "notmuch-message-" nil ".eml")))
(shell-command-to-string (format "cp '%s' '%s'" msg-path temp))
(start-process-shell-command "email" nil (format "xdg-open '%s'" temp))))
;;;###autoload
(defun +notmuch/open-message-with-mail-app-notmuch-show ()
(interactive)
(let* ((msg-path (car (plist-get (notmuch-show-get-message-properties) :filename)))
(temp (make-temp-file "notmuch-message-" nil ".eml")))
(shell-command-to-string (format "cp '%s' '%s'" msg-path temp))
(start-process-shell-command "email" nil (format "xdg-open '%s'" temp))))
;;
;; Advice
;;;###autoload
(defun +notmuch*dont-confirm-on-kill-process (orig-fn &rest args)
"Don't prompt for confirmation when killing notmuch sentinel."
(let (confirm-kill-processes)
(apply orig-fn args)))
;; (defun +notmuch*hello-insert-searches (title query-list &rest options)
;; (widget-insert (propertize title 'face 'org-agenda-structure))
;; (if (and notmuch-hello-first-run (plist-get options :initially-hidden))
;; (add-to-list 'notmuch-hello-hidden-sections title))
;; (let ((is-hidden (member title notmuch-hello-hidden-sections))
;; (widget-push-button-prefix "")
;; (widget-push-button-suffix "")
;; (start (point)))
;; (if is-hidden
;; (widget-create 'push-button
;; :notify `(lambda (widget &rest ignore)
;; (setq notmuch-hello-hidden-sections
;; (delete ,title notmuch-hello-hidden-sections))
;; (notmuch-hello-update))
;; (propertize " +" 'face 'org-agenda-structure))
;; (widget-create 'push-button
;; :notify `(lambda (widget &rest ignore)
;; (add-to-list 'notmuch-hello-hidden-sections
;; ,title)
;; (notmuch-hello-update))
;; " -"))
;; (widget-insert "\n")
;; (when (not is-hidden)
;; (let ((searches (apply 'notmuch-hello-query-counts query-list options)))
;; (when (or (not (plist-get options :hide-if-empty))
;; searches)
;; (widget-insert "\n")
;; (notmuch-hello-insert-buttons searches)
;; (indent-rigidly start (point) notmuch-hello-indent))))))
;; (defun +notmuch*hello-insert-saved-searches ()
;; "Insert the saved-searches section."
;; (let ((searches (notmuch-hello-query-counts
;; (if notmuch-saved-search-sort-function
;; (funcall notmuch-saved-search-sort-function
;; notmuch-saved-searches)
;; notmuch-saved-searches)
;; :show-empty-searches notmuch-show-empty-saved-searches)))
;; (when searches
;; (widget-insert (propertize "Notmuch" 'face 'org-agenda-date-today))
;; (widget-insert "\n\n")
;; (widget-insert (propertize "Saved searches" 'face 'org-agenda-structure))
;; (widget-insert "\n\n")
;; (let ((start (point)))
;; (notmuch-hello-insert-buttons searches)
;; (indent-rigidly start (point) notmuch-hello-indent)))))
;; (defun +notmuch*hello-insert-buttons (searches)
;; (let* ((widest (notmuch-hello-longest-label searches))
;; (tags-and-width (notmuch-hello-tags-per-line widest))
;; (tags-per-line (car tags-and-width))
;; (column-width (cdr tags-and-width))
;; (column-indent 0)
;; (count 0)
;; (reordered-list (notmuch-hello-reflect searches tags-per-line))
;; ;; Hack the display of the buttons used.
;; (widget-push-button-prefix "")
;; (widget-push-button-suffix ""))
;; ;; dme: It feels as though there should be a better way to
;; ;; implement this loop than using an incrementing counter.
;; (mapc (lambda (elem)
;; ;; (not elem) indicates an empty slot in the matrix.
;; (when elem
;; (if (> column-indent 0)
;; (widget-insert (make-string column-indent ? )))
;; (let* ((name (plist-get elem :name))
;; (query (plist-get elem :query))
;; (oldest-first (case (plist-get elem :sort-order)
;; (newest-first nil)
;; (oldest-first t)
;; (otherwise notmuch-search-oldest-first)))
;; (search-type (eq (plist-get elem :search-type) 'tree))
;; (msg-count (plist-get elem :count)))
;; (widget-insert (format "\n%5s "
;; (notmuch-hello-nice-number msg-count)))
;; (widget-create 'push-button
;; :notify #'notmuch-hello-widget-search
;; :notmuch-search-terms query
;; :notmuch-search-oldest-first oldest-first
;; :notmuch-search-type search-type
;; name)
;; (setq column-indent
;; (1+ (max 0 (- column-width (length name)))))))
;; (setq count (1+ count))
;; (when (eq (% count tags-per-line) 0)
;; (setq column-indent 0)
;; (widget-insert "\n")))
;; reordered-list)
;; ;; If the last line was not full (and hence did not include a
;; ;; carriage return), insert one now.
;; (unless (eq (% count tags-per-line) 0)
;; (widget-insert "\n"))))

View file

@ -0,0 +1,74 @@
;;; app/notmuch/config.el -*- lexical-binding: t; -*-
;; FIXME This module is a WIP!
(defvar +notmuch-sync-backend 'gmi
"Which backend to use. Can be either gmi, mbsync, offlineimap or nil (manual).")
(defvar +notmuch-mail-folder "~/.mail/account.gmail"
"Where your email folder is located (for use with gmailieer).")
(after! notmuch
(set-company-backend! 'notmuch-message-mode
'(notmuch-company (company-ispell :with company-yasnippet)))
(set-popup-rule! "^\\*notmuch-hello" :side 'left :size 30 :ttl 0)
(setq notmuch-fcc-dirs nil
notmuch-show-logo nil
notmuch-message-headers-visible nil
message-kill-buffer-on-exit t
message-send-mail-function 'message-send-mail-with-sendmail
notmuch-search-oldest-first nil
send-mail-function 'sendmail-send-it
;; sendmail-program "/usr/local/bin/msmtp"
notmuch-search-result-format
'(("date" . "%12s ")
("count" . "%-7s ")
("authors" . "%-30s ")
("subject" . "%-72s ")
("tags" . "(%s)"))
notmuch-tag-formats
'(("unread" (propertize tag 'face 'notmuch-tag-unread)))
notmuch-hello-sections
'(notmuch-hello-insert-saved-searches
notmuch-hello-insert-alltags)
notmuch-saved-searches
'((:name "inbox" :query "tag:inbox not tag:trash" :key "i")
(:name "flagged" :query "tag:flagged" :key "f")
(:name "sent" :query "tag:sent" :key "s")
(:name "drafts" :query "tag:draft" :key "d"))
notmuch-archive-tags '("-inbox" "-unread"))
;; (setq-hook! 'notmuch-show-mode-hook line-spacing 0)
(add-to-list 'doom-real-buffer-functions #'notmuch-interesting-buffer nil #'eq)
(advice-add #'notmuch-start-notmuch-sentinel :around #'+notmuch*dont-confirm-on-kill-process)
;; Visual enhancements
(defun +notmuch|center-window ()
(setq-local visual-fill-column-width 90)
(visual-fill-column-mode))
(add-hook 'notmuch-show-mode-hook #'+notmuch|center-window)
;; modeline doesn't have much use in these modes
(add-hook! (notmuch-show-mode notmuch-tree-mode notmuch-search-mode)
#'hide-mode-line-mode))
(def-package! org-mime
:after (org notmuch)
:config (setq org-mime-library 'mml))
(def-package! counsel-notmuch
:when (featurep! :completion ivy)
:commands counsel-notmuch
:after notmuch)
(def-package! helm-notmuch
:when (featurep! :completion helm)
:commands helm-notmuch
:after notmuch)

Some files were not shown because too many files have changed in this diff Show more