Merge branch 'develop' into add_shellcheck
This commit is contained in:
commit
e5e05f9d51
584 changed files with 32999 additions and 15507 deletions
39
.github/ISSUE_TEMPLATE
vendored
39
.github/ISSUE_TEMPLATE
vendored
|
@ -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
13
.gitignore
vendored
|
@ -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
|
||||
|
|
21
.travis.yml
21
.travis.yml
|
@ -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
|
||||
|
|
1005
CHANGELOG.org
1005
CHANGELOG.org
File diff suppressed because it is too large
Load diff
2
LICENSE
2
LICENSE
|
@ -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
108
Makefile
|
@ -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
272
README.md
|
@ -1,137 +1,197 @@
|
|||

|
||||
[](https://travis-ci.org/hlissner/doom-emacs)
|
||||
[](https://travis-ci.org/hlissner/doom-emacs)
|
||||
[](./LICENSE)
|
||||
<img src="https://raw.githubusercontent.com/hlissner/doom-emacs/screenshots/main.png" alt="Main screenshot" />
|
||||
|
||||
[](/../../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> |
|
||||
<a href="/../../tree/screenshots">screenshots</a> |
|
||||
<a href="/../../faq.org">faq</a> |
|
||||
<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
107
bin/doom
Executable 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))))))))
|
533
bin/doom-doctor
533
bin/doom-doctor
|
@ -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,110 +99,139 @@
|
|||
(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")
|
||||
(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."
|
||||
(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 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.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))
|
||||
(warn! "Warning: Windows detected")
|
||||
(explain! "DOOM was designed for MacOS and Linux. Expect a bumpy ride!"))
|
||||
(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")
|
||||
(if (not (fboundp 'find-font))
|
||||
(progn
|
||||
(warn! "Warning: unable to detect font")
|
||||
(explain! "The `find-font' function is missing. This could indicate the incorrect "
|
||||
"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"))
|
||||
"/fonts/"))
|
||||
('darwin (concat (getenv "HOME") "/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))
|
||||
(success! "Found font %s" font)
|
||||
(warn! "Warning: couldn't find %s font in %s"
|
||||
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."))))))
|
||||
;; are all default fonts present?
|
||||
(section! "Checking your fonts...")
|
||||
(if (not (fboundp 'find-font))
|
||||
(progn
|
||||
(warn! "Warning: unable to detect font")
|
||||
(explain! "The `find-font' function is missing. This could indicate the incorrect "
|
||||
"version of Emacs is being used!"))
|
||||
;; all-the-icons fonts
|
||||
(let ((font-dest (pcase system-type
|
||||
(`gnu/linux (concat (or (getenv "XDG_DATA_HOME")
|
||||
"~/.local/share")
|
||||
"/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))
|
||||
(success! "Found font %s" font)
|
||||
(warn! "Warning: couldn't find %s font in %s"
|
||||
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 feel free to ignore this warning."))))))
|
||||
|
||||
;; gnutls-cli & openssl
|
||||
(section! "test-gnutls")
|
||||
(cond ((executable-find "gnutls-cli"))
|
||||
((executable-find "openssl")
|
||||
(let* ((output (sh "openssl ciphers -v"))
|
||||
(protocols
|
||||
(let (protos)
|
||||
(mapcar (lambda (row)
|
||||
(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)))
|
||||
(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!
|
||||
"This may not affect your Emacs experience, but there are security "
|
||||
"vulnerabilities in the SSL2/3 & TLS1.0 protocols. You should use "
|
||||
"TLS 1.1+, which wasn't introduced until OpenSSL v1.0.1.\n\n"
|
||||
;; gnutls-cli & openssl
|
||||
(section! "Checking gnutls/openssl...")
|
||||
(cond ((executable-find "gnutls-cli"))
|
||||
((executable-find "openssl")
|
||||
(let* ((output (sh "openssl ciphers -v"))
|
||||
(protocols
|
||||
(let (protos)
|
||||
(mapcar (lambda (row)
|
||||
(add-to-list 'protos (cadr (split-string row " " t))))
|
||||
(split-string (sh "openssl ciphers -v") "\n"))
|
||||
(delq nil protos))))
|
||||
(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!
|
||||
"This may not affect your Emacs experience, but there are security "
|
||||
"vulnerabilities in the SSL2/3 & TLS1.0 protocols. You should use "
|
||||
"TLS 1.1+, which wasn't introduced until OpenSSL v1.0.1.\n\n"
|
||||
|
||||
"Please consider updating (or install gnutls-cli, which is preferred).")))))
|
||||
(t
|
||||
(check! t
|
||||
"Please consider updating (or install gnutls-cli, which is preferred).")))))
|
||||
(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,127 +244,156 @@
|
|||
|
||||
"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")
|
||||
(explain!
|
||||
"This may cause 'pecular error' errors with the Doom doctor, and is likely to "
|
||||
"interfere with package management. Your mileage may vary."
|
||||
(when (eq system-type 'darwin)
|
||||
(concat "\nMacOS users are advised to install Emacs via homebrew with one of the following:\n"
|
||||
" brew install emacs --with-gnutls"
|
||||
" or"
|
||||
" brew tap d12frosted/emacs-plus"
|
||||
" brew install emacs-plus"))))
|
||||
;; 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."
|
||||
(when (eq system-type 'darwin)
|
||||
(concat "\nMacOS users are advised to install Emacs via homebrew with one of the following:\n"
|
||||
" brew install emacs --with-gnutls"
|
||||
" or"
|
||||
" brew tap d12frosted/emacs-plus"
|
||||
" brew install emacs-plus"))))
|
||||
|
||||
((not (fboundp 'url-retrieve-synchronously))
|
||||
(error! "Can't find url-retrieve-synchronously function. Are you running Emacs 24+?"))
|
||||
((not (fboundp 'url-retrieve-synchronously))
|
||||
(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))
|
||||
'empty)
|
||||
('timed-out 'timeout)
|
||||
('error e))
|
||||
(pcase it
|
||||
(`empty (error! "Couldn't reach %s" url))
|
||||
(`timeout (error! "Timed out trying to contact %s" ex))
|
||||
(_
|
||||
(error! "Failed to validate %s" url)
|
||||
(when doom-debug-mode
|
||||
(explain! (pp-to-string it)))))))
|
||||
(dolist (url '("https://self-signed.badssl.com"
|
||||
"https://wrong.host.badssl.com/"))
|
||||
(check! (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))))
|
||||
(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)))))))
|
||||
((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"))
|
||||
(when! (condition-case-unless-debug e
|
||||
(unless (let ((inhibit-message t)) (url-retrieve-synchronously url))
|
||||
'empty)
|
||||
('timed-out 'timeout)
|
||||
('error e))
|
||||
(pcase it
|
||||
(`empty (error! "Couldn't reach %s" url))
|
||||
(`timeout (error! "Timed out trying to contact %s" ex))
|
||||
(_
|
||||
(error! "Failed to validate %s" url)
|
||||
(explain! (pp-to-string it))))))
|
||||
(dolist (url '("https://self-signed.badssl.com"
|
||||
"https://wrong.host.badssl.com/"))
|
||||
(when! (condition-case-unless-debug e
|
||||
(if (let ((inhibit-message t)) (url-retrieve-synchronously url))
|
||||
t
|
||||
'empty)
|
||||
('timed-out 'timeout)
|
||||
('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")
|
||||
(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))))
|
||||
(warn! "Warning: BSD tar detected")
|
||||
(explain!
|
||||
"QUELPA (through package-build) uses the system tar to build plugins, but it "
|
||||
"expects GNU tar. BSD tar *could* cause errors during package installation or "
|
||||
"updating from non-ELPA sources."
|
||||
(when (eq system-type 'darwin)
|
||||
(concat "\nMacOS users can install gnu-tar via homebrew:\n"
|
||||
" brew install gnu-tar"))))
|
||||
(check! t ; very unlikely
|
||||
;; 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
|
||||
(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 "
|
||||
"expects GNU tar. BSD tar *could* cause errors during package installation or "
|
||||
"updating from non-ELPA sources."
|
||||
(when (eq system-type 'darwin)
|
||||
(concat "\nMacOS users can install gnu-tar via homebrew:\n"
|
||||
" brew install gnu-tar"))))
|
||||
(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
25
bin/doom.cmd
Normal 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
|
|
@ -3,41 +3,40 @@
|
|||
# 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
|
||||
emacs --daemon
|
||||
trap cleanup EXIT INT TERM
|
||||
daemon=1
|
||||
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
|
||||
key="\"$opt\""
|
||||
break
|
||||
# keys=$(emacsclient -e "(+org-capture-available-keys)" | cut -d '"' -f2)
|
||||
while getopts hk opt; do
|
||||
key="\"$opt\""
|
||||
break
|
||||
done
|
||||
shift $((OPTIND-1))
|
||||
|
||||
[ -t 0 ] && str="$*" || str=$(cat)
|
||||
|
||||
if [[ $daemon ]]; then
|
||||
emacsclient -a "" \
|
||||
-c -F '((name . "org-capture") (width . 70) (height . 25))' \
|
||||
-e "(+org-capture/open-frame \"$str\" ${key:-nil})"
|
||||
emacsclient -a "" \
|
||||
-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.
|
||||
emacsclient -a "" \
|
||||
-e "(+org-capture/open-frame \"$str\" ${key:-nil})"
|
||||
# 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
|
||||
|
|
126
bin/org-tangle
126
bin/org-tangle
|
@ -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"
|
||||
"[1mUsage:[0m"
|
||||
(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"
|
||||
"[1mExample:[0m\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"
|
||||
"[1mOptions:[0m\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))
|
||||
|
|
|
@ -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 (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)))))))
|
||||
(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 (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
95
core/autoload/cache.el
Normal 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
68
core/autoload/cli.el
Normal 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
83
core/autoload/config.el
Normal 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)))
|
|
@ -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."
|
||||
;;;###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")
|
||||
(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/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)
|
||||
(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) " "))
|
||||
"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))))))
|
||||
|
||||
;;;###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))))))
|
||||
(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"))))
|
||||
|
|
|
@ -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
210
core/autoload/files.el
Normal 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)))
|
|
@ -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
47
core/autoload/hydras.el
Normal 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))
|
92
core/autoload/line-numbers.el
Normal file
92
core/autoload/line-numbers.el
Normal 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)
|
|
@ -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)))
|
||||
|
|
@ -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")))))))
|
||||
|
|
@ -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)))
|
||||
|
|
|
@ -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)))
|
|
@ -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))
|
||||
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)))))
|
||||
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 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
|
||||
`(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)))))))
|
||||
(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 ()
|
||||
(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"))
|
||||
(let ((quelpa-upgrade-p t))
|
||||
(quelpa (assq name quelpa-cache))))
|
||||
('elpa
|
||||
(`quelpa
|
||||
(condition-case e
|
||||
(let ((quelpa-upgrade-p t))
|
||||
(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
|
||||
(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)))))
|
||||
(selection (if packages
|
||||
(completing-read "Update package: "
|
||||
(mapcar #'car packages)
|
||||
nil t)
|
||||
(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)
|
||||
|
|
|
@ -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
127
core/autoload/projects.el
Normal 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)))))
|
|
@ -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*"))
|
||||
(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))
|
||||
(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|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|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
|
||||
(rename-buffer (format "*doom:scratch (%s)*" (projectile-project-name))))
|
||||
(setq default-directory old-project)
|
||||
(when (and (not (eq major-mode mode))
|
||||
derived-p
|
||||
(functionp mode))
|
||||
(funcall mode))
|
||||
(if text (insert text))
|
||||
(run-hooks 'doom-scratch-buffer-hook)
|
||||
(current-buffer))))
|
||||
(doom-project-name))))))
|
||||
|
||||
;;;###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/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/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."
|
||||
(defun doom/revert-scratch-buffer ()
|
||||
"Revert scratch buffer to last persistent state."
|
||||
(interactive)
|
||||
(doom-popup-buffer (doom--create-scratch-buffer t)))
|
||||
(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
119
core/autoload/sessions.el
Normal 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"))))
|
|
@ -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)))))
|
||||
|
|
@ -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
181
core/autoload/text.el
Normal 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))
|
|
@ -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
368
core/cli/autoloads.el
Normal 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
190
core/cli/byte-compile.el
Normal 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
7
core/cli/debug.el
Normal 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
123
core/cli/env.el
Normal 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
179
core/cli/packages.el
Normal 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
85
core/cli/patch-macos.el
Normal 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
92
core/cli/quickstart.el
Normal 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
69
core/cli/test.el
Normal 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
85
core/cli/upgrade.el
Normal 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
164
core/core-cli.el
Normal 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
|
|
@ -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
|
||||
;;
|
||||
|
||||
;; revert buffers for changed files
|
||||
(global-auto-revert-mode 1)
|
||||
(setq auto-revert-verbose nil)
|
||||
(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)))
|
||||
|
||||
;; enabled by default in Emacs 25+. No thanks.
|
||||
(electric-indent-mode -1)
|
||||
(def-package! autorevert
|
||||
;; revert buffers for changed files
|
||||
:after-call after-find-file
|
||||
:config
|
||||
(setq auto-revert-verbose nil)
|
||||
(global-auto-revert-mode +1))
|
||||
|
||||
;; savehist / saveplace
|
||||
(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))
|
||||
(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))
|
||||
(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))
|
||||
|
||||
;; Branching undo
|
||||
(def-package! undo-tree
|
||||
|
||||
(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
|
||||
(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 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))
|
||||
|
||||
|
||||
(def-package! undo-tree
|
||||
;; Branching & persistent undo
|
||||
:after-call (doom-switch-buffer-hook after-find-file)
|
||||
:config
|
||||
(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
|
||||
|
|
|
@ -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,188 +229,203 @@ 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
|
||||
:v visual
|
||||
:i insert
|
||||
:e emacs
|
||||
:o operator
|
||||
:m motion
|
||||
:r replace
|
||||
:n normal
|
||||
:v visual
|
||||
:i insert
|
||||
:e emacs
|
||||
: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
|
||||
\"C-x C-r\" 'a-global-keybind
|
||||
(map! :map magit-mode-map
|
||||
: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))))
|
||||
(:when IS-MAC
|
||||
:n \"M-s\" 'some-fn
|
||||
:i \"M-o\" (lambda (interactive) (message \"Hi\"))))"
|
||||
(doom--map-process rest))
|
||||
|
||||
(provide 'core-keybinds)
|
||||
;;; core-keybinds.el ends here
|
||||
|
|
579
core/core-lib.el
579
core/core-lib.el
|
@ -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)))
|
||||
#'progn
|
||||
#'with-no-warnings)
|
||||
(with-eval-after-load ',feature ,@forms)))
|
||||
`(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)
|
||||
(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)
|
||||
(let ((old-fn (symbol-function 'write-region)))
|
||||
(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)
|
||||
(save-silently t))
|
||||
,@forms))))
|
||||
"Run FORMS without making any output."
|
||||
`(cond (noninteractive
|
||||
(let ((old-fn (symbol-function 'write-region)))
|
||||
(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))))
|
||||
,@forms)))
|
||||
((or doom-debug-mode debug-on-error debug-on-quit)
|
||||
,@forms)
|
||||
((let ((inhibit-message t)
|
||||
(save-silently t))
|
||||
(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)
|
||||
(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)
|
||||
(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)))
|
||||
,@(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))))))
|
||||
(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))))))
|
||||
(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
|
||||
(fset ',hook-name
|
||||
(lambda ()
|
||||
(and (fboundp ',mode)
|
||||
(not (bound-and-true-p ,mode))
|
||||
(and buffer-file-name (not (file-remote-p buffer-file-name)))
|
||||
,(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))))))
|
||||
(match
|
||||
`(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
472
core/core-modules.el
Normal 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
|
|
@ -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))
|
||||
|
||||
(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))
|
||||
;; 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)
|
||||
|
||||
;; stop copying each visual state move to the clipboard:
|
||||
;; https://bitbucket.org/lyro/evil/issue/336/osx-visual-state-copies-the-region-on
|
||||
;; 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
|
||||
|
|
|
@ -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,399 +32,120 @@
|
|||
;; 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
|
||||
(message "Installing core packages")
|
||||
(unless doom--refreshed-p
|
||||
(package-refresh-contents))
|
||||
(dolist (package core-packages)
|
||||
(let ((inhibit-message t))
|
||||
(package-install package))
|
||||
(if (package-installed-p package)
|
||||
(message "✓ Installed %s" package)
|
||||
(error "✕ Couldn't install %s" package)))
|
||||
(message "Installing core packages...done")))
|
||||
(setq doom-init-p t))))
|
||||
(package-initialize))))))
|
||||
|
||||
(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)))))
|
||||
(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))
|
||||
(dolist (package core-packages)
|
||||
(let ((inhibit-message t))
|
||||
(package-install package))
|
||||
(if (package-installed-p package)
|
||||
(message "✓ Installed %s" package)
|
||||
(error "✕ Couldn't install %s" package)))
|
||||
(message "Installing core packages...done")))
|
||||
|
||||
|
||||
;;
|
||||
;; 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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
802
core/core-ui.el
802
core/core-ui.el
|
@ -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
|
||||
cursor-in-non-selected-windows nil ; hide cursors in other windows
|
||||
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)
|
||||
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)
|
||||
|
||||
;; Highlights the current line
|
||||
(def-package! hl-line ; built-in
|
||||
(def-package! hl-line
|
||||
;; Highlights the current line
|
||||
: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
|
||||
visual-fill-column-width
|
||||
;; take Emacs 26 line numbers into account
|
||||
(+ (if (boundp 'display-line-numbers) 6 0)
|
||||
fill-column)))
|
||||
(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 ?\ [?·] [?.])))
|
||||
|
||||
|
||||
;;
|
||||
;; Line numbers
|
||||
;;; 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 EMACS26+ 6 0) fill-column))
|
||||
|
||||
|
||||
;;
|
||||
;;; 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.
|
||||
;; Line number column. A faster (or equivalent, in the worst case) line number
|
||||
;; plugin than `linum-mode'.
|
||||
;; `nlinum' is used for Emacs 25 users, as Emacs 26+ has native line numbers.
|
||||
(def-package! nlinum
|
||||
:unless (boundp 'display-line-numbers)
|
||||
:commands nlinum-mode
|
||||
;; Line number column. A faster (or equivalent, in the worst case) line number
|
||||
;; plugin than `linum-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
|
||||
|
|
590
core/core.el
590
core/core.el
|
@ -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
|
||||
(prefer-coding-system 'utf-8) ; pretty
|
||||
(setq locale-coding-system 'utf-8) ; please
|
||||
(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
37
core/doctor.el
Normal 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."))
|
|
@ -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
29
core/templates/BUG_REPORT
Normal 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 -->
|
||||
|
||||
-------------------------------------------------------------------
|
30
core/templates/QUICKSTART_INTRO
Normal file
30
core/templates/QUICKSTART_INTRO
Normal 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!
|
12
core/templates/VANILLA_SANDBOX
Normal file
12
core/templates/VANILLA_SANDBOX
Normal 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.
|
|
@ -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
|
|
@ -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))))
|
|
@ -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"))
|
||||
"[31mHello World[0m"))
|
||||
(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"))))))
|
|
@ -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))))))
|
|
@ -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"))))))
|
|
@ -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")))))
|
|
@ -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)))))
|
115
core/test/test-autoload-buffers.el
Normal file
115
core/test/test-autoload-buffers.el
Normal 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")))
|
54
core/test/test-autoload-files.el
Normal file
54
core/test/test-autoload-files.el
Normal 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))))
|
10
core/test/test-autoload-help.el
Normal file
10
core/test/test-autoload-help.el
Normal 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)))
|
37
core/test/test-autoload-message.el
Normal file
37
core/test/test-autoload-message.el
Normal 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 "[31mHello World[0m"))
|
||||
|
||||
(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")))))
|
138
core/test/test-autoload-package.el
Normal file
138
core/test/test-autoload-package.el
Normal 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))))
|
261
core/test/test-core-keybinds.el
Normal file
261
core/test/test-core-keybinds.el
Normal 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))))))))
|
92
core/test/test-core-lib.el
Normal file
92
core/test/test-core-lib.el
Normal 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
|
6
core/test/test-core-modules.el
Normal file
6
core/test/test-core-modules.el
Normal file
|
@ -0,0 +1,6 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-core-modules.el
|
||||
|
||||
;; (require 'core-modules)
|
||||
|
||||
(describe "core-modules")
|
4
core/test/test-core-packages.el
Normal file
4
core/test/test-core-packages.el
Normal file
|
@ -0,0 +1,4 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-core-packages.el
|
||||
|
||||
(describe "core-packages")
|
35
core/test/test-core-projects.el
Normal file
35
core/test/test-core-projects.el
Normal 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
17
core/test/test-core-ui.el
Normal 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
117
core/test/test-core.el
Normal 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
30
docs/ISSUE_TEMPLATE.md
Normal 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>
|
|
@ -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
20
early-init.el
Normal 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
73
init.el
Normal 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"))
|
235
init.example.el
235
init.example.el
|
@ -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
|
||||
ivy ; a search engine for love and life
|
||||
;helm ; the *other* search engine for love and life
|
||||
;ido ; the other *other* search engine...
|
||||
;;helm ; the *other* search engine for love and life
|
||||
;;ido ; the other *other* search engine...
|
||||
ivy ; a search engine for love and life
|
||||
|
||||
: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))
|
||||
|
|
12
init.test.el
12
init.test.el
|
@ -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
174
modules/README.org
Normal 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=:
|
61
modules/app/calendar/autoload.el
Normal file
61
modules/app/calendar/autoload.el
Normal 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))
|
56
modules/app/calendar/config.el
Normal file
56
modules/app/calendar/config.el
Normal 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)
|
6
modules/app/calendar/packages.el
Normal file
6
modules/app/calendar/packages.el
Normal file
|
@ -0,0 +1,6 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; app/calendar/packages.el
|
||||
|
||||
(package! calfw)
|
||||
(package! calfw-org)
|
||||
(package! org-gcal)
|
71
modules/app/calendar/readme.org
Normal file
71
modules/app/calendar/readme.org
Normal 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
|
45
modules/app/email/+gmail.el
Normal file
45
modules/app/email/+gmail.el
Normal 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))
|
|
@ -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,14 +77,15 @@ 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"
|
||||
'((mu4e-sent-folder . "/Lissner.net/Sent Mail")
|
||||
(mu4e-drafts-folder . "/Lissner.net/Drafts")
|
||||
(mu4e-trash-folder . "/Lissner.net/Trash")
|
||||
(mu4e-refile-folder . "/Lissner.net/All Mail")
|
||||
(smtpmail-smtp-user . "henrik@lissner.net")
|
||||
(user-mail-address . "henrik@lissner.net")
|
||||
(mu4e-compose-signature . "---\nHenrik Lissner"))
|
||||
t)
|
||||
(set-email-account! "Lissner.net"
|
||||
'((mu4e-sent-folder . "/Lissner.net/Sent Mail")
|
||||
(mu4e-drafts-folder . "/Lissner.net/Drafts")
|
||||
(mu4e-trash-folder . "/Lissner.net/Trash")
|
||||
(mu4e-refile-folder . "/Lissner.net/All Mail")
|
||||
(smtpmail-smtp-user . "henrik@lissner.net")
|
||||
(user-mail-address . "henrik@lissner.net")
|
||||
(mu4e-compose-signature . "---\nHenrik Lissner"))
|
||||
t)
|
||||
#+END_SRC
|
||||
|
||||
** TODO mbsync
|
||||
|
|
|
@ -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)))
|
||||
|
||||
|
|
|
@ -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)
|
||||
((featurep! :completion helm) #'completing-read)
|
||||
(t #'ido-completing-read))
|
||||
;; close message after sending it
|
||||
message-kill-buffer-on-exit t
|
||||
mu4e-completing-read-function
|
||||
(cond ((featurep! :completion ivy) #'ivy-completing-read)
|
||||
((featurep! :completion helm) #'completing-read)
|
||||
(t #'ido-completing-read))
|
||||
;; 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"))
|
||||
|
|
|
@ -1,59 +1,118 @@
|
|||
#+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"
|
||||
`(:tls t
|
||||
:nick "doom"
|
||||
:sasl-username "myusername"
|
||||
:sasl-password "mypassword"
|
||||
:channels ("#emacs")))
|
||||
(set-irc-server! "chat.freenode.net"
|
||||
`(:tls t
|
||||
:nick "doom"
|
||||
:sasl-username "myusername"
|
||||
:sasl-password "mypassword"
|
||||
: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"
|
||||
`(:tls t
|
||||
:nick "doom"
|
||||
:sasl-username ,(+pass-get-user "irc/freenode.net")
|
||||
:sasl-password ,(+pass-get-secret "irc/freenode.net")
|
||||
:channels ("#emacs")))
|
||||
(set-irc-server! "chat.freenode.net"
|
||||
`(:tls t
|
||||
:nick "doom"
|
||||
:sasl-username ,(+pass-get-user "irc/freenode.net")
|
||||
:sasl-password ,(+pass-get-secret "irc/freenode.net")
|
||||
: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"
|
||||
`(:tls t
|
||||
:nick "doom"
|
||||
:sasl-username ,(+pass-get-user "irc/freenode.net")
|
||||
:sasl-password (lambda (&rest _) (+pass-get-secret "irc/freenode.net"))
|
||||
:channels ("#emacs")))
|
||||
(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"))
|
||||
:channels ("#emacs")))
|
||||
#+END_SRC
|
||||
|
||||
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"
|
||||
'(:tls t
|
||||
:nick "doom"
|
||||
:sasl-password my-nickserver-password
|
||||
:channels ("#emacs")))
|
||||
(set-irc-server! "chat.freenode.net"
|
||||
'(:tls t
|
||||
:port 6697
|
||||
:nick "doom"
|
||||
:sasl-password my-nickserver-password
|
||||
:channels ("#emacs")))
|
||||
#+END_SRC
|
||||
|
||||
* TODO Troubleshooting
|
||||
|
|
|
@ -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)
|
||||
(cl-loop for network in circe-network-options
|
||||
collect (circe (car network))))))
|
||||
(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)))
|
||||
(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)))
|
||||
|
|
9
modules/app/irc/autoload/settings.el
Normal file
9
modules/app/irc/autoload/settings.el
Normal 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)))
|
|
@ -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
|
||||
|
|
189
modules/app/notmuch/autoload.el
Normal file
189
modules/app/notmuch/autoload.el
Normal 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"))))
|
74
modules/app/notmuch/config.el
Normal file
74
modules/app/notmuch/config.el
Normal 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
Loading…
Add table
Add a link
Reference in a new issue