From d4b4c33550e05d9b8f0af4b1c7ef89dedc1ab44f Mon Sep 17 00:00:00 2001 From: Derek Taylor Date: Mon, 16 Dec 2019 20:21:19 -0600 Subject: [PATCH] Moving to Doom Emacs! --- .emacs.d/LICENSE | 22 + .emacs.d/README.md | 178 +++ .emacs.d/bin/doom | 106 ++ .emacs.d/bin/doom.cmd | 25 + .emacs.d/bin/org-capture | 42 + .emacs.d/bin/org-tangle | 144 ++ .emacs.d/core/autoload/buffers.el | 370 +++++ .emacs.d/core/autoload/cache.el | 95 ++ .emacs.d/core/autoload/cli.el | 45 + .emacs.d/core/autoload/config.el | 142 ++ .emacs.d/core/autoload/debug.el | 344 +++++ .emacs.d/core/autoload/files.el | 343 +++++ .emacs.d/core/autoload/fonts.el | 136 ++ .emacs.d/core/autoload/format.el | 243 ++++ .emacs.d/core/autoload/help.el | 646 +++++++++ .emacs.d/core/autoload/packages.el | 203 +++ .emacs.d/core/autoload/plist.el | 93 ++ .emacs.d/core/autoload/projects.el | 135 ++ .emacs.d/core/autoload/scratch.el | 173 +++ .emacs.d/core/autoload/sessions.el | 128 ++ .emacs.d/core/autoload/text.el | 274 ++++ .emacs.d/core/autoload/themes.el | 51 + .emacs.d/core/autoload/ui.el | 232 ++++ .emacs.d/core/cli/autoloads.el | 407 ++++++ .emacs.d/core/cli/byte-compile.el | 204 +++ .emacs.d/core/cli/debug.el | 29 + .emacs.d/core/cli/doctor.el | 203 +++ .emacs.d/core/cli/env.el | 131 ++ .emacs.d/core/cli/help.el | 102 ++ .emacs.d/core/cli/install.el | 102 ++ .emacs.d/core/cli/packages.el | 317 +++++ .emacs.d/core/cli/test.el | 104 ++ .emacs.d/core/cli/upgrade.el | 113 ++ .emacs.d/core/core-cli.el | 304 +++++ .emacs.d/core/core-editor.el | 561 ++++++++ .emacs.d/core/core-keybinds.el | 425 ++++++ .emacs.d/core/core-lib.el | 428 ++++++ .emacs.d/core/core-modules.el | 603 +++++++++ .emacs.d/core/core-packages.el | 329 +++++ .emacs.d/core/core-projects.el | 282 ++++ .emacs.d/core/core-ui.el | 649 +++++++++ .emacs.d/core/core.el | 553 ++++++++ .emacs.d/core/packages.el | 40 + .emacs.d/core/templates/BUG_REPORT | 29 + .emacs.d/core/templates/QUICKSTART_INTRO | 25 + .emacs.d/core/templates/VANILLA_SANDBOX | 12 + .emacs.d/core/test/helpers.el | 69 + .emacs.d/core/test/init.el | 17 + .emacs.d/core/test/packages.el | 4 + .emacs.d/core/test/test-autoload-buffers.el | 160 +++ .emacs.d/core/test/test-autoload-files.el | 164 +++ .emacs.d/core/test/test-autoload-format.el | 44 + .emacs.d/core/test/test-autoload-package.el | 5 + .emacs.d/core/test/test-core-keybinds.el | 256 ++++ .emacs.d/core/test/test-core-lib.el | 277 ++++ .emacs.d/core/test/test-core-modules.el | 23 + .emacs.d/core/test/test-core-packages.el | 5 + .emacs.d/core/test/test-core-projects.el | 40 + .emacs.d/core/test/test-core-ui.el | 106 ++ .emacs.d/core/test/test-core.el | 127 ++ .emacs.d/docs/api.org | 569 ++++++++ .emacs.d/docs/contributing.org | 170 +++ .emacs.d/docs/faq.org | 1131 ++++++++++++++++ .emacs.d/docs/getting_started.org | 1187 +++++++++++++++++ .emacs.d/docs/index.org | 276 ++++ .emacs.d/docs/workflow.org | 73 + .emacs.d/early-init.el | 27 + .emacs.d/init.el | 151 +-- .emacs.d/init.example.el | 171 +++ .emacs.d/modules/app/calendar/README.org | 66 + .emacs.d/modules/app/calendar/autoload.el | 61 + .emacs.d/modules/app/calendar/config.el | 51 + .emacs.d/modules/app/calendar/packages.el | 6 + .emacs.d/modules/app/irc/README.org | 141 ++ .emacs.d/modules/app/irc/autoload/irc.el | 123 ++ .emacs.d/modules/app/irc/autoload/settings.el | 9 + .emacs.d/modules/app/irc/config.el | 235 ++++ .emacs.d/modules/app/irc/packages.el | 5 + .emacs.d/modules/app/regex/autoload/export.el | 14 + .emacs.d/modules/app/regex/autoload/regex.el | 272 ++++ .emacs.d/modules/app/regex/config.el | 51 + .emacs.d/modules/app/rss/autoload.el | 117 ++ .emacs.d/modules/app/rss/config.el | 74 + .emacs.d/modules/app/rss/packages.el | 5 + .emacs.d/modules/app/twitter/README.org | 96 ++ .emacs.d/modules/app/twitter/autoload.el | 103 ++ .emacs.d/modules/app/twitter/config.el | 79 ++ .emacs.d/modules/app/twitter/packages.el | 5 + .emacs.d/modules/app/write/README.org | 131 ++ .emacs.d/modules/app/write/autoload.el | 43 + .emacs.d/modules/app/write/config.el | 56 + .emacs.d/modules/app/write/doctor.el | 7 + .emacs.d/modules/app/write/packages.el | 12 + .../modules/completion/company/README.org | 131 ++ .../modules/completion/company/autoload.el | 156 +++ .emacs.d/modules/completion/company/config.el | 133 ++ .../modules/completion/company/packages.el | 8 + .../completion/company/test/test-company.el | 75 ++ .../modules/completion/helm/autoload/evil.el | 42 + .../modules/completion/helm/autoload/helm.el | 96 ++ .../completion/helm/autoload/posframe.el | 59 + .emacs.d/modules/completion/helm/config.el | 185 +++ .emacs.d/modules/completion/helm/packages.el | 16 + .emacs.d/modules/completion/ido/config.el | 59 + .emacs.d/modules/completion/ido/packages.el | 7 + .emacs.d/modules/completion/ivy/README.org | 194 +++ .../modules/completion/ivy/autoload/evil.el | 14 + .../modules/completion/ivy/autoload/hydras.el | 33 + .../modules/completion/ivy/autoload/ivy.el | 366 +++++ .../completion/ivy/autoload/posframe.el | 16 + .emacs.d/modules/completion/ivy/config.el | 350 +++++ .emacs.d/modules/completion/ivy/doctor.el | 3 + .emacs.d/modules/completion/ivy/packages.el | 22 + .../modules/config/default/+emacs-bindings.el | 412 ++++++ .emacs.d/modules/config/default/+emacs.el | 26 + .../modules/config/default/+evil-bindings.el | 624 +++++++++ .emacs.d/modules/config/default/+evil.el | 21 + .emacs.d/modules/config/default/README.org | 46 + .../config/default/autoload/default.el | 347 +++++ .emacs.d/modules/config/default/config.el | 405 ++++++ .emacs.d/modules/config/default/packages.el | 9 + .emacs.d/modules/config/literate/autoload.el | 14 + .emacs.d/modules/config/literate/init.el | 51 + .emacs.d/modules/editor/evil/+commands.el | 102 ++ .emacs.d/modules/editor/evil/+everywhere.el | 238 ++++ .emacs.d/modules/editor/evil/README.org | 176 +++ .../modules/editor/evil/autoload/advice.el | 198 +++ .../modules/editor/evil/autoload/embrace.el | 39 + .emacs.d/modules/editor/evil/autoload/evil.el | 180 +++ .emacs.d/modules/editor/evil/autoload/ex.el | 190 +++ .../modules/editor/evil/autoload/files.el | 33 + .../editor/evil/autoload/textobjects.el | 13 + .../editor/evil/autoload/unimpaired.el | 200 +++ .emacs.d/modules/editor/evil/config.el | 587 ++++++++ .emacs.d/modules/editor/evil/packages.el | 29 + .../modules/editor/evil/test/test-evil.el | 69 + .../modules/editor/file-templates/README.org | 79 ++ .../modules/editor/file-templates/autoload.el | 118 ++ .../modules/editor/file-templates/config.el | 159 +++ .../modules/editor/file-templates/packages.el | 5 + .../file-templates/templates/.editorconfig | 7 + .../templates/android-mode/__build.gradle | 49 + .../file-templates/templates/c++-mode/__cpp | 7 + .../file-templates/templates/c++-mode/__hpp | 10 + .../templates/c++-mode/__main.cpp | 13 + .../templates/c++-mode/__winmain.cpp | 12 + .../file-templates/templates/c-mode/__c | 7 + .../file-templates/templates/c-mode/__h | 10 + .../templates/direnv-envrc-mode/__envrc | 15 + .../templates/dockerfile-mode/__ | 4 + .../templates/emacs-ert-mode/__ | 3 + .../templates/emacs-lisp-mode/__doom-doctor | 3 + .../templates/emacs-lisp-mode/__doom-module | 3 + .../templates/emacs-lisp-mode/__doom-packages | 4 + .../templates/emacs-lisp-mode/__doom-test | 4 + .../templates/emacs-lisp-mode/__initfile | 6 + .../file-templates/templates/fish-mode/__ | 3 + .../templates/gitignore-mode/__ | 31 + .../file-templates/templates/go-mode/__.go | 7 + .../templates/go-mode/__main.go | 9 + .../templates/java-mode/.yas-setup.el | 9 + .../file-templates/templates/java-mode/__ | 9 + .../file-templates/templates/java-mode/__main | 11 + .../templates/js-mode/__gulpfile.js | 4 + .../templates/js-mode/__webpack.config.js | 21 + .../file-templates/templates/json-mode/__ | 3 + .../templates/json-mode/__bower.json | 14 + .../templates/json-mode/__package.json | 13 + .../templates/love-mode/__conf.lua | 40 + .../templates/love-mode/__main.lua | 0 .../templates/makefile-gmake-mode/__ | 9 + .../templates/makefile-gmake-mode/__cpp | 46 + .../file-templates/templates/markdown-mode/__ | 6 + .../templates/markdown-mode/__jekyll-post | 7 + .../file-templates/templates/nix-mode/__ | 9 + .../templates/nix-mode/__shell.nix | 9 + .../file-templates/templates/nose-mode/__ | 0 .../file-templates/templates/nxml-mode/__ | 2 + .../file-templates/templates/org-mode/__ | 6 + .../templates/org-mode/__contact.org | 17 + .../templates/org-mode/__doom-readme | 42 + .../templates/org-mode/__invoice.org | 21 + .../templates/org-mode/__project.org | 9 + .../file-templates/templates/php-mode/__ | 3 + .../templates/php-mode/__.class.php | 7 + .../file-templates/templates/python-mode/__ | 5 + .../templates/python-mode/__setup.py | 0 .../file-templates/templates/rspec-mode/__ | 3 + .../templates/rspec-mode/__.rspec | 3 + .../templates/rspec-mode/__helper | 9 + .../file-templates/templates/ruby-mode/__ | 3 + .../templates/ruby-mode/__.gemspec | 0 .../templates/ruby-mode/__Gemfile | 3 + .../templates/ruby-mode/__Rakefile | 16 + .../templates/ruby-mode/__module | 12 + .../templates/rust-mode/__Cargo.toml | 5 + .../templates/rust-mode/__main.rs | 5 + .../file-templates/templates/scss-mode/__ | 0 .../templates/scss-mode/__master.scss | 0 .../templates/scss-mode/__normalize.scss | 0 .../file-templates/templates/sh-mode/__ | 3 + .../file-templates/templates/sh-mode/__zunit | 5 + .../file-templates/templates/slim-mode/__ | 17 + .../file-templates/templates/snippet-mode/__ | 8 + .../templates/solidity-mode/__sol | 10 + .../templates/text-mode/__license | 5 + .../templates/text-mode/__license-apache | 207 +++ .../templates/text-mode/__license-apache-bp | 19 + .../templates/text-mode/__license-bsd2 | 28 + .../templates/text-mode/__license-bsd3 | 31 + .../templates/text-mode/__license-gpl3 | 680 ++++++++++ .../templates/text-mode/__license-gpl3-bp | 21 + .../templates/text-mode/__license-lgpl3 | 171 +++ .../templates/text-mode/__license-mit | 28 + .../templates/text-mode/__license-mozilla | 380 ++++++ .../templates/text-mode/__license-mozilla-bp | 9 + .../templates/text-mode/__license-unlicense | 30 + .../file-templates/templates/web-mode/__.html | 25 + .../templates/web-mode/__jekyll-layout.html | 0 .../templates/yaml-mode/__docker-compose.yml | 15 + .emacs.d/modules/editor/fold/README.org | 32 + .emacs.d/modules/editor/fold/autoload/fold.el | 155 +++ .../modules/editor/fold/autoload/hideshow.el | 83 ++ .emacs.d/modules/editor/fold/config.el | 79 ++ .emacs.d/modules/editor/fold/packages.el | 5 + .../modules/editor/format/autoload/evil.el | 8 + .../modules/editor/format/autoload/format.el | 239 ++++ .../editor/format/autoload/settings.el | 203 +++ .emacs.d/modules/editor/format/config.el | 58 + .emacs.d/modules/editor/format/packages.el | 4 + .../modules/editor/format/test/test-format.el | 103 ++ .emacs.d/modules/editor/god/autoload.el | 46 + .emacs.d/modules/editor/god/config.el | 28 + .emacs.d/modules/editor/god/doctor.el | 8 + .emacs.d/modules/editor/god/packages.el | 4 + .emacs.d/modules/editor/lispy/README.org | 36 + .emacs.d/modules/editor/lispy/config.el | 29 + .emacs.d/modules/editor/lispy/packages.el | 7 + .../multiple-cursors/autoload/evil-mc.el | 89 ++ .../modules/editor/multiple-cursors/config.el | 157 +++ .../editor/multiple-cursors/packages.el | 9 + .emacs.d/modules/editor/objed/README.org | 22 + .emacs.d/modules/editor/objed/config.el | 29 + .emacs.d/modules/editor/objed/packages.el | 4 + .emacs.d/modules/editor/parinfer/README.org | 23 + .emacs.d/modules/editor/parinfer/config.el | 19 + .emacs.d/modules/editor/parinfer/packages.el | 14 + .../modules/editor/rotate-text/autoload.el | 19 + .../modules/editor/rotate-text/packages.el | 4 + .emacs.d/modules/editor/snippets/README.org | 46 + .../editor/snippets/autoload/settings.el | 10 + .../editor/snippets/autoload/snippets.el | 304 +++++ .emacs.d/modules/editor/snippets/config.el | 101 ++ .emacs.d/modules/editor/snippets/packages.el | 10 + .emacs.d/modules/editor/word-wrap/README.org | 81 ++ .emacs.d/modules/editor/word-wrap/autoload.el | 91 ++ .emacs.d/modules/editor/word-wrap/config.el | 26 + .emacs.d/modules/editor/word-wrap/packages.el | 4 + .emacs.d/modules/emacs/dired/README.org | 43 + .emacs.d/modules/emacs/dired/autoload.el | 15 + .emacs.d/modules/emacs/dired/config.el | 196 +++ .emacs.d/modules/emacs/dired/doctor.el | 4 + .emacs.d/modules/emacs/dired/packages.el | 12 + .emacs.d/modules/emacs/electric/autoload.el | 26 + .emacs.d/modules/emacs/electric/config.el | 18 + .emacs.d/modules/emacs/ibuffer/README.org | 36 + .emacs.d/modules/emacs/ibuffer/config.el | 68 + .emacs.d/modules/emacs/ibuffer/packages.el | 5 + .emacs.d/modules/emacs/vc/autoload/evil.el | 8 + .emacs.d/modules/emacs/vc/autoload/hydra.el | 41 + .emacs.d/modules/emacs/vc/autoload/vc.el | 43 + .emacs.d/modules/emacs/vc/config.el | 73 + .emacs.d/modules/emacs/vc/packages.el | 11 + .emacs.d/modules/email/mu4e/README.org | 123 ++ .emacs.d/modules/email/mu4e/autoload/email.el | 88 ++ .emacs.d/modules/email/mu4e/autoload/evil.el | 21 + .emacs.d/modules/email/mu4e/config.el | 184 +++ .emacs.d/modules/email/mu4e/packages.el | 4 + .emacs.d/modules/email/notmuch/autoload.el | 235 ++++ .emacs.d/modules/email/notmuch/config.el | 92 ++ .emacs.d/modules/email/notmuch/packages.el | 9 + .emacs.d/modules/email/wanderlust/autoload.el | 4 + .emacs.d/modules/email/wanderlust/config.el | 79 ++ .emacs.d/modules/email/wanderlust/packages.el | 10 + .emacs.d/modules/input/chinese/README.org | 42 + .emacs.d/modules/input/chinese/config.el | 50 + .emacs.d/modules/input/chinese/packages.el | 8 + .emacs.d/modules/input/japanese/README.org | 40 + .emacs.d/modules/input/japanese/config.el | 60 + .emacs.d/modules/input/japanese/doctor.el | 5 + .emacs.d/modules/input/japanese/packages.el | 7 + .emacs.d/modules/lang/agda/README.org | 4 + .emacs.d/modules/lang/agda/config.el | 31 + .emacs.d/modules/lang/agda/packages.el | 15 + .emacs.d/modules/lang/assembly/autoload.el | 4 + .emacs.d/modules/lang/assembly/packages.el | 6 + .emacs.d/modules/lang/cc/README.org | 174 +++ .emacs.d/modules/lang/cc/autoload.el | 188 +++ .emacs.d/modules/lang/cc/config.el | 263 ++++ .emacs.d/modules/lang/cc/doctor.el | 24 + .emacs.d/modules/lang/cc/packages.el | 28 + .emacs.d/modules/lang/clojure/autoload.el | 44 + .emacs.d/modules/lang/clojure/config.el | 161 +++ .emacs.d/modules/lang/clojure/packages.el | 8 + .../lang/common-lisp/autoload/common-lisp.el | 5 + .emacs.d/modules/lang/common-lisp/config.el | 135 ++ .emacs.d/modules/lang/common-lisp/doctor.el | 6 + .emacs.d/modules/lang/common-lisp/packages.el | 6 + .emacs.d/modules/lang/coq/README.org | 6 + .emacs.d/modules/lang/coq/config.el | 96 ++ .emacs.d/modules/lang/coq/packages.el | 6 + .emacs.d/modules/lang/crystal/config.el | 25 + .emacs.d/modules/lang/crystal/doctor.el | 5 + .emacs.d/modules/lang/crystal/packages.el | 8 + .emacs.d/modules/lang/csharp/README.org | 39 + .emacs.d/modules/lang/csharp/autoload.el | 10 + .emacs.d/modules/lang/csharp/config.el | 70 + .emacs.d/modules/lang/csharp/doctor.el | 7 + .emacs.d/modules/lang/csharp/packages.el | 10 + .emacs.d/modules/lang/data/config.el | 53 + .emacs.d/modules/lang/data/packages.el | 11 + .emacs.d/modules/lang/elixir/README.org | 62 + .emacs.d/modules/lang/elixir/config.el | 69 + .emacs.d/modules/lang/elixir/packages.el | 8 + .emacs.d/modules/lang/elm/config.el | 22 + .emacs.d/modules/lang/elm/packages.el | 7 + .emacs.d/modules/lang/emacs-lisp/autoload.el | 176 +++ .emacs.d/modules/lang/emacs-lisp/config.el | 184 +++ .emacs.d/modules/lang/emacs-lisp/packages.el | 15 + .emacs.d/modules/lang/erlang/config.el | 25 + .emacs.d/modules/lang/erlang/packages.el | 13 + .emacs.d/modules/lang/ess/README.org | 50 + .emacs.d/modules/lang/ess/autoload.el | 15 + .emacs.d/modules/lang/ess/config.el | 80 ++ .emacs.d/modules/lang/ess/packages.el | 5 + .emacs.d/modules/lang/factor/README.org | 43 + .emacs.d/modules/lang/factor/config.el | 48 + .emacs.d/modules/lang/factor/packages.el | 4 + .emacs.d/modules/lang/faust/README.org | 46 + .emacs.d/modules/lang/faust/autoload.el | 13 + .emacs.d/modules/lang/faust/config.el | 25 + .emacs.d/modules/lang/faust/doctor.el | 5 + .emacs.d/modules/lang/faust/packages.el | 4 + .emacs.d/modules/lang/fsharp/README.org | 58 + .emacs.d/modules/lang/fsharp/config.el | 19 + .emacs.d/modules/lang/fsharp/doctor.el | 5 + .emacs.d/modules/lang/fsharp/packages.el | 4 + .emacs.d/modules/lang/go/README.org | 97 ++ .emacs.d/modules/lang/go/autoload.el | 50 + .emacs.d/modules/lang/go/config.el | 75 ++ .emacs.d/modules/lang/go/doctor.el | 23 + .emacs.d/modules/lang/go/packages.el | 15 + .emacs.d/modules/lang/haskell/+dante.el | 41 + .emacs.d/modules/lang/haskell/+intero.el | 30 + .emacs.d/modules/lang/haskell/+lsp.el | 10 + .emacs.d/modules/lang/haskell/README.org | 168 +++ .emacs.d/modules/lang/haskell/autoload.el | 14 + .emacs.d/modules/lang/haskell/config.el | 41 + .emacs.d/modules/lang/haskell/doctor.el | 18 + .emacs.d/modules/lang/haskell/packages.el | 12 + .emacs.d/modules/lang/hy/config.el | 8 + .emacs.d/modules/lang/hy/packages.el | 4 + .emacs.d/modules/lang/idris/README.org | 3 + .emacs.d/modules/lang/idris/config.el | 19 + .emacs.d/modules/lang/idris/packages.el | 4 + .emacs.d/modules/lang/java/+eclim.el | 50 + .emacs.d/modules/lang/java/+lsp.el | 12 + .emacs.d/modules/lang/java/+meghanada.el | 29 + .emacs.d/modules/lang/java/autoload.el | 73 + .emacs.d/modules/lang/java/config.el | 48 + .emacs.d/modules/lang/java/doctor.el | 9 + .emacs.d/modules/lang/java/packages.el | 16 + .emacs.d/modules/lang/javascript/README.org | 139 ++ .emacs.d/modules/lang/javascript/autoload.el | 122 ++ .emacs.d/modules/lang/javascript/config.el | 283 ++++ .emacs.d/modules/lang/javascript/doctor.el | 6 + .emacs.d/modules/lang/javascript/packages.el | 22 + .emacs.d/modules/lang/julia/autoload.el | 20 + .emacs.d/modules/lang/julia/config.el | 34 + .emacs.d/modules/lang/julia/packages.el | 5 + .emacs.d/modules/lang/kotlin/autoload.el | 15 + .emacs.d/modules/lang/kotlin/config.el | 18 + .emacs.d/modules/lang/kotlin/doctor.el | 8 + .emacs.d/modules/lang/kotlin/packages.el | 7 + .emacs.d/modules/lang/latex/+fontification.el | 88 ++ .emacs.d/modules/lang/latex/+ref.el | 42 + .emacs.d/modules/lang/latex/+viewers.el | 56 + .emacs.d/modules/lang/latex/README.org | 78 ++ .emacs.d/modules/lang/latex/autoload.el | 49 + .emacs.d/modules/lang/latex/config.el | 153 +++ .emacs.d/modules/lang/latex/packages.el | 23 + .emacs.d/modules/lang/lean/config.el | 36 + .emacs.d/modules/lang/lean/packages.el | 7 + .emacs.d/modules/lang/ledger/README.org | 56 + .emacs.d/modules/lang/ledger/config.el | 58 + .emacs.d/modules/lang/ledger/packages.el | 10 + .emacs.d/modules/lang/lua/autoload/lua.el | 51 + .../modules/lang/lua/autoload/moonscript.el | 16 + .emacs.d/modules/lang/lua/config.el | 48 + .emacs.d/modules/lang/lua/packages.el | 14 + .emacs.d/modules/lang/markdown/README.org | 159 +++ .emacs.d/modules/lang/markdown/autoload.el | 93 ++ .emacs.d/modules/lang/markdown/config.el | 90 ++ .emacs.d/modules/lang/markdown/doctor.el | 22 + .emacs.d/modules/lang/markdown/packages.el | 13 + .emacs.d/modules/lang/nim/README.org | 52 + .emacs.d/modules/lang/nim/config.el | 30 + .emacs.d/modules/lang/nim/doctor.el | 9 + .emacs.d/modules/lang/nim/packages.el | 9 + .emacs.d/modules/lang/nix/autoload.el | 41 + .emacs.d/modules/lang/nix/config.el | 27 + .emacs.d/modules/lang/nix/doctor.el | 9 + .emacs.d/modules/lang/nix/packages.el | 11 + .emacs.d/modules/lang/ocaml/README.org | 93 ++ .emacs.d/modules/lang/ocaml/autoload.el | 9 + .emacs.d/modules/lang/ocaml/config.el | 116 ++ .emacs.d/modules/lang/ocaml/doctor.el | 24 + .emacs.d/modules/lang/ocaml/packages.el | 25 + .emacs.d/modules/lang/org/README.org | 210 +++ .../lang/org/autoload/contrib-dragndrop.el | 12 + .../lang/org/autoload/contrib-ipython.el | 152 +++ .../lang/org/autoload/contrib-present.el | 97 ++ .../modules/lang/org/autoload/org-attach.el | 101 ++ .emacs.d/modules/lang/org/autoload/org-avy.el | 19 + .../modules/lang/org/autoload/org-capture.el | 163 +++ .../modules/lang/org/autoload/org-export.el | 47 + .../modules/lang/org/autoload/org-link.el | 49 + .../modules/lang/org/autoload/org-refile.el | 83 ++ .../modules/lang/org/autoload/org-tables.el | 97 ++ .emacs.d/modules/lang/org/autoload/org.el | 440 ++++++ .emacs.d/modules/lang/org/config.el | 1002 ++++++++++++++ .../modules/lang/org/contrib/dragndrop.el | 60 + .emacs.d/modules/lang/org/contrib/ipython.el | 41 + .emacs.d/modules/lang/org/contrib/journal.el | 16 + .emacs.d/modules/lang/org/contrib/pomodoro.el | 15 + .emacs.d/modules/lang/org/contrib/present.el | 54 + .emacs.d/modules/lang/org/doctor.el | 6 + .emacs.d/modules/lang/org/packages.el | 81 ++ .emacs.d/modules/lang/org/test/test-org.el | 133 ++ .emacs.d/modules/lang/perl/README.org | 13 + .emacs.d/modules/lang/perl/config.el | 10 + .emacs.d/modules/lang/perl/packages.el | 7 + .emacs.d/modules/lang/php/README.org | 67 + .emacs.d/modules/lang/php/autoload.el | 26 + .emacs.d/modules/lang/php/config.el | 111 ++ .emacs.d/modules/lang/php/doctor.el | 6 + .emacs.d/modules/lang/php/packages.el | 19 + .emacs.d/modules/lang/plantuml/autoload.el | 63 + .emacs.d/modules/lang/plantuml/config.el | 29 + .emacs.d/modules/lang/plantuml/doctor.el | 10 + .emacs.d/modules/lang/plantuml/packages.el | 6 + .emacs.d/modules/lang/purescript/config.el | 22 + .emacs.d/modules/lang/purescript/packages.el | 12 + .emacs.d/modules/lang/python/README.org | 136 ++ .../modules/lang/python/autoload/conda.el | 17 + .../modules/lang/python/autoload/pyenv.el | 30 + .../modules/lang/python/autoload/python.el | 62 + .emacs.d/modules/lang/python/config.el | 313 +++++ .emacs.d/modules/lang/python/doctor.el | 25 + .emacs.d/modules/lang/python/packages.el | 34 + .emacs.d/modules/lang/qt/autoload.el | 4 + .emacs.d/modules/lang/qt/packages.el | 5 + .emacs.d/modules/lang/racket/autoload.el | 12 + .emacs.d/modules/lang/racket/config.el | 59 + .emacs.d/modules/lang/racket/doctor.el | 11 + .emacs.d/modules/lang/racket/packages.el | 4 + .emacs.d/modules/lang/rest/README.org | 55 + .emacs.d/modules/lang/rest/autoload.el | 28 + .emacs.d/modules/lang/rest/config.el | 36 + .emacs.d/modules/lang/rest/packages.el | 7 + .emacs.d/modules/lang/rst/config.el | 18 + .emacs.d/modules/lang/rst/packages.el | 4 + .emacs.d/modules/lang/ruby/autoload.el | 15 + .emacs.d/modules/lang/ruby/config.el | 165 +++ .emacs.d/modules/lang/ruby/doctor.el | 12 + .emacs.d/modules/lang/ruby/packages.el | 29 + .emacs.d/modules/lang/rust/README.org | 83 ++ .emacs.d/modules/lang/rust/autoload.el | 26 + .emacs.d/modules/lang/rust/config.el | 64 + .emacs.d/modules/lang/rust/doctor.el | 31 + .emacs.d/modules/lang/rust/packages.el | 6 + .emacs.d/modules/lang/scala/autoload.el | 50 + .emacs.d/modules/lang/scala/config.el | 24 + .emacs.d/modules/lang/scala/doctor.el | 9 + .emacs.d/modules/lang/scala/packages.el | 5 + .emacs.d/modules/lang/scheme/README.org | 41 + .emacs.d/modules/lang/scheme/autoload.el | 8 + .emacs.d/modules/lang/scheme/config.el | 45 + .emacs.d/modules/lang/scheme/packages.el | 4 + .emacs.d/modules/lang/sh/README.org | 19 + .emacs.d/modules/lang/sh/autoload.el | 38 + .emacs.d/modules/lang/sh/config.el | 64 + .emacs.d/modules/lang/sh/doctor.el | 5 + .emacs.d/modules/lang/sh/packages.el | 8 + .emacs.d/modules/lang/solidity/README.org | 47 + .emacs.d/modules/lang/solidity/config.el | 26 + .emacs.d/modules/lang/solidity/doctor.el | 8 + .emacs.d/modules/lang/solidity/packages.el | 6 + .emacs.d/modules/lang/swift/config.el | 34 + .emacs.d/modules/lang/swift/packages.el | 11 + .emacs.d/modules/lang/terra/autoload.el | 8 + .emacs.d/modules/lang/terra/config.el | 11 + .emacs.d/modules/lang/terra/packages.el | 9 + .emacs.d/modules/lang/web/+css.el | 66 + .emacs.d/modules/lang/web/+html.el | 127 ++ .emacs.d/modules/lang/web/autoload/css.el | 82 ++ .emacs.d/modules/lang/web/autoload/evil.el | 21 + .emacs.d/modules/lang/web/autoload/html.el | 130 ++ .emacs.d/modules/lang/web/config.el | 47 + .emacs.d/modules/lang/web/doctor.el | 12 + .emacs.d/modules/lang/web/packages.el | 24 + .emacs.d/modules/lang/web/test/test-web.el | 92 ++ .../modules/term/eshell/autoload/commands.el | 30 + .../modules/term/eshell/autoload/company.el | 58 + .../modules/term/eshell/autoload/eshell.el | 323 +++++ .emacs.d/modules/term/eshell/autoload/evil.el | 69 + .../modules/term/eshell/autoload/prompts.el | 34 + .../modules/term/eshell/autoload/settings.el | 26 + .emacs.d/modules/term/eshell/config.el | 180 +++ .emacs.d/modules/term/eshell/packages.el | 7 + .emacs.d/modules/term/shell/autoload.el | 100 ++ .emacs.d/modules/term/shell/config.el | 5 + .emacs.d/modules/term/shell/packages.el | 4 + .emacs.d/modules/term/term/autoload.el | 52 + .emacs.d/modules/term/term/config.el | 10 + .emacs.d/modules/term/term/packages.el | 5 + .emacs.d/modules/term/vterm/README.org | 108 ++ .emacs.d/modules/term/vterm/autoload.el | 57 + .emacs.d/modules/term/vterm/config.el | 18 + .emacs.d/modules/term/vterm/doctor.el | 13 + .emacs.d/modules/term/vterm/packages.el | 4 + .emacs.d/modules/tools/ansible/config.el | 25 + .emacs.d/modules/tools/ansible/packages.el | 10 + .../tools/debugger/autoload/debugger.el | 85 ++ .../modules/tools/debugger/autoload/evil.el | 34 + .emacs.d/modules/tools/debugger/config.el | 112 ++ .emacs.d/modules/tools/debugger/packages.el | 9 + .emacs.d/modules/tools/direnv/README.org | 93 ++ .emacs.d/modules/tools/direnv/config.el | 69 + .emacs.d/modules/tools/direnv/doctor.el | 4 + .emacs.d/modules/tools/direnv/packages.el | 4 + .emacs.d/modules/tools/docker/README.org | 135 ++ .emacs.d/modules/tools/docker/config.el | 10 + .emacs.d/modules/tools/docker/packages.el | 6 + .../modules/tools/editorconfig/README.org | 18 + .../modules/tools/editorconfig/autoload.el | 7 + .emacs.d/modules/tools/editorconfig/config.el | 55 + .emacs.d/modules/tools/editorconfig/doctor.el | 5 + .../modules/tools/editorconfig/packages.el | 4 + .emacs.d/modules/tools/ein/README.org | 67 + .emacs.d/modules/tools/ein/autoload/ein.el | 24 + .emacs.d/modules/tools/ein/autoload/hydra.el | 47 + .emacs.d/modules/tools/ein/config.el | 43 + .emacs.d/modules/tools/ein/packages.el | 5 + .emacs.d/modules/tools/eval/README.org | 125 ++ .emacs.d/modules/tools/eval/autoload/eval.el | 111 ++ .emacs.d/modules/tools/eval/autoload/evil.el | 23 + .emacs.d/modules/tools/eval/autoload/repl.el | 142 ++ .../modules/tools/eval/autoload/settings.el | 66 + .emacs.d/modules/tools/eval/config.el | 92 ++ .emacs.d/modules/tools/eval/packages.el | 6 + .emacs.d/modules/tools/flycheck/autoload.el | 13 + .emacs.d/modules/tools/flycheck/config.el | 80 ++ .emacs.d/modules/tools/flycheck/packages.el | 7 + .emacs.d/modules/tools/flyspell/autoload.el | 27 + .emacs.d/modules/tools/flyspell/config.el | 84 ++ .emacs.d/modules/tools/flyspell/doctor.el | 4 + .emacs.d/modules/tools/flyspell/packages.el | 9 + .emacs.d/modules/tools/gist/autoload/evil.el | 19 + .emacs.d/modules/tools/gist/config.el | 28 + .emacs.d/modules/tools/gist/packages.el | 4 + .emacs.d/modules/tools/lookup/README.org | 197 +++ .../modules/tools/lookup/autoload/docsets.el | 105 ++ .../modules/tools/lookup/autoload/evil.el | 22 + .../modules/tools/lookup/autoload/lookup.el | 322 +++++ .../modules/tools/lookup/autoload/online.el | 71 + .emacs.d/modules/tools/lookup/config.el | 167 +++ .emacs.d/modules/tools/lookup/packages.el | 25 + .emacs.d/modules/tools/lsp/README.org | 73 + .emacs.d/modules/tools/lsp/autoload.el | 4 + .emacs.d/modules/tools/lsp/config.el | 150 +++ .emacs.d/modules/tools/lsp/packages.el | 7 + .emacs.d/modules/tools/macos/autoload.el | 51 + .emacs.d/modules/tools/magit/autoload.el | 137 ++ .emacs.d/modules/tools/magit/config.el | 167 +++ .emacs.d/modules/tools/magit/packages.el | 10 + .emacs.d/modules/tools/make/autoload.el | 25 + .emacs.d/modules/tools/make/packages.el | 4 + .emacs.d/modules/tools/pass/autoload.el | 140 ++ .emacs.d/modules/tools/pass/config.el | 39 + .emacs.d/modules/tools/pass/packages.el | 16 + .emacs.d/modules/tools/pass/test/test-pass.el | 41 + .emacs.d/modules/tools/pdf/autoload/pdf.el | 30 + .emacs.d/modules/tools/pdf/config.el | 74 + .emacs.d/modules/tools/pdf/packages.el | 4 + .emacs.d/modules/tools/prodigy/autoload.el | 34 + .emacs.d/modules/tools/prodigy/config.el | 21 + .emacs.d/modules/tools/prodigy/packages.el | 4 + .emacs.d/modules/tools/rgb/autoload.el | 14 + .emacs.d/modules/tools/rgb/packages.el | 5 + .emacs.d/modules/tools/terraform/README.org | 69 + .emacs.d/modules/tools/terraform/config.el | 15 + .emacs.d/modules/tools/terraform/doctor.el | 4 + .emacs.d/modules/tools/terraform/packages.el | 6 + .emacs.d/modules/tools/tmux/autoload/evil.el | 17 + .emacs.d/modules/tools/tmux/autoload/tmux.el | 136 ++ .emacs.d/modules/tools/upload/config.el | 51 + .emacs.d/modules/tools/upload/packages.el | 5 + .emacs.d/modules/ui/deft/README.org | 11 + .emacs.d/modules/ui/deft/config.el | 32 + .emacs.d/modules/ui/deft/packages.el | 4 + .emacs.d/modules/ui/doom-dashboard/README.org | 20 + .../modules/ui/doom-dashboard/autoload.el | 28 + .emacs.d/modules/ui/doom-dashboard/config.el | 455 +++++++ .../test/test-doom-dashboard.el | 40 + .emacs.d/modules/ui/doom-quit/README.org | 8 + .emacs.d/modules/ui/doom-quit/config.el | 32 + .emacs.d/modules/ui/doom/README.org | 93 ++ .emacs.d/modules/ui/doom/config.el | 96 ++ .emacs.d/modules/ui/doom/packages.el | 6 + .emacs.d/modules/ui/fill-column/autoload.el | 7 + .emacs.d/modules/ui/fill-column/packages.el | 4 + .emacs.d/modules/ui/hl-todo/README.org | 13 + .emacs.d/modules/ui/hl-todo/config.el | 28 + .emacs.d/modules/ui/hl-todo/packages.el | 4 + .emacs.d/modules/ui/hydra/README.org | 31 + .emacs.d/modules/ui/hydra/autoload/windows.el | 47 + .emacs.d/modules/ui/hydra/config.el | 15 + .emacs.d/modules/ui/hydra/packages.el | 4 + .emacs.d/modules/ui/indent-guides/config.el | 13 + .emacs.d/modules/ui/indent-guides/packages.el | 4 + .emacs.d/modules/ui/modeline/+light.el | 558 ++++++++ .emacs.d/modules/ui/modeline/README.org | 120 ++ .emacs.d/modules/ui/modeline/autoload.el | 48 + .emacs.d/modules/ui/modeline/config.el | 79 ++ .emacs.d/modules/ui/modeline/packages.el | 8 + .emacs.d/modules/ui/nav-flash/README.org | 37 + .emacs.d/modules/ui/nav-flash/autoload.el | 45 + .emacs.d/modules/ui/nav-flash/config.el | 33 + .emacs.d/modules/ui/nav-flash/packages.el | 4 + .emacs.d/modules/ui/neotree/README.org | 9 + .emacs.d/modules/ui/neotree/autoload.el | 71 + .emacs.d/modules/ui/neotree/config.el | 81 ++ .emacs.d/modules/ui/neotree/packages.el | 4 + .emacs.d/modules/ui/ophints/README.org | 23 + .emacs.d/modules/ui/ophints/config.el | 33 + .emacs.d/modules/ui/ophints/packages.el | 6 + .emacs.d/modules/ui/popup/+hacks.el | 344 +++++ .emacs.d/modules/ui/popup/README.org | 140 ++ .emacs.d/modules/ui/popup/autoload/popup.el | 617 +++++++++ .../modules/ui/popup/autoload/settings.el | 190 +++ .emacs.d/modules/ui/popup/config.el | 175 +++ .emacs.d/modules/ui/popup/test/test-popup.el | 212 +++ .emacs.d/modules/ui/pretty-code/+fira.el | 122 ++ .emacs.d/modules/ui/pretty-code/+hasklig.el | 58 + .emacs.d/modules/ui/pretty-code/+iosevka.el | 232 ++++ .../modules/ui/pretty-code/+pragmata-pro.el | 259 ++++ .emacs.d/modules/ui/pretty-code/autoload.el | 55 + .emacs.d/modules/ui/pretty-code/config.el | 93 ++ .emacs.d/modules/ui/tabs/README.org | 18 + .emacs.d/modules/ui/tabs/autoload.el | 77 ++ .emacs.d/modules/ui/tabs/config.el | 71 + .emacs.d/modules/ui/tabs/packages.el | 4 + .emacs.d/modules/ui/treemacs/autoload.el | 41 + .emacs.d/modules/ui/treemacs/config.el | 45 + .emacs.d/modules/ui/treemacs/packages.el | 9 + .emacs.d/modules/ui/unicode/README.org | 10 + .emacs.d/modules/ui/unicode/autoload.el | 21 + .emacs.d/modules/ui/unicode/packages.el | 4 + .emacs.d/modules/ui/vc-gutter/autoload.el | 27 + .emacs.d/modules/ui/vc-gutter/config.el | 111 ++ .emacs.d/modules/ui/vc-gutter/packages.el | 4 + .../modules/ui/vi-tilde-fringe/autoload.el | 5 + .../modules/ui/vi-tilde-fringe/packages.el | 4 + .emacs.d/modules/ui/window-select/README.org | 57 + .emacs.d/modules/ui/window-select/config.el | 39 + .emacs.d/modules/ui/window-select/packages.el | 9 + .emacs.d/modules/ui/workspaces/README.org | 104 ++ .../modules/ui/workspaces/autoload/evil.el | 39 + .../ui/workspaces/autoload/workspaces.el | 531 ++++++++ .emacs.d/modules/ui/workspaces/config.el | 222 +++ .emacs.d/modules/ui/workspaces/packages.el | 5 + .../ui/workspaces/test/test-workspaces.el | 124 ++ 683 files changed, 51877 insertions(+), 100 deletions(-) create mode 100644 .emacs.d/LICENSE create mode 100644 .emacs.d/README.md create mode 100755 .emacs.d/bin/doom create mode 100644 .emacs.d/bin/doom.cmd create mode 100755 .emacs.d/bin/org-capture create mode 100755 .emacs.d/bin/org-tangle create mode 100644 .emacs.d/core/autoload/buffers.el create mode 100644 .emacs.d/core/autoload/cache.el create mode 100644 .emacs.d/core/autoload/cli.el create mode 100644 .emacs.d/core/autoload/config.el create mode 100644 .emacs.d/core/autoload/debug.el create mode 100644 .emacs.d/core/autoload/files.el create mode 100644 .emacs.d/core/autoload/fonts.el create mode 100644 .emacs.d/core/autoload/format.el create mode 100644 .emacs.d/core/autoload/help.el create mode 100644 .emacs.d/core/autoload/packages.el create mode 100644 .emacs.d/core/autoload/plist.el create mode 100644 .emacs.d/core/autoload/projects.el create mode 100644 .emacs.d/core/autoload/scratch.el create mode 100644 .emacs.d/core/autoload/sessions.el create mode 100644 .emacs.d/core/autoload/text.el create mode 100644 .emacs.d/core/autoload/themes.el create mode 100644 .emacs.d/core/autoload/ui.el create mode 100644 .emacs.d/core/cli/autoloads.el create mode 100644 .emacs.d/core/cli/byte-compile.el create mode 100644 .emacs.d/core/cli/debug.el create mode 100644 .emacs.d/core/cli/doctor.el create mode 100644 .emacs.d/core/cli/env.el create mode 100644 .emacs.d/core/cli/help.el create mode 100644 .emacs.d/core/cli/install.el create mode 100644 .emacs.d/core/cli/packages.el create mode 100644 .emacs.d/core/cli/test.el create mode 100644 .emacs.d/core/cli/upgrade.el create mode 100644 .emacs.d/core/core-cli.el create mode 100644 .emacs.d/core/core-editor.el create mode 100644 .emacs.d/core/core-keybinds.el create mode 100644 .emacs.d/core/core-lib.el create mode 100644 .emacs.d/core/core-modules.el create mode 100644 .emacs.d/core/core-packages.el create mode 100644 .emacs.d/core/core-projects.el create mode 100644 .emacs.d/core/core-ui.el create mode 100644 .emacs.d/core/core.el create mode 100644 .emacs.d/core/packages.el create mode 100644 .emacs.d/core/templates/BUG_REPORT create mode 100644 .emacs.d/core/templates/QUICKSTART_INTRO create mode 100644 .emacs.d/core/templates/VANILLA_SANDBOX create mode 100644 .emacs.d/core/test/helpers.el create mode 100644 .emacs.d/core/test/init.el create mode 100644 .emacs.d/core/test/packages.el create mode 100644 .emacs.d/core/test/test-autoload-buffers.el create mode 100644 .emacs.d/core/test/test-autoload-files.el create mode 100644 .emacs.d/core/test/test-autoload-format.el create mode 100644 .emacs.d/core/test/test-autoload-package.el create mode 100644 .emacs.d/core/test/test-core-keybinds.el create mode 100644 .emacs.d/core/test/test-core-lib.el create mode 100644 .emacs.d/core/test/test-core-modules.el create mode 100644 .emacs.d/core/test/test-core-packages.el create mode 100644 .emacs.d/core/test/test-core-projects.el create mode 100644 .emacs.d/core/test/test-core-ui.el create mode 100644 .emacs.d/core/test/test-core.el create mode 100644 .emacs.d/docs/api.org create mode 100644 .emacs.d/docs/contributing.org create mode 100644 .emacs.d/docs/faq.org create mode 100644 .emacs.d/docs/getting_started.org create mode 100644 .emacs.d/docs/index.org create mode 100644 .emacs.d/docs/workflow.org create mode 100644 .emacs.d/early-init.el mode change 100755 => 100644 .emacs.d/init.el create mode 100644 .emacs.d/init.example.el create mode 100644 .emacs.d/modules/app/calendar/README.org create mode 100644 .emacs.d/modules/app/calendar/autoload.el create mode 100644 .emacs.d/modules/app/calendar/config.el create mode 100644 .emacs.d/modules/app/calendar/packages.el create mode 100644 .emacs.d/modules/app/irc/README.org create mode 100644 .emacs.d/modules/app/irc/autoload/irc.el create mode 100644 .emacs.d/modules/app/irc/autoload/settings.el create mode 100644 .emacs.d/modules/app/irc/config.el create mode 100644 .emacs.d/modules/app/irc/packages.el create mode 100644 .emacs.d/modules/app/regex/autoload/export.el create mode 100644 .emacs.d/modules/app/regex/autoload/regex.el create mode 100644 .emacs.d/modules/app/regex/config.el create mode 100644 .emacs.d/modules/app/rss/autoload.el create mode 100644 .emacs.d/modules/app/rss/config.el create mode 100644 .emacs.d/modules/app/rss/packages.el create mode 100644 .emacs.d/modules/app/twitter/README.org create mode 100644 .emacs.d/modules/app/twitter/autoload.el create mode 100644 .emacs.d/modules/app/twitter/config.el create mode 100644 .emacs.d/modules/app/twitter/packages.el create mode 100644 .emacs.d/modules/app/write/README.org create mode 100644 .emacs.d/modules/app/write/autoload.el create mode 100644 .emacs.d/modules/app/write/config.el create mode 100644 .emacs.d/modules/app/write/doctor.el create mode 100644 .emacs.d/modules/app/write/packages.el create mode 100644 .emacs.d/modules/completion/company/README.org create mode 100644 .emacs.d/modules/completion/company/autoload.el create mode 100644 .emacs.d/modules/completion/company/config.el create mode 100644 .emacs.d/modules/completion/company/packages.el create mode 100644 .emacs.d/modules/completion/company/test/test-company.el create mode 100644 .emacs.d/modules/completion/helm/autoload/evil.el create mode 100644 .emacs.d/modules/completion/helm/autoload/helm.el create mode 100644 .emacs.d/modules/completion/helm/autoload/posframe.el create mode 100644 .emacs.d/modules/completion/helm/config.el create mode 100644 .emacs.d/modules/completion/helm/packages.el create mode 100644 .emacs.d/modules/completion/ido/config.el create mode 100644 .emacs.d/modules/completion/ido/packages.el create mode 100644 .emacs.d/modules/completion/ivy/README.org create mode 100644 .emacs.d/modules/completion/ivy/autoload/evil.el create mode 100644 .emacs.d/modules/completion/ivy/autoload/hydras.el create mode 100644 .emacs.d/modules/completion/ivy/autoload/ivy.el create mode 100644 .emacs.d/modules/completion/ivy/autoload/posframe.el create mode 100644 .emacs.d/modules/completion/ivy/config.el create mode 100644 .emacs.d/modules/completion/ivy/doctor.el create mode 100644 .emacs.d/modules/completion/ivy/packages.el create mode 100644 .emacs.d/modules/config/default/+emacs-bindings.el create mode 100644 .emacs.d/modules/config/default/+emacs.el create mode 100644 .emacs.d/modules/config/default/+evil-bindings.el create mode 100644 .emacs.d/modules/config/default/+evil.el create mode 100644 .emacs.d/modules/config/default/README.org create mode 100644 .emacs.d/modules/config/default/autoload/default.el create mode 100644 .emacs.d/modules/config/default/config.el create mode 100644 .emacs.d/modules/config/default/packages.el create mode 100644 .emacs.d/modules/config/literate/autoload.el create mode 100644 .emacs.d/modules/config/literate/init.el create mode 100644 .emacs.d/modules/editor/evil/+commands.el create mode 100644 .emacs.d/modules/editor/evil/+everywhere.el create mode 100644 .emacs.d/modules/editor/evil/README.org create mode 100644 .emacs.d/modules/editor/evil/autoload/advice.el create mode 100644 .emacs.d/modules/editor/evil/autoload/embrace.el create mode 100644 .emacs.d/modules/editor/evil/autoload/evil.el create mode 100644 .emacs.d/modules/editor/evil/autoload/ex.el create mode 100644 .emacs.d/modules/editor/evil/autoload/files.el create mode 100644 .emacs.d/modules/editor/evil/autoload/textobjects.el create mode 100644 .emacs.d/modules/editor/evil/autoload/unimpaired.el create mode 100644 .emacs.d/modules/editor/evil/config.el create mode 100644 .emacs.d/modules/editor/evil/packages.el create mode 100644 .emacs.d/modules/editor/evil/test/test-evil.el create mode 100644 .emacs.d/modules/editor/file-templates/README.org create mode 100644 .emacs.d/modules/editor/file-templates/autoload.el create mode 100644 .emacs.d/modules/editor/file-templates/config.el create mode 100644 .emacs.d/modules/editor/file-templates/packages.el create mode 100644 .emacs.d/modules/editor/file-templates/templates/.editorconfig create mode 100644 .emacs.d/modules/editor/file-templates/templates/android-mode/__build.gradle create mode 100644 .emacs.d/modules/editor/file-templates/templates/c++-mode/__cpp create mode 100644 .emacs.d/modules/editor/file-templates/templates/c++-mode/__hpp create mode 100644 .emacs.d/modules/editor/file-templates/templates/c++-mode/__main.cpp create mode 100644 .emacs.d/modules/editor/file-templates/templates/c++-mode/__winmain.cpp create mode 100644 .emacs.d/modules/editor/file-templates/templates/c-mode/__c create mode 100644 .emacs.d/modules/editor/file-templates/templates/c-mode/__h create mode 100644 .emacs.d/modules/editor/file-templates/templates/direnv-envrc-mode/__envrc create mode 100644 .emacs.d/modules/editor/file-templates/templates/dockerfile-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/emacs-ert-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-doctor create mode 100644 .emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-module create mode 100644 .emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-packages create mode 100644 .emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-test create mode 100644 .emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__initfile create mode 100644 .emacs.d/modules/editor/file-templates/templates/fish-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/gitignore-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/go-mode/__.go create mode 100644 .emacs.d/modules/editor/file-templates/templates/go-mode/__main.go create mode 100644 .emacs.d/modules/editor/file-templates/templates/java-mode/.yas-setup.el create mode 100644 .emacs.d/modules/editor/file-templates/templates/java-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/java-mode/__main create mode 100644 .emacs.d/modules/editor/file-templates/templates/js-mode/__gulpfile.js create mode 100644 .emacs.d/modules/editor/file-templates/templates/js-mode/__webpack.config.js create mode 100644 .emacs.d/modules/editor/file-templates/templates/json-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/json-mode/__bower.json create mode 100644 .emacs.d/modules/editor/file-templates/templates/json-mode/__package.json create mode 100644 .emacs.d/modules/editor/file-templates/templates/love-mode/__conf.lua create mode 100644 .emacs.d/modules/editor/file-templates/templates/love-mode/__main.lua create mode 100644 .emacs.d/modules/editor/file-templates/templates/makefile-gmake-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/makefile-gmake-mode/__cpp create mode 100644 .emacs.d/modules/editor/file-templates/templates/markdown-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/markdown-mode/__jekyll-post create mode 100644 .emacs.d/modules/editor/file-templates/templates/nix-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/nix-mode/__shell.nix create mode 100644 .emacs.d/modules/editor/file-templates/templates/nose-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/nxml-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/org-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/org-mode/__contact.org create mode 100644 .emacs.d/modules/editor/file-templates/templates/org-mode/__doom-readme create mode 100644 .emacs.d/modules/editor/file-templates/templates/org-mode/__invoice.org create mode 100644 .emacs.d/modules/editor/file-templates/templates/org-mode/__project.org create mode 100644 .emacs.d/modules/editor/file-templates/templates/php-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/php-mode/__.class.php create mode 100755 .emacs.d/modules/editor/file-templates/templates/python-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/python-mode/__setup.py create mode 100755 .emacs.d/modules/editor/file-templates/templates/rspec-mode/__ create mode 100755 .emacs.d/modules/editor/file-templates/templates/rspec-mode/__.rspec create mode 100755 .emacs.d/modules/editor/file-templates/templates/rspec-mode/__helper create mode 100644 .emacs.d/modules/editor/file-templates/templates/ruby-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/ruby-mode/__.gemspec create mode 100644 .emacs.d/modules/editor/file-templates/templates/ruby-mode/__Gemfile create mode 100644 .emacs.d/modules/editor/file-templates/templates/ruby-mode/__Rakefile create mode 100644 .emacs.d/modules/editor/file-templates/templates/ruby-mode/__module create mode 100644 .emacs.d/modules/editor/file-templates/templates/rust-mode/__Cargo.toml create mode 100644 .emacs.d/modules/editor/file-templates/templates/rust-mode/__main.rs create mode 100644 .emacs.d/modules/editor/file-templates/templates/scss-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/scss-mode/__master.scss create mode 100644 .emacs.d/modules/editor/file-templates/templates/scss-mode/__normalize.scss create mode 100644 .emacs.d/modules/editor/file-templates/templates/sh-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/sh-mode/__zunit create mode 100644 .emacs.d/modules/editor/file-templates/templates/slim-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/snippet-mode/__ create mode 100644 .emacs.d/modules/editor/file-templates/templates/solidity-mode/__sol create mode 100644 .emacs.d/modules/editor/file-templates/templates/text-mode/__license create mode 100644 .emacs.d/modules/editor/file-templates/templates/text-mode/__license-apache create mode 100644 .emacs.d/modules/editor/file-templates/templates/text-mode/__license-apache-bp create mode 100644 .emacs.d/modules/editor/file-templates/templates/text-mode/__license-bsd2 create mode 100644 .emacs.d/modules/editor/file-templates/templates/text-mode/__license-bsd3 create mode 100644 .emacs.d/modules/editor/file-templates/templates/text-mode/__license-gpl3 create mode 100644 .emacs.d/modules/editor/file-templates/templates/text-mode/__license-gpl3-bp create mode 100644 .emacs.d/modules/editor/file-templates/templates/text-mode/__license-lgpl3 create mode 100644 .emacs.d/modules/editor/file-templates/templates/text-mode/__license-mit create mode 100644 .emacs.d/modules/editor/file-templates/templates/text-mode/__license-mozilla create mode 100644 .emacs.d/modules/editor/file-templates/templates/text-mode/__license-mozilla-bp create mode 100644 .emacs.d/modules/editor/file-templates/templates/text-mode/__license-unlicense create mode 100644 .emacs.d/modules/editor/file-templates/templates/web-mode/__.html create mode 100644 .emacs.d/modules/editor/file-templates/templates/web-mode/__jekyll-layout.html create mode 100644 .emacs.d/modules/editor/file-templates/templates/yaml-mode/__docker-compose.yml create mode 100644 .emacs.d/modules/editor/fold/README.org create mode 100644 .emacs.d/modules/editor/fold/autoload/fold.el create mode 100644 .emacs.d/modules/editor/fold/autoload/hideshow.el create mode 100644 .emacs.d/modules/editor/fold/config.el create mode 100644 .emacs.d/modules/editor/fold/packages.el create mode 100644 .emacs.d/modules/editor/format/autoload/evil.el create mode 100644 .emacs.d/modules/editor/format/autoload/format.el create mode 100644 .emacs.d/modules/editor/format/autoload/settings.el create mode 100644 .emacs.d/modules/editor/format/config.el create mode 100644 .emacs.d/modules/editor/format/packages.el create mode 100644 .emacs.d/modules/editor/format/test/test-format.el create mode 100644 .emacs.d/modules/editor/god/autoload.el create mode 100644 .emacs.d/modules/editor/god/config.el create mode 100644 .emacs.d/modules/editor/god/doctor.el create mode 100644 .emacs.d/modules/editor/god/packages.el create mode 100644 .emacs.d/modules/editor/lispy/README.org create mode 100644 .emacs.d/modules/editor/lispy/config.el create mode 100644 .emacs.d/modules/editor/lispy/packages.el create mode 100644 .emacs.d/modules/editor/multiple-cursors/autoload/evil-mc.el create mode 100644 .emacs.d/modules/editor/multiple-cursors/config.el create mode 100644 .emacs.d/modules/editor/multiple-cursors/packages.el create mode 100644 .emacs.d/modules/editor/objed/README.org create mode 100644 .emacs.d/modules/editor/objed/config.el create mode 100644 .emacs.d/modules/editor/objed/packages.el create mode 100644 .emacs.d/modules/editor/parinfer/README.org create mode 100644 .emacs.d/modules/editor/parinfer/config.el create mode 100644 .emacs.d/modules/editor/parinfer/packages.el create mode 100644 .emacs.d/modules/editor/rotate-text/autoload.el create mode 100644 .emacs.d/modules/editor/rotate-text/packages.el create mode 100644 .emacs.d/modules/editor/snippets/README.org create mode 100644 .emacs.d/modules/editor/snippets/autoload/settings.el create mode 100644 .emacs.d/modules/editor/snippets/autoload/snippets.el create mode 100644 .emacs.d/modules/editor/snippets/config.el create mode 100644 .emacs.d/modules/editor/snippets/packages.el create mode 100644 .emacs.d/modules/editor/word-wrap/README.org create mode 100644 .emacs.d/modules/editor/word-wrap/autoload.el create mode 100644 .emacs.d/modules/editor/word-wrap/config.el create mode 100644 .emacs.d/modules/editor/word-wrap/packages.el create mode 100644 .emacs.d/modules/emacs/dired/README.org create mode 100644 .emacs.d/modules/emacs/dired/autoload.el create mode 100755 .emacs.d/modules/emacs/dired/config.el create mode 100644 .emacs.d/modules/emacs/dired/doctor.el create mode 100644 .emacs.d/modules/emacs/dired/packages.el create mode 100644 .emacs.d/modules/emacs/electric/autoload.el create mode 100644 .emacs.d/modules/emacs/electric/config.el create mode 100644 .emacs.d/modules/emacs/ibuffer/README.org create mode 100644 .emacs.d/modules/emacs/ibuffer/config.el create mode 100644 .emacs.d/modules/emacs/ibuffer/packages.el create mode 100644 .emacs.d/modules/emacs/vc/autoload/evil.el create mode 100644 .emacs.d/modules/emacs/vc/autoload/hydra.el create mode 100644 .emacs.d/modules/emacs/vc/autoload/vc.el create mode 100644 .emacs.d/modules/emacs/vc/config.el create mode 100644 .emacs.d/modules/emacs/vc/packages.el create mode 100644 .emacs.d/modules/email/mu4e/README.org create mode 100644 .emacs.d/modules/email/mu4e/autoload/email.el create mode 100644 .emacs.d/modules/email/mu4e/autoload/evil.el create mode 100644 .emacs.d/modules/email/mu4e/config.el create mode 100644 .emacs.d/modules/email/mu4e/packages.el create mode 100644 .emacs.d/modules/email/notmuch/autoload.el create mode 100644 .emacs.d/modules/email/notmuch/config.el create mode 100644 .emacs.d/modules/email/notmuch/packages.el create mode 100644 .emacs.d/modules/email/wanderlust/autoload.el create mode 100644 .emacs.d/modules/email/wanderlust/config.el create mode 100644 .emacs.d/modules/email/wanderlust/packages.el create mode 100644 .emacs.d/modules/input/chinese/README.org create mode 100644 .emacs.d/modules/input/chinese/config.el create mode 100644 .emacs.d/modules/input/chinese/packages.el create mode 100644 .emacs.d/modules/input/japanese/README.org create mode 100644 .emacs.d/modules/input/japanese/config.el create mode 100644 .emacs.d/modules/input/japanese/doctor.el create mode 100644 .emacs.d/modules/input/japanese/packages.el create mode 100644 .emacs.d/modules/lang/agda/README.org create mode 100644 .emacs.d/modules/lang/agda/config.el create mode 100644 .emacs.d/modules/lang/agda/packages.el create mode 100644 .emacs.d/modules/lang/assembly/autoload.el create mode 100644 .emacs.d/modules/lang/assembly/packages.el create mode 100644 .emacs.d/modules/lang/cc/README.org create mode 100644 .emacs.d/modules/lang/cc/autoload.el create mode 100644 .emacs.d/modules/lang/cc/config.el create mode 100644 .emacs.d/modules/lang/cc/doctor.el create mode 100644 .emacs.d/modules/lang/cc/packages.el create mode 100644 .emacs.d/modules/lang/clojure/autoload.el create mode 100644 .emacs.d/modules/lang/clojure/config.el create mode 100644 .emacs.d/modules/lang/clojure/packages.el create mode 100644 .emacs.d/modules/lang/common-lisp/autoload/common-lisp.el create mode 100644 .emacs.d/modules/lang/common-lisp/config.el create mode 100644 .emacs.d/modules/lang/common-lisp/doctor.el create mode 100644 .emacs.d/modules/lang/common-lisp/packages.el create mode 100644 .emacs.d/modules/lang/coq/README.org create mode 100644 .emacs.d/modules/lang/coq/config.el create mode 100644 .emacs.d/modules/lang/coq/packages.el create mode 100644 .emacs.d/modules/lang/crystal/config.el create mode 100644 .emacs.d/modules/lang/crystal/doctor.el create mode 100644 .emacs.d/modules/lang/crystal/packages.el create mode 100644 .emacs.d/modules/lang/csharp/README.org create mode 100644 .emacs.d/modules/lang/csharp/autoload.el create mode 100644 .emacs.d/modules/lang/csharp/config.el create mode 100644 .emacs.d/modules/lang/csharp/doctor.el create mode 100644 .emacs.d/modules/lang/csharp/packages.el create mode 100644 .emacs.d/modules/lang/data/config.el create mode 100644 .emacs.d/modules/lang/data/packages.el create mode 100644 .emacs.d/modules/lang/elixir/README.org create mode 100644 .emacs.d/modules/lang/elixir/config.el create mode 100644 .emacs.d/modules/lang/elixir/packages.el create mode 100644 .emacs.d/modules/lang/elm/config.el create mode 100644 .emacs.d/modules/lang/elm/packages.el create mode 100644 .emacs.d/modules/lang/emacs-lisp/autoload.el create mode 100644 .emacs.d/modules/lang/emacs-lisp/config.el create mode 100644 .emacs.d/modules/lang/emacs-lisp/packages.el create mode 100644 .emacs.d/modules/lang/erlang/config.el create mode 100644 .emacs.d/modules/lang/erlang/packages.el create mode 100644 .emacs.d/modules/lang/ess/README.org create mode 100644 .emacs.d/modules/lang/ess/autoload.el create mode 100644 .emacs.d/modules/lang/ess/config.el create mode 100644 .emacs.d/modules/lang/ess/packages.el create mode 100644 .emacs.d/modules/lang/factor/README.org create mode 100644 .emacs.d/modules/lang/factor/config.el create mode 100644 .emacs.d/modules/lang/factor/packages.el create mode 100644 .emacs.d/modules/lang/faust/README.org create mode 100644 .emacs.d/modules/lang/faust/autoload.el create mode 100644 .emacs.d/modules/lang/faust/config.el create mode 100644 .emacs.d/modules/lang/faust/doctor.el create mode 100644 .emacs.d/modules/lang/faust/packages.el create mode 100644 .emacs.d/modules/lang/fsharp/README.org create mode 100644 .emacs.d/modules/lang/fsharp/config.el create mode 100644 .emacs.d/modules/lang/fsharp/doctor.el create mode 100644 .emacs.d/modules/lang/fsharp/packages.el create mode 100644 .emacs.d/modules/lang/go/README.org create mode 100644 .emacs.d/modules/lang/go/autoload.el create mode 100644 .emacs.d/modules/lang/go/config.el create mode 100644 .emacs.d/modules/lang/go/doctor.el create mode 100644 .emacs.d/modules/lang/go/packages.el create mode 100644 .emacs.d/modules/lang/haskell/+dante.el create mode 100644 .emacs.d/modules/lang/haskell/+intero.el create mode 100644 .emacs.d/modules/lang/haskell/+lsp.el create mode 100644 .emacs.d/modules/lang/haskell/README.org create mode 100644 .emacs.d/modules/lang/haskell/autoload.el create mode 100644 .emacs.d/modules/lang/haskell/config.el create mode 100644 .emacs.d/modules/lang/haskell/doctor.el create mode 100644 .emacs.d/modules/lang/haskell/packages.el create mode 100644 .emacs.d/modules/lang/hy/config.el create mode 100644 .emacs.d/modules/lang/hy/packages.el create mode 100644 .emacs.d/modules/lang/idris/README.org create mode 100644 .emacs.d/modules/lang/idris/config.el create mode 100644 .emacs.d/modules/lang/idris/packages.el create mode 100644 .emacs.d/modules/lang/java/+eclim.el create mode 100644 .emacs.d/modules/lang/java/+lsp.el create mode 100644 .emacs.d/modules/lang/java/+meghanada.el create mode 100644 .emacs.d/modules/lang/java/autoload.el create mode 100644 .emacs.d/modules/lang/java/config.el create mode 100644 .emacs.d/modules/lang/java/doctor.el create mode 100644 .emacs.d/modules/lang/java/packages.el create mode 100644 .emacs.d/modules/lang/javascript/README.org create mode 100644 .emacs.d/modules/lang/javascript/autoload.el create mode 100644 .emacs.d/modules/lang/javascript/config.el create mode 100644 .emacs.d/modules/lang/javascript/doctor.el create mode 100644 .emacs.d/modules/lang/javascript/packages.el create mode 100644 .emacs.d/modules/lang/julia/autoload.el create mode 100644 .emacs.d/modules/lang/julia/config.el create mode 100644 .emacs.d/modules/lang/julia/packages.el create mode 100644 .emacs.d/modules/lang/kotlin/autoload.el create mode 100644 .emacs.d/modules/lang/kotlin/config.el create mode 100644 .emacs.d/modules/lang/kotlin/doctor.el create mode 100644 .emacs.d/modules/lang/kotlin/packages.el create mode 100644 .emacs.d/modules/lang/latex/+fontification.el create mode 100644 .emacs.d/modules/lang/latex/+ref.el create mode 100644 .emacs.d/modules/lang/latex/+viewers.el create mode 100644 .emacs.d/modules/lang/latex/README.org create mode 100644 .emacs.d/modules/lang/latex/autoload.el create mode 100644 .emacs.d/modules/lang/latex/config.el create mode 100644 .emacs.d/modules/lang/latex/packages.el create mode 100644 .emacs.d/modules/lang/lean/config.el create mode 100644 .emacs.d/modules/lang/lean/packages.el create mode 100644 .emacs.d/modules/lang/ledger/README.org create mode 100644 .emacs.d/modules/lang/ledger/config.el create mode 100644 .emacs.d/modules/lang/ledger/packages.el create mode 100644 .emacs.d/modules/lang/lua/autoload/lua.el create mode 100644 .emacs.d/modules/lang/lua/autoload/moonscript.el create mode 100644 .emacs.d/modules/lang/lua/config.el create mode 100644 .emacs.d/modules/lang/lua/packages.el create mode 100644 .emacs.d/modules/lang/markdown/README.org create mode 100644 .emacs.d/modules/lang/markdown/autoload.el create mode 100644 .emacs.d/modules/lang/markdown/config.el create mode 100644 .emacs.d/modules/lang/markdown/doctor.el create mode 100644 .emacs.d/modules/lang/markdown/packages.el create mode 100644 .emacs.d/modules/lang/nim/README.org create mode 100644 .emacs.d/modules/lang/nim/config.el create mode 100644 .emacs.d/modules/lang/nim/doctor.el create mode 100644 .emacs.d/modules/lang/nim/packages.el create mode 100644 .emacs.d/modules/lang/nix/autoload.el create mode 100644 .emacs.d/modules/lang/nix/config.el create mode 100644 .emacs.d/modules/lang/nix/doctor.el create mode 100644 .emacs.d/modules/lang/nix/packages.el create mode 100644 .emacs.d/modules/lang/ocaml/README.org create mode 100644 .emacs.d/modules/lang/ocaml/autoload.el create mode 100644 .emacs.d/modules/lang/ocaml/config.el create mode 100644 .emacs.d/modules/lang/ocaml/doctor.el create mode 100644 .emacs.d/modules/lang/ocaml/packages.el create mode 100644 .emacs.d/modules/lang/org/README.org create mode 100644 .emacs.d/modules/lang/org/autoload/contrib-dragndrop.el create mode 100644 .emacs.d/modules/lang/org/autoload/contrib-ipython.el create mode 100644 .emacs.d/modules/lang/org/autoload/contrib-present.el create mode 100644 .emacs.d/modules/lang/org/autoload/org-attach.el create mode 100644 .emacs.d/modules/lang/org/autoload/org-avy.el create mode 100644 .emacs.d/modules/lang/org/autoload/org-capture.el create mode 100644 .emacs.d/modules/lang/org/autoload/org-export.el create mode 100644 .emacs.d/modules/lang/org/autoload/org-link.el create mode 100644 .emacs.d/modules/lang/org/autoload/org-refile.el create mode 100644 .emacs.d/modules/lang/org/autoload/org-tables.el create mode 100644 .emacs.d/modules/lang/org/autoload/org.el create mode 100644 .emacs.d/modules/lang/org/config.el create mode 100644 .emacs.d/modules/lang/org/contrib/dragndrop.el create mode 100644 .emacs.d/modules/lang/org/contrib/ipython.el create mode 100644 .emacs.d/modules/lang/org/contrib/journal.el create mode 100644 .emacs.d/modules/lang/org/contrib/pomodoro.el create mode 100644 .emacs.d/modules/lang/org/contrib/present.el create mode 100644 .emacs.d/modules/lang/org/doctor.el create mode 100644 .emacs.d/modules/lang/org/packages.el create mode 100644 .emacs.d/modules/lang/org/test/test-org.el create mode 100644 .emacs.d/modules/lang/perl/README.org create mode 100644 .emacs.d/modules/lang/perl/config.el create mode 100644 .emacs.d/modules/lang/perl/packages.el create mode 100644 .emacs.d/modules/lang/php/README.org create mode 100644 .emacs.d/modules/lang/php/autoload.el create mode 100644 .emacs.d/modules/lang/php/config.el create mode 100644 .emacs.d/modules/lang/php/doctor.el create mode 100644 .emacs.d/modules/lang/php/packages.el create mode 100644 .emacs.d/modules/lang/plantuml/autoload.el create mode 100644 .emacs.d/modules/lang/plantuml/config.el create mode 100644 .emacs.d/modules/lang/plantuml/doctor.el create mode 100644 .emacs.d/modules/lang/plantuml/packages.el create mode 100644 .emacs.d/modules/lang/purescript/config.el create mode 100644 .emacs.d/modules/lang/purescript/packages.el create mode 100644 .emacs.d/modules/lang/python/README.org create mode 100644 .emacs.d/modules/lang/python/autoload/conda.el create mode 100644 .emacs.d/modules/lang/python/autoload/pyenv.el create mode 100644 .emacs.d/modules/lang/python/autoload/python.el create mode 100644 .emacs.d/modules/lang/python/config.el create mode 100644 .emacs.d/modules/lang/python/doctor.el create mode 100644 .emacs.d/modules/lang/python/packages.el create mode 100644 .emacs.d/modules/lang/qt/autoload.el create mode 100644 .emacs.d/modules/lang/qt/packages.el create mode 100644 .emacs.d/modules/lang/racket/autoload.el create mode 100644 .emacs.d/modules/lang/racket/config.el create mode 100644 .emacs.d/modules/lang/racket/doctor.el create mode 100644 .emacs.d/modules/lang/racket/packages.el create mode 100644 .emacs.d/modules/lang/rest/README.org create mode 100644 .emacs.d/modules/lang/rest/autoload.el create mode 100644 .emacs.d/modules/lang/rest/config.el create mode 100644 .emacs.d/modules/lang/rest/packages.el create mode 100644 .emacs.d/modules/lang/rst/config.el create mode 100644 .emacs.d/modules/lang/rst/packages.el create mode 100644 .emacs.d/modules/lang/ruby/autoload.el create mode 100644 .emacs.d/modules/lang/ruby/config.el create mode 100644 .emacs.d/modules/lang/ruby/doctor.el create mode 100644 .emacs.d/modules/lang/ruby/packages.el create mode 100644 .emacs.d/modules/lang/rust/README.org create mode 100644 .emacs.d/modules/lang/rust/autoload.el create mode 100644 .emacs.d/modules/lang/rust/config.el create mode 100644 .emacs.d/modules/lang/rust/doctor.el create mode 100644 .emacs.d/modules/lang/rust/packages.el create mode 100644 .emacs.d/modules/lang/scala/autoload.el create mode 100644 .emacs.d/modules/lang/scala/config.el create mode 100644 .emacs.d/modules/lang/scala/doctor.el create mode 100644 .emacs.d/modules/lang/scala/packages.el create mode 100644 .emacs.d/modules/lang/scheme/README.org create mode 100644 .emacs.d/modules/lang/scheme/autoload.el create mode 100644 .emacs.d/modules/lang/scheme/config.el create mode 100644 .emacs.d/modules/lang/scheme/packages.el create mode 100644 .emacs.d/modules/lang/sh/README.org create mode 100644 .emacs.d/modules/lang/sh/autoload.el create mode 100755 .emacs.d/modules/lang/sh/config.el create mode 100644 .emacs.d/modules/lang/sh/doctor.el create mode 100644 .emacs.d/modules/lang/sh/packages.el create mode 100644 .emacs.d/modules/lang/solidity/README.org create mode 100644 .emacs.d/modules/lang/solidity/config.el create mode 100644 .emacs.d/modules/lang/solidity/doctor.el create mode 100644 .emacs.d/modules/lang/solidity/packages.el create mode 100644 .emacs.d/modules/lang/swift/config.el create mode 100644 .emacs.d/modules/lang/swift/packages.el create mode 100644 .emacs.d/modules/lang/terra/autoload.el create mode 100644 .emacs.d/modules/lang/terra/config.el create mode 100644 .emacs.d/modules/lang/terra/packages.el create mode 100644 .emacs.d/modules/lang/web/+css.el create mode 100644 .emacs.d/modules/lang/web/+html.el create mode 100644 .emacs.d/modules/lang/web/autoload/css.el create mode 100644 .emacs.d/modules/lang/web/autoload/evil.el create mode 100644 .emacs.d/modules/lang/web/autoload/html.el create mode 100644 .emacs.d/modules/lang/web/config.el create mode 100644 .emacs.d/modules/lang/web/doctor.el create mode 100644 .emacs.d/modules/lang/web/packages.el create mode 100644 .emacs.d/modules/lang/web/test/test-web.el create mode 100644 .emacs.d/modules/term/eshell/autoload/commands.el create mode 100644 .emacs.d/modules/term/eshell/autoload/company.el create mode 100644 .emacs.d/modules/term/eshell/autoload/eshell.el create mode 100644 .emacs.d/modules/term/eshell/autoload/evil.el create mode 100644 .emacs.d/modules/term/eshell/autoload/prompts.el create mode 100644 .emacs.d/modules/term/eshell/autoload/settings.el create mode 100644 .emacs.d/modules/term/eshell/config.el create mode 100644 .emacs.d/modules/term/eshell/packages.el create mode 100644 .emacs.d/modules/term/shell/autoload.el create mode 100644 .emacs.d/modules/term/shell/config.el create mode 100644 .emacs.d/modules/term/shell/packages.el create mode 100644 .emacs.d/modules/term/term/autoload.el create mode 100644 .emacs.d/modules/term/term/config.el create mode 100644 .emacs.d/modules/term/term/packages.el create mode 100644 .emacs.d/modules/term/vterm/README.org create mode 100644 .emacs.d/modules/term/vterm/autoload.el create mode 100644 .emacs.d/modules/term/vterm/config.el create mode 100644 .emacs.d/modules/term/vterm/doctor.el create mode 100644 .emacs.d/modules/term/vterm/packages.el create mode 100644 .emacs.d/modules/tools/ansible/config.el create mode 100644 .emacs.d/modules/tools/ansible/packages.el create mode 100644 .emacs.d/modules/tools/debugger/autoload/debugger.el create mode 100644 .emacs.d/modules/tools/debugger/autoload/evil.el create mode 100644 .emacs.d/modules/tools/debugger/config.el create mode 100644 .emacs.d/modules/tools/debugger/packages.el create mode 100644 .emacs.d/modules/tools/direnv/README.org create mode 100644 .emacs.d/modules/tools/direnv/config.el create mode 100644 .emacs.d/modules/tools/direnv/doctor.el create mode 100644 .emacs.d/modules/tools/direnv/packages.el create mode 100644 .emacs.d/modules/tools/docker/README.org create mode 100644 .emacs.d/modules/tools/docker/config.el create mode 100644 .emacs.d/modules/tools/docker/packages.el create mode 100644 .emacs.d/modules/tools/editorconfig/README.org create mode 100644 .emacs.d/modules/tools/editorconfig/autoload.el create mode 100644 .emacs.d/modules/tools/editorconfig/config.el create mode 100644 .emacs.d/modules/tools/editorconfig/doctor.el create mode 100644 .emacs.d/modules/tools/editorconfig/packages.el create mode 100644 .emacs.d/modules/tools/ein/README.org create mode 100644 .emacs.d/modules/tools/ein/autoload/ein.el create mode 100644 .emacs.d/modules/tools/ein/autoload/hydra.el create mode 100644 .emacs.d/modules/tools/ein/config.el create mode 100644 .emacs.d/modules/tools/ein/packages.el create mode 100644 .emacs.d/modules/tools/eval/README.org create mode 100644 .emacs.d/modules/tools/eval/autoload/eval.el create mode 100644 .emacs.d/modules/tools/eval/autoload/evil.el create mode 100644 .emacs.d/modules/tools/eval/autoload/repl.el create mode 100644 .emacs.d/modules/tools/eval/autoload/settings.el create mode 100644 .emacs.d/modules/tools/eval/config.el create mode 100644 .emacs.d/modules/tools/eval/packages.el create mode 100644 .emacs.d/modules/tools/flycheck/autoload.el create mode 100644 .emacs.d/modules/tools/flycheck/config.el create mode 100644 .emacs.d/modules/tools/flycheck/packages.el create mode 100644 .emacs.d/modules/tools/flyspell/autoload.el create mode 100644 .emacs.d/modules/tools/flyspell/config.el create mode 100644 .emacs.d/modules/tools/flyspell/doctor.el create mode 100644 .emacs.d/modules/tools/flyspell/packages.el create mode 100644 .emacs.d/modules/tools/gist/autoload/evil.el create mode 100644 .emacs.d/modules/tools/gist/config.el create mode 100644 .emacs.d/modules/tools/gist/packages.el create mode 100644 .emacs.d/modules/tools/lookup/README.org create mode 100644 .emacs.d/modules/tools/lookup/autoload/docsets.el create mode 100644 .emacs.d/modules/tools/lookup/autoload/evil.el create mode 100644 .emacs.d/modules/tools/lookup/autoload/lookup.el create mode 100644 .emacs.d/modules/tools/lookup/autoload/online.el create mode 100644 .emacs.d/modules/tools/lookup/config.el create mode 100644 .emacs.d/modules/tools/lookup/packages.el create mode 100644 .emacs.d/modules/tools/lsp/README.org create mode 100644 .emacs.d/modules/tools/lsp/autoload.el create mode 100644 .emacs.d/modules/tools/lsp/config.el create mode 100644 .emacs.d/modules/tools/lsp/packages.el create mode 100644 .emacs.d/modules/tools/macos/autoload.el create mode 100644 .emacs.d/modules/tools/magit/autoload.el create mode 100644 .emacs.d/modules/tools/magit/config.el create mode 100644 .emacs.d/modules/tools/magit/packages.el create mode 100644 .emacs.d/modules/tools/make/autoload.el create mode 100644 .emacs.d/modules/tools/make/packages.el create mode 100644 .emacs.d/modules/tools/pass/autoload.el create mode 100644 .emacs.d/modules/tools/pass/config.el create mode 100644 .emacs.d/modules/tools/pass/packages.el create mode 100644 .emacs.d/modules/tools/pass/test/test-pass.el create mode 100644 .emacs.d/modules/tools/pdf/autoload/pdf.el create mode 100644 .emacs.d/modules/tools/pdf/config.el create mode 100644 .emacs.d/modules/tools/pdf/packages.el create mode 100644 .emacs.d/modules/tools/prodigy/autoload.el create mode 100644 .emacs.d/modules/tools/prodigy/config.el create mode 100644 .emacs.d/modules/tools/prodigy/packages.el create mode 100644 .emacs.d/modules/tools/rgb/autoload.el create mode 100644 .emacs.d/modules/tools/rgb/packages.el create mode 100644 .emacs.d/modules/tools/terraform/README.org create mode 100644 .emacs.d/modules/tools/terraform/config.el create mode 100644 .emacs.d/modules/tools/terraform/doctor.el create mode 100644 .emacs.d/modules/tools/terraform/packages.el create mode 100644 .emacs.d/modules/tools/tmux/autoload/evil.el create mode 100644 .emacs.d/modules/tools/tmux/autoload/tmux.el create mode 100644 .emacs.d/modules/tools/upload/config.el create mode 100644 .emacs.d/modules/tools/upload/packages.el create mode 100644 .emacs.d/modules/ui/deft/README.org create mode 100644 .emacs.d/modules/ui/deft/config.el create mode 100644 .emacs.d/modules/ui/deft/packages.el create mode 100644 .emacs.d/modules/ui/doom-dashboard/README.org create mode 100644 .emacs.d/modules/ui/doom-dashboard/autoload.el create mode 100644 .emacs.d/modules/ui/doom-dashboard/config.el create mode 100644 .emacs.d/modules/ui/doom-dashboard/test/test-doom-dashboard.el create mode 100644 .emacs.d/modules/ui/doom-quit/README.org create mode 100644 .emacs.d/modules/ui/doom-quit/config.el create mode 100644 .emacs.d/modules/ui/doom/README.org create mode 100644 .emacs.d/modules/ui/doom/config.el create mode 100644 .emacs.d/modules/ui/doom/packages.el create mode 100644 .emacs.d/modules/ui/fill-column/autoload.el create mode 100644 .emacs.d/modules/ui/fill-column/packages.el create mode 100644 .emacs.d/modules/ui/hl-todo/README.org create mode 100644 .emacs.d/modules/ui/hl-todo/config.el create mode 100644 .emacs.d/modules/ui/hl-todo/packages.el create mode 100644 .emacs.d/modules/ui/hydra/README.org create mode 100644 .emacs.d/modules/ui/hydra/autoload/windows.el create mode 100644 .emacs.d/modules/ui/hydra/config.el create mode 100644 .emacs.d/modules/ui/hydra/packages.el create mode 100644 .emacs.d/modules/ui/indent-guides/config.el create mode 100644 .emacs.d/modules/ui/indent-guides/packages.el create mode 100644 .emacs.d/modules/ui/modeline/+light.el create mode 100644 .emacs.d/modules/ui/modeline/README.org create mode 100644 .emacs.d/modules/ui/modeline/autoload.el create mode 100644 .emacs.d/modules/ui/modeline/config.el create mode 100644 .emacs.d/modules/ui/modeline/packages.el create mode 100644 .emacs.d/modules/ui/nav-flash/README.org create mode 100644 .emacs.d/modules/ui/nav-flash/autoload.el create mode 100644 .emacs.d/modules/ui/nav-flash/config.el create mode 100644 .emacs.d/modules/ui/nav-flash/packages.el create mode 100644 .emacs.d/modules/ui/neotree/README.org create mode 100644 .emacs.d/modules/ui/neotree/autoload.el create mode 100644 .emacs.d/modules/ui/neotree/config.el create mode 100644 .emacs.d/modules/ui/neotree/packages.el create mode 100644 .emacs.d/modules/ui/ophints/README.org create mode 100644 .emacs.d/modules/ui/ophints/config.el create mode 100644 .emacs.d/modules/ui/ophints/packages.el create mode 100644 .emacs.d/modules/ui/popup/+hacks.el create mode 100644 .emacs.d/modules/ui/popup/README.org create mode 100644 .emacs.d/modules/ui/popup/autoload/popup.el create mode 100644 .emacs.d/modules/ui/popup/autoload/settings.el create mode 100644 .emacs.d/modules/ui/popup/config.el create mode 100644 .emacs.d/modules/ui/popup/test/test-popup.el create mode 100644 .emacs.d/modules/ui/pretty-code/+fira.el create mode 100644 .emacs.d/modules/ui/pretty-code/+hasklig.el create mode 100644 .emacs.d/modules/ui/pretty-code/+iosevka.el create mode 100644 .emacs.d/modules/ui/pretty-code/+pragmata-pro.el create mode 100644 .emacs.d/modules/ui/pretty-code/autoload.el create mode 100644 .emacs.d/modules/ui/pretty-code/config.el create mode 100644 .emacs.d/modules/ui/tabs/README.org create mode 100644 .emacs.d/modules/ui/tabs/autoload.el create mode 100644 .emacs.d/modules/ui/tabs/config.el create mode 100644 .emacs.d/modules/ui/tabs/packages.el create mode 100644 .emacs.d/modules/ui/treemacs/autoload.el create mode 100644 .emacs.d/modules/ui/treemacs/config.el create mode 100644 .emacs.d/modules/ui/treemacs/packages.el create mode 100644 .emacs.d/modules/ui/unicode/README.org create mode 100644 .emacs.d/modules/ui/unicode/autoload.el create mode 100644 .emacs.d/modules/ui/unicode/packages.el create mode 100644 .emacs.d/modules/ui/vc-gutter/autoload.el create mode 100644 .emacs.d/modules/ui/vc-gutter/config.el create mode 100644 .emacs.d/modules/ui/vc-gutter/packages.el create mode 100644 .emacs.d/modules/ui/vi-tilde-fringe/autoload.el create mode 100644 .emacs.d/modules/ui/vi-tilde-fringe/packages.el create mode 100644 .emacs.d/modules/ui/window-select/README.org create mode 100644 .emacs.d/modules/ui/window-select/config.el create mode 100644 .emacs.d/modules/ui/window-select/packages.el create mode 100644 .emacs.d/modules/ui/workspaces/README.org create mode 100644 .emacs.d/modules/ui/workspaces/autoload/evil.el create mode 100644 .emacs.d/modules/ui/workspaces/autoload/workspaces.el create mode 100644 .emacs.d/modules/ui/workspaces/config.el create mode 100644 .emacs.d/modules/ui/workspaces/packages.el create mode 100644 .emacs.d/modules/ui/workspaces/test/test-workspaces.el diff --git a/.emacs.d/LICENSE b/.emacs.d/LICENSE new file mode 100644 index 0000000..02b93be --- /dev/null +++ b/.emacs.d/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +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 +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.emacs.d/README.md b/.emacs.d/README.md new file mode 100644 index 0000000..4ad52f3 --- /dev/null +++ b/.emacs.d/README.md @@ -0,0 +1,178 @@ + + Made with Doom Emacs + + + Made for Emacs 26.1+ + + + Build status: develop + + + Join our discord server + +

+ +![Doom Emacs Screenshot](https://raw.githubusercontent.com/hlissner/doom-emacs/screenshots/main.png) + +

+ Screenshots + | + Get started + | + Contribute + | + Documentation + | + FAQ +

+ +--- + +**Quick start** + +```bash +git clone https://github.com/hlissner/doom-emacs ~/.emacs.d +~/.emacs.d/bin/doom install +``` + +**Table of Contents** + +- [What is Doom Emacs](#what-is-doom-emacs) + - [Doom's mantras](#dooms-mantras) + - [Features](#features) +- [Getting Help](#getting-help) + - [Community](#community) + - [Troubleshooting](#troubleshooting) + - [Contributing](#contributing) + +# What is Doom Emacs + + + + + +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 succumbing to the [dark side][url:evil-mode]. This is his config. + +Doom is a configuration for [GNU Emacs](https://www.gnu.org/software/emacs/) +designed to make Emacs faster and easier to customize. It can serve as framework +for your own configuration or a resource for fellow Emacs enthusiasts who want +to learn more about our favorite OS. + +## Doom's mantras + +- **Gotta go fast.** Startup and run-time performance are high priorities. + Expensive functionality (built-in or in plugins) is modified and optimized + toward this end, otherwise, they must be opt-in. +- **Close to metal.** There's less between you and vanilla Emacs, by design. + There's less to grok. Modules should be syntactically sweet and backend logic + explicit and abstraction-light. The code itself ought to be designed as if + grokking it were part of the user experience; and it is! +- **Opinionated, but not stubborn.** Doom is a bundle of reasonable defaults + and curated opinions, but you aren't stuck with it. Use as little or as much + of it as you like. Use it as-is as a complete Emacs distribution; disable + everything and use it as a baseline for your own; or anywhere in between. +- **Your system, your rules.** There are more ways to set up your programming + environment than there are dislikes on Youtube Rewind '18, so Doom and its + plugins promise not to *automatically* (and definitely not *silently*) install + system dependencies. This means fonts, packages and programs. `doom doctor` + will tell you what's missing though! + +## Features + +- A curated set of sane defaults for all packages, all (major) OSes, and Emacs + itself. +- Support for *many* programming languages. Too many to list. Includes syntax + highlighting, linters/checker integration, inline code evaluation, code + completion (where possible), REPLs, documentation lookups, snippets, and more! +- Support for *many* tools, like docker, pass, ansible, terraform, and more. +- Minimalistic good looks inspired by modern editors. +- A modular architecture for a more organized Emacs configuration. +- A custom elisp library to help you simplify your config. +- A declarative [package management system][doom:packages] (powered by + [straight.el][url:straight]) with a command line interface. Install packages + from anywhere, not just (M)ELPA. +- Vim-emulation powered by [evil-mode][url:evil-mode], including ports of + popular vim plugins and functionality. +- A Spacemacs-esque [keybinding scheme][doom:bindings], centered around leader + and localleader prefix keys (SPC and SPCm, by + default). +- A rule-based [popup management system][doom:popups] that dictates how + temporary or disposable buffers are displayed (and disposed of). +- Automatic indentation detection and [editorconfig][url:editorconfig] + integration. Let someone else argue about tabs vs **\_\***spaces**\*\_**. +- Project-management tools and framework-specific minor modes with their own + snippets libraries. +- Project search (and replace) utilities, powered by + [the_silver_searcher][url:the_silver_searcher] or [ripgrep][url:ripgrep]. +- Isolated and persistent workspaces (also substitutes for vim tabs). +- An environment variables file generator and loader, so that Emacs can + perfectly inherit your shell configuration. +- Everything is optional! + +# Getting Help + +## Community + +We have [a Discord server][url:discord]! Hop on and say hi! + +## Troubleshooting + +Encountered strange behavior or an error? Here are some things to try before you +shoot off that bug report: + +- 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 the issue go away. Never debug issues with a + byte-compiled config, it will 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. + +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 messages or +warnings logged to the \*Messages\* buffer (can be opened with SPC h +e or `M-x view-echo-area-messages`). + +It's a great help if you included a backtrace with errors, i.e. `M-x +toggle-debug-on-error` then recreating the error(s). + +## Contributing + +Doom (and my Emacs work in general) is a labor of love and incurable madness, +done on my spare time. If you'd like to support my work, there are many things +you can do to help; I welcome any contribution! + +- I love pull requests and bug reports. Check out the [Contributing + Guidelines][docs:contributing] 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 studies, ventures in indie gamedev, and + my freelance work. + +[docs:wiki]: docs/index.org +[docs:wiki-quickstart]: docs/getting_started.org +[docs:wiki-modules]: docs/index.org#Module%20List +[docs:wiki-customization]: docs/getting_started.org#Customize +[docs:contributing]: docs/contributing.org +[docs:faq]: docs/faq.org +[github:new-issue]: https://github.com/hlissner/doom-emacs/issues/new +[doom:bindings]: modules/config/default/+evil-bindings.el +[doom:packages]: core/autoload/packages.el +[doom:popups]: modules/ui/popup/README.org +[url:discord]: https://discord.gg/bcZ6P3y +[url:liberapay]: https://liberapay.com/hlissner/donate +[url:paypal]: https://paypal.me/henriklissner/10 +[url:editorconfig]: http://editorconfig.org/ +[url:evil-mode]: https://github.com/emacs-evil/evil +[url:ripgrep]: https://github.com/BurntSushi/ripgrep +[url:the_silver_searcher]: https://github.com/ggreer/the_silver_searcher +[url:straight]: https://github.com/raxod502/straight.el diff --git a/.emacs.d/bin/doom b/.emacs.d/bin/doom new file mode 100755 index 0000000..47dc9b7 --- /dev/null +++ b/.emacs.d/bin/doom @@ -0,0 +1,106 @@ +#!/usr/bin/env sh +:; ( echo "$EMACS" | grep -q "term" ) && EMACS=emacs || EMACS=${EMACS:-emacs} # -*-emacs-lisp-*- +:; command -v $EMACS >/dev/null || { >&2 echo "Can't find emacs in your PATH"; exit 1; } +:; VERSION=$($EMACS --version | head -n1) +:; case "$VERSION" in *\ 2[0-5].[0-9]) echo "Detected Emacs $VERSION"; echo "Doom only supports Emacs 26.1 and newer"; echo; exit 2 ;; esac +:; DOOMBASE="$(dirname "$0")/.." +:; [ "$1" = -d ] || [ "$1" = --debug ] && { shift; export DEBUG=1; } +:; [ "$1" = run ] && { cd "$DOOMBASE"; shift; exec $EMACS -q --no-splash -l bin/doom "$@"; exit 0; } +:; exec $EMACS --script "$0" -- "$@" +:; exit 0 + +(let* ((loaddir (file-name-directory (file-truename load-file-name))) + (emacsdir (getenv "EMACSDIR")) + (user-emacs-directory (or emacsdir (expand-file-name "../" loaddir))) + (load-prefer-newer t)) + + (push (expand-file-name "core" user-emacs-directory) load-path) + (require 'core) + (require 'core-cli) + + (defcli! :main + ((help-p ["-h" "--help"] "Same as help command") + (debug-p ["-d" "--debug"] "Turns on doom-debug-mode (and debug-on-error)") + (yes-p ["-y" "--yes"] "Auto-accept all confirmation prompts") + (emacsdir ["--emacsdir" dir] "Use the emacs config at DIR (e.g. ~/.emacs.d)") + (doomdir ["--doomdir" dir] "Use the private module at DIR (e.g. ~/.doom.d)") + (localdir ["--localdir" dir] "Use DIR as your local storage directory") + &optional command &rest args) + "A command line interface for managing Doom Emacs. + +Includes package management, diagnostics, unit tests, and byte-compilation. + +This tool also makes it trivial to launch Emacs out of a different folder or +with a different private module." + :bare t + (when emacsdir + (setq user-emacs-directory (file-name-as-directory emacsdir)) + (print! (info "EMACSDIR=%s") localdir)) + (when doomdir + (setenv "DOOMDIR" doomdir) + (print! (info "DOOMDIR=%s") localdir)) + (when localdir + (setenv "DOOMLOCALDIR" localdir) + (print! (info "DOOMLOCALDIR=%s") localdir)) + (when debug-p + (setenv "DEBUG" "1") + (setq doom-debug-mode t) + (print! (info "Debug mode on"))) + (when yes-p + (setenv "YES" "1") + (setq doom-auto-accept t) + (print! (info "Auto-yes on"))) + (when help-p + (push command args) + (setq command "help")) + + ;; Reload core in case any of the directories were changed. + (when (or emacsdir doomdir localdir) + (load! "core/core.el" user-emacs-directory)) + + (cond ((not noninteractive) + (print! "Doom launched out of %s (test mode)" (path user-emacs-directory)) + (load! "init.el" user-emacs-directory) + (doom-run-all-startup-hooks-h)) + + ((null command) + (doom-cli-execute "help")) + + ((condition-case e + (let ((start-time (current-time))) + (and (doom-cli-execute command args) + (print! (success "Finished! (%.4fs)") + (float-time + (time-subtract (current-time) + start-time))))) + (user-error + (print! (error "%s\n") (error-message-string e)) + (print! (yellow "See 'doom help %s' for documentation on this command.") (car args)) + (error "")) ; Ensure non-zero exit code + ((debug error) + (print! (error "There was an unexpected error:")) + (print-group! + (print! "%s %s" (bold "Type:") (car e)) + (print! (bold "Message:")) + (print-group! + (print! "%s" (get (car e) 'error-message))) + (print! (bold "Data:")) + (print-group! + (if (cdr e) + (dolist (item (cdr e)) + (print! "%S" item)) + (print! "n/a")))) + (unless debug-on-error + (terpri) + (print! + (concat "Run the command again with the -d (or --debug) switch to enable debug\n" + "mode and (hopefully) generate a backtrace from this error:\n" + "\n %s\n\n" + "If you file a bug report, please include it!") + (string-join (append (list (file-name-nondirectory load-file-name) "-d" command) + args) + " ")) + (error ""))))))) ; Ensure non-zero exit code + + (doom-cli-execute :main (cdr (member "--" argv))) + (setq argv nil)) diff --git a/.emacs.d/bin/doom.cmd b/.emacs.d/bin/doom.cmd new file mode 100644 index 0000000..7cba4ca --- /dev/null +++ b/.emacs.d/bin/doom.cmd @@ -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-h" +) ELSE ( + emacs --quick --script .\doom -- %* +) + +POPD >NUL +ECHO ON diff --git a/.emacs.d/bin/org-capture b/.emacs.d/bin/org-capture new file mode 100755 index 0000000..15d6f0a --- /dev/null +++ b/.emacs.d/bin/org-capture @@ -0,0 +1,42 @@ +#!/usr/bin/env sh + +# Open an org-capture popup frame from the shell. This opens a temporary emacs +# daemon if emacs isn't already running. +# +# Usage: org-capture [-k KEY] [MESSAGE] +# Examples: +# org-capture -k n "To the mind that is still, the whole universe surrenders." + +set -e + +cleanup() { + emacsclient --eval '(let (kill-emacs-hook) (kill-emacs))' +} + +# If emacs isn't running, we start a temporary daemon, solely for this window. +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 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) (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})" +fi diff --git a/.emacs.d/bin/org-tangle b/.emacs.d/bin/org-tangle new file mode 100755 index 0000000..7323c51 --- /dev/null +++ b/.emacs.d/bin/org-tangle @@ -0,0 +1,144 @@ +#!/usr/bin/env sh +":"; exec emacs --quick --script "$0" -- "$@" # -*- mode: emacs-lisp; lexical-binding: t; -*- +;;; bin/org-tangle + +;; 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 -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 + +(require 'cl-lib) +(require 'ob-tangle) + +(defun usage () + (with-temp-buffer + (insert (format "%s %s [OPTIONS] [TARGETS...]\n" + "Usage:" + (file-name-nondirectory load-file-name)) + "\n" + "A command line interface for tangling org-mode files. TARGETS can be\n" + "files or folders (which are searched for org files recursively).\n" + "\n" + "This is useful for literate configs that rely on command line\n" + "workflows to build it.\n" + "\n" + "Example:\n" + " org-tangle some-file.org\n" + " org-tangle literate/config/\n" + " org-tangle -p -l sh scripts.org > do_something.sh\n" + " org-tangle -p -l python -t tagA -t tagB file.org | python\n" + "\n" + "Options:\n" + " -a --all\t\tTangle all blocks by default\n" + " -l --lang LANG\tOnly tangle blocks written in LANG\n" + " -p --print\t\tPrint tangled output to stdout than to files\n" + " -t --tag TAG\n" + " --and TAG\n" + " --or TAG\n" + " Lets you tangle org blocks by tag. You may have more than one\n" + " of these options.\n") + (princ (buffer-string)))) + +(defun *org-babel-tangle (orig-fn &rest args) + "Don't write tangled blocks to files, print them to stdout." + (cl-letf (((symbol-function 'write-region) + (lambda (start end filename &optional append visit lockname mustbenew) + (princ (buffer-string))))) + (apply orig-fn args))) + +(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 "-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))))) + + (dolist (file srcs) + (org-babel-tangle-file file nil lang)) + (kill-emacs 0)) diff --git a/.emacs.d/core/autoload/buffers.el b/.emacs.d/core/autoload/buffers.el new file mode 100644 index 0000000..65c094e --- /dev/null +++ b/.emacs.d/core/autoload/buffers.el @@ -0,0 +1,370 @@ +;;; core/autoload/buffers.el -*- lexical-binding: t; -*- + +;;;###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-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. See +`doom-real-buffer-p' for more information.") + +;;;###autoload +(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. 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 (&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 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-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-dired-buffer-p (buf) + "Returns non-nil if BUF is a dired buffer." + (with-current-buffer buf (derived-mode-p 'dired-mode))) + +;;;###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-visible-buffer-p (buf) + "Return non-nil if BUF is visible." + (get-buffer-window buf)) + +;;;###autoload +(defun doom-buried-buffer-p (buf) + "Return non-nil if BUF is not visible." + (not (doom-visible-buffer-p buf))) + +;;;###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." + (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) + "Return a list of buffers whose `major-mode' is `eq' to MODE(S). + +If DERIVED-P, test with `derived-mode-p', otherwise use `eq'." + (let ((modes (doom-enlist modes))) + (cl-remove-if-not (if derived-p + (lambda (buf) + (with-current-buffer buf + (apply #'derived-mode-p modes))) + (lambda (buf) + (memq (buffer-local-value 'major-mode buf) modes))) + (or buffer-list (doom-buffer-list))))) + +;;;###autoload +(defun doom-visible-windows (&optional window-list) + "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) + "Return a list of visible buffers (i.e. not buried)." + (if buffer-list + (cl-remove-if-not #'get-buffer-window buffer-list) + (delete-dups (mapcar #'window-buffer (window-list))))) + +;;;###autoload +(defun doom-buried-buffers (&optional buffer-list) + "Get a list of buffers that are buried." + (cl-remove-if #'get-buffer-window (or buffer-list (doom-buffer-list)))) + +;;;###autoload +(defun doom-matching-buffers (pattern &optional buffer-list) + "Get a list of all buffers that match the regex PATTERN." + (cl-loop for buf in (or buffer-list (doom-buffer-list)) + when (string-match-p pattern (buffer-name buf)) + collect buf)) + +;;;###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-and-windows (buffer) + "Kill the buffer and delete all the windows it's displayed in." + (dolist (window (get-buffer-window-list buffer)) + (unless (one-window-p t) + (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)) + (kill-buffer buf)))) + + +;; +;; Hooks + +;;;###autoload +(defun doom-mark-buffer-as-real-h () + "Hook function that marks the current buffer as real." + (doom-set-buffer-real (current-buffer) t)) + + +;; +;; Interactive commands + +;;;###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 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) + (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 buffer-list interactive) + "Kill all buffers and closes their windows. + +If the prefix arg is passed, doesn't close windows and only kill buffers that +belong to the current project." + (interactive + (list (if current-prefix-arg + (doom-project-buffer-list) + (doom-buffer-list)) + t)) + (if (null buffer-list) + (message "No buffers to kill") + (save-some-buffers) + (when (memq (current-buffer) buffer-list) + (switch-to-buffer (doom-fallback-buffer))) + (mapc #'doom-kill-buffer-and-windows buffer-list) + (delete-other-windows) + (when interactive + (message "Killed %s buffers" + (- (length buffer-list) + (length (cl-remove-if-not #'buffer-live-p buffer-list))))))) + +;;;###autoload +(defun doom/kill-other-buffers (&optional buffer-list interactive) + "Kill all other buffers (besides the current one). + +If the prefix arg is passed, kill only buffers that belong to the current +project." + (interactive + (list (delq (current-buffer) + (if current-prefix-arg + (doom-project-buffer-list) + (doom-buffer-list))) + t)) + (mapc #'doom-kill-buffer-and-windows buffer-list) + (when interactive + (message "Killed %s buffers" + (- (length buffer-list) + (length (cl-remove-if-not #'buffer-live-p buffer-list)))))) + +;;;###autoload +(defun doom/kill-matching-buffers (pattern &optional buffer-list interactive) + "Kill buffers that match PATTERN in BUFFER-LIST. + +If the prefix arg is passed, only kill matching buffers in the current project." + (interactive + (list (read-regexp "Buffer pattern: ") + (if current-prefix-arg + (doom-project-buffer-list) + (doom-buffer-list)) + t)) + (doom-kill-matching-buffers pattern buffer-list) + (when interactive + (message "Killed %d buffer(s)" + (- (length buffer-list) + (length (cl-remove-if-not #'buffer-live-p buffer-list)))))) + +;;;###autoload +(defun doom/kill-buried-buffers (&optional buffer-list interactive) + "Kill buffers that are buried. + +If PROJECT-P (universal argument), only kill buried buffers belonging to the +current project." + (interactive + (list (doom-buried-buffers + (if current-prefix-arg (doom-project-buffer-list))) + t)) + (mapc #'kill-buffer buffer-list) + (when interactive + (message "Killed %s buried buffers" + (- (length buffer-list) + (length (cl-remove-if-not #'buffer-live-p buffer-list)))))) + +;;;###autoload +(defun doom/kill-project-buffers (project &optional interactive) + "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) + t)) + (when project + (let ((buffers (doom-project-buffer-list project))) + (doom-kill-buffers-fixup-windows buffers) + (when interactive + (message "Killed %d buffer(s)" + (- (length buffers) + (length (cl-remove-if-not #'buffer-live-p buffers)))))))) diff --git a/.emacs.d/core/autoload/cache.el b/.emacs.d/core/autoload/cache.el new file mode 100644 index 0000000..79db071 --- /dev/null +++ b/.emacs.d/core/autoload/cache.el @@ -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-h () + "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-h) + + +;; +;; 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))) diff --git a/.emacs.d/core/autoload/cli.el b/.emacs.d/core/autoload/cli.el new file mode 100644 index 0000000..f0582ef --- /dev/null +++ b/.emacs.d/core/autoload/cli.el @@ -0,0 +1,45 @@ +;;; core/autoload/cli.el -*- lexical-binding: t; -*- + +;; +;;; Library + +;;;###autoload +(defun doom-call-process (command &rest args) + "Execute COMMAND with ARGS synchronously. + +Returns (STATUS . OUTPUT) when it is done, where STATUS is the returned error +code of the process and OUTPUT is its stdout output." + (with-temp-buffer + (cons (or (apply #'call-process command nil t nil args) + -1) + (string-trim (buffer-string))))) + +;;;###autoload +(defun doom-exec-process (command &rest args) + "Execute COMMAND with ARGS synchronously. + +Unlike `doom-call-process', this pipes output to `standard-output' on the fly to +simulate 'exec' in the shell, so batch scripts could run external programs +synchronously without sacrificing their output. + +Warning: freezes indefinitely on any stdin prompt." + ;; FIXME Is there any way to handle prompts? + (with-temp-buffer + (cons (let ((process + (make-process :name "doom-sh" + :buffer (current-buffer) + :command (cons command args) + :connection-type 'pipe)) + done-p) + (set-process-filter + process (lambda (_process output) + (princ output (current-buffer)) + (princ output))) + (set-process-sentinel + process (lambda (process _event) + (when (memq (process-status process) '(exit stop)) + (setq done-p t)))) + (while (not done-p) + (sit-for 0.1)) + (process-exit-status process)) + (string-trim (buffer-string))))) diff --git a/.emacs.d/core/autoload/config.el b/.emacs.d/core/autoload/config.el new file mode 100644 index 0000000..df4e716 --- /dev/null +++ b/.emacs.d/core/autoload/config.el @@ -0,0 +1,142 @@ +;;; core/autoload/config.el -*- lexical-binding: t; -*- + +(defvar doom-bin-dir (concat doom-emacs-dir "bin/")) +(defvar doom-bin (concat doom-bin-dir "doom")) + +;;;###autoload +(defvar doom-reload-hook nil + "A list of hooks to run when `doom/reload' is called.") + +;;;###autoload +(defvar doom-reloading-p nil + "TODO") + +;;;###autoload +(defun doom/open-private-config () + "Browse your `doom-private-dir'." + (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 () + "Search for a file in `doom-private-dir'." + (interactive) + (doom-project-find-file doom-private-dir)) + +;;;###autoload +(defun doom/goto-doomblock () + "Open your private init.el and go to your `doom!' block." + (interactive) + (find-file (expand-file-name "init.el" doom-private-dir)) + (goto-char + (or (save-excursion + (goto-char (point-min)) + (search-forward "(doom!" nil t)) + (point)))) + +;;;###autoload +(defun doom/goto-config-file () + "Open your private config.el file." + (interactive) + (find-file (expand-file-name "config.el" doom-private-dir))) + +;;;###autoload +(defun doom/goto-packages-file () + "Open your private packages.el file." + (interactive) + (find-file (expand-file-name "packages.el" doom-private-dir))) + + +;; +;;; Managements + +(cl-defmacro doom--compile (command &key on-success on-failure) + (declare (indent defun)) + `(with-current-buffer (compile ,command) + (add-hook + 'compilation-finish-functions + (lambda (_buf status) + (if (equal status "finished\n") + ,on-success + ,on-failure)) + nil 'local))) + +;;;###autoload +(defun doom/reload () + "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) + (require 'core-cli) + (when (and IS-WINDOWS (file-exists-p doom-env-file)) + (warn "Can't regenerate envvar file from within Emacs. Run 'doom env' from the console")) + (doom--compile (format "%s refresh -e" doom-bin) + :on-success + (let ((doom-reloading-p t)) + (doom-initialize 'force) + (with-demoted-errors "PRIVATE CONFIG ERROR: %s" + (general-auto-unbind-keys) + (unwind-protect + (doom-initialize-modules 'force) + (general-auto-unbind-keys t))) + (run-hook-wrapped 'doom-reload-hook #'doom-try-run-hook) + (print! (success "Config successfully reloaded!"))) + :on-failure + (user-error "Failed to reload your config"))) + +;;;###autoload +(defun doom/reload-autoloads () + "Reload only `doom-autoload-file' and `doom-package-autoload-file'. + +This is much faster and safer than `doom/reload', but not as comprehensive. This +reloads your package and module visibility, but does not install new packages or +remove orphaned ones. It also doesn't reload your private config. + +It is useful to only pull in changes performed by 'doom refresh' on the command +line." + (interactive) + (require 'core-cli) + (require 'core-packages) + (doom-initialize-packages) + (doom-cli-reload-autoloads nil 'force)) + +;;;###autoload +(defun doom/reload-env (&optional arg) + "Regenerates and/or reloads your envvar file. + +If passed the prefix ARG, clear the envvar file. Uses the same mechanism as +'bin/doom env'. + +An envvar file contains a snapshot of your shell environment, which can be +imported into Emacs." + (interactive "P") + (when IS-WINDOWS + (user-error "Cannot reload envvar file from within Emacs on Windows, run it from cmd.exe")) + (doom--compile + (format "%s -ic '%s env%s'" + (string-trim + (shell-command-to-string + (format "getent passwd %S | cut -d: -f7" + (user-login-name)))) + doom-bin (if arg " -c" "")) + :on-success + (let ((doom-reloading-p t)) + (unless arg + (doom-load-envvars-file doom-env-file))) + :on-failure + (error "Failed to generate env file"))) + +;;;###autoload +(defun doom/upgrade () + "Run 'doom upgrade' then prompt to restart Emacs." + (interactive) + (doom--compile (format "%s upgrade" doom-bin) + :on-success + (when (y-or-n-p "You must restart Emacs for the upgrade to take effect.\n\nRestart Emacs?") + (doom/restart-and-restore)))) diff --git a/.emacs.d/core/autoload/debug.el b/.emacs.d/core/autoload/debug.el new file mode 100644 index 0000000..b6d70d4 --- /dev/null +++ b/.emacs.d/core/autoload/debug.el @@ -0,0 +1,344 @@ +;;; core/autoload/debug.el -*- lexical-binding: t; -*- + +;;;###autoload +(defun doom-run-all-startup-hooks-h () + "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-h" + (run-hook-wrapped 'after-init-hook #'doom-try-run-hook) + (setq after-init-time (current-time)) + (mapc (doom-rpartial #'run-hook-wrapped #'doom-try-run-hook) + (list 'delayed-warnings-hook + 'emacs-startup-hook 'tty-setup-hook + 'window-setup-hook))) + + +;; +;;; Helpers + +(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)))) + +;;;###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))) + (cl-letf + (((symbol-function 'sh) + (lambda (&rest args) + (cdr (apply #'doom-call-process args))))) + `((emacs + (version . ,emacs-version) + (features ,@system-configuration-features) + (build . ,(format-time-string "%b %d, %Y" emacs-build-time)) + (buildopts ,system-configuration-options) + (windowsys . ,(if noninteractive 'batch window-system)) + (daemonp . ,(cond ((daemonp) 'daemon) + ((and (require 'server) + (server-running-p)) + 'server-running)))) + (doom + (version . ,doom-version) + (build . ,(sh "git" "log" "-1" "--format=%D %h %ci"))) + (system + (type . ,system-type) + (config . ,system-configuration) + (shell . ,shell-file-name) + (uname . ,(if IS-WINDOWS + "n/a" + (sh "uname" "-msrv"))) + (path . ,(mapcar #'abbreviate-file-name exec-path))) + (config + (envfile + . ,(cond ((file-exists-p doom-env-file) 'envvar-file) + ((featurep 'exec-path-from-shell) 'exec-path-from-shell))) + (elc-files + . ,(length (doom-files-in `(,@doom-modules-dirs + ,doom-core-dir + ,doom-private-dir) + :type 'files :match "\\.elc$"))) + (modules + ,@(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 + collect + (let ((flags (doom-module-get cat (cdr key) :flags))) + (if flags + `(,(cdr key) ,@flags) + (cdr key)))) + '("n/a"))) + (packages + ,@(or (condition-case e + (cl-loop for (name . plist) in (doom-package-list) + if (cl-find :private (plist-get plist :modules) + :key #'car) + collect + (if-let (splist (doom-plist-delete (copy-sequence plist) + :modules)) + (prin1-to-string (cons name splist)) + name)) + (error (format "<%S>" e))) + '("n/a"))) + (elpa + ,@(or (condition-case e + (progn + (package-initialize) + (cl-loop for (name . _) in package-alist + collect (format "%s" name))) + (error (format "<%S>" e))) + '("n/a")))))))) + + +;; +;;; Commands + +;;;###autoload +(defun doom/version () + "Display the current version of Doom & Emacs, including the current Doom +branch and commit." + (interactive) + (require 'vc-git) + (let ((default-directory doom-core-dir)) + (print! "Doom v%s (Emacs v%s)\nBranch: %s\nCommit: %s\nBuild date: %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") + (or (cdr (doom-call-process "git" "log" "-1" "--format=%ci")) + "n/a")))) + +;;;###autoload +(defun doom/info (&optional raw) + "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 "P") + (let ((buffer (get-buffer-create "*doom-info*")) + (info (doom-info))) + (with-current-buffer buffer + (unless (or noninteractive + (eq major-mode 'markdown-mode) + (not (fboundp 'markdown-mode))) + (markdown-mode)) + (erase-buffer) + (if raw + (progn + (save-excursion + (pp info (current-buffer))) + (when (search-forward "(modules " nil t) + (goto-char (match-beginning 0)) + (cl-destructuring-bind (beg . end) + (bounds-of-thing-at-point 'sexp) + (let ((sexp (prin1-to-string (sexp-at-point)))) + (delete-region beg end) + (insert sexp))))) + (insert "
\n\n```\n") + (dolist (group info) + (insert! "%-8s%-10s %s\n" + ((car group) + (caadr group) + (cdadr group))) + (dolist (spec (cddr group)) + (insert! (indent 8 "%-10s %s\n") + ((car spec) (cdr spec))))) + (insert "```\n
")) + (if noninteractive + (print! (buffer-string)) + (switch-to-buffer buffer) + (kill-new (buffer-string)) + (print! (green "Copied markdown to clipboard")))))) + +;;;###autoload +(defun doom/am-i-secure () + "Test to see if your root certificates are securely configured in emacs." + (declare (interactive-only t)) + (interactive) + (unless (string-match-p "\\_" system-configuration-features) + (warn "gnutls support isn't built into Emacs, there may be problems")) + (if-let* ((bad-hosts + (cl-loop for bad + in '("https://wrong.host.badssl.com/" + "https://self-signed.badssl.com/") + if (condition-case _e + (url-retrieve-synchronously bad) + (error nil)) + collect bad))) + (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)) + (warn "Something went wrong.\n\n%s" (pp-to-string status)) + (message "Your trust roots are set up properly.\n\n%s" (pp-to-string status)) + t))))) + + +;; +;;; Vanilla sandbox + +(defun doom--run-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 noninteractive nil + doom-debug-mode t + load-path ',load-path + package--init-file-ensured t + package-user-dir ,package-user-dir + package-archives ',package-archives + user-emacs-directory ,doom-emacs-dir) + (with-eval-after-load 'undo-tree + ;; undo-tree throws errors because `buffer-undo-tree' isn't + ;; corrrectly initialized + (setq-default buffer-undo-tree (make-undo-tree)))) + (pcase mode + (`vanilla-doom+ ; Doom core + modules - private config + `((load-file ,(expand-file-name "core.el" doom-core-dir)) + (doom-initialize) + (doom-initialize-core) + (add-hook 'window-setup-hook #'doom-display-benchmark-h) + (setq doom-modules ',doom-modules) + (maphash (lambda (key plist) + (let ((doom--current-module key) + (doom--current-flags (plist-get plist :flags))) + (load! "init" (doom-module-locate-path (car key) (cdr key)) t))) + doom-modules) + (maphash (lambda (key plist) + (let ((doom--current-module key) + (doom--current-flags (plist-get plist :flags))) + (load! "config" (doom-module-locate-path (car key) (cdr key)) t))) + doom-modules) + (run-hook-wrapped 'doom-init-modules-hook #'doom-try-run-hook) + (doom-run-all-startup-hooks-h))) + (`vanilla-doom ; only Doom core + `((load-file ,(expand-file-name "core.el" doom-core-dir)) + (doom-initialize) + (doom-initialize-core) + (doom-run-all-startup-hooks-h))) + (`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)) + (mapconcat #'shell-quote-argument args " "))))) + (error + (delete-file file) + (signal (car e) (cdr e))))))) + +(fset 'doom--run-vanilla-emacs (lambda! (doom--run-sandbox 'vanilla))) +(fset 'doom--run-vanilla-doom (lambda! (doom--run-sandbox 'vanilla-doom))) +(fset 'doom--run-vanilla-doom+ (lambda! (doom--run-sandbox 'vanilla-doom+))) +(fset 'doom--run-full-doom (lambda! (doom--run-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-current-buffer) + map)) + +(define-derived-mode doom-sandbox-emacs-lisp-mode emacs-lisp-mode "Sandbox Elisp" + "TODO") + +;;;###autoload +(defun doom/sandbox () + "Open the Emacs Lisp sandbox. + +This is a test bed for running Emacs Lisp in another instance of Emacs with +varying amounts of Doom loaded, including: + + a) vanilla Emacs (nothing loaded), + b) vanilla Doom (only Doom core), + c) Doom + modules - your private config or + c) Doom + modules + your private config (a complete Doom session) + +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:sandbox*") + (exists (get-buffer buffer-name)) + (buf (get-buffer-create buffer-name))) + (with-current-buffer buf + (doom-sandbox-emacs-lisp-mode) + (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 + +;;;###autoload +(defun doom/report-bug () + "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) + (browse-url "https://github.com/hlissner/doom-emacs/issues/new/choose")) + + +;; +;;; 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) + (profiler-report) + (profiler-stop)) + (setq doom--profiler (not doom--profiler))) + +;;;###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 + jka-compr-verbose value + lsp-log-io value + gcmh-verbose value) + (message "Debug mode %s" (if value "on" "off")))) diff --git a/.emacs.d/core/autoload/files.el b/.emacs.d/core/autoload/files.el new file mode 100644 index 0000000..f7cd347 --- /dev/null +++ b/.emacs.d/core/autoload/files.el @@ -0,0 +1,343 @@ +;;; core/autoload/files.el -*- lexical-binding: t; -*- + +(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 `file-exists-p!' and `project-file-exists-p!'." + (declare (pure t) (side-effect-free t)) + (let ((exists-fn (if (fboundp 'projectile-file-exists-p) + #'projectile-file-exists-p + #'file-exists-p))) + (if (and (listp spec) + (memq (car spec) '(or and))) + (cons (car spec) + (mapcar (doom-rpartial #'doom--resolve-path-forms directory) + (cdr spec))) + (let ((filevar (make-symbol "file"))) + `(let* ((file-name-handler-alist nil) + (,filevar ,spec)) + (and (stringp ,filevar) + ,(if directory + `(let ((default-directory ,directory)) + (,exists-fn ,filevar)) + (list exists-fn filevar)) + ,filevar)))))) + +(defun doom--path (&rest segments) + (let (file-name-handler-alist) + (let ((dir (pop segments))) + (unless segments + (setq dir (expand-file-name dir))) + (while segments + (setq dir (expand-file-name (car segments) dir) + segments (cdr segments))) + dir))) + +;;;###autoload +(defun doom-glob (&rest segments) + "Construct a path from SEGMENTS and expand glob patterns. +Returns nil if the path doesn't exist." + (let* (case-fold-search + file-name-handler-alist + (dir (apply #'doom--path segments))) + (if (string-match-p "[[*?]" dir) + (file-expand-wildcards dir t) + (if (file-exists-p dir) + dir)))) + +;;;###autoload +(defun doom-path (&rest segments) + "Constructs a file path from SEGMENTS." + (if segments + (apply #'doom--path segments) + (file!))) + +;;;###autoload +(defun doom-dir (&rest segments) + "Constructs a path from SEGMENTS. +See `doom-path'." + (when-let (path (apply #'doom-path segments)) + (directory-file-name (file-name-directory path)))) + +;;;###autoload +(cl-defun doom-files-in + (paths &rest rest + &key + filter + map + (full t) + (follow-symlinks t) + (type 'files) + (relative-to (unless full default-directory)) + (depth 99999) + (mindepth 0) + (match "/[^._][^/]+")) + "Return a list of files/directories in PATHS (one string 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." + (let (file-name-handler-alist + result) + (dolist (file (mapcan (doom-rpartial #'doom-glob "*") (doom-enlist paths))) + (cond ((file-directory-p file) + (nconcq! result + (and (memq type '(t dirs)) + (string-match-p match file) + (not (and filter (funcall filter file))) + (not (and (file-symlink-p file) + (not follow-symlinks))) + (<= mindepth 0) + (list (cond (map (funcall map file)) + (relative-to (file-relative-name file relative-to)) + (file)))) + (and (>= depth 1) + (apply #'doom-files-in file + (append (list :mindepth (1- mindepth) + :depth (1- depth) + :relative-to relative-to) + rest))))) + ((and (memq type '(t files)) + (string-match-p match file) + (not (and filter (funcall filter file))) + (<= mindepth 0)) + (push (if relative-to + (file-relative-name file relative-to) + file) + result)))) + result)) + +;;;###autoload +(defun doom-file-cookie-p (file &optional cookie null-value) + "Returns the evaluated result of FORM in a ;;;###COOKIE FORM at the top of +FILE. + +If COOKIE doesn't exist, return NULL-VALUE." + (unless (file-exists-p file) + (signal 'file-missing file)) + (unless (file-readable-p file) + (error "%S is unreadable" file)) + (with-temp-buffer + (insert-file-contents file nil 0 256) + (if (re-search-forward (format "^;;;###%s " (regexp-quote (or cookie "if"))) + nil t) + (let ((load-file-name file)) + (eval (sexp-at-point) t)) + null-value))) + +;;;###autoload +(defmacro file-exists-p! (files &optional directory) + "Returns non-nil if the FILES in DIRECTORY all exist. + +DIRECTORY is a path; defaults to `default-directory'. + +Returns the last file found to meet the rules set by FILES, which can be a +single file or nested compound statement of `and' and `or' statements." + `(let ((p ,(doom--resolve-path-forms files directory))) + (and p (expand-file-name p ,directory)))) + +;;;###autoload +(defun doom-file-size (file &optional dir) + "Returns the size of FILE (in DIR) in bytes." + (let ((file (expand-file-name file dir))) + (unless (file-exists-p file) + (error "Couldn't find file %S" file)) + (unless (file-readable-p file) + (error "File %S is unreadable; can't acquire its filesize" + file)) + (nth 7 (file-attributes file)))) + +(defvar w32-get-true-file-attributes) +;;;###autoload +(defun doom-directory-size (dir) + "Returns the size of FILE (in DIR) in kilobytes." + (unless (file-directory-p dir) + (error "Directory %S does not exist" dir)) + (if (executable-find "du") + (/ (string-to-number (cdr (doom-call-process "du" "-sb" dir))) + 1024.0) + ;; REVIEW This is slow and terribly inaccurate, but it's something + (let ((w32-get-true-file-attributes t) + (file-name-handler-alist dir) + (max-lisp-eval-depth 5000) + (sum 0.0)) + (dolist (attrs (directory-files-and-attributes dir nil nil t) sum) + (unless (member (car attrs) '("." "..")) + (cl-incf + sum (if (eq (nth 1 attrs) t) ; is directory + (doom-directory-size (expand-file-name (car attrs) dir)) + (/ (nth 8 attrs) 1024.0)))))))) + + +;; +;;; 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 + (list (read-file-name "Copy file to: ") + current-prefix-arg)) + (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 + (list (read-file-name "Move file to: ") + current-prefix-arg)) + (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-current-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))) + +(defun doom--sudo-file (file) + (let ((host (or (file-remote-p file 'host) "localhost"))) + (concat "/" (when (file-remote-p file) + (concat (file-remote-p file 'method) ":" + (if-let (user (file-remote-p file 'user)) + (concat user "@" host) + host) + "|")) + "sudo:root@" host + ":" (or (file-remote-p file 'localname) + file)))) + +;;;###autoload +(defun doom/sudo-find-file (file) + "Open FILE as root." + (interactive "FOpen file as root: ") + (find-file (doom--sudo-file file))) + +;;;###autoload +(defun doom/sudo-this-file () + "Open the current file as root." + (interactive) + (find-alternate-file (doom--sudo-file buffer-file-name))) diff --git a/.emacs.d/core/autoload/fonts.el b/.emacs.d/core/autoload/fonts.el new file mode 100644 index 0000000..f7e5f11 --- /dev/null +++ b/.emacs.d/core/autoload/fonts.el @@ -0,0 +1,136 @@ +;;; core/autoload/fonts.el -*- lexical-binding: t; -*- + +;;;###autoload +(defvar doom-font-increment 2 + "How many steps to increase the font size each time `doom/increase-font-size' +or `doom/decrease-font-size' are invoked.") + +;;;###autoload +(defvar doom-big-font nil + "The font to use for `doom-big-font-mode'. If nil, `doom-font' will be used, +scaled up by `doom-big-font-increment'. See `doom-font' for details on +acceptable values for this variable.") + +;;;###autoload +(defvar doom-big-font-increment 8 + "How many steps to increase the font size (with `doom-font' as the base) when +`doom-big-font-mode' is enabled and `doom-big-font' is nil.") + + +;; +;;; Library + +(defun doom--font-name (fontname frame) + (when (query-fontset fontname) + (when-let (ascii (assq 'ascii (aref (fontset-info fontname frame) 2))) + (setq fontname (nth 2 ascii)))) + (or (x-decompose-font-name fontname) + (error "Cannot decompose font name"))) + +(defun doom--frame-list (&optional frame) + "Return a list consisting of FRAME and all of FRAME's child frames." + (let ((frame (or frame (selected-frame)))) + (cons (selected-frame) + (cl-loop for fr in (frame-list) + if (eq (frame-parameter fr 'parent-frame) frame) + collect fr)))) + +;;;###autoload +(defun doom-adjust-font-size (increment &optional frame) + "Increase size of font in FRAME by INCREMENT. +FRAME parameter defaults to current frame." + (if (null increment) + (let ((frames (doom--frame-list frame))) + (dolist (frame frames) + (when (frame-parameter frame 'font-scale) + (set-frame-parameter frame 'font-scale nil))) + (set-frame-font doom-font 'keep-size frames) + (and frames t)) + (let (success) + (dolist (frame (doom--frame-list frame)) + (let* ((font (frame-parameter frame 'font)) + (font (doom--font-name font frame)) + (increment (* increment doom-font-increment)) + (zoom-factor (or (frame-parameter frame 'font-scale) 0))) + (let ((new-size (+ (string-to-number (aref font xlfd-regexp-pixelsize-subnum)) + increment))) + (unless (> new-size 0) + (error "Font is too small at %d" new-size)) + (aset font xlfd-regexp-pixelsize-subnum (number-to-string new-size))) + ;; Set point size & width to "*", so frame width will adjust to new font size + (aset font xlfd-regexp-pointsize-subnum "*") + (aset font xlfd-regexp-avgwidth-subnum "*") + (setq font (x-compose-font-name font)) + (unless (x-list-fonts font) + (error "Cannot change font size")) + (set-frame-parameter frame 'font font) + (set-frame-parameter frame 'font-scale (+ zoom-factor increment)) + (setq success t))) + (when success + ;; Unlike `set-frame-font', `set-frame-parameter' won't trigger this + (run-hooks 'after-setting-font-hook) + t)))) + + +;; +;;; Commands + +;;;###autoload +(defun doom/reload-font () + "Reload your fonts, if they're set. +See `doom-init-fonts-h'." + (interactive) + (when doom-font + (set-frame-font doom-font t)) + (doom-init-fonts-h) + (mapc #'doom-init-extra-fonts-h (frame-list))) + +;;;###autoload +(defun doom/increase-font-size (count) + "Enlargens the font size across the current and child frames." + (interactive "p") + (doom-adjust-font-size count)) + +;;;###autoload +(defun doom/decrease-font-size (count) + "Shrinks the font size across the current and child frames." + (interactive "p") + (doom-adjust-font-size (- count))) + +;;;###autoload +(defun doom/reset-font-size () + "Reset font size and `text-scale'. + +Assuming it has been adjusted via `doom/increase-font-size' and +`doom/decrease-font-size', or `text-scale-*' commands." + (interactive) + (let (success) + (when (and (boundp 'text-scale-mode-amount) + (/= text-scale-mode-amount 0)) + (text-scale-set 0) + (setq success t)) + (when (doom-adjust-font-size nil) + (setq success t)) + (unless success + (user-error "The font hasn't been resized")))) + +;;;###autoload +(define-minor-mode doom-big-font-mode + "A global mode that resizes the font, for streams, screen-sharing and +presentations. + +This uses `doom/increase-font-size' under the hood, and enlargens the font by +`doom-big-font-increment'." + :init-value nil + :lighter " BIG" + :global t + (unless doom-font + (user-error "`doom-font' must be set to a valid font")) + (if doom-big-font + (set-frame-font (if doom-big-font-mode doom-big-font doom-font) + 'keep-size (doom--frame-list)) + (doom-adjust-font-size + (and doom-big-font-mode + (integerp doom-big-font-increment) + (/= doom-big-font-increment 0) + doom-big-font-increment)))) diff --git a/.emacs.d/core/autoload/format.el b/.emacs.d/core/autoload/format.el new file mode 100644 index 0000000..4f87aaa --- /dev/null +++ b/.emacs.d/core/autoload/format.el @@ -0,0 +1,243 @@ +;;; core/autoload/format.el -*- lexical-binding: t; -*- + +(defvar doom-format-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)) + "An alist of fg/bg/fx names mapped to ansi codes and term-color-* variables. + +This serves as the cipher for converting (COLOR ...) function calls in `print!' +and `format!' into colored output, where COLOR is any car of this list.") + +(defvar doom-format-class-alist + `((color . doom--format-color) + (class . doom--format-class) + (indent . doom--format-indent) + (autofill . doom--format-autofill) + + (success . (lambda (str &rest args) + (apply #'doom--format-color 'green (format "✓ %s" str) args))) + (warn . (lambda (str &rest args) + (apply #'doom--format-color 'yellow (format "! %s" str) args))) + (error . (lambda (str &rest args) + (apply #'doom--format-color 'red (format "x %s" str) args))) + (info . (lambda (str &rest args) + (concat "- " (if args (apply #'format str args) str)))) + (start . (lambda (str &rest args) + (concat "> " (if args (apply #'format str args) str)))) + (debug . (lambda (str &rest args) + (if doom-debug-mode + (if args + (apply #'format str args) + (format "%s" str)) + ""))) + (path . abbreviate-file-name) + (symbol . symbol-name) + (relpath . (lambda (str &optional dir) + (if (or (not str) + (not (stringp str)) + (string-empty-p str)) + str + (let ((dir (or dir (file-truename default-directory))) + (str (file-truename str))) + (if (file-in-directory-p str dir) + (file-relative-name str dir) + (abbreviate-file-name str)))))) + (filename . file-name-nondirectory) + (dirname . (lambda (path) + (unless (file-directory-p path) + (setq path (file-name-directory path))) + (directory-file-name path)))) + "An alist of text classes that map to transformation functions. + +Any of these classes can be called like functions from within `format!' and +`print!' calls, which will transform their input.") + +(defvar doom-format-indent 0 + "Level to rigidly indent text returned by `format!' and `print!'.") + +(defvar doom-format-indent-increment 2 + "Steps in which to increment `doom-format-indent' for consecutive levels.") + +(defvar doom-format-backend + (if noninteractive 'ansi 'text-properties) + "Determines whether to print colors with ANSI codes or with text properties. + +Accepts 'ansi and 'text-properties. nil means don't render colors.") + + +;; +;;; Library + +;;;###autoload +(defun doom--format (output) + (if (string-empty-p (string-trim output)) + "" + (concat (make-string doom-format-indent 32) + (replace-regexp-in-string + "\n" (concat "\n" (make-string doom-format-indent 32)) + output t t)))) + +;;;###autoload +(defun doom--format-print (output) + (unless (string-empty-p output) + (princ output) + (when (or noninteractive (not (eq standard-output t))) + (terpri)) ; newline + t)) + +;;;###autoload +(defun doom--format-indent (width text &optional prefix) + "Indent TEXT by WIDTH spaces. If ARGS, format TEXT with them." + (with-temp-buffer + (setq text (format "%s" text)) + (insert text) + (indent-rigidly (point-min) (point-max) width) + (when (stringp prefix) + (when (> width 2) + (goto-char (point-min)) + (beginning-of-line-text) + (delete-char (- (length prefix))) + (insert prefix))) + (buffer-string))) + +;;;###autoload +(defun doom--format-autofill (&rest msgs) + "Ensure MSG is split into lines no longer than `fill-column'." + (with-temp-buffer + (let ((fill-column 76)) + (dolist (line msgs) + (when line + (insert (format "%s" line)))) + (fill-region (point-min) (point-max)) + (buffer-string)))) + +;;;###autoload +(defun doom--format-color (style format &rest args) + "Apply STYLE to formatted MESSAGE with ARGS. + +STYLE is a symbol that correlates to `doom-format-ansi-alist'. + +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-format-ansi-alist))) + (format (format "%s" format)) + (message (if args (apply #'format format args) format))) + (unless code + (error "%S is an invalid color" style)) + (pcase doom-format-backend + (`ansi + (format "\e[%dm%s\e[%dm" code message 0)) + (`text-properties + (require 'term) ; piggyback on term's color faces + (propertize + message + 'face + (append (get-text-property 0 'face format) + (cond ((>= code 40) + `(:background ,(caddr (assq style doom-format-ansi-alist)))) + ((>= code 30) + `(:foreground ,(face-foreground (caddr (assq style doom-format-ansi-alist))))) + ((cddr (assq style doom-format-ansi-alist))))))) + (_ message)))) + +;;;###autoload +(defun doom--format-class (class format &rest args) + "Apply CLASS to formatted format with ARGS. + +CLASS is derived from `doom-format-class-alist', and can contain any arbitrary, +transformative logic." + (let (fn) + (cond ((setq fn (cdr (assq class doom-format-class-alist))) + (if (functionp fn) + (apply fn format args) + (error "%s does not have a function" class))) + (args (apply #'format format args)) + (format)))) + +;;;###autoload +(defun doom--format-apply (forms &optional sub) + "Replace color-name functions with calls to `doom--format-color'." + (cond ((null forms) nil) + ((listp forms) + (append (cond ((not (symbolp (car forms))) + (list (doom--format-apply (car forms)))) + (sub + (list (car forms))) + ((assq (car forms) doom-format-ansi-alist) + `(doom--format-color ',(car forms))) + ((assq (car forms) doom-format-class-alist) + `(doom--format-class ',(car forms))) + ((list (car forms)))) + (doom--format-apply (cdr forms) t) + nil)) + (forms))) + +;;;###autoload +(defmacro format! (message &rest 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." + `(doom--format (format ,@(doom--format-apply `(,message ,@args))))) + +;;;###autoload +(defmacro print-group! (&rest body) + "Indents any `print!' or `format!' output within BODY." + `(let ((doom-format-indent (+ doom-format-indent-increment doom-format-indent))) + ,@body)) + +;;;###autoload +(defmacro print! (message &rest args) + "Uses `message' in interactive sessions and `princ' otherwise (prints to +standard out). + +Can be colored using (color ...) blocks: + + (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." + `(doom--format-print (format! ,message ,@args))) + +;;;###autoload +(defmacro insert! (message &rest args) + "Like `insert'; the last argument must be format arguments for MESSAGE. + +\(fn MESSAGE... ARGS)" + `(insert (format! (concat ,message ,@(butlast args)) + ,@(car (last args))))) + +;;;###autoload +(defmacro error! (message &rest args) + "Like `error', but with the power of `format!'." + `(error (format! ,message ,@args))) + +;;;###autoload +(defmacro user-error! (message &rest args) + "Like `user-error', but with the power of `format!'." + `(user-error (format! ,message ,@args))) diff --git a/.emacs.d/core/autoload/help.el b/.emacs.d/core/autoload/help.el new file mode 100644 index 0000000..fa010b9 --- /dev/null +++ b/.emacs.d/core/autoload/help.el @@ -0,0 +1,646 @@ +;;; core/autoload/help.el -*- lexical-binding: t; -*- + +(defvar doom--help-major-mode-module-alist + '((dockerfile-mode :tools docker) + (agda2-mode :lang agda) + (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) + (clojurescript-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) + (idris-mode :lang idris) + (java-mode :lang java) + (js2-mode :lang javascript) + (rjsx-mode :lang javascript) + (typescript-mode :lang javascript) + (coffee-mode :lang javascript) + (julia-mode :lang julia) + (kotlin-mode :lang kotlin) + (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) + (terra-mode :lang terra)) + "An alist mapping major modes to Doom modules. + +This is used by `doom/help-modules' to auto-select the module corresponding to +the current major-modea.") + + +;; +;;; Helpers + +;;;###autoload +(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)) + + +;; +;;; Custom describe commands + +;;;###autoload (defalias 'doom/describe-autodefs #'doom/help-autodefs) +;;;###autoload (defalias 'doom/describe-module #'doom/help-modules) +;;;###autoload (defalias 'doom/describe-package #'doom/help-packages) + +;;;###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)))) + (let ((symbol + (cond ((stringp mode) (intern mode)) + ((symbolp mode) mode) + ((error "Expected a symbol/string, got a %s" (type-of mode)))))) + (if (fboundp symbol) + (helpful-function symbol) + (helpful-variable symbol)))) + +;;;###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)))))) + + +;; +;;; Documentation commands + +(defvar org-agenda-files) +(defun doom--org-headings (files &optional depth include-files) + "TODO" + (require 'org) + (let* ((default-directory doom-docs-dir) + (org-agenda-files (mapcar #'expand-file-name (doom-enlist files))) + (depth (if (integerp depth) depth))) + (message "Loading search results...") + (unwind-protect + (delq + nil + (org-map-entries + (lambda () + (cl-destructuring-bind (level _reduced-level _todo _priority text tags) + (org-heading-components) + (when (and (or (null depth) + (<= level depth)) + (or (null tags) + (not (string-match-p ":TOC" tags)))) + (let ((path (org-get-outline-path))) + (list (string-join + (list (string-join + (append (when include-files + (list (or (+org-get-global-property "TITLE") + (file-relative-name (buffer-file-name))))) + path + (list (replace-regexp-in-string org-link-any-re "\\4" text))) + " > ") + tags) + " ") + (buffer-file-name) + (point)))))) + t 'agenda)) + (mapc #'kill-buffer org-agenda-new-buffers) + (setq org-agenda-new-buffers nil)))) + +(defvar ivy-sort-functions-alist) +;;;###autoload +(defun doom-completing-read-org-headings (prompt files &optional depth include-files initial-input extra-candidates) + "TODO" + (let ((alist + (append (doom--org-headings files depth include-files) + extra-candidates)) + ivy-sort-functions-alist) + (if-let (result (completing-read prompt alist nil nil initial-input)) + (cl-destructuring-bind (file &optional location) + (cdr (assoc result alist)) + (find-file file) + (cond ((functionp location) + (funcall location)) + (location + (goto-char location))) + (ignore-errors + (when (outline-invisible-p) + (save-excursion + (outline-previous-visible-heading 1) + (org-show-subtree))))) + (user-error "Aborted")))) + +;;;###autoload +(defun doom/homepage () + "Open the doom emacs homepage in the browser." + (interactive) + (browse-url "https://doomemacs.org")) + +;;;###autoload +(defun doom/help () + "Open Doom's user manual." + (interactive) + (find-file (expand-file-name "index.org" doom-docs-dir))) + +;;;###autoload +(defun doom/help-search (&optional initial-input) + "Search Doom's documentation and jump to a headline." + (interactive) + (doom-completing-read-org-headings + "Find in Doom help: " + (list "getting_started.org" + "contributing.org" + "troubleshooting.org" + "tutorials.org" + "faq.org") + 2 t initial-input + (mapcar (lambda (x) + (setcar x (concat "Doom Modules > " (car x))) + x) + (doom--help-modules-list)))) + +;;;###autoload +(defun doom/help-news-search (&optional initial-input) + "Search headlines in Doom's newsletters." + (interactive) + (doom-completing-read-org-headings + "Find in News: " + (nreverse (doom-files-in (expand-file-name "news" doom-docs-dir) + :match "/[0-9]" + :relative-to doom-docs-dir)) + nil t initial-input)) + +;;;###autoload +(defun doom/help-faq (&optional initial-input) + "Search Doom's FAQ and jump to a question." + (interactive) + (doom-completing-read-org-headings + "Find in FAQ: " (list "faq.org") + 2 nil initial-input)) + +;;;###autoload +(defun doom/help-news () + "Open a Doom newsletter. +The latest newsletter will be selected by default." + (interactive) + (let* ((default-directory (expand-file-name "news/" doom-docs-dir)) + (news-files (doom-files-in default-directory))) + (find-file + (read-file-name (format "Open Doom newsletter (current: v%s): " + doom-version) + default-directory + (if (member doom-version news-files) + doom-version + (concat (mapconcat #'number-to-string + (nbutlast (version-to-list doom-version) 1) + ".") + ".x")) + t doom-version)))) + +;;;###autoload +(defun doom/help-autodefs (autodef) + "Open documentation for an autodef. + +An autodef is a Doom concept. It is a function or macro that is always defined, +whether or not its containing module is disabled (in which case it will safely +no-op without evaluating its arguments). This syntactic sugar lets you use them +without needing to check if they are available." + (interactive + (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)))) + +(defun doom--help-modules-list () + (cl-loop for path in (cdr (doom-module-load-path 'all)) + for (cat . mod) = (doom-module-from-path path) + for readme-path = (or (doom-module-locate-path cat mod "README.org") + (doom-module-locate-path cat mod)) + for format = (format "%s %s" cat mod) + if (doom-module-p cat mod) + collect (list format readme-path) + else if (and cat mod) + collect (list (propertize format 'face 'font-lock-comment-face) + readme-path))) + +(defun doom--help-current-module-str () + (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))))))) + ((when buffer-file-name + (when-let (mod (doom-module-from-path buffer-file-name)) + (unless (memq (car mod) '(:core :private)) + (format "%s %s" (car mod) (cdr mod)))))) + ((when-let (mod (cdr (assq major-mode doom--help-major-mode-module-alist))) + (format "%s %s" + (symbol-name (car mod)) + (symbol-name (cadr mod))))))) + +;;;###autoload +(defun doom/help-modules (category module) + "Open the documentation for a Doom module. + +CATEGORY is a keyword and MODULE is a symbol. e.g. :editor 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--help-major-mode-module-alist')." + (interactive + (mapcar #'intern + (split-string + (completing-read "Describe module: " + (doom--help-modules-list) nil t nil nil + (doom--help-current-module-str)) + " " t))) + (cl-check-type category symbol) + (cl-check-type module symbol) + (cl-destructuring-bind (module-string path) + (or (assoc (format "%s %s" category module) (doom--help-modules-list)) + (user-error "'%s %s' is not a valid module" category module)) + (unless (file-readable-p path) + (error "Can't find or read %S module at %S" module-string path)) + (if (not (file-directory-p path)) + (find-file path) + (if (y-or-n-p (format "The %S module has no README file. Explore its directory?" + module-string)) + (doom-project-browse path) + (user-error "Aborted module lookup"))))) + + +;; +;;; `doom/help-packages' + +(defun doom--help-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")))))))) + +(defun doom--help-package-configs (package) + (let ((default-directory doom-emacs-dir)) + ;; TODO Use ripgrep instead + (split-string + (cdr (doom-call-process + "git" "grep" "--no-break" "--no-heading" "--line-number" + (format "%s %s\\($\\| \\)" + "\\(^;;;###package\\|(after!\\|(use-package!\\)" + package) + ":(exclude)*.org")) + "\n" t))) + +;;;###autoload +(defun doom/help-packages (package) + "Like `describe-package', but for packages installed by Doom modules. + +Only shows installed packages. Includes information about where packages are +defined and configured. + +If prefix arg is present, refresh the cache." + (interactive + (let ((guess (or (function-called-at-point) + (symbol-at-point)))) + (require 'finder-inf nil t) + (require 'core-packages) + (doom-initialize-packages) + (let ((packages (delete-dups + (append (mapcar #'car package-alist) + (mapcar #'car package--builtins) + (mapcar #'intern (hash-table-keys straight--build-cache)) + (mapcar #'car (doom-package-list 'all)) + nil)))) + (unless (memq guess packages) + (setq guess nil)) + (list + (intern + (completing-read (if guess + (format "Select package to search for (default %s): " + guess) + "Describe package: ") + packages nil t nil nil + (if guess (symbol-name guess)))))))) + (require 'core-packages) + (doom-initialize-packages) + (if (or (package-desc-p package) + (and (symbolp package) + (or (assq package package-alist) + (assq package package--builtins)))) + (describe-package package) + (help-setup-xref (list #'doom/help-packages package) + (called-interactively-p 'interactive)) + (with-help-window (help-buffer))) + (save-excursion + (with-current-buffer (help-buffer) + (let ((inhibit-read-only t) + (indent (make-string 13 ? ))) + (goto-char (point-max)) + (if (re-search-forward "^ *Status: " nil t) + (progn + (end-of-line) + (insert "\n")) + (search-forward "\n\n" nil t)) + + (package--print-help-section "Package") + (insert (symbol-name package) "\n") + + (package--print-help-section "Source") + (insert (or (pcase (doom-package-backend package) + (`straight + (format! "Straight (%s)\n%s" + (let ((default-directory (straight--build-dir (symbol-name package)))) + (cdr + (doom-call-process "git" "log" "-1" "--format=%D %h %ci"))) + (indent + 13 (string-trim + (pp-to-string + (doom-package-build-recipe package)))))) + (`elpa + (format "[M]ELPA %s" (doom--package-url package))) + (`builtin "Built-in") + (_ (abbreviate-file-name (symbol-file package)))) + "unknown") + "\n") + + (when (gethash (symbol-name package) straight--build-cache) + (package--print-help-section "Modules") + (insert "Declared by the following Doom modules:\n") + (dolist (m (doom-package-get package :modules)) + (insert indent) + (doom--help-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 "Configs") + (insert "This package is configured in the following locations:") + (dolist (location (doom--help-package-configs package)) + (insert "\n" indent) + (insert-text-button + location + 'face 'link + 'follow-link t + 'action + `(lambda (_) + (cl-destructuring-bind (file line _match) + ',(split-string location ":") + (find-file (expand-file-name file doom-emacs-dir)) + (goto-char (point-min)) + (forward-line (1- (string-to-number line))) + (recenter))))) + + (insert "\n\n"))))) + +(defvar doom--package-cache nil) +(defun doom--package-list () + (let* ((guess (or (function-called-at-point) + (symbol-at-point)))) + (require 'finder-inf nil t) + (unless package--initialized + (package-initialize t)) + (let ((packages (or doom--package-cache + (progn + (message "Reading packages...") + (cl-delete-duplicates + (append (mapcar 'car package-alist) + (mapcar 'car package--builtins) + (mapcar 'car package-archive-contents))))))) + (setq doom--package-cache packages) + (unless (memq guess packages) + (setq guess nil)) + (intern (completing-read (if guess + (format "Select package to search for (default %s): " + guess) + "Describe package: ") + packages nil t nil nil + (if guess (symbol-name guess))))))) + +(defun doom--package-url (package) + (cond ((assq package package--builtins) + (user-error "Package is built into Emacs and cannot be looked up")) + ((when-let (location (locate-library (symbol-name package))) + (with-temp-buffer + (insert-file-contents (concat (file-name-sans-extension location) ".el") + nil 0 4096) + (let ((case-fold-search t)) + (when (re-search-forward " \\(?:URL\\|homepage\\|Website\\): \\(http[^\n]+\\)\n" nil t) + (match-string-no-properties 1)))))) + ((and (ignore-errors (eq (doom-package-backend package) 'quelpa)) + (let* ((plist (cdr (doom-package-prop package :recipe))) + (fetcher (plist-get plist :fetcher))) + (pcase fetcher + (`git (plist-get plist :url)) + (`github (format "https://github.com/%s.git" (plist-get plist :repo))) + (`gitlab (format "https://gitlab.com/%s.git" (plist-get plist :repo))) + (`bitbucket (format "https://bitbucket.com/%s" (plist-get plist :repo))) + (`wiki (format "https://www.emacswiki.org/emacs/download/%s" + (or (car-safe (doom-enlist (plist-get plist :files))) + (format "%s.el" package)))) + (_ (plist-get plist :url)))))) + ((and (require 'package nil t) + (or package-archive-contents (doom-refresh-packages-maybe)) + (pcase (package-desc-archive (cadr (assq package package-archive-contents))) + ("org" "https://orgmode.org") + ((or "melpa" "melpa-mirror") + (format "https://melpa.org/#/%s" package)) + ("gnu" + (format "https://elpa.gnu.org/packages/%s.html" package)) + (archive + (if-let (src (cdr (assoc package package-archives))) + (format "%s" src) + (user-error "%S isn't installed through any known source (%s)" + package archive)))))) + ((user-error "Cannot find the homepage for %S" package)))) + +;;;###autoload +(defun doom/help-package-config (package) + "Jump to any `use-package!', `after!' or ;;;###package block for PACKAGE. + +This only searches `doom-emacs-dir' (typically ~/.emacs.d) and does not include +config blocks in your private config." + (interactive (list (doom--package-list))) + (cl-destructuring-bind (file line _match) + (split-string + (completing-read + "Jump to config: " + (or (doom--help-package-configs package) + (user-error "This package isn't configured by you or Doom"))) + ":") + (find-file (expand-file-name file doom-emacs-dir)) + (goto-char (point-min)) + (forward-line (1- line)) + (recenter))) + +;;;###autoload +(defun doom/help-package-homepage (package) + "Open PACKAGE's repo or homepage in your browser." + (interactive (list (doom--package-list))) + (browse-url (doom--package-url package))) + +;;;###autoload +(defun doom/help-search-load-path (query) + "Perform a text search on your `load-path'. +Uses the symbol at point or the current selection, if available." + (interactive + (let ((query + ;; TODO Generalize this later; into something the lookup module and + ;; project search commands could as well + (if (use-region-p) + (buffer-substring-no-properties (region-beginning) (region-end)) + (or (symbol-name (symbol-at-point)) "")))) + (list (read-string + (format "Search load-path (default: %s): " query) + nil 'git-grep query)))) + ;; REVIEW Replace with deadgrep + (grep-find + (mapconcat + #'shell-quote-argument + (append (list "rg" "-L" "--search-zip" "--no-heading" "--color=never" query) + (cl-remove-if-not #'file-directory-p load-path)) + " "))) + +;; TODO factor our the duplicate code between this and the above +;;;###autoload +(defun doom/help-search-loaded-files (query) + "Perform a text search on your `load-path'. +Uses the symbol at point or the current selection, if available." + (interactive + (let ((query + ;; TODO Generalize this later; into something the lookup module and + ;; project search commands could as well. + (if (use-region-p) + (buffer-substring-no-properties (region-beginning) (region-end)) + (or (symbol-name (symbol-at-point)) "")))) + (list (read-string + (format "Search load-path (default: %s): " query) + nil 'git-grep query)))) + (unless (executable-find "rg") + (user-error "Can't find ripgrep on your system")) + (require 'elisp-refs) + ;; REVIEW Replace with deadgrep + (grep-find + (mapconcat + #'shell-quote-argument + (append (list "rg" "-L" "--search-zip" "--no-heading" "--color=never" query) + (cl-remove-if-not #'file-directory-p (elisp-refs--loaded-paths))) + " "))) diff --git a/.emacs.d/core/autoload/packages.el b/.emacs.d/core/autoload/packages.el new file mode 100644 index 0000000..7406ddd --- /dev/null +++ b/.emacs.d/core/autoload/packages.el @@ -0,0 +1,203 @@ +;;; core/autoload/packages.el -*- lexical-binding: t; -*- + +;; +;;; Package metadata + +;;;###autoload +(defun doom-package-get (package &optional prop nil-value) + "Returns PACKAGE's `package!' recipe from `doom-packages'." + (let ((plist (cdr (assq package doom-packages)))) + (if prop + (if (plist-member plist prop) + (plist-get plist prop) + nil-value) + plist))) + +;;;###autoload +(defun doom-package-recipe (package &optional prop nil-value) + "Returns the `straight' recipe PACKAGE was registered with." + (let ((plist (gethash (symbol-name package) straight--recipe-cache))) + (if prop + (if (plist-member plist prop) + (plist-get plist prop) + nil-value) + plist))) + +;;;###autoload +(defun doom-package-build-recipe (package &optional prop nil-value) + "Returns the `straight' recipe PACKAGE was installed with." + (let ((plist (nth 2 (gethash (symbol-name package) straight--build-cache)))) + (if prop + (if (plist-member plist prop) + (plist-get plist prop) + nil-value) + plist))) + +;;;###autoload +(defun doom-package-build-time (package) + "TODO" + (car (gethash (symbol-name package) straight--build-cache))) + +;;;###autoload +(defun doom-package-dependencies (package &optional recursive noerror) + "Return a list of dependencies for a package." + (let ((deps (nth 1 (gethash (symbol-name package) straight--build-cache)))) + (if recursive + (nconc deps (mapcan (lambda (dep) (doom-package-dependencies dep t t)) + deps)) + deps))) + +(defun doom-package-depending-on (package &optional noerror) + "Return a list of packages that depend on the package named NAME." + (cl-check-type name symbol) + ;; can't get dependencies for built-in packages + (unless (or (doom-package-build-recipe name) + noerror) + (error "Couldn't find %s, is it installed?" name)) + (cl-loop for pkg in (hash-table-keys straight--build-cache) + for deps = (doom-package-dependencies pkg) + if (memq package deps) + collect pkg + and append (doom-package-depending-on pkg t))) + + +;; +;;; Predicate functions + +;;;###autoload +(defun doom-package-built-in-p (package) + "Return non-nil if PACKAGE (a symbol) is built-in." + (eq (doom-package-build-recipe package :type) + 'built-in)) + +;;;###autoload +(defun doom-package-installed-p (package) + "Return non-nil if PACKAGE (a symbol) is installed." + (file-directory-p (straight--build-dir (symbol-name package)))) + +;;;###autoload +(defun doom-package-registered-p (package) + "Return non-nil if PACKAGE (a symbol) has been registered with `package!'. + +Excludes packages that have a non-nil :built-in property." + (when-let (plist (doom-package-get package)) + (not (plist-get plist :ignore)))) + +;;;###autoload +(defun doom-package-private-p (package) + "Return non-nil if PACKAGE was installed by the user's private config." + (assq :private (doom-package-get package :modules))) + +;;;###autoload +(defun doom-package-protected-p (package) + "Return non-nil if PACKAGE is protected. + +A protected package cannot be deleted and will be auto-installed if missing." + (memq package doom-core-packages)) + +;;;###autoload +(defun doom-package-core-p (package) + "Return non-nil if PACKAGE is a core Doom package." + (or (doom-package-protected-p package) + (assq :core (doom-package-get package :modules)))) + +;;;###autoload +(defun doom-package-backend (package) + "Return 'straight, 'builtin, 'elpa or 'other, depending on how PACKAGE is +installed." + (cond ((gethash (symbol-name package) straight--build-cache) + 'straight) + ((or (doom-package-built-in-p package) + (assq package package--builtins)) + 'builtin) + ((assq package package-alist) + 'elpa) + ('other))) + +;;;###autoload +(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) + ;; TODO + ;; (when (doom-package-installed-p name) + ;; (when-let* ((doom-recipe (assq name doom-packages)) + ;; (install-recipe (doom-package-recipe))) + ;; (not (equal (cdr quelpa-recipe) + ;; (cdr (plist-get (cdr doom-recipe) :recipe)))))) + ) + + +;; +;;; Package list getters + +(defun doom--read-module-packages-file (file &optional noeval noerror) + (with-temp-buffer ; prevent buffer-local settings from propagating + (condition-case e + (if (not noeval) + (load file noerror t t) + (when (file-readable-p file) + (insert-file-contents file) + (delay-mode-hooks (emacs-lisp-mode)) + (while (search-forward "(package! " nil t) + (save-excursion + (goto-char (match-beginning 0)) + (unless (let ((ppss (syntax-ppss))) + (or (nth 3 ppss) + (nth 4 ppss))) + (cl-destructuring-bind (name . plist) + (cdr (sexp-at-point)) + (push (cons + name (plist-put + plist :modules + (list (doom-module-from-path file)))) + doom-packages))))))) + ((debug error) + (signal 'doom-package-error + (list (doom-module-from-path file) + 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 ((doom-interactive-mode t) + (doom-modules (doom-modules)) + doom-packages + doom-disabled-packages) + (doom--read-module-packages-file + (doom-path doom-core-dir "packages.el") all-p t) + (let ((private-packages (doom-path doom-private-dir "packages.el"))) + (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 + :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)) + (nreverse doom-packages))) + + +;; +;;; Main functions + +;;;###autoload +(defun doom/reload-packages () + "Reload `doom-packages', `package' and `quelpa'." + (interactive) + ;; HACK straight.el must be loaded for this to work + (message "Reloading packages") + (doom-initialize-packages t) + (message "Reloading packages...DONE")) diff --git a/.emacs.d/core/autoload/plist.el b/.emacs.d/core/autoload/plist.el new file mode 100644 index 0000000..aeb9c7f --- /dev/null +++ b/.emacs.d/core/autoload/plist.el @@ -0,0 +1,93 @@ +;;; core/autoload/plist.el -*- lexical-binding: t; -*- + +;; +;;; Macros + +;;;###autoload +(cl-defmacro doplist! ((arglist plist &optional retval) &rest body) + "Loop over a PLIST's (property value) pairs then return RETVAL. + +Evaluate BODY with either ARGLIST bound to (cons PROP VAL) or, if ARGLIST is a +list, the pair is destructured into (CAR . CDR)." + (declare (indent 1)) + (let ((plist-var (make-symbol "plist"))) + `(let ((,plist-var (copy-sequence ,plist))) + (while ,plist-var + (let ,(if (listp arglist) + `((,(pop arglist) (pop ,plist-var)) + (,(pop arglist) (pop ,plist-var))) + `((,arglist (cons (pop ,plist-var) + (pop ,plist-var))))) + ,@body)) + ,retval))) + +;;;###autoload +(defmacro plist-put! (plist &rest rest) + "Set each PROP VALUE pair in REST to PLIST in-place." + `(cl-loop for (prop value) + on (list ,@rest) by #'cddr + do ,(if (symbolp plist) + `(setq ,plist (plist-put ,plist prop value)) + `(plist-put ,plist prop value)))) + +;;;###autoload +(defmacro plist-delete! (plist prop) + "Delete PROP from PLIST in-place." + `(setq ,plist (doom-plist-delete ,plist ,prop))) + +;;;###autoload +(defmacro with-plist! (plist props &rest body) + "With props bound from PLIST to PROPS, evaluate BODY. + +PROPS is a list of symbols. Each one is converted to a keyword and then its +value is looked up in the PLIST and bound to the symbol for the duration of +BODY." + (declare (indent 2)) + (let ((plist-sym (make-symbol "plist"))) + `(let* ((,plist-sym ,plist) + ,@(cl-loop for prop in props + collect + `(,prop + (plist-get + ,plist-sym + ,(doom-keyword-intern (symbol-name prop)))))) + ,@body))) + + +;; +;;; Library + +;;;###autoload +(defun doom-plist-get (plist prop &optional nil-value) + "Return PROP in PLIST, if it exists. Otherwise NIL-VALUE." + (if-let (val (plist-member plist prop)) + (cadr val) + nil-value)) + +;;;###autoload +(defun doom-plist-merge (from-plist to-plist) + "Destructively merge FROM-PLIST onto TO-PLIST" + (let ((plist (copy-sequence from-plist))) + (while plist + (plist-put! to-plist (pop plist) (pop plist))) + to-plist)) + +;;;###autoload +(defun doom-plist-delete-nil (plist) + "Delete `nil' properties from a copy of PLIST." + (let (p) + (while plist + (if (car plist) + (plist-put! p (car plist) (nth 1 plist))) + (setq plist (cddr plist))) + p)) + +;;;###autoload +(defun doom-plist-delete (plist prop) + "Delete PROP from a copy of PLIST." + (let (p) + (while plist + (if (not (eq prop (car plist))) + (plist-put! p (car plist) (nth 1 plist))) + (setq plist (cddr plist))) + p)) diff --git a/.emacs.d/core/autoload/projects.el b/.emacs.d/core/autoload/projects.el new file mode 100644 index 0000000..6decf35 --- /dev/null +++ b/.emacs.d/core/autoload/projects.el @@ -0,0 +1,135 @@ +;;; core/autoload/projects.el -*- lexical-binding: t; -*- + +(defvar projectile-project-root nil) + +;;;###autoload (autoload 'projectile-relevant-known-projects "projectile") + +;;;###autodef +(cl-defun set-project-type! (name &key predicate compile run test configure dir) + "Add a project type to `projectile-project-type'." + (declare (indent 1)) + (after! projectile + (add-to-list 'projectile-project-types + (list name + 'marker-files predicate + 'compilation-dir dir + 'configure-command configure + 'compile-command compile + 'test-command test + 'run-command run)))) + + +;; +;;; Macros + +;;;###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)) + (unless (file-readable-p dir) + (error "Directory %S isn't readable" 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 (doom-module-p :completion 'ivy) + #'counsel-projectile-find-file + #'projectile-find-file))) + ((fboundp 'counsel-file-jump) ; ivy only + (call-interactively #'counsel-file-jump)) + ((project-current) + (project-find-file-in nil (list default-directory) nil)) + ((fboundp 'helm-find-files) + (call-interactively #'helm-find-files)) + ((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 ((doom-module-p :completion 'ivy) + #'counsel-find-file) + ((doom-module-p :completion 'helm) + #'helm-find-files) + (#'find-file))))) diff --git a/.emacs.d/core/autoload/scratch.el b/.emacs.d/core/autoload/scratch.el new file mode 100644 index 0000000..a93456e --- /dev/null +++ b/.emacs.d/core/autoload/scratch.el @@ -0,0 +1,173 @@ +;;; core/autoload/scratch.el -*- lexical-binding: t; -*- + +(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 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 created.") + + +(defun doom--load-persistent-scratch-buffer (name) + (setq-local doom-scratch-current-project + (or name + doom-scratch-default-file)) + (let ((scratch-file + (expand-file-name doom-scratch-current-project + doom-scratch-dir))) + (make-directory doom-scratch-dir t) + (when (file-readable-p scratch-file) + (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." + (with-current-buffer + (get-buffer-create (if project-name + (format "*doom:scratch (%s)*" project-name) + "*doom:scratch*")) + (setq default-directory directory) + (unless doom-scratch-current-project + (doom--load-persistent-scratch-buffer project-name) + (when (and (eq major-mode 'fundamental-mode) + (functionp mode)) + (funcall mode))) + (cl-pushnew (current-buffer) doom-scratch-buffers) + (add-hook 'kill-buffer-hook #'doom-persist-scratch-buffer-h nil 'local) + (add-hook 'doom-switch-buffer-hook #'doom-persist-scratch-buffers-after-switch-h) + (run-hooks 'doom-scratch-buffer-created-hook) + (current-buffer))) + + +;; +;;; Persistent scratch buffer + +;;;###autoload +(defun doom-persist-scratch-buffer-h () + "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-h () + "Save all scratch buffers to `doom-scratch-dir'." + (setq doom-scratch-buffers + (cl-delete-if-not #'buffer-live-p doom-scratch-buffers)) + (dolist (buffer doom-scratch-buffers) + (with-current-buffer buffer + (doom-persist-scratch-buffer-h)))) + +;;;###autoload +(defun doom-persist-scratch-buffers-after-switch-h () + "Kill scratch buffers when they are no longer visible, saving them to disk." + (unless (cl-some #'get-buffer-window doom-scratch-buffers) + (mapc #'kill-buffer doom-scratch-buffers) + (remove-hook 'doom-switch-buffer-hook #'doom-persist-scratch-buffers-after-switch-h))) + +;;;###autoload +(unless noninteractive + (add-hook 'kill-emacs-hook #'doom-persist-scratch-buffers-h)) + + +;; +;;; Commands + +;;;###autoload +(defun doom/open-scratch-buffer (&optional arg project-p) + "Opens the (persistent) scratch buffer in a popup. + +If passed the prefix ARG, switch to it in the current window. +If PROJECT-P is non-nil, open a persistent scratch buffer associated with the + current project." + (interactive "P") + (let (projectile-enable-caching) + (funcall + (if arg + #'switch-to-buffer + #'pop-to-buffer) + (doom-scratch-buffer + (cond ((eq doom-scratch-buffer-major-mode t) + (unless (or buffer-read-only + (derived-mode-p 'special-mode) + (string-match-p "^ ?\\*" (buffer-name))) + major-mode)) + ((null doom-scratch-buffer-major-mode) + nil) + ((symbolp doom-scratch-buffer-major-mode) + doom-scratch-buffer-major-mode)) + default-directory + (when project-p + (doom-project-name)))))) + +;;;###autoload +(defun doom/switch-to-scratch-buffer (&optional project-p) + "Like `doom/open-scratch-buffer', but switches to it in the current window. + +If passed the prefix arg, open project scratch buffer." + (interactive "P") + (doom/open-scratch-buffer t project-p)) + +;;;###autoload +(defun doom/open-project-scratch-buffer (&optional current-window) + "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 current-window 'project)) + +;;;###autoload +(defun doom/switch-to-project-scratch-buffer () + "Like `doom/open-project-scratch-buffer', but switches to it in the current +window." + (interactive) + (doom/open-project-scratch-buffer t)) + +;;;###autoload +(defun doom/revert-scratch-buffer () + "Revert scratch buffer to last persistent state." + (interactive) + (unless (string-match-p "^\\*doom:scratch" (buffer-name)) + (user-error "Not in a scratch buffer")) + (when (doom--load-persistent-scratch-buffer doom-scratch-current-project) + (message "Reloaded scratch buffer"))) + +;;;###autoload +(defun doom/delete-persistent-scratch-file (&optional arg) + "Deletes a scratch buffer file in `doom-scratch-dir'. + +If prefix ARG, delete all persistent scratches." + (interactive) + (if arg + (progn + (delete-directory doom-scratch-dir t) + (message "Cleared %S" (abbreviate-file-name doom-scratch-dir))) + (make-directory doom-scratch-dir t) + (let ((file (read-file-name "Delete scratch file > " doom-scratch-dir "scratch"))) + (if (not (file-exists-p file)) + (message "%S does not exist" (abbreviate-file-name file)) + (delete-file file) + (message "Successfully deleted %S" (abbreviate-file-name file)))))) diff --git a/.emacs.d/core/autoload/sessions.el b/.emacs.d/core/autoload/sessions.el new file mode 100644 index 0000000..2e6ec18 --- /dev/null +++ b/.emacs.d/core/autoload/sessions.el @@ -0,0 +1,128 @@ +;;; core/autoload/sessions.el -*- lexical-binding: t; -*- + +(defvar desktop-base-file-name) +(defvar desktop-dirname) +(defvar desktop-restore-eager) +(defvar desktop-file-modtime) + + +;; +;;; Helpers + +;;;###autoload +(defun doom-session-file (&optional name) + "TODO" + (cond ((require 'persp-mode nil t) + (expand-file-name (or name persp-auto-save-fname) persp-save-dir)) + ((require 'desktop nil t) + (if name + (expand-file-name name (file-name-directory (desktop-full-file-name))) + (desktop-full-file-name))) + ((error "No session backend available")))) + +;;;###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) + ;; Prevents confirmation prompts + (let ((desktop-file-modtime (nth 5 (file-attributes (desktop-full-file-name))))) + (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")))) + + +;; +;;; Command line switch + +;;;###autoload +(defun doom-restore-session-handler (&rest _) + "TODO" + (add-hook 'window-setup-hook #'doom-load-session 'append)) + + +;; +;;; 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) + (file-name-nondirectory session-file) + t) + (user-error "No session selected. Aborting"))))) + (unless file + (error "No session file selected")) + (message "Loading '%s' session" file) + (doom-load-session file) + (message "Session restored. Welcome back.")) + +;;;###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) + (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") + (setq doom-autosave-session nil) + (doom/quicksave-session) + (restart-emacs + (delq nil (list (if debug "--debug-init") "--restore")))) diff --git a/.emacs.d/core/autoload/text.el b/.emacs.d/core/autoload/text.el new file mode 100644 index 0000000..8c08c5a --- /dev/null +++ b/.emacs.d/core/autoload/text.el @@ -0,0 +1,274 @@ +;;; 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)))))))))))) + +;;;###autoload +(defun doom-point-in-comment-p (&optional pos) + "Return non-nil if POS is in a comment. + +POS defaults to the current position." + ;; REVIEW Should we cache `syntax-ppss'? + (let* ((pos (or pos (point))) + (ppss (syntax-ppss pos))) + (or (nth 4 ppss) + (nth 8 ppss) + (and (< pos (point-max)) + (memq (char-syntax (char-after pos)) '(?< ?>)) + (not (eq (char-after pos) ?\n))) + (when-let (s (car (syntax-after pos))) + (or (and (/= 0 (logand (lsh 1 16) s)) + (nth 4 (doom-syntax-ppss (+ pos 2)))) + (and (/= 0 (logand (lsh 1 17) s)) + (nth 4 (doom-syntax-ppss (+ pos 1)))) + (and (/= 0 (logand (lsh 1 18) s)) + (nth 4 (doom-syntax-ppss (- pos 1)))) + (and (/= 0 (logand (lsh 1 19) s)) + (nth 4 (doom-syntax-ppss (- pos 2))))))))) + +;;;###autoload +(defun doom-point-in-string-p (&optional pos) + "Return non-nil if POS is in a string." + ;; REVIEW Should we cache `syntax-ppss'? + (nth 3 (syntax-ppss pos))) + +;;;###autoload +(defun doom-point-in-string-or-comment-p (&optional pos) + "Return non-nil if POS is in a string or comment." + (or (doom-point-in-string-p pos) + (doom-point-in-comment-p pos))) + + +;; +;;; Commands + +(defvar doom--last-backward-pt most-positive-fixnum) +;;;###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 ((pt (point))) + (cl-destructuring-bind (bol . bot) + (save-excursion + (beginning-of-visual-line) + (cons (point) + (progn (skip-chars-forward " \t\r") + (point)))) + (cond ((> pt bot) + (goto-char bot)) + ((= pt bol) + (goto-char (min doom--last-backward-pt bot)) + (setq doom--last-backward-pt most-positive-fixnum)) + ((<= pt bot) + (setq doom--last-backward-pt pt) + (goto-char bol)))))) + +(defvar doom--last-forward-pt -1) +;;;###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 (if (not visual-line-mode) + (line-end-position) + (save-excursion (end-of-visual-line) (point))))) + (if (or (and (< (point) eol) + (sp-point-in-comment)) + (not (sp-point-in-comment eol))) + (if (= (point) eol) + (progn + (goto-char doom--last-forward-pt) + (setq doom--last-forward-pt -1)) + (setq doom--last-forward-pt (point)) + (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))) + (when (> doom--last-forward-pt boc) + (setq boc doom--last-forward-pt)) + (if (or (= eol (point)) + (> boc (point))) + (progn + (goto-char boc) + (setq doom--last-forward-pt -1)) + (setq doom--last-forward-pt (point)) + (goto-char eol)))))) + +;;;###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)) + +;;;###autoload +(defun doom/toggle-indent-style () + "Switch between tabs and spaces indentation style in the current buffer." + (interactive) + (setq indent-tabs-mode (not indent-tabs-mode)) + (message "Indent style changed to %s" (if indent-tabs-mode "tabs" "spaces"))) + +(defvar editorconfig-lisp-use-default-indent) +;;;###autoload +(defun doom/set-indent-width (width) + "Change the indentation size to WIDTH of the current buffer. + +The effectiveness of this command is significantly improved if you have +editorconfig or dtrt-indent installed." + (interactive + (list (if (integerp current-prefix-arg) + current-prefix-arg + (read-number "New indent size: ")))) + (setq tab-width width) + (setq-local standard-indent width) + (when (boundp 'evil-shift-width) + (setq evil-shift-width width)) + (cond ((require 'editorconfig nil t) + (let (editorconfig-lisp-use-default-indent) + (editorconfig-set-indentation nil width))) + ((require 'dtrt-indent nil t) + (when-let (var (nth 2 (assq major-mode dtrt-indent-hook-mapping-list))) + (doom-log "Updated %s = %d" var width) + (set var width)))) + (message "Changed indentation to %d" width)) + + +;; +;;; Hooks + +;;;###autoload +(defun doom-enable-delete-trailing-whitespace-h () + "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-h () + "Disables the automatic deletion of trailing whitespaces upon file save. + +i.e. disables `ws-butler-mode' in the current buffer." + (ws-butler-mode -1)) + +;;;###autoload +(defun doom-enable-show-trailing-whitespace-h () + "Enable `show-trailing-whitespace' in the current buffer." + (setq-local show-trailing-whitespace t)) + +;;;###autoload +(defun doom-disable-show-trailing-whitespace-h () + "Disable `show-trailing-whitespace' in the current buffer." + (setq-local show-trailing-whitespace nil)) diff --git a/.emacs.d/core/autoload/themes.el b/.emacs.d/core/autoload/themes.el new file mode 100644 index 0000000..2fb84ef --- /dev/null +++ b/.emacs.d/core/autoload/themes.el @@ -0,0 +1,51 @@ +;;; core/autoload/themes.el -*- lexical-binding: t; -*- + +(defun doom--custom-theme-set-face (spec) + (cond ((listp (car spec)) + (cl-loop for face in (car spec) + collect + (car (doom--custom-theme-set-face (cons face (cdr spec)))))) + ((keywordp (cadr spec)) + `((,(car spec) ((t ,(cdr spec)))))) + (`((,(car spec) ,(cdr spec)))))) + +;;;###autoload +(defmacro custom-theme-set-faces! (theme &rest specs) + "Apply a list of face SPECS as user customizations for THEME. + +THEME can be a single symbol or list thereof. If nil, apply these settings to +all themes. It will apply to all themes once they are loaded." + (declare (indent defun)) + `(let ((fn (gensym "doom--customize-themes-h-"))) + (fset + fn (lambda () + (let (custom--inhibit-theme-enable) + (dolist (theme (doom-enlist (or ,theme 'user))) + (when (or (eq theme 'user) + (custom-theme-enabled-p theme)) + (apply #'custom-theme-set-faces theme + (mapcan #'doom--custom-theme-set-face + (list ,@specs)))))))) + (when (or doom-init-theme-p (null doom-theme)) + (funcall fn)) + (add-hook 'doom-load-theme-hook fn 'append))) + +;;;###autoload +(defmacro custom-set-faces! (&rest specs) + "Apply a list of face SPECS as user customizations. + +This is a drop-in replacement for `custom-set-face' that allows for a simplified +face format." + (declare (indent defun)) + `(custom-theme-set-faces! 'user ,@specs)) + +(defvar doom--prefer-theme-elc) +;;;###autoload +(defun doom/reload-theme () + "Reload the current color theme." + (interactive) + (let ((theme (or (car-safe custom-enabled-themes) doom-theme))) + (when theme + (mapc #'disable-theme custom-enabled-themes)) + (load-theme doom-theme 'noconfirm) + (doom/reload-font))) diff --git a/.emacs.d/core/autoload/ui.el b/.emacs.d/core/autoload/ui.el new file mode 100644 index 0000000..80735e2 --- /dev/null +++ b/.emacs.d/core/autoload/ui.el @@ -0,0 +1,232 @@ +;;; core/autoload/ui.el -*- lexical-binding: t; -*- + +;; +;; Public library + +;;;###autoload +(defun doom-resize-window (window new-size &optional horizontal force-p) + "Resize a window to NEW-SIZE. If HORIZONTAL, do it width-wise. +If FORCE-P is omitted when `window-size-fixed' is non-nil, resizing will fail." + (with-selected-window (or window (selected-window)) + (let ((window-size-fixed (unless force-p window-size-fixed))) + (enlarge-window (- new-size (if horizontal (window-width) (window-height))) + horizontal)))) + +;;;###autoload +(defun doom-quit-p (&optional prompt) + "Prompt the user for confirmation when killing Emacs. + +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 + +;;;###autoload +(defun doom-recenter-a (&rest _) + "Generic advisor for recentering window (typically :after other functions)." + (recenter)) + +;;;###autoload +(defun doom-shut-up-a (orig-fn &rest args) + "Generic advisor for silencing noisy functions. + +In interactive Emacs, this just inhibits messages from appearing in the +minibuffer. They are still logged to *Messages*. + +In tty Emacs, messages suppressed completely." + (quiet! (apply orig-fn args))) + + +;; +;; Hooks + +;;;###autoload +(defun doom-apply-ansi-color-to-compilation-buffer-h () + "Applies ansi codes to the compilation buffers. Meant for +`compilation-filter-hook'." + (with-silent-modifications + (ansi-color-apply-on-region compilation-filter-start (point)))) + +;;;###autoload +(defun doom-disable-show-paren-mode-h () + "Turn off `show-paren-mode' buffer-locally." + (setq-local show-paren-mode nil)) + + +;; +;; 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) + (defvar doom--line-number-style display-line-numbers-type) + (let* ((styles `(t ,(if 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) + (setq display-line-numbers next) + (message "Switched to %s line numbers" + (pcase next + (`t "normal") + (`nil "disabled") + (_ (symbol-name next)))))) + +;;;###autoload +(defun doom/delete-frame () + "Delete the current frame, but ask for confirmation if it isn't empty." + (interactive) + (if (cdr (frame-list)) + (when (doom-quit-p "Close frame?") + (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 ?_) + (when (and (bound-and-true-p +popup-mode) + (+popup-window-p)) + (user-error "Cannot maximize a popup, use `+popup/raise' first or use `doom/window-enlargen' instead")) + (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 ?_) + (let* ((window (selected-window)) + (dedicated-p (window-dedicated-p window)) + (preserved-p (window-parameter window 'window-preserved-size)) + (ignore-window-parameters t)) + (unwind-protect + (progn + (when dedicated-p + (set-window-dedicated-p window nil)) + (when preserved-p + (set-window-parameter window 'window-preserved-size nil)) + (maximize-window window)) + (set-window-dedicated-p window dedicated-p) + (when preserved-p + (set-window-parameter window 'window-preserved-size preserved-p))) + 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 doom--narrowed-base-buffer nil) +;;;###autoload +(defun doom/narrow-buffer-indirectly (beg end) + "Restrict editing in this buffer to the current region, indirectly. + +This recursively creates indirect clones of the current buffer so that the +narrowing doesn't affect other windows displaying the same buffer. Call +`doom/widen-indirectly-narrowed-buffer' to undo it (incrementally). + +Inspired from http://demonastery.org/2013/04/emacs-evil-narrow-region/" + (interactive + (list (or (bound-and-true-p evil-visual-beginning) (region-beginning)) + (or (bound-and-true-p evil-visual-end) (region-end)))) + (unless (region-active-p) + (setq beg (line-beginning-position) + end (line-end-position))) + (deactivate-mark) + (let ((orig-buffer (current-buffer))) + (with-current-buffer (switch-to-buffer (clone-indirect-buffer nil nil)) + (narrow-to-region beg end) + (setq-local doom--narrowed-base-buffer orig-buffer)))) + +;;;###autoload +(defun doom/widen-indirectly-narrowed-buffer (&optional arg) + "Widens narrowed buffers. + +This command will incrementally kill indirect buffers (under the assumption they +were created by `doom/narrow-buffer-indirectly') and switch to their base +buffer. + +If ARG, then kill all indirect buffers, return the base buffer and widen it. + +If the current buffer is not an indirect buffer, it is `widen'ed." + (interactive "P") + (unless (buffer-narrowed-p) + (user-error "Buffer isn't narrowed")) + (let ((orig-buffer (current-buffer)) + (base-buffer doom--narrowed-base-buffer)) + (cond ((or (not base-buffer) + (not (buffer-live-p base-buffer))) + (widen)) + (arg + (let ((buffer orig-buffer) + (buffers-to-kill (list orig-buffer))) + (while (setq buffer (buffer-local-value 'doom--narrowed-base-buffer buffer)) + (push buffer buffers-to-kill)) + (switch-to-buffer (buffer-base-buffer)) + (mapc #'kill-buffer (remove (current-buffer) buffers-to-kill)))) + ((switch-to-buffer base-buffer) + (kill-buffer orig-buffer))))) + +;;;###autoload +(defun doom/toggle-narrow-buffer (beg end) + "Narrow the buffer to BEG END. If narrowed, widen it." + (interactive + (list (or (bound-and-true-p evil-visual-beginning) (region-beginning)) + (or (bound-and-true-p evil-visual-end) (region-end)))) + (if (buffer-narrowed-p) + (widen) + (unless (region-active-p) + (setq beg (line-beginning-position) + end (line-end-position))) + (narrow-to-region beg end))) diff --git a/.emacs.d/core/cli/autoloads.el b/.emacs.d/core/cli/autoloads.el new file mode 100644 index 0000000..fa113ed --- /dev/null +++ b/.emacs.d/core/cli/autoloads.el @@ -0,0 +1,407 @@ +;;; core/cli/autoloads.el -*- lexical-binding: t; -*- + +(defvar doom-autoload-excluded-packages '("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.") + +;; externs +(defvar autoload-timestamps) +(defvar generated-autoload-load-name) +(defvar generated-autoload-file) + + +;; +;;; Helpers + +(defun doom--cli-delete-autoloads-file (file) + "Delete FILE (an autoloads file) and accompanying *.elc file, if any." + (cl-check-type file string) + (when (file-exists-p file) + (when-let (buf (find-buffer-visiting 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))) + t)) + +(defun doom--cli-warn-refresh-session-h () + (message "Restart or reload Doom Emacs 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--cli-byte-compile-file (file) + (let ((byte-compile-warnings (if doom-debug-mode byte-compile-warnings)) + (byte-compile-dynamic t) + (byte-compile-dynamic-docstrings t)) + (condition-case-unless-debug e + (when (byte-compile-file file) + (unless doom-interactive-mode + (add-hook 'doom-cli-post-success-execute-hook #'doom--cli-warn-refresh-session-h)) + (let (noninteractive) + (load file 'noerror 'nomessage 'nosuffix))) + ((debug error) + (let ((backup-file (concat file ".bk"))) + (print! (warn "Copied backup to %s") (relpath backup-file)) + (copy-file file backup-file 'overwrite)) + (doom--cli-delete-autoloads-file file) + (signal 'doom-autoload-error (list file e)))))) + +(defun doom-cli-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-cli-reload-core-autoloads force-p)) + ((file-equal-p file doom-package-autoload-file) + (doom-cli-reload-package-autoloads force-p)) + ((error "Invalid autoloads file: %s" file))) + (doom-cli-reload-core-autoloads force-p) + (doom-cli-reload-package-autoloads force-p))) + + +;; +;;; Doom autoloads + +(defun doom--cli-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--cli-generate-autoloads (targets) + (let ((n 0)) + (dolist (file targets) + (insert + (with-temp-buffer + (cond ((not (doom-file-cookie-p file "if" t)) + (print! (debug "Ignoring %s") (relpath file))) + + ((let ((generated-autoload-load-name (file-name-sans-extension file)) + ;; Prevent `autoload-find-file' from firing file hooks, + ;; e.g. adding to recentf. + find-file-hook + write-file-functions + ;; Prevent a possible source of crashes when there's a + ;; syntax error in the autoloads file + debug-on-error) + (quiet! (autoload-generate-file-autoloads file (current-buffer)))) + (print! (debug "Nothing in %s") (relpath file))) + + ((cl-incf n) + (print! (debug "Scanning %s...") (relpath file)))) + (buffer-string)))) + (print! (class (if (> n 0) 'success 'info) + "Scanned %d file(s)") + n))) + +(defun doom--cli-expand-autoload-paths (&optional allow-internal-paths) + (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 + (straight--directory-files (straight--build-dir) nil t) + load-path))) + (defvar doom--autoloads-path-cache nil) + (while (re-search-forward "^\\s-*(\\(?:custom-\\)?autoload\\s-+'[^ ]+\\s-+\"\\([^\"]*\\)\"" nil t) + (let ((path (match-string 1))) + (replace-match + (or (cdr (assoc path doom--autoloads-path-cache)) + (when-let* ((libpath (or (and allow-internal-paths + (locate-library path nil (cons doom-emacs-dir doom-modules-dirs))) + (locate-library path))) + (libpath (file-name-sans-extension libpath)) + (libpath (abbreviate-file-name libpath))) + (push (cons path libpath) doom--autoloads-path-cache) + libpath) + path) + t t nil 1))))) + +(defun doom--cli-generate-autodefs-1 (path &optional member-p) + (let (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 (doom-module-from-path 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 + (appendq! + forms + (list (if member-p + (make-autoload sexp path) + (let ((docstring + (format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s" + origin + (if (stringp (car body)) + (pop body) + "No documentation.")))) + (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 + (print! "- Ignoring autodef %s (%s)" name e) + nil)))) + `(put ',name 'doom-module ',origin))))) + + ((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 target #'ignore + docstring + (format "THIS FUNCTION DOES NOTHING BECAUSE %s IS DISABLED\n\n%s" + origin docstring))) + (appendq! forms `((put ',name 'doom-module ',origin) + (defalias ',name #',target ,docstring)))))) + + (member-p (push sexp forms))))) + forms)) + +(defun doom--cli-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) + (if-let (forms (doom--cli-generate-autodefs-1 path (member path enabled-targets))) + (concat (mapconcat #'prin1-to-string (nreverse forms) "\n") + "\n") + ""))))) + +(defun doom--cli-cleanup-autoloads () + (goto-char (point-min)) + (when (re-search-forward "^;;\\(;[^\n]*\\| no-byte-compile: t\\)\n" nil t) + (replace-match "" t t))) + +(defun doom-cli-reload-core-autoloads (&optional force-p) + "Refreshes `doom-autoload-file', if necessary (or if FORCE-P is non-nil). + +It scans and reads autoload cookies (;;;###autoload) in core/autoload/*.el, +modules/*/*/autoload.el and modules/*/*/autoload/*.el, and generates +`doom-autoload-file'. + +Run this whenever your `doom!' block, or a module autoload file, is modified." + (require 'autoload) + (let* ((default-directory doom-emacs-dir) + (doom-modules (doom-modules)) + + ;; The following bindings are in `package-generate-autoloads'. + ;; Presumably for a good reason, so I just copied them + (backup-inhibited t) + (version-control 'never) + (case-fold-search nil) ; reduce magic + (autoload-timestamps nil) + + ;; Where we'll store the files we'll scan for autoloads. This should + ;; contain *all* autoload files, even in disabled modules, so we can + ;; scan those for autodefs. We start with the core libraries. + (targets (doom-glob doom-core-dir "autoload/*.el")) + ;; A subset of `targets' in enabled modules + (active-targets (copy-sequence targets))) + + (dolist (path (doom-module-load-path 'all-p)) + (when-let* ((files (cons (doom-glob path "autoload.el") + (doom-files-in (doom-path path "autoload") + :match "\\.el$"))) + (files (delq nil files))) + (appendq! targets files) + (when (or (doom-module-from-path path 'enabled-only) + (file-equal-p path doom-private-dir)) + (appendq! active-targets files)))) + + (print! (start "Checking core autoloads file")) + (print-group! + (if (and (not force-p) + (file-exists-p doom-autoload-file) + (not (file-newer-than-file-p doom-emacs-dir doom-autoload-file)) + (not (cl-loop for dir + in (append (doom-glob doom-private-dir "init.el*") + targets) + if (file-newer-than-file-p dir doom-autoload-file) + return t))) + (ignore + (print! (success "Skipping core autoloads, they are up-to-date")) + (doom-load-autoloads-file doom-autoload-file)) + (if (doom--cli-delete-autoloads-file doom-autoload-file) + (print! (success "Deleted old %s") (filename doom-autoload-file)) + (make-directory (file-name-directory doom-autoload-file) t)) + + (print! (start "Regenerating core autoloads file")) + (print-group! + (with-temp-file doom-autoload-file + (doom--cli-generate-header 'doom-cli-reload-core-autoloads) + (save-excursion + (doom--cli-generate-autoloads active-targets) + (print! (success "Generated new autoloads.el"))) + ;; Replace autoload paths (only for module autoloads) with absolute + ;; paths for faster resolution during load and simpler `load-path' + (save-excursion + (doom--cli-expand-autoload-paths 'allow-internal-paths) + (print! (success "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--cli-generate-autodefs targets (reverse active-targets)) + (print! (success "Generated autodefs"))) + ;; Remove byte-compile-inhibiting file variables so we can byte-compile + ;; the file, and autoload comments. + (doom--cli-cleanup-autoloads) + (print! (success "Cleaned up autoloads")))) + ;; Byte compile it to give the file a chance to reveal errors (and buy us a + ;; few marginal performance boosts) + (print! "> Byte-compiling %s..." (relpath doom-autoload-file)) + (when (doom--cli-byte-compile-file doom-autoload-file) + (print-group! + (print! (success "Compiled %s") (relpath doom-autoload-file))))) + t))) + + +;; +;;; Package autoloads + +(defun doom--generate-package-autoloads () + "Concatenates package autoload files, let-binds `load-file-name' around +them,and remove unnecessary `provide' statements or blank links." + (dolist (pkg (hash-table-keys straight--build-cache)) + (unless (member pkg doom-autoload-excluded-packages) + (let ((file (straight--autoloads-file pkg))) + (when (file-exists-p file) + (insert-file-contents file) + (save-excursion + (while (re-search-forward "\\(?:\\_" nil t) + ;; `load-file-name' is meaningless in a concatenated + ;; mega-autoloads file, so we replace references to it and #$ with + ;; the file they came from. + (unless (doom-point-in-string-or-comment-p) + (replace-match (prin1-to-string (abbreviate-file-name file)) + t t)))) + (while (re-search-forward "^\\(?:;;\\(.*\n\\)\\|\n\\|(provide '[^\n]+\\)" nil t) + (unless (doom-point-in-string-p) + (replace-match "" t t))) + (unless (bolp) (insert "\n"))))))) + +(defun doom--generate-var-cache () + "Print a `setq' form for expensive-to-initialize variables, so we can cache +them in Doom's autoloads file." + (doom-initialize-packages) + (prin1 `(setq load-path ',load-path + auto-mode-alist ',auto-mode-alist + Info-directory-list ',Info-directory-list + doom-disabled-packages ',doom-disabled-packages) + (current-buffer))) + +(defun doom--cleanup-package-autoloads () + "Remove (some) forms that modify `load-path' or `auto-mode-alist'. + +These variables are cached all at once and at later, so these removed statements +served no purpose but to waste cycles." + (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-cli-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." + (require 'autoload) + (print! (start "Checking package autoloads file")) + (print-group! + (if (and (not force-p) + (file-exists-p doom-package-autoload-file) + (not (file-newer-than-file-p package-user-dir doom-package-autoload-file)) + (not (cl-loop for dir in (straight--directory-files (straight--build-dir)) + if (cl-find-if + (lambda (dir) + (file-newer-than-file-p dir doom-package-autoload-file)) + (doom-glob (straight--build-dir dir) "*.el")) + return t)) + (not (cl-loop with doom-modules = (doom-modules) + 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! (success "Skipping package autoloads, they are up-to-date")) + (doom-load-autoloads-file doom-package-autoload-file)) + (let (;; The following bindings are in `package-generate-autoloads'. + ;; Presumably for a good reason, so I just copied them + (backup-inhibited t) + (version-control 'never) + (case-fold-search nil) ; reduce magic + (autoload-timestamps nil)) + + (if (doom--cli-delete-autoloads-file doom-package-autoload-file) + (print! (success "Deleted old %s") (filename doom-package-autoload-file)) + (make-directory (file-name-directory doom-autoload-file) t)) + + (print! (start "Regenerating package autoloads file")) + (print-group! + (with-temp-file doom-package-autoload-file + (doom--cli-generate-header 'doom-cli-reload-package-autoloads) + + (save-excursion + ;; Cache important and expensive-to-initialize state here. + (doom--generate-var-cache) + (print! (success "Cached package state")) + ;; Concatenate the autoloads of all installed packages. + (doom--generate-package-autoloads) + (print! (success "Package autoloads included"))) + + ;; Replace autoload paths (only for module autoloads) with absolute + ;; paths for faster resolution during load and simpler `load-path' + (save-excursion + (doom--cli-expand-autoload-paths) + (print! (success "Expanded module autoload paths"))) + + ;; 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! (success "Removed load-path/auto-mode-alist entries")))) + ;; Byte compile it to give the file a chance to reveal errors (and buy us a + ;; few marginal performance boosts) + (print! (start "Byte-compiling %s...") (relpath doom-package-autoload-file)) + (when (doom--cli-byte-compile-file doom-package-autoload-file) + (print-group! + (print! (success "Compiled %s") (relpath doom-package-autoload-file)))))) + t)) diff --git a/.emacs.d/core/cli/byte-compile.el b/.emacs.d/core/cli/byte-compile.el new file mode 100644 index 0000000..ab65a3a --- /dev/null +++ b/.emacs.d/core/cli/byte-compile.el @@ -0,0 +1,204 @@ +;;; core/cli/byte-compile.el -*- lexical-binding: t; -*- + +(defcli! (compile c) + ((recompile-p ["-r" "--recompile"]) + &rest targets) + "Byte-compiles your config or selected modules. + + compile [TARGETS...] + compile :core :private lang/python + compile feature lang + +Accepts :core and :private as special arguments, which target Doom's core files +and your private config files, respectively. To recompile your packages, use +'doom rebuild' instead." + (doom-cli-byte-compile targets recompile-p)) + +(defcli! clean () + "Delete all *.elc files." + :bare t + (doom-clean-byte-compiled-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")) + (member filename (list "packages.el" "doctor.el"))))) + +(cl-defun doom-cli-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) + (doom-modules (doom-modules)) + (byte-compile-verbose doom-debug-mode) + (byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local)) + + ;; In case it is changed during compile-time + (auto-mode-alist auto-mode-alist) + (noninteractive t) + + targets) + + (let (target-dirs) + (dolist (module (delete-dups modules)) + (pcase module + (":core" + (push (doom-glob doom-emacs-dir "init.el") targets) + (push doom-core-dir target-dirs)) + (":private" + (push doom-private-dir target-dirs)) + ((pred file-directory-p) + (push module target-dirs)) + ((pred (string-match "^\\([^/]+\\)/\\([^/]+\\)$")) + (push (doom-module-locate-path + (doom-keyword-intern (match-string 1 module)) + (intern (match-string 2 module))) + target-dirs)) + (_ (user-error "%S is not a valid target" module)))) + + (and (or (null modules) (member ":private" modules)) + (not recompile-p) + (not (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?")))) + (user-error "Aborting")) + + ;; 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. + (let ((doom-interactive-mode 'byte-compile)) + (doom-initialize) + (doom-initialize-packages) + (doom-initialize-core)) + + ;; + (unless target-dirs + (push (doom-glob doom-emacs-dir "init.el") targets) + ;; If no targets were supplied, then we use your module list. + (appendq! target-dirs + (list doom-core-dir) + (nreverse + (cl-remove-if-not + (lambda (path) (file-in-directory-p path doom-emacs-dir)) + ;; Omit `doom-private-dir', which is always first + (cdr (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. + (appendq! targets + (doom-files-in target-dirs + :match "\\.el$" + :filter #'doom--byte-compile-ignore-file-p))) + + (unless targets + (print! + (if targets + (warn "Couldn't find any valid targets") + (info "No targets to %scompile" (if recompile-p "re" "")))) + (cl-return nil)) + + (print! + (start (if recompile-p + "Recompiling stale elc files..." + "Byte-compiling your config (may take a while)..."))) + (print-group! + (require 'use-package) + (condition-case e + (let ((total-ok 0) + (total-fail 0) + (total-noop 0) + (use-package-defaults use-package-defaults) + (use-package-expand-minimally t) + 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) + + (unless recompile-p + (doom-clean-byte-compiled-files)) + + (dolist (target (delete-dups (delq nil targets))) + (cl-incf + (if (not (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))))) + total-noop + (pcase (if (doom-file-cookie-p target "if" t) + (byte-compile-file target) + 'no-byte-compile) + (`no-byte-compile + (print! (info "Ignored %s") (relpath target)) + total-noop) + (`nil + (print! (error "Failed to compile %s") (relpath target)) + total-fail) + (_ + (print! (success "Compiled %s") (relpath target)) + (load target t t) + total-ok))))) + (print! (class (if (= total-fail 0) 'success 'error) + "%s %d/%d file(s) (%d ignored)") + (if recompile-p "Recompiled" "Compiled") + total-ok (- (length targets) total-noop) + total-noop) + t) + ((debug error) + (print! (error "\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.'" + (require 'core-modules) + (print! (start "Cleaning .elc files")) + (print-group! + (cl-loop with default-directory = doom-emacs-dir + with success = nil + for path + in (append (doom-glob doom-emacs-dir "*.elc") + (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)) + if (file-exists-p path) + do (delete-file path) + and do (print! (success "Deleted %s") (relpath path)) + and do (setq success t) + finally do + (print! (if success + (success "All elc files deleted") + (info "No elc files to clean")))) + t)) diff --git a/.emacs.d/core/cli/debug.el b/.emacs.d/core/cli/debug.el new file mode 100644 index 0000000..d36f73d --- /dev/null +++ b/.emacs.d/core/cli/debug.el @@ -0,0 +1,29 @@ +;;; core/cli/debug.el -*- lexical-binding: t; -*- + +;; +;;; Commands + +(defcli! info + ((format ["--json" "--md" "--lisp"] "What format to dump info into")) + "Output system info in markdown for bug reports." + (pcase format + ("--json" + (require 'json) + (with-temp-buffer + (insert (json-encode (doom-info))) + (json-pretty-print-buffer) + (print! (buffer-string)))) + ("--md" + (doom/info)) + ((or `nil "--lisp") + (doom/info 'raw)) + (_ + (user-error "I don't understand %S. Did you mean --json, --md/--markdown or --lisp?" + format))) + nil) + +(defcli! (version v) () + "Show version information for Doom & Emacs." + :bare t + (doom/version) + nil) diff --git a/.emacs.d/core/cli/doctor.el b/.emacs.d/core/cli/doctor.el new file mode 100644 index 0000000..d50cfb5 --- /dev/null +++ b/.emacs.d/core/cli/doctor.el @@ -0,0 +1,203 @@ +;;; core/cli/doctor.el -*- lexical-binding: t; -*- + +(defvar doom-warnings ()) +(defvar doom-errors ()) + +;;; Helpers +(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))))) + +(defmacro assert! (condition message &rest args) + `(unless ,condition + (error! ,message ,@args))) + + +;;; Logging +(defmacro error! (&rest args) + `(progn (unless inhibit-message (print! (error ,@args))) + (push (format! (error ,@args)) doom-errors))) +(defmacro warn! (&rest args) + `(progn (unless inhibit-message (print! (warn ,@args))) + (push (format! (warn ,@args)) doom-warnings))) +(defmacro success! (&rest args) + `(print! (green ,@args))) +(defmacro section! (&rest args) + `(print! (bold (blue ,@args)))) + +(defmacro explain! (&rest args) + `(print-group! (print! (autofill ,@args)))) + + +;; +;;; CLI commands + +(defcli! (doctor doc) () + "Diagnoses common issues on your system. + +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." + :bare t + (print! "The doctor will see you now...\n") + + ;; REVIEW Refactor me + (print! (start "Checking your Emacs version...")) + (when EMACS27+ + (warn! "Emacs %s detected. Emacs HEAD is unstable and may cause errors." + emacs-version)) + + (print! (start "Checking for Emacs config conflicts...")) + (when (file-exists-p "~/.emacs") + (warn! "Detected an ~/.emacs file, which may prevent Doom from loading") + (explain! "If Emacs finds an ~/.emacs file, it will ignore ~/.emacs.d, where Doom is " + "typically installed. If you're seeing a vanilla Emacs splash screen, this " + "may explain why. If you use Chemacs, you may ignore this warning.")) + + (print! (start "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)) + (print! (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."))) + + (print! (start "Checking for stale elc files...")) + (elc-check-dir user-emacs-directory) + + (print! (start "Checking Doom Emacs...")) + (condition-case-unless-debug ex + (print-group! + (let ((doom-interactive-mode 'doctor)) + (doom-initialize 'force) + (doom-initialize-core) + (doom-initialize-modules)) + + (print! (success "Initialized Doom Emacs %s") doom-version) + (print! + (if (hash-table-p doom-modules) + (success "Detected %d modules" (hash-table-count doom-modules)) + (warn "Failed to load any modules. Do you have an private init.el?"))) + + (print! (success "Detected %d packages") (length doom-packages)) + + (print! (start "Checking Doom core for irregularities...")) + (print-group! + ;; 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")) + (when-let (size (ignore-errors (doom-file-size file doom-cache-dir))) + (when (> size 1048576) ; larger than 1mb + (warn! "%s is too large (%.02fmb). This may cause freezes or odd startup delays" + file (/ size 1024)) + (explain! "Consider deleting it from your system (manually)")))) + + (unless (ignore-errors (executable-find doom-projectile-fd-binary)) + (warn! "Couldn't find the `fd' binary; project file searches will be slightly slower") + (unless (executable-find "rg") + (warn! "Couldn't find the `rg' binary either; project file searches will be even slower"))) + + (require 'projectile) + (when (projectile-project-root "~") + (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")) + (print! (warn "Both %S and '~/.doom.d' exist on your system") + (path doom-private-dir)) + (explain! "Doom will only load one of these (~/.config/doom takes precedence). Possessing\n" + "both is rarely intentional; you should one or the other.")) + + ;; Check for 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 + (when (and (pcase system-type + (`gnu/linux (concat (or (getenv "XDG_DATA_HOME") + "~/.local/share") + "/fonts/")) + (`darwin "~/Library/Fonts/")) + (require 'all-the-icons nil t)) + (with-temp-buffer + (insert (cdr (doom-call-process "fc-list"))) + (dolist (font all-the-icons-font-names) + (if (save-excursion (re-search-backward font nil t)) + (success! "Found font %s" font) + (print! (warn "Warning: couldn't find %S font") font) + (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."))))))) + + (print! (start "Checking for stale elc files in your DOOMDIR...")) + (when (file-directory-p doom-private-dir) + (print-group! + (elc-check-dir doom-private-dir))) + + (when doom-modules + (print! (start "Checking your enabled modules...")) + (advice-add #'require :around #'doom-shut-up-a) + (maphash (lambda (key plist) + (let (doom-local-errors + doom-local-warnings) + (let (doom-errors + doom-warnings) + (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 for name in (let (doom-packages + doom-disabled-packages) + (load packages-file 'noerror 'nomessage) + (mapcar #'car doom-packages)) + unless (or (doom-package-get name :disable) + (eval (doom-package-get name :ignore)) + (doom-package-built-in-p name) + (doom-package-installed-p name)) + do (print! (error "%s is not installed") name)) + (let ((inhibit-message t)) + (load doctor-file 'noerror 'nomessage))) + (file-missing (error! "%s" (error-message-string ex))) + (error (error! "Syntax error: %s" ex))) + (when (or doom-errors doom-warnings) + (print-group! + (print! (start (bold "%s %s")) (car key) (cdr key)) + (print! "%s" (string-join (append doom-errors doom-warnings) "\n"))) + (setq doom-local-errors doom-errors + doom-local-warnings doom-warnings))) + (appendq! doom-errors doom-local-errors) + (appendq! doom-warnings doom-local-warnings))) + doom-modules))) + (error + (warn! "Attempt to load DOOM failed\n %s\n" + (or (cdr-safe ex) (car ex))) + (setq doom-modules nil))) + + ;; Final report + (message "") + (dolist (msg (list (list doom-errors "error" 'red) + (list doom-warnings "warning" 'yellow))) + (when (car msg) + (print! (color (nth 2 msg) + (if (cdr msg) + "There are %d %ss!" + "There is %d %s!") + (length (car msg)) (nth 1 msg))))) + (unless (or doom-errors doom-warnings) + (success! "Everything seems fine, happy Emacs'ing!")) + t) diff --git a/.emacs.d/core/cli/env.el b/.emacs.d/core/cli/env.el new file mode 100644 index 0000000..9b28a68 --- /dev/null +++ b/.emacs.d/core/cli/env.el @@ -0,0 +1,131 @@ +;;; core/cli/env.el -*- lexical-binding: t; -*- + +(defcli! env + ((clear-p ["-c" "--clear"] "Clear and delete your envvar file") + (outputfile ["-o" PATH] + "Generate the envvar file at PATH. Note that envvar files that aren't in +`doom-env-file' won't be loaded automatically at startup. You will need to +load them manually from your private config with the `doom-load-envvars-file' +function.")) + "Creates or regenerates your envvars file. + +The envvars file is created by scraping your (interactive) shell environment +into newline-delimited KEY=VALUE pairs. Typically by running '$SHELL -ic env' +(or '$SHELL -c set' on windows). Doom loads this file at startup (if it exists) +to ensure Emacs mirrors your shell environment (particularly to ensure PATH and +SHELL are correctly set). + +This is useful in cases where you cannot guarantee that Emacs (or the daemon) +will be launched from the correct environment (e.g. on MacOS or through certain +app launchers on Linux). + +This file is automatically regenerated when you run this command or 'doom +refresh'. However, 'doom refresh' will only regenerate this file if it exists. + +Why this over exec-path-from-shell? + + 1. `exec-path-from-shell' spawns (at least) one process at startup to scrape + your shell environment. This can be arbitrarily slow depending on the + user's shell configuration. A single program (like pyenv or nvm) or config + framework (like oh-my-zsh) could undo all of Doom's startup optimizations + in one fell swoop. + + 2. `exec-path-from-shell' only scrapes some state from your shell. You have to + be proactive in order to get it to capture all the envvars relevant to your + development environment. + + I'd rather it inherit your shell environment /correctly/ (and /completely/) + or not at all. It frontloads the debugging process rather than hiding it + until it you least want to deal with it." + (let ((env-file (expand-file-name (or outputfile doom-env-file)))) + (cond (clear-p + (unless (file-exists-p env-file) + (user-error! "%S does not exist to be cleared" + (path env-file))) + (delete-file env-file) + (print! (success "Successfully deleted %S") + (path env-file))) + + (args + (user-error "I don't understand 'doom env %s'" + (string-join args " "))) + + ((doom-cli-reload-env-file 'force env-file))))) + + +;; +;; Helpers + +(defvar doom-env-ignored-vars + '("^DBUS_SESSION_BUS_ADDRESS$" + "^GPG_AGENT_INFO$" + "^GPG_TTY$" + "^HOME$" + "^PS1$" + "^PWD$" + "^R?PROMPT$" + "^SSH_AGENT_PID$" + "^SSH_AUTH_SOCK$" + "^TERM$" + ;; Doom envvars + "^DEBUG$" + "^INSECURE$" + "^YES$" + "^__") + "Environment variables to not save in `doom-env-file'. + +Each string is a regexp, matched against variable names to omit from +`doom-env-file'.") + +(defun doom-cli-reload-env-file (&optional force-p env-file) + "Generates `doom-env-file', if it doesn't exist (or if FORCE-P). + +This scrapes the variables from your shell environment by running +`doom-env-executable' through `shell-file-name' with `doom-env-switches'. By +default, on Linux, this is '$SHELL -ic /usr/bin/env'. Variables in +`doom-env-ignored-vars' are removed." + (let ((env-file (if env-file + (expand-file-name env-file) + doom-env-file))) + (when (or force-p (not (file-exists-p env-file))) + (with-temp-file env-file + (print! (start "%s envvars file at %S") + (if (file-exists-p env-file) + "Regenerating" + "Generating") + (path env-file)) + (let ((process-environment doom--initial-process-environment)) + (print! (info "Scraping shell environment")) + (print-group! + (when doom-interactive-mode + (user-error "'doom env' must be run on the command line, not an interactive session")) + (goto-char (point-min)) + (insert + (concat + "# -*- mode: dotenv -*-\n" + (format "# Generated from a %s shell environent\n" shell-file-name) + "# ---------------------------------------------------------------------------\n" + "# This file was auto-generated by `doom env'. It contains a list of environment\n" + "# variables scraped from your default shell (excluding variables blacklisted\n" + "# in doom-env-ignored-vars).\n" + "#\n" + (if (file-equal-p env-file doom-env-file) + (concat "# It is NOT safe to edit this file. Changes will be overwritten next time you\n" + "# run 'doom refresh'. To create a safe-to-edit envvar file use:\n#\n" + "# doom env -o ~/.doom.d/myenv\n#\n" + "# And load it with (doom-load-envvars-file \"~/.doom.d/myenv\").\n") + (concat "# This file is safe to edit by hand, but needs to be loaded manually with:\n#\n" + "# (doom-load-envvars-file \"path/to/this/file\")\n#\n" + "# Use 'doom env -o path/to/this/file' to regenerate it.")) + "# ---------------------------------------------------------------------------\n\n")) + ;; We assume that this noninteractive session was spawned from the + ;; user's interactive shell, therefore we just dump + ;; `process-environment' to a file. + (dolist (env process-environment) + (if (cl-find-if (doom-rpartial #'string-match-p (car (split-string env "="))) + doom-env-ignored-vars) + (print! (info "Ignoring %s") env) + (insert env "\n"))) + (print! (success "Successfully generated %S") + (path env-file)) + t)))))) diff --git a/.emacs.d/core/cli/help.el b/.emacs.d/core/cli/help.el new file mode 100644 index 0000000..39e136b --- /dev/null +++ b/.emacs.d/core/cli/help.el @@ -0,0 +1,102 @@ +;;; core/cli/help.el -*- lexical-binding: t; -*- + +(defun doom--cli-print-signature (cli) + (print! (bold "Usage: doom %s%s%s") + (if (doom-cli-internal-p cli) + "" + (concat (doom-cli-name cli) " ")) + (if-let* ((optlist (doom-cli-optlist cli)) + (flags (cl-loop for opt in optlist + append (doom-cli-option-flags opt))) + (fn (doom-partial #'string-prefix-p "--"))) + (concat (when-let (short-flags (cl-remove-if fn flags)) + ;; TODO Show arguments of short flags + (format "[-%s]" + (string-join (mapcar (doom-rpartial #'substring 1 nil) short-flags) + ""))) + ;; TODO Show long flags + ;; (when-let (long-flags (cl-remove-if-not fn flags)) + ;; (concat " " (string-join long-flags " "))) + " ") + "") + (if-let (arglist (doom-cli-arglist cli)) + (string-join (append (cl-loop for arg in arglist + until (memq arg cl--lambda-list-keywords) + collect (upcase (symbol-name arg))) + (cl-loop for arg in (cdr (memq '&optional arglist)) + until (memq arg cl--lambda-list-keywords) + collect (format "[%s]" (upcase (symbol-name arg))))) + " ") + "")) + (when-let (aliases (doom-cli-aliases cli)) + (print! "Aliases: %s" (string-join aliases ", ")))) + +(defun doom--cli-print-desc (cli &optional short) + (print! "%s" + (if short + (car (split-string (doom-cli-desc cli) "\n")) + (doom-cli-desc cli)))) + +(defun doom--cli-print-short-desc (cli) + (doom--cli-print-desc cli 'short)) + +(defun doom--cli-print-options (cli) + (when-let (optlist (doom-cli-optlist cli)) + (print! (bold "Options:")) + (print-group! + (cl-loop for opt in optlist + for desc = (doom-cli-option-desc opt) + for args = (doom-cli-option-args opt) + for flagstr = (string-join (doom-cli-option-flags opt) ", ") + do + ;; TODO Adjust columns dynamically + (print! "%-18s" + (concat flagstr + (when-let (arg (car args)) + (concat " " (upcase (symbol-name arg)))))) + (print-group! + (print! (autofill "%s") desc)))))) + + +(defun doom--cli-print (cli) + (doom--cli-print-signature cli) + (terpri) + (doom--cli-print-desc cli) + (terpri) + (doom--cli-print-options cli)) + + +;; +;;; Commands + +(defcli! (help h) (&optional command) + "Describe a command or list them all." + :bare t + (if command + (doom--cli-print (doom-cli-get (intern command))) + (doom--cli-print (doom-cli-get :main)) + (terpri) + (print! (bold "Commands:")) + (print-group! + (dolist (group (seq-group-by (lambda (cli) + (plist-get (doom-cli-plist cli) :group)) + (cl-loop for name being the hash-keys of doom--cli-commands + for cli = (gethash name doom--cli-commands) + if (and (doom-cli-p cli) + (not (doom-cli-internal-p cli)) + (not (plist-get (doom-cli-plist cli) :hidden))) + collect cli))) + (if (null (car group)) + (dolist (cli (cdr group)) + (print! "%-16s %s" + (doom-cli-name cli) + (car (split-string (doom-cli-desc cli) "\n")))) + (print! "%-26s %s" + (bold (concat (car group) ":")) + (gethash (car group) doom--cli-groups)) + (print-group! + (dolist (cli (cdr group)) + (print! "%-16s %s" + (doom-cli-name cli) + (car (split-string (doom-cli-desc cli) "\n")))))) + (terpri))))) diff --git a/.emacs.d/core/cli/install.el b/.emacs.d/core/cli/install.el new file mode 100644 index 0000000..bb21dac --- /dev/null +++ b/.emacs.d/core/cli/install.el @@ -0,0 +1,102 @@ +;;; core/cli/install.el -*- lexical-binding: t; -*- + +(defcli! (install i) + ((noconfig-p ["--no-config"] "Don't create DOOMDIR or dummy files therein") + (noenv-p ["--no-env"] "Don't generate an envvars file (see 'doom help env')") + (noinstall-p ["--no-install"] "Don't auto-install packages") + (nofonts-p ["--no-fonts"] "Don't install (or prompt to install) all-the-icons fonts") + &rest _args) + "Installs and sets up Doom Emacs for the first time. + +This command does the following: + + 1. Creates DOOMDIR at ~/.doom.d, + 2. Copies ~/.emacs.d/init.example.el to $DOOMDIR/init.el (if it doesn't exist), + 3. Creates dummy files for $DOOMDIR/{config,packages}.el, + 4. Prompts you to generate an envvar file (same as 'doom env'), + 5. Installs any dependencies of enabled modules (specified by $DOOMDIR/init.el), + 6. And prompts to install all-the-icons' fonts + +This command is idempotent and safe to reuse. + +The location of DOOMDIR can be changed with the -p option, or by setting the +DOOMDIR environment variable. e.g. + + doom -p ~/.config/doom install + DOOMDIR=~/.config/doom doom install" + :bare t + (print! (green "Installing Doom Emacs!\n")) + (let ((default-directory (doom-path "~"))) + ;; Create `doom-private-dir' + (if noconfig-p + (print! (warn "Not copying private config template, as requested")) + (print! (start "Creating %s") (relpath doom-private-dir)) + (make-directory doom-private-dir 'parents) + (print-group! + (print! (success "Created %s") (relpath doom-private-dir))) + + ;; Create init.el, config.el & packages.el + (mapc (lambda (file) + (cl-destructuring-bind (filename . fn) file + (if (file-exists-p! filename doom-private-dir) + (print! (warn "%s already exists, skipping") filename) + (print! (info "Creating %s%s") (relpath doom-private-dir) filename) + (with-temp-file (doom-path doom-private-dir filename) + (funcall fn)) + (print! (success "Done!"))))) + '(("init.el" . + (lambda () + (insert-file-contents (doom-path doom-emacs-dir "init.example.el")))) + ("config.el" . + (lambda () + (insert! ";;; %sconfig.el -*- lexical-binding: t; -*-\n\n" + ";; Place your private configuration here\n" + ((relpath doom-private-dir))))) + ("packages.el" . + (lambda () + (insert! ";; -*- no-byte-compile: t; -*-\n;;; %spackages.el\n\n" + ";;; Examples:\n" + ";; (package! some-package)\n" + ";; (package! another-package :recipe (:host github :repo \"username/repo\"))\n" + ";; (package! builtin-package :disable t)\n" + ((relpath doom-private-dir)))))))) + + ;; 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 'force 'noerror) + (doom-initialize-modules) + + ;; Ask if user would like an envvar file generated + (if noenv-p + (print! (warn "Not generating envvars file, as requested")) + (if (file-exists-p doom-env-file) + (print! (info "Envvar file already exists, skipping")) + (when (or doom-auto-accept + (y-or-n-p "Generate an env file? (see `doom help env` for details)")) + (doom-cli-reload-env-file 'force-p)))) + + ;; Install Doom packages + (if noinstall-p + (print! (warn "Not installing plugins, as requested")) + (print! "Installing plugins") + (doom-cli-packages-install)) + + (print! "Regenerating autoloads files") + (doom-cli-reload-autoloads nil 'force-p) + + (if nofonts-p + (print! (warn "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) + (let ((window-system (cond (IS-MAC 'ns) + (IS-LINUX 'x)))) + (all-the-icons-install-fonts 'yes)))) + + (when (file-exists-p "~/.emacs") + (print! (warn "A ~/.emacs file was detected. This conflicts with Doom and should be deleted!"))) + + (print! (success "\nFinished! Doom is ready to go!\n")) + (with-temp-buffer + (doom-template-insert "QUICKSTART_INTRO") + (print! (buffer-string))))) diff --git a/.emacs.d/core/cli/packages.el b/.emacs.d/core/cli/packages.el new file mode 100644 index 0000000..0eec01b --- /dev/null +++ b/.emacs.d/core/cli/packages.el @@ -0,0 +1,317 @@ +;; -*- no-byte-compile: t; -*- +;;; core/cli/packages.el + +(defcli! (update u) () + "Updates packages. + +This works by fetching all installed package repos and checking the distance +between HEAD and FETCH_HEAD. This can take a while. + +This excludes packages whose `package!' declaration contains a non-nil :freeze +or :ignore property." + (straight-check-all) + (doom-cli-reload-core-autoloads) + (when (doom-cli-packages-update) + (doom-cli-reload-package-autoloads 'force-p)) + t) + +(defcli! (build b) + ((rebuild-p ["-r"] "Only rebuild packages that need rebuilding")) + "Byte-compiles & symlinks installed packages. + +This ensures that all needed files are symlinked from their package repo and +their elisp files are byte-compiled. This is especially necessary if you upgrade +Emacs (as byte-code is generally not forward-compatible)." + (when (doom-cli-packages-build (not rebuild-p)) + (doom-cli-reload-package-autoloads 'force-p)) + t) + +(defcli! (purge p) + ((nobuilds-p ["-b" "--no-builds"] "Don't purge unneeded (built) packages") + (noelpa-p ["-p" "--no-elpa"] "Don't purge ELPA packages") + (norepos-p ["-r" "--no-repos"] "Don't purge unused straight repos") + (regraft-p ["-g" "--regraft"] "Regraft git repos (ie. compact them)")) + "Deletes orphaned packages & repos, and compacts them. + +Purges all installed ELPA packages (as they are considered temporary). Purges +all orphaned package repos and builds. If -g/--regraft is supplied, the git +repos among them will be regrafted and compacted to ensure they are as small as +possible. + +It is a good idea to occasionally run this doom purge -g to ensure your package +list remains lean." + (straight-check-all) + (when (doom-cli-packages-purge + (not noelpa-p) + (not norepos-p) + (not nobuilds-p) + regraft-p) + (doom-cli-reload-package-autoloads 'force-p)) + t) + +;; (defcli! rollback () ; TODO doom rollback +;; "" +;; (user-error "Not implemented yet, sorry!")) + + +;; +;;; Library + +(defun doom-cli-packages-install () + "Installs missing packages. + +This function will install any primary package (i.e. a package with a `package!' +declaration) or dependency thereof that hasn't already been." + (print! (start "Installing & building packages...")) + (print-group! + (let ((n 0)) + (dolist (package (hash-table-keys straight--recipe-cache)) + (straight--with-plist (gethash package straight--recipe-cache) + (local-repo) + (let ((existed-p (file-directory-p (straight--repos-dir package)))) + (condition-case-unless-debug e + (and (straight-use-package (intern package) nil nil (make-string (1- (or doom-format-indent 1)) 32)) + (not existed-p) + (file-directory-p (straight--repos-dir package)) + (cl-incf n)) + (error + (signal 'doom-package-error + (list e (straight--process-get-output)))))))) + (if (= n 0) + (ignore (print! (success "No packages need to be installed"))) + (print! (success "Installed & built %d packages") n) + t)))) + + +(defun doom-cli-packages-build (&optional force-p) + "(Re)build all packages." + (print! (start "(Re)building %spackages...") (if force-p "all " "")) + (print-group! + (let ((n 0)) + (if force-p + (let ((straight--packages-to-rebuild :all) + (straight--packages-not-to-rebuild (make-hash-table :test #'equal))) + (dolist (package (hash-table-keys straight--recipe-cache)) + (straight-use-package + (intern package) nil (lambda (_) (cl-incf n) nil) + (make-string (1- (or doom-format-indent 1)) 32)))) + (dolist (recipe (hash-table-values straight--recipe-cache)) + (straight--with-plist recipe (package local-repo no-build) + (unless (or no-build (null local-repo)) + ;; REVIEW We do these modification checks manually because + ;; Straight's checks seem to miss stale elc files. Need + ;; more tests to confirm this. + (when (or (ignore-errors + (gethash package straight--packages-to-rebuild)) + (gethash package straight--cached-package-modifications) + (not (file-directory-p (straight--build-dir package))) + (cl-loop for file + in (doom-files-in (straight--build-dir package) + :match "\\.el$" + :full t) + for elc-file = (byte-compile-dest-file file) + if (and (file-exists-p elc-file) + (file-newer-than-file-p file elc-file)) + return t)) + (let ((straight-use-package-pre-build-functions + straight-use-package-pre-build-functions)) + (add-hook 'straight-use-package-pre-build-functions + (lambda (&rest _) (cl-incf n))) + (let ((straight--packages-to-rebuild :all) + (straight--packages-not-to-rebuild (make-hash-table :test #'equal))) + (straight-use-package + (intern package) nil nil + (make-string (or doom-format-indent 0) 32))) + (straight--byte-compile-package recipe) + (dolist (dep (straight--get-dependencies package)) + (when-let (recipe (gethash dep straight--recipe-cache)) + (straight--byte-compile-package recipe))))))))) + (if (= n 0) + (ignore (print! (success "No packages need rebuilding"))) + (doom--finalize-straight) + (print! (success "Rebuilt %d package(s)" n)) + t)))) + + +(defun doom-cli-packages-update () + "Updates packages." + (print! (start "Updating packages (this may take a while)...")) + ;; TODO Refactor me + (let ((straight--repos-dir (straight--repos-dir)) + (straight--packages-to-rebuild (make-hash-table :test #'equal)) + (total (hash-table-count straight--repo-cache)) + (i 1) + errors) + (print-group! + (dolist (recipe (hash-table-values straight--repo-cache)) + (straight--with-plist recipe (package type local-repo) + (condition-case-unless-debug e + (let ((default-directory (straight--repos-dir local-repo))) + (if (not (file-in-directory-p default-directory straight--repos-dir)) + (print! (warn "[%d/%d] Skipping %s because it is local") + i total package) + (let ((commit (straight-vc-get-commit type local-repo))) + (if (not (straight-vc-fetch-from-remote recipe)) + (print! (warn "\033[K(%d/%d) Failed to fetch %s" i total package)) + (let ((output (straight--process-get-output))) + (straight-merge-package package) + (let ((newcommit (straight-vc-get-commit type local-repo))) + (if (string= commit newcommit) + (print! (start "\033[K(%d/%d) %s is up-to-date\033[1A") i total package) + (ignore-errors + (delete-directory (straight--build-dir package) 'recursive)) + (puthash package t straight--packages-to-rebuild) + (print! (info "\033[K(%d/%d) Updating %s...") i total package) + (unless (string-empty-p output) + (print-group! + (print! (info "%s") output) + (when (eq type 'git) + (straight--call "git" "log" "--oneline" newcommit (concat "^" commit)) + (print-group! + (print! "%s" (straight--process-get-output)))))) + (print! (success "(%d/%d) %s updated (%s -> %s)") i total package + (substring commit 0 7) + (substring newcommit 0 7)))))))) + (cl-incf i)) + (user-error + (signal 'user-error (error-message-string e))) + (error + (print! (warn "(%d/%d) Encountered error with %s" i total package)) + (print-group! + (print! (error "%s" e)) + (print-group! (print! (info "%s" (straight--process-get-output))))) + (push package errors))))) + (princ "\033[K") + (when errors + (print! (error "There were %d errors, the offending packages are: %s") + (length errors) (string-join errors ", "))) + (if (hash-table-empty-p straight--packages-to-rebuild) + (ignore + (print! (success "All %d packages are up-to-date") + (hash-table-count straight--repo-cache))) + (let ((count (hash-table-count straight--packages-to-rebuild)) + (packages (hash-table-keys straight--packages-to-rebuild))) + (sort packages #'string-lessp) + (doom--finalize-straight) + (doom-cli-packages-build) + (print! (success "Updated %d package(s)") count)) + t)))) + + +;;; PURGE (for the emperor) +(defun doom--cli-packages-purge-build (build) + (let ((build-dir (straight--build-dir build))) + (delete-directory build-dir 'recursive) + (if (file-directory-p build-dir) + (ignore (print! (error "Failed to purg build/%s" build))) + (print! (success "Purged build/%s" build)) + t))) + +(defun doom--cli-packages-purge-builds (builds) + (if (not builds) + (progn (print! (info "No builds to purge")) + 0) + (length + (delq nil (mapcar #'doom--cli-packages-purge-build builds))))) + +(defun doom--cli-packages-regraft-repo (repo) + (let ((default-directory (straight--repos-dir repo))) + (if (not (file-directory-p ".git")) + (ignore (print! (warn "\033[Krepos/%s is not a git repo, skipping" repo))) + (let ((before-size (doom-directory-size default-directory))) + (straight--call "git" "reset" "--hard") + (straight--call "git" "clean" "-ffd") + (if (not (car (straight--call "git" "replace" "--graft" "HEAD"))) + (print! (info "\033[Krepos/%s is already compact\033[1A" repo)) + (straight--call "git" "gc") + (print! (success "\033[KRegrafted repos/%s (from %0.1fKB to %0.1fKB)") + repo before-size (doom-directory-size default-directory)) + (print-group! (print! "%s" (straight--process-get-output))))) + t))) + +(defun doom--cli-packages-regraft-repos (repos) + (if (not repos) + (progn (print! (info "No repos to regraft")) + 0) + (let ((before-size (doom-directory-size (straight--repos-dir)))) + (print-group! + (prog1 (delq nil (mapcar #'doom--cli-packages-regraft-repo repos)) + (princ "\033[K") + (let ((after-size (doom-directory-size (straight--repos-dir)))) + (print! (success "Finished regrafting. Size before: %0.1fKB and after: %0.1fKB (%0.1fKB)") + before-size after-size + (- after-size before-size)))))))) + +(defun doom--cli-packages-purge-repo (repo) + (let ((repo-dir (straight--repos-dir repo))) + (delete-directory repo-dir 'recursive) + (ignore-errors + (delete-file (straight--modified-file repo))) + (if (file-directory-p repo-dir) + (ignore (print! (error "Failed to purge repos/%s" repo))) + (print! (success "Purged repos/%s" repo)) + t))) + +(defun doom--cli-packages-purge-repos (repos) + (if (not repos) + (progn (print! (info "No repos to purge")) + 0) + (length + (delq nil (mapcar #'doom--cli-packages-purge-repo repos))))) + +(defun doom--cli-packages-purge-elpa () + (unless (bound-and-true-p package--initialized) + (package-initialize)) + (let ((packages (cl-loop for (package desc) in package-alist + for dir = (package-desc-dir desc) + if (file-in-directory-p dir package-user-dir) + collect (cons package dir)))) + (if (not package-alist) + (progn (print! (info "No ELPA packages to purge")) + 0) + (mapc (doom-rpartial #'delete-directory 'recursive) + (mapcar #'cdr packages)) + (length packages)))) + +(defun doom-cli-packages-purge (&optional elpa-p builds-p repos-p regraft-repos-p) + "Auto-removes orphaned packages and repos. + +An orphaned package is a package that isn't a primary package (i.e. doesn't have +a `package!' declaration) or isn't depended on by another primary package. + +If BUILDS-P, include straight package builds. +If REPOS-P, include straight repos. +If ELPA-P, include packages installed with package.el (M-x package-install)." + (print! (start "Searching for orphaned packages to purge (for the emperor)...")) + (cl-destructuring-bind (&optional builds-to-purge repos-to-purge repos-to-regraft) + (let ((rdirs (straight--directory-files (straight--repos-dir) nil nil 'sort)) + (bdirs (straight--directory-files (straight--build-dir) nil nil 'sort))) + (list (cl-remove-if (doom-rpartial #'gethash straight--profile-cache) + bdirs) + (cl-remove-if (doom-rpartial #'straight--checkhash straight--repo-cache) + rdirs) + (cl-remove-if-not (doom-rpartial #'straight--checkhash straight--repo-cache) + rdirs))) + (let (success) + (print-group! + (if (not builds-p) + (print! (info "Skipping builds")) + (and (/= 0 (doom--cli-packages-purge-builds builds-to-purge)) + (setq success t) + (straight-prune-build-cache))) + (if (not elpa-p) + (print! (info "Skipping elpa packages")) + (and (/= 0 (doom--cli-packages-purge-elpa)) + (setq success t))) + (if (not repos-p) + (print! (info "Skipping repos")) + (and (/= 0 (doom--cli-packages-purge-repos repos-to-purge)) + (setq success t))) + (if (not regraft-repos-p) + (print! (info "Skipping regrafting")) + (print! (start "Regrafting %d repos..." (length repos-to-regraft))) + (and (doom--cli-packages-regraft-repos repos-to-regraft) + (setq success t))) + (when success + (doom--finalize-straight) + t))))) diff --git a/.emacs.d/core/cli/test.el b/.emacs.d/core/cli/test.el new file mode 100644 index 0000000..0d5949a --- /dev/null +++ b/.emacs.d/core/cli/test.el @@ -0,0 +1,104 @@ +;;; core/cli/test.el -*- lexical-binding: t; -*- + +(defun doom--emacs-binary () + (let ((emacs-binary-path (doom-path invocation-directory invocation-name)) + (runemacs-binary-path (if IS-WINDOWS (doom-path invocation-directory "runemacs.exe")))) + (if (and runemacs-binary-path (file-exists-p runemacs-binary-path)) + runemacs-binary-path + emacs-binary-path))) + + +(defcli! test (&rest targets) + "Run Doom unit tests." + :bare t + (doom-initialize 'force 'noerror) + (require 'ansi-color) + (let (files read-files) + (unless targets + (setq targets + (cons doom-core-dir + (cl-remove-if-not + (doom-rpartial #'file-in-directory-p doom-emacs-dir) + ;; Omit `doom-private-dir', which is always first + (let (doom-modules) + (load (expand-file-name "test/init" doom-core-dir) nil t) + (cdr (doom-module-load-path))))))) + (while targets + (let ((target (pop targets))) + ;; FIXME Module targets don't work + (cond ((equal target ":core") + (appendq! files (nreverse (doom-glob doom-core-dir "test/test-*.el")))) + ((file-directory-p target) + (setq target (expand-file-name target)) + (appendq! files (nreverse (doom-glob target "test/test-*.el")))) + ((file-exists-p target) + (push target files))))) + (setenv "DOOMLOCALDIR" (concat doom-local-dir "test/")) + (setenv "DOOMDIR" (concat doom-core-dir "test/")) + (with-temp-buffer + (print! (start "Bootstrapping test environment, if necessary...")) + (cl-destructuring-bind (status . output) + (doom-exec-process + (doom--emacs-binary) + "--batch" + "--eval" + (prin1-to-string + `(progn + (setq user-emacs-directory ,doom-emacs-dir + doom-auto-accept t) + (require 'core ,(locate-library "core")) + (require 'core-cli) + (doom-initialize 'force 'noerror) + (doom-initialize-modules) + (doom-cli-reload-core-autoloads 'force) + (when (doom-cli-packages-install) + (doom-cli-reload-package-autoloads 'force))))) + (unless (zerop status) + (error "Failed to bootstrap unit tests")))) + (with-temp-buffer + (dolist (file files) + (if (doom-file-cookie-p file "if" t) + (cl-destructuring-bind (_status . output) + (apply #'doom-exec-process + (doom--emacs-binary) + "--batch" + "-l" (concat doom-core-dir "core.el") + "-l" (concat doom-core-dir "test/helpers.el") + (append (when (file-in-directory-p file doom-modules-dir) + (list "-f" "doom-initialize-core")) + (list "-l" file + "-f" "buttercup-run"))) + (insert (replace-regexp-in-string ansi-color-control-seq-regexp "" output)) + (push file read-files)) + (print! (info "Ignoring %s" (relpath file))))) + (let ((total 0) + (total-failed 0) + (i 0)) + (print! "\n----------------------------------------\nTests finished") + (print-group! + (goto-char (point-min)) + (while (re-search-forward "^Ran \\([0-9]+\\) specs, \\([0-9]+\\) failed," nil t) + (let ((ran (string-to-number (match-string 1))) + (failed (string-to-number (match-string 2)))) + (when (> failed 0) + (terpri) + (print! (warn "(%s) Failed %d/%d tests") + (path (nth i read-files)) + failed ran) + (save-excursion + (print-group! + (print! + "%s" (string-trim + (buffer-substring + (match-beginning 0) + (dotimes (_ failed (point)) + (search-backward "========================================")))))))) + (cl-incf total ran) + (cl-incf total-failed failed) + (cl-incf i)))) + (terpri) + (if (= total-failed 0) + (print! (success "Ran %d tests successfully." total total-failed)) + (print! (error "Ran %d tests, %d failed") total total-failed) + (kill-emacs 1))) + t))) diff --git a/.emacs.d/core/cli/upgrade.el b/.emacs.d/core/cli/upgrade.el new file mode 100644 index 0000000..54ccb66 --- /dev/null +++ b/.emacs.d/core/cli/upgrade.el @@ -0,0 +1,113 @@ +;;; core/cli/upgrade.el -*- lexical-binding: t; -*- + +(defcli! (upgrade up) + ((force-p ["-f" "--force"])) + "Updates Doom and packages. + +This requires that ~/.emacs.d is a git repo, and is the equivalent of the +following shell commands: + + cd ~/.emacs.d + git pull --rebase + bin/doom clean + bin/doom refresh + bin/doom update" + :bare t + (if (delq + nil (list + (doom-cli-upgrade doom-auto-accept force-p) + (doom-cli-execute "refresh") + (when (doom-cli-packages-update) + (doom-cli-reload-package-autoloads 'force) + t))) + (print! (success "Done! Restart Emacs for changes to take effect.")) + (print! "Nothing to do. Doom is up-to-date!"))) + + +;; +;;; library + +(defvar doom-repo-url "https://github.com/hlissner/doom-emacs" + "The git repo url for Doom Emacs.") +(defvar doom-repo-remote "_upgrade" + "The name to use as our staging remote.") + +(defun doom--working-tree-dirty-p (dir) + (cl-destructuring-bind (success . stdout) + (doom-call-process "git" "status" "--porcelain" "-uno") + (if (= 0 success) + (split-string stdout "\n" t) + (error "Failed to check working tree in %s" dir)))) + + +(defun doom-cli-upgrade (&optional auto-accept-p force-p) + "Upgrade Doom to the latest version non-destructively." + (require 'vc-git) + (let ((default-directory doom-emacs-dir) + process-file-side-effects) + (print! (start "Preparing to upgrade Doom Emacs and its packages...")) + + (let* ((branch (vc-git--symbolic-ref doom-emacs-dir)) + (target-remote (format "%s/%s" doom-repo-remote branch))) + (unless branch + (error! (if (file-exists-p! ".git" doom-emacs-dir) + "Couldn't find Doom's .git directory. Was Doom cloned properly?" + "Couldn't detect what branch you're on. Is Doom detached?"))) + + ;; We assume that a dirty .emacs.d is intentional and abort + (when-let (dirty (doom--working-tree-dirty-p default-directory)) + (if (not force-p) + (user-error! "%s\n\n%s\n\n %s" + (format "Refusing to upgrade because %S has been modified." (path doom-emacs-dir)) + "Either stash/undo your changes or run 'doom upgrade -f' to discard local changes." + (string-join dirty "\n")) + (print! (info "You have local modifications in Doom's source. Discarding them...")) + (doom-call-process "git" "reset" "--hard" (format "origin/%s" branch)) + (doom-call-process "git" "clean" "-ffd"))) + + (doom-call-process "git" "remote" "remove" doom-repo-remote) + (unwind-protect + (let (result) + (or (zerop (car (doom-call-process "git" "remote" "add" doom-repo-remote doom-repo-url))) + (error "Failed to add %s to remotes" doom-repo-remote)) + (or (zerop (car (setq result (doom-call-process "git" "fetch" "--tags" doom-repo-remote branch)))) + (error "Failed to fetch from upstream")) + + (let ((this-rev (vc-git--rev-parse "HEAD")) + (new-rev (vc-git--rev-parse target-remote))) + (cond + ((and (null this-rev) + (null new-rev)) + (error "Failed to get revisions for %s" target-remote)) + + ((equal this-rev new-rev) + (print! (success "Doom is already up-to-date!")) + t) + + ((print! (info "A new version of Doom Emacs is available!\n\n Old revision: %s (%s)\n New revision: %s (%s)\n" + (substring this-rev 0 10) + (cdr (doom-call-process "git" "log" "-1" "--format=%cr" "HEAD")) + (substring new-rev 0 10) + (cdr (doom-call-process "git" "log" "-1" "--format=%cr" target-remote)))) + + (when (and (not auto-accept-p) + (y-or-n-p "View the comparison diff in your browser?")) + (print! (info "Opened github in your browser.")) + (browse-url (format "https://github.com/hlissner/doom-emacs/compare/%s...%s" + this-rev + new-rev))) + + (if (not (or auto-accept-p + (y-or-n-p "Proceed with upgrade?"))) + (ignore (print! (error "Aborted"))) + (print! (start "Upgrading Doom Emacs...")) + (print-group! + (doom-clean-byte-compiled-files) + (if (and (zerop (car (doom-call-process "git" "reset" "--hard" target-remote))) + (equal (vc-git--rev-parse "HEAD") new-rev)) + (print! (info "%s") (cdr result)) + (error "Failed to check out %s" (substring new-rev 0 10))) + (print! (success "Finished upgrading Doom Emacs"))) + t))))) + (ignore-errors + (doom-call-process "git" "remote" "remove" doom-repo-remote)))))) diff --git a/.emacs.d/core/core-cli.el b/.emacs.d/core/core-cli.el new file mode 100644 index 0000000..6e1aac6 --- /dev/null +++ b/.emacs.d/core/core-cli.el @@ -0,0 +1,304 @@ +;;; -*- lexical-binding: t; no-byte-compile: t; -*- + +(require 'seq) + +;; Eagerly load these libraries because we may be in a session that hasn't been +;; fully initialized (e.g. where autoloads files haven't been generated or +;; `load-path' populated). +(load! "autoload/cli") +(load! "autoload/debug") +(load! "autoload/files") +(load! "autoload/format") +(load! "autoload/plist") + + +;; +;;; Variables + +(defvar doom-auto-accept (getenv "YES") + "If non-nil, Doom will auto-accept any confirmation prompts during batch +commands like `doom-cli-packages-install', `doom-cli-packages-update' and +`doom-packages-autoremove'.") + +(defvar doom--cli-p nil) +(defvar doom--cli-commands (make-hash-table :test 'equal)) +(defvar doom--cli-groups (make-hash-table :test 'equal)) +(defvar doom--cli-group nil) + +(cl-defstruct + (doom-cli + (:constructor nil) + (:constructor + make-doom-cli + (name &key desc aliases optlist arglist plist fn + &aux + (optlist + (cl-loop for (symbol options desc) in optlist + for ((_ . options) (_ . params)) + = (seq-group-by #'stringp options) + collect + (make-doom-cli-option :symbol symbol + :flags options + :args params + :desc desc)))))) + (name nil :read-only t) + (desc "TODO") + aliases + optlist + arglist + plist + (fn (lambda (_) (print! "But nobody came!")))) + +(cl-defstruct doom-cli-option + (symbol) + (flags ()) + (args ()) + (desc "TODO")) + +(defun doom--cli-get-option (cli flag) + (cl-find-if (doom-partial #'member flag) + (doom-cli-optlist cli) + :key #'doom-cli-option-flags)) + +(defun doom--cli-process (cli args) + (let* ((args (copy-sequence args)) + (arglist (copy-sequence (doom-cli-arglist cli))) + (expected (or (cl-position-if (doom-rpartial #'memq cl--lambda-list-keywords) + arglist) + (length arglist))) + (got 0) + restvar + rest + alist) + (catch 'done + (while args + (let ((arg (pop args))) + (cond ((eq (car arglist) '&rest) + (setq restvar (cadr arglist) + rest (cons arg args)) + (throw 'done t)) + + ((string-match "^\\(--\\([a-zA-Z0-9][a-zA-Z0-9-_]*\\)\\)\\(?:=\\(.+\\)\\)?$" arg) + (let* ((fullflag (match-string 1 arg)) + (opt (doom--cli-get-option cli fullflag))) + (unless opt + (user-error "Unrecognized switch %S" (concat "--" (match-string 2 arg)))) + (setf (alist-get (doom-cli-option-symbol opt) alist) + (or (if (doom-cli-option-args opt) + (or (match-string 3 arg) + (pop args) + (user-error "%S expected an argument, but got none" + fullflag)) + (if (match-string 3 arg) + (user-error "%S was not expecting an argument, but got %S" + fullflag (match-string 3 arg)) + fullflag)))))) + + ((string-match "^\\(-\\([a-zA-Z0-9]+\\)\\)$" arg) + (let ((fullflag (match-string 1 arg)) + (flag (match-string 2 arg))) + (dolist (switch (split-string flag "" t)) + (if-let (opt (doom--cli-get-option cli (concat "-" switch))) + (setf (alist-get (doom-cli-option-symbol opt) alist) + (if (doom-cli-option-args opt) + (or (pop args) + (user-error "%S expected an argument, but got none" + fullflag)) + fullflag)) + (user-error "Unrecognized switch %S" (concat "-" switch)))))) + + (arglist + (cl-incf got) + (let ((spec (pop arglist))) + (when (eq spec '&optional) + (setq spec (pop arglist))) + (setf (alist-get spec alist) arg)) + (when (null arglist) + (throw 'done t))) + + (t + (push arg args) + (throw 'done t)))))) + (when (< got expected) + (error "Expected %d arguments, got %d" expected got)) + (when rest + (setf (alist-get restvar alist) rest)) + alist)) + +(defun doom-cli-get (command) + "Return a CLI object associated by COMMAND name (string)." + (cond ((null command) nil) + ((doom-cli-p command) command) + ((doom-cli-get + (gethash (cond ((symbolp command) command) + ((stringp command) (intern command)) + (command)) + doom--cli-commands))))) + +(defun doom-cli-internal-p (cli) + "Return non-nil if CLI is an internal (non-public) command." + (string-prefix-p ":" (doom-cli-name cli))) + +(defun doom-cli-execute (command &optional args) + "Execute COMMAND (string) with ARGS (list of strings). + +Executes a cli defined with `defcli!' with the name or alias specified by +COMMAND, and passes ARGS to it." + (if-let (cli (doom-cli-get command)) + (funcall (doom-cli-fn cli) + (doom--cli-process cli args)) + (user-error "Couldn't find any %S command" command))) + +(defmacro defcli! (name speclist &optional docstring &rest body) + "Defines a CLI command. + +COMMAND is a symbol or a list of symbols representing the aliases for this +command. DOCSTRING is a string description; its first line should be short +(under 60 characters), as it will be used as a summary for 'doom help'. + +SPECLIST is a specification for options and arguments, which can be a list +specification for an option/switch in the following format: + + (VAR [FLAGS... ARGS...] DESCRIPTION) + +Otherwise, SPECLIST accepts the same argument specifiers as `defun'. + +BODY will be run when this dispatcher is called." + (declare (indent 2) (doc-string 3)) + (unless (stringp docstring) + (push docstring body) + (setq docstring "TODO")) + (let ((names (doom-enlist name)) + (optlist (cl-remove-if-not #'listp speclist)) + (arglist (cl-remove-if #'listp speclist)) + (plist (cl-loop for (key val) on body by #'cddr + if (keywordp key) + nconc (list key val) into plist + else return plist))) + `(let ((name ',(car names)) + (aliases ',(cdr names)) + (plist ',plist)) + (when doom--cli-group + (setq plist (plist-put plist :group doom--cli-group))) + (puthash + name + (make-doom-cli (symbol-name name) + :desc ,docstring + :aliases (mapcar #'symbol-name aliases) + :arglist ',arglist + :optlist ',optlist + :plist plist + :fn + (lambda (--alist--) + (ignore --alist--) + (let ,(cl-loop for opt in speclist + for optsym = (if (listp opt) (car opt) opt) + unless (memq optsym cl--lambda-list-keywords) + collect (list optsym `(cdr (assq ',optsym --alist--)))) + ,@(unless (plist-get plist :bare) + '((unless doom-init-p + (doom-initialize 'force 'noerror) + (doom-initialize-modules)))) + ,@body))) + doom--cli-commands) + (when aliases + (mapc (doom-rpartial #'puthash name doom--cli-commands) + aliases))))) + +(defmacro defcligroup! (name docstring &rest body) + "Declare all enclosed cli commands are part of the NAME group." + (declare (indent defun) (doc-string 2)) + `(let ((doom--cli-group ,name)) + (puthash doom--cli-group ,docstring doom--cli-groups) + ,@body)) + + +;; +;;; CLI Commands + +(load! "cli/help") +(load! "cli/install") + +(defcligroup! "Maintenance" + "For managing your config and packages" + (defcli! (refresh re sync) + ((if-necessary-p ["-n" "--if-necessary"] "Only regenerate autoloads files if necessary") + (inhibit-envvar-p ["-e"] "Don't regenerate the envvar file") + (prune-p ["-p" "--prune"] "Purge orphaned packages & regraft repos")) + "Ensure Doom is properly set up. + +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." + :bare t + (print! (start "Initiating a refresh of Doom Emacs...")) + (print-group! + (let (success) + (when (and (not inhibit-envvar-p) + (file-exists-p doom-env-file)) + (doom-cli-reload-env-file 'force)) + + ;; Ensures that no pre-existing state pollutes the generation of the new + ;; autoloads files. + (mapc #'doom--cli-delete-autoloads-file + (list doom-autoload-file + doom-package-autoload-file)) + (doom-initialize 'force 'noerror) + (doom-initialize-modules) + + (doom-cli-reload-core-autoloads (not if-necessary-p)) + (unwind-protect + (progn + (and (doom-cli-packages-install) + (setq success t)) + (and (doom-cli-packages-build) + (setq success t)) + (and (doom-cli-packages-purge prune-p 'builds-p prune-p prune-p) + (setq success t))) + (doom-cli-reload-package-autoloads (or success (not if-necessary-p))) + (doom-cli-byte-compile nil 'recompile)) + t))) + + (load! "cli/env") + (load! "cli/upgrade") + (load! "cli/packages") + (load! "cli/autoloads")) + +(defcligroup! "Diagnostics" + "For troubleshooting and diagnostics" + (load! "cli/doctor") + (load! "cli/debug") + (load! "cli/test")) + +(defcligroup! "Compilation" + "For compiling Doom and your config" + (load! "cli/byte-compile")) + +(defcligroup! "Utilities" + "Conveniences for interacting with Doom externally" + (defcli! run () + "Run Doom Emacs from bin/doom's parent directory. + +All arguments are passed on to Emacs. + + 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.") + + ;; (load! "cli/batch") + ;; (load! "cli/org") + ) + +(provide 'core-cli) +;;; core-cli.el ends here diff --git a/.emacs.d/core/core-editor.el b/.emacs.d/core/core-editor.el new file mode 100644 index 0000000..a641245 --- /dev/null +++ b/.emacs.d/core/core-editor.el @@ -0,0 +1,561 @@ +;;; core-editor.el -*- lexical-binding: t; -*- + +(defvar doom-detect-indentation-excluded-modes '(fundamental-mode so-long-mode) + "A list of major modes in which indentation should be automatically +detected.") + +(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-local doom-large-file-p nil) +(put 'doom-large-file-p 'permanent-local t) + +(defvar doom-large-file-size-alist '((t . 1.0)) + "An alist mapping major mode to filesize thresholds. + +If a file is opened and discovered to be larger than the threshold, Doom +performs emergency optimizations to prevent Emacs from hanging, crashing or +becoming unusably slow. + +These thresholds are in MB, and is used by `doom--optimize-for-large-files-a'.") + +(defvar doom-large-file-excluded-modes + '(so-long-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-h' will ignore.") + + +;; +;;; File handling + +(defadvice! doom--optimize-for-large-files-a (orig-fn &rest args) + "Set `doom-large-file-p' if the file is too large. + +Uses `doom-large-file-size-alist' to determine when a file is too large. When +`doom-large-file-p' is set, other plugins can detect this and reduce their +runtime costs (or disable themselves) to ensure the buffer is as fast as +possible." + :around #'after-find-file + (if (setq doom-large-file-p + (and buffer-file-name + (not doom-large-file-p) + (file-readable-p buffer-file-name) + (> (nth 7 (file-attributes buffer-file-name)) + (* 1024 1024 + (cdr (or (assq major-mode doom-large-file-size-alist) + (assq 't doom-large-file-size-alist))))))) + (prog1 (apply orig-fn args) + (if (memq major-mode doom-large-file-excluded-modes) + (setq doom-large-file-p nil) + (so-long-minor-mode +1) + (message "Large file detected! Cutting a few corners to improve performance..."))) + (apply orig-fn args))) + +;; Resolve symlinks when opening files, so that any operations are conducted +;; from the file's true directory (like `find-file'). +(setq find-file-visit-truename t + vc-follow-symlinks t) + +;; Disable the warning "X and Y are the same file". It's fine to ignore this +;; warning as it will redirect you to the existing buffer anyway. +(setq find-file-suppress-same-file-warnings t) + +;; Create missing directories when we open a file that doesn't exist under a +;; directory tree that may not exist. +(add-hook! 'find-file-not-found-functions + (defun doom-create-missing-directories-h () + "Automatically create missing directories when creating new files." + (unless (file-remote-p buffer-file-name) + (let ((parent-directory (file-name-directory buffer-file-name))) + (and (not (file-directory-p parent-directory)) + (y-or-n-p (format "Directory `%s' does not exist! Create it?" + parent-directory)) + (progn (make-directory parent-directory 'parents) + t)))))) + +;; Don't autosave files or create lock/history/backup files. We don't want +;; copies of potentially sensitive material floating around, and we'll rely on +;; git and our own good fortune instead. Fingers crossed! +(setq auto-save-default nil + create-lockfiles nil + make-backup-files nil + ;; But have a place to store them in case we do use them... + auto-save-list-file-name (concat doom-cache-dir "autosave") + backup-directory-alist `(("." . ,(concat doom-cache-dir "backup/")))) + +(add-hook! 'after-save-hook + (defun doom-guess-mode-h () + "Guess mode when saving a file in `fundamental-mode'." + (and (eq major-mode 'fundamental-mode) + (buffer-file-name (buffer-base-buffer)) + (eq (current-buffer) (window-buffer (selected-window))) ; only visible buffers + (set-auto-mode)))) + + +;; +;;; Formatting + +;; Indentation +(setq-default tab-width 4 + tab-always-indent t + indent-tabs-mode nil + fill-column 80) + +;; Word wrapping +(setq-default word-wrap t + truncate-lines t + truncate-partial-width-windows nil) + +(setq sentence-end-double-space nil + delete-trailing-lines nil + require-final-newline t + tabify-regexp "^\t* [ \t]+") ; for :retab + +;; Favor hard-wrapping in text modes +(add-hook 'text-mode-hook #'auto-fill-mode) + + +;; +;;; Clipboard / kill-ring + +;; Eliminate duplicates in the kill ring. That is, if you kill the same thing +;; twice, you won't have to use M-y twice to get past it to older entries in the +;; kill ring. +(setq kill-do-not-save-duplicates t) + +;; +(setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)) + +;; Save clipboard contents into kill-ring before replacing them +(setq save-interprogram-paste-before-kill t) + +;; Fixes the clipboard in tty Emacs by piping clipboard I/O through xclip, xsel, +;; pb{copy,paste}, wl-copy, termux-clipboard-get, or getclip (cygwin). +(add-hook! 'tty-setup-hook + (defun doom-init-clipboard-in-tty-emacs-h () + (and (not (getenv "SSH_CONNECTION")) + (require 'xclip nil t) + (xclip-mode +1)))) + + +;; +;;; Extra file extensions to support + +(push '("/LICENSE\\'" . text-mode) auto-mode-alist) +(push '("\\.log\\'" . text-mode) auto-mode-alist) + + +;; +;;; Built-in plugins + +(use-package! autorevert + ;; revert buffers when their files/state have changed + :hook (focus-in . doom-auto-revert-buffers-h) + :hook (after-save . doom-auto-revert-buffers-h) + :hook (doom-switch-buffer . doom-auto-revert-buffer-h) + :hook (doom-switch-window . doom-auto-revert-buffer-h) + :config + (setq auto-revert-verbose t ; let us know when it happens + auto-revert-use-notify nil + auto-revert-stop-on-user-input nil + ;; Only prompts for confirmation when buffer is unsaved. + revert-without-query (list ".")) + + ;; Instead of using `auto-revert-mode' or `global-auto-revert-mode', we employ + ;; lazy auto reverting on `focus-in-hook' and `doom-switch-buffer-hook'. + ;; + ;; This is because autorevert abuses the heck out of inotify handles which can + ;; grind Emacs to a halt if you do expensive IO (outside of Emacs) on the + ;; files you have open (like compression). We only really need to revert + ;; changes when we switch to a buffer or when we focus the Emacs frame. + (defun doom-auto-revert-buffer-h () + "Auto revert current buffer, if necessary." + (unless (or auto-revert-mode (active-minibuffer-window)) + (auto-revert-handler))) + + (defun doom-auto-revert-buffers-h () + "Auto revert stale buffers in visible windows, if necessary." + (dolist (buf (doom-visible-buffers)) + (with-current-buffer buf + (doom-auto-revert-buffer-h))))) + + +(use-package! recentf + ;; Keep track of recently opened files + :defer-incrementally easymenu tree-widget timer + :after-call after-find-file + :commands recentf-open-files + :config + (defun doom--recent-file-truename (file) + (if (or (file-remote-p file nil t) + (not (file-remote-p file))) + (file-truename file) + file)) + (setq recentf-filename-handlers '(doom--recent-file-truename abbreviate-file-name)) + + (setq recentf-save-file (concat doom-cache-dir "recentf") + recentf-auto-cleanup 'never + recentf-max-menu-items 0 + recentf-max-saved-items 200) + + (add-hook! '(doom-switch-window-hook write-file-functions) + (defun doom--recentf-touch-buffer-h () + "Bump file in recent file list when it is switched or written to." + (when buffer-file-name + (recentf-add-file buffer-file-name)) + ;; Return nil for `write-file-functions' + nil)) + + (add-hook! 'dired-mode-hook + (defun doom--recentf-add-dired-directory-h () + "Add dired directory to recentf file list." + (recentf-add-file default-directory))) + + (when doom-interactive-mode + (add-hook 'kill-emacs-hook #'recentf-cleanup) + (quiet! (recentf-mode +1)))) + + +(use-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) + + (add-hook! 'kill-emacs-hook + (defun doom-unpropertize-kill-ring-h () + "Remove text properties from `kill-ring' for a smaller 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))))) + + +(use-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") + save-place-limit 100) + + (defadvice! doom--recenter-on-load-saveplace-a (&rest _) + "Recenter on cursor when loading a saved place." + :after-while #'save-place-find-file-hook + (if buffer-file-name (ignore-errors (recenter)))) + + (defadvice! doom--dont-prettify-saveplace-cache-a (orig-fn) + "`save-place-alist-to-file' uses `pp' to prettify the contents of its cache. +`pp' can be expensive for longer lists, and there's no reason to prettify cache +files, so we replace calls to `pp' with the much faster `prin1'." + :around #'save-place-alist-to-file + (cl-letf (((symbol-function #'pp) + (symbol-function #'prin1))) + (funcall orig-fn))) + + (save-place-mode +1)) + + +(use-package! server + :when (display-graphic-p) + :after-call pre-command-hook after-find-file focus-out-hook + :init + (when-let (name (getenv "EMACS_SERVER_NAME")) + (setq server-name name)) + :config + (unless (server-running-p) + (server-start))) + + +;; +;;; Packages + +(use-package! better-jumper + :after-call pre-command-hook + :preface + ;; REVIEW Suppress byte-compiler warning spawning a *Compile-Log* buffer at + ;; startup. This can be removed once gilbertw1/better-jumper#2 is merged. + (defvar better-jumper-local-mode nil) + :init + (global-set-key [remap evil-jump-forward] #'better-jumper-jump-forward) + (global-set-key [remap evil-jump-backward] #'better-jumper-jump-backward) + (global-set-key [remap xref-pop-marker-stack] #'better-jumper-jump-backward) + :config + (better-jumper-mode +1) + (add-hook 'better-jumper-post-jump-hook #'recenter) + + (defadvice! doom-set-jump-a (orig-fn &rest args) + "Set a jump point and ensure ORIG-FN doesn't set any new jump points." + (better-jumper-set-jump (if (markerp (car args)) (car args))) + (let ((evil--jumps-jumping t) + (better-jumper--jumping t)) + (apply orig-fn args))) + + (defadvice! doom-set-jump-maybe-a (orig-fn &rest args) + "Set a jump point if ORIG-FN returns non-nil." + (let ((origin (point-marker)) + (result + (let* ((evil--jumps-jumping t) + (better-jumper--jumping t)) + (apply orig-fn args)))) + (unless result + (with-current-buffer (marker-buffer origin) + (better-jumper-set-jump + (if (markerp (car args)) + (car args) + origin)))) + result)) + + (defun doom-set-jump-h () + "Run `better-jumper-set-jump' but return nil, for short-circuiting hooks." + (better-jumper-set-jump) + nil) + + ;; Creates a jump point before killing a buffer. This allows you to undo + ;; killing a buffer easily (only works with file buffers though; it's not + ;; possible to resurrect special buffers). + (advice-add #'kill-current-buffer :around #'doom-set-jump-a) + + ;; Create a jump point before jumping with imenu. + (advice-add #'imenu :around #'doom-set-jump-a)) + + +(use-package! dtrt-indent + ;; Automatic detection of indent settings + :when doom-interactive-mode + :defer t + :init + (add-hook! '(change-major-mode-after-body-hook read-only-mode-hook) + (defun doom-detect-indentation-h () + (unless (or (not after-init-time) + doom-inhibit-indent-detection + doom-large-file-p + (memq major-mode doom-detect-indentation-excluded-modes) + (member (substring (buffer-name) 0 1) '(" " "*"))) + ;; Don't display messages in the echo area, but still log them + (let ((inhibit-message (not doom-debug-mode))) + (dtrt-indent-mode +1))))) + :config + ;; Enable dtrt-indent even in smie modes so that it can update `tab-width', + ;; `standard-indent' and `evil-shift-width' there as well. + (setq dtrt-indent-run-after-smie t) + ;; Reduced from the default of 5000 for slightly faster analysis + (setq dtrt-indent-max-lines 2000) + + ;; always keep tab-width up-to-date + (push '(t tab-width) dtrt-indent-hook-generic-mapping-list) + + (defvar dtrt-indent-run-after-smie) + (defadvice! doom--fix-broken-smie-modes-a (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." + :around #'dtrt-indent-mode + (let ((dtrt-indent-run-after-smie dtrt-indent-run-after-smie)) + (cl-letf* ((old-smie-config-guess (symbol-function 'smie-config-guess)) + (old-smie-config--guess (symbol-function 'symbol-config--guess)) + ((symbol-function 'symbol-config--guess) + (lambda (beg end) + (funcall old-smie-config--guess beg (min end 10000)))) + ((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))))) + + +(use-package! helpful + ;; a better *help* buffer + :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) + + (defun doom-use-helpful-a (orig-fn &rest args) + "Force ORIG-FN to use helpful instead of the old describe-* commands." + (cl-letf (((symbol-function #'describe-function) #'helpful-function) + ((symbol-function #'describe-variable) #'helpful-variable)) + (apply orig-fn args))) + + (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))))))) + + +;;;###package imenu +(add-hook 'imenu-after-jump-hook #'recenter) + + +(use-package! smartparens + ;; 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 + ;; Load default smartparens rules for various languages + (require 'smartparens-config) + + ;; Overlays are too distracting and not terribly helpful. show-parens does + ;; this for us already, so... + (setq sp-highlight-pair-overlay nil + sp-highlight-wrap-overlay nil + sp-highlight-wrap-tag-overlay nil) + ;; But if someone does want overlays enabled, evil users will be stricken with + ;; an off-by-one issue where smartparens assumes you're outside the pair when + ;; you're really at the last character in insert mode. We must correct this + ;; vile injustice. + (setq sp-show-pair-from-inside t) + ;; ...and stay highlighted until we've truly escaped the pair! + (setq sp-cancel-autoskip-on-backward-movement nil) + ;; The default is 100, because smartparen's scans are relatively expensive + ;; (especially with large pair lists for somoe modes), we halve it, as a + ;; better compromise between performance and accuracy. + (setq sp-max-prefix-length 50) + ;; This speeds up smartparens. No pair has any business being longer than 4 + ;; characters; if they must, the modes that need it set it buffer-locally. + (setq sp-max-pair-length 4) + ;; This isn't always smart enough to determine when we're in a string or not. + ;; See https://github.com/Fuco1/smartparens/issues/783. + (setq sp-escape-quotes-after-insert nil) + + ;; Silence some harmless but annoying echo-area spam + (dolist (key '(:unmatched-expression :no-matching-tag)) + (setf (cdr (assq key sp-message-alist)) nil)) + + (add-hook! 'minibuffer-setup-hook + (defun doom-init-smartparens-in-minibuffer-maybe-h () + "Enable `smartparens-mode' in the minibuffer, during `eval-expression' or +`evil-ex'." + (when (memq this-command '(eval-expression evil-ex)) + (smartparens-mode)))) + + ;; You're likely writing lisp in the minibuffer, therefore, disable these + ;; quote pairs, which lisps doesn't use for strings: + (sp-local-pair 'minibuffer-inactive-mode "'" nil :actions nil) + (sp-local-pair 'minibuffer-inactive-mode "`" nil :actions nil) + + ;; Smartparens breaks evil-mode's replace state + (defvar doom-buffer-smartparens-mode nil) + (add-hook! 'evil-replace-state-exit-hook + (defun doom-enable-smartparens-mode-maybe-h () + (when doom-buffer-smartparens-mode + (turn-on-smartparens-mode) + (kill-local-variable 'doom-buffer-smartparens-mode)))) + (add-hook! 'evil-replace-state-entry-hook + (defun doom-disable-smartparens-mode-maybe-h () + (when smartparens-mode + (setq-local doom-buffer-smartparens-mode t) + (turn-off-smartparens-mode)))) + + (smartparens-global-mode +1)) + + +(use-package! so-long + :after-call after-find-file + :config + (when doom-interactive-mode + (global-so-long-mode +1)) + ;; Don't disable syntax highlighting and line numbers, or make the buffer + ;; read-only, in `so-long-minor-mode', so we can have a basic editing + ;; experience in them, at least. It will remain off in `so-long-mode', + ;; however, because long files have a far bigger impact on Emacs performance. + (delq! 'font-lock-mode so-long-minor-modes) + (delq! 'display-line-numbers-mode so-long-minor-modes) + (delq! 'buffer-read-only so-long-variable-overrides 'assq) + ;; ...but at least reduce the level of syntax highlighting + (add-to-list 'so-long-variable-overrides '(font-lock-maximum-decoration . 1)) + ;; Text files could possibly be too long too + (add-to-list 'so-long-target-modes 'text-mode) + ;; But disable everything else that may be unnecessary/expensive for large + ;; or wide buffers. + (appendq! so-long-minor-modes + '(flycheck-mode + flyspell-mode + eldoc-mode + smartparens-mode + highlight-numbers-mode + better-jumper-local-mode + ws-butler-mode + auto-composition-mode + undo-tree-mode + highlight-indent-guides-mode + hl-fill-column-mode)) + ;; HACK Fix #2183: `so-long-detected-long-line-p' tries to parse comment + ;; syntax, but in some buffers comment state isn't initialized, leading + ;; to a wrong-type-argument: stringp error. + (defun doom-buffer-has-long-lines-p () + (when (bound-and-true-p comment-use-syntax) + (so-long-detected-long-line-p))) + (setq so-long-predicate #'doom-buffer-has-long-lines-p)) + + +(use-package! undo-tree + ;; Branching & persistent undo + :after-call doom-switch-buffer-hook after-find-file + :config + (setq undo-tree-auto-save-history t + ;; Increase undo-limits by a factor of ten to avoid emacs prematurely + ;; truncating the undo history and corrupting the tree. See + ;; https://github.com/syl20bnr/spacemacs/issues/12110 + undo-limit 800000 + undo-strong-limit 12000000 + undo-outer-limit 120000000 + undo-tree-history-directory-alist + `(("." . ,(concat doom-cache-dir "undo-tree-hist/")))) + + ;; Compress undo-tree history files with zstd, if available. File size isn't + ;; the (only) concern here: the file IO barrier is slow for Emacs to cross; + ;; reading a tiny file and piping it in-memory through zstd is *slightly* + ;; faster than Emacs reading the entire undo-tree file from the get go (on + ;; SSDs). Whether or not that's true in practice, we still enjoy zstd's ~80% + ;; file savings (these files add up over time and zstd is so incredibly fast). + (when (executable-find "zstd") + (defadvice! doom--undo-tree-make-history-save-file-name-a (file) + :filter-return #'undo-tree-make-history-save-file-name + (concat file ".zst"))) + + ;; Strip text properties from undo-tree data to stave off bloat. File size + ;; isn't the concern here; undo cache files bloat easily, which can cause + ;; freezing, crashes, GC-induced stuttering or delays when opening files. + (defadvice! doom--undo-tree-strip-text-properties-a (&rest _) + :before #'undo-list-transfer-to-tree + (dolist (item buffer-undo-list) + (and (consp item) + (stringp (car item)) + (setcar item (substring-no-properties (car item)))))) + + (global-undo-tree-mode +1)) + + +(use-package! ws-butler + ;; a less intrusive `delete-trailing-whitespaces' on save + :after-call after-find-file + :config + (appendq! 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 diff --git a/.emacs.d/core/core-keybinds.el b/.emacs.d/core/core-keybinds.el new file mode 100644 index 0000000..a79258a --- /dev/null +++ b/.emacs.d/core/core-keybinds.el @@ -0,0 +1,425 @@ +;;; core-keybinds.el -*- lexical-binding: t; -*- + +;; 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!' are ignored (i.e. omitted +;; entirely for performance reasons). + +(defvar doom-leader-key "SPC" + "The leader prefix key for Evil users.") + +(defvar doom-leader-alt-key "M-SPC" + "An alternative leader prefix key, used for Insert and Emacs states, and for +non-evil users.") + +(defvar doom-localleader-key "SPC m" + "The localleader prefix key, for major-mode specific commands.") + +(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.") + +(defvar doom-leader-map (make-sparse-keymap) + "An overriding keymap for keys.") + + +;; +;;; Keybind settings + +(when IS-MAC + (setq mac-command-modifier 'super + mac-option-modifier 'meta)) + + +;; +;;; 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 when C-g is pressed (or ESC in normal mode, for evil users). + +More specifically, when `doom/escape' is pressed. 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 + +(use-package general + :init + ;; Convenience aliases + (defalias 'define-key! #'general-def) + (defalias 'unmap! #'general-unbind)) + +;; HACK `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 (cadr (memq :which-key udef))) + (prependq! + wkforms `((which-key-add-key-based-replacements + (general--concat t doom-leader-alt-key ,key) + ,desc) + (which-key-add-key-based-replacements + (general--concat t doom-leader-key ,key) + ,desc)))))))) + (macroexp-progn + (cons `(after! which-key ,@(nreverse wkforms)) + (nreverse forms))))) + +(defmacro define-leader-key! (&rest args) + "Define 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 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 insert) + :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. +(add-hook! 'doom-after-init-modules-hook + (defun doom-init-leader-keys-h () + "Bind `doom-leader-key' and `doom-leader-alt-key'." + (let ((map general-override-mode-map)) + (if (not (featurep 'evil)) + (progn + (cond ((equal doom-leader-alt-key "C-c") + (set-keymap-parent doom-leader-map mode-specific-map)) + ((equal doom-leader-alt-key "C-x") + (set-keymap-parent doom-leader-map ctl-x-map))) + (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)))) + + +;; +;;; Packages + +(use-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-add-key-based-replacements doom-leader-key "") + (which-key-add-key-based-replacements doom-localleader-key "") + + (which-key-mode +1)) + + +;; +;;; `map!' macro + +(defvar doom-evil-state-alist + '((?n . normal) + (?v . visual) + (?i . insert) + (?e . emacs) + (?o . operator) + (?m . motion) + (?r . replace) + (?g . global)) + "A list of cons cells that map a letter to a evil state symbol.") + +(defun doom--map-keyword-to-states (keyword) + "Convert a KEYWORD into a list of evil state symbols. + +For example, :nvi will map to (list 'normal 'visual 'insert). See +`doom-evil-state-alist' to customize this." + (cl-loop for l across (substring (symbol-name keyword) 1) + if (cdr (assq l doom-evil-state-alist)) collect it + else do (error "not a valid state: %s" l))) + + +;; 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 :localleader 'lisp-indent-function 'defun) +(put :map 'lisp-indent-function 'defun) +(put :mode 'lisp-indent-function 'defun) +(put :prefix 'lisp-indent-function 'defun) +(put :prefix-map 'lisp-indent-function 'defun) + +;; specials +(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))) + (:map + (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-map + (cl-destructuring-bind (prefix . desc) + (doom-enlist (pop rest)) + (let ((keymap (intern (format "doom-leader-%s-map" desc)))) + (setq rest + (append (list :desc desc prefix keymap + :prefix prefix) + rest)) + (push `(defvar ,keymap (make-sparse-keymap)) + doom--map-forms)))) + (: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--map-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 convenience macro for defining keybinds, powered by `general'. + +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 + :g global (binds the key without evil `current-global-map') + + 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'). + +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) + :prefix [PREFIX] [...] set keybind prefix for following keys. PREFIX + can be a cons cell: (PREFIX . DESCRIPTION) + :prefix-map [PREFIX] [...] same as :prefix, but defines a prefix keymap + where the following keys will be bound. DO NOT + USE THIS IN YOUR PRIVATE CONFIG. + :after [FEATURE] [...] apply keybinds when [FEATURE] loads + :textobj KEY INNER-FN OUTER-FN define a text object keybind pair + :when [CONDITION] [...] + :unless [CONDITION] [...] + + Any of the above properties may be nested, so that they only apply to a + certain group of keybinds." + (doom--map-process rest)) + +(provide 'core-keybinds) +;;; core-keybinds.el ends here diff --git a/.emacs.d/core/core-lib.el b/.emacs.d/core/core-lib.el new file mode 100644 index 0000000..f2dfa95 --- /dev/null +++ b/.emacs.d/core/core-lib.el @@ -0,0 +1,428 @@ +;;; core-lib.el -*- lexical-binding: t; -*- + +(require 'cl-lib) +(require 'subr-x) + +;; +;;; Helpers + +(defun doom--resolve-hook-forms (hooks) + "Converts a list of modes into a list of hook symbols. + +If a mode is quoted, it is left as is. If the entire HOOKS list is quoted, the +list is returned as-is." + (declare (pure t) (side-effect-free t)) + (let ((hook-list (doom-enlist (doom-unquote hooks)))) + (if (eq (car-safe hooks) 'quote) + hook-list + (cl-loop for hook in hook-list + if (eq (car-safe hook) 'quote) + collect (cadr hook) + else collect (intern (format "%s-hook" (symbol-name hook))))))) + +(defun doom--setq-hook-fns (hooks rest &optional singles) + (unless (or singles (= 0 (% (length rest) 2))) + (signal 'wrong-number-of-arguments (list #'evenp (length rest)))) + (cl-loop with vars = (let ((args rest) + vars) + (while args + (push (if singles + (list (pop args)) + (cons (pop args) (pop args))) + vars)) + (nreverse vars)) + for hook in (doom--resolve-hook-forms hooks) + for mode = (string-remove-suffix "-hook" (symbol-name hook)) + append + (cl-loop for (var . val) in vars + collect + (list var val hook + (intern (format "doom--setq-%s-for-%s-h" + var mode)))))) + + +;; +;;; 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-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))) + +(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 keyword keyword) + (substring (symbol-name keyword) 1)) + +(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) + (when (bound-and-true-p doom--current-module) + (propertize + (format "[%s/%s] " + (doom-keyword-name (car doom--current-module)) + (cdr doom--current-module)) + 'face 'warning)) + format-string) + ,@args)))) + +(defalias 'doom-partial #'apply-partially) + +(defun doom-rpartial (fn &rest args) + "Return a function that is a partial application of FUN to right-hand ARGS. + +ARGS is a list of the last N arguments to pass to FUN. The result is a new +function which does the same as FUN, except that the last N arguments are fixed +at the values with which this function was called." + (lambda (&rest pre-args) + (apply fn (append pre-args args)))) + + +;; +;;; Sugars + +(defmacro λ! (&rest body) + "Expands to (lambda () (interactive) ,@body). +A factory for quickly producing interaction commands, particularly for keybinds +or aliases." + (declare (doc-string 1)) + `(lambda () (interactive) ,@body)) +(defalias 'lambda! 'λ!) + +(defun λ!! (command &optional arg) + "Expands to a command that interactively calls COMMAND with prefix ARG. +A factory for quickly producing interactive, prefixed commands for keybinds or +aliases." + (declare (doc-string 1)) + (lambda () (interactive) + (let ((current-prefix-arg arg)) + (call-interactively command)))) +(defalias 'lambda!! 'λ!!) + +(defun file! () + "Return the emacs lisp file this macro is called from." + (cond ((bound-and-true-p byte-compile-current-file)) + (load-file-name) + ((stringp (car-safe current-load-list)) + (car current-load-list)) + (buffer-file-name) + ((error "Cannot get this file-path")))) + +(defun dir! () + "Returns the directory of the emacs lisp file this macro is called from." + (when-let (path (file!)) + (directory-file-name (file-name-directory path)))) + +(defmacro setq! (&rest settings) + "A stripped-down `customize-set-variable' with the syntax of `setq'." + (macroexp-progn + (cl-loop for (var val) on settings by 'cddr + collect `(funcall (or (get ',var 'custom-set) #'set) + ',var ,val)))) + +(defmacro pushnew! (place &rest values) + "Push VALUES sequentially into PLACE, if they aren't already present. +This is a variadic `cl-pushnew'." + (let ((var (make-symbol "result"))) + `(dolist (,var (list ,@values) (with-no-warnings ,place)) + (cl-pushnew ,var ,place :test #'equal)))) + +(defmacro prependq! (sym &rest lists) + "Prepend LISTS to SYM in place." + `(setq ,sym (append ,@lists ,sym))) + +(defmacro appendq! (sym &rest lists) + "Append LISTS to SYM in place." + `(setq ,sym (append ,sym ,@lists))) + +(defmacro nconcq! (sym &rest lists) + "Append LISTS to SYM by altering them in place." + `(setq ,sym (nconc ,sym ,@lists))) + +(defmacro delq! (elt list &optional fetcher) + "`delq' ELT from LIST in-place. + +If FETCHER is a function, ELT is used as the key in LIST (an alist)." + `(setq ,list + (delq ,(if fetcher + `(funcall ,fetcher ,elt ,list) + elt) + ,list))) + +(defmacro letenv! (envvars &rest body) + "Lexically bind ENVVARS in BODY, like `let' but for `process-environment'." + `(let ((process-environment (copy-sequence process-environment))) + (dolist (var ',envvars) + (setenv (car var) (cadr var))) + ,@body)) + +(defmacro add-load-path! (&rest dirs) + "Add DIRS to `load-path', relative to the current file. +The current file is the file from which `add-to-load-path!' is used." + `(let ((default-directory ,(dir!)) + file-name-handler-alist) + (dolist (dir (list ,@dirs)) + (cl-pushnew (expand-file-name dir) load-path)))) + +(defmacro add-transient-hook! (hook-or-function &rest forms) + "Attaches a self-removing function to HOOK-OR-FUNCTION. + +FORMS are evaluated once, when that function/hook is first invoked, then never +again. + +HOOK-OR-FUNCTION can be a quoted hook or a sharp-quoted function (which will be +advised)." + (declare (indent 1)) + (let ((append (if (eq (car forms) :after) (pop forms))) + (fn (intern (format "doom--transient-%s-h" (sxhash hook-or-function))))) + `(let ((sym ,hook-or-function)) + (defun ,fn (&rest _) + ,@forms + (let ((sym ,hook-or-function)) + (cond ((functionp sym) (advice-remove sym #',fn)) + ((symbolp sym) (remove-hook sym #',fn)))) + (unintern ',fn nil)) + (cond ((functionp sym) + (advice-add ,hook-or-function ,(if append :after :before) #',fn)) + ((symbolp sym) + (put ',fn 'permanent-local-hook t) + (add-hook sym #',fn ,append)))))) + +(defmacro add-hook! (hooks &rest rest) + "A convenience macro for adding N functions to M hooks. + +If N and M = 1, there's no benefit to using this macro over `add-hook'. + +This macro accepts, in order: + + 1. Optional properties :local and/or :append, which will make the hook + buffer-local or append to the list of hooks (respectively), + 2. The hook(s) to be added to: either an unquoted mode, an unquoted list of + modes, a quoted hook variable or a quoted list of hook variables. If + unquoted, '-hook' will be appended to each symbol. + 3. The function(s) to be added: this can be one function, a list thereof, a + list of `defun's, or body forms (implicitly wrapped in a closure). + +\(fn HOOKS [:append :local] FUNCTIONS)" + (declare (indent (lambda (indent-point state) + (goto-char indent-point) + (when (looking-at-p "\\s-*(") + (lisp-indent-defform state indent-point)))) + (debug t)) + (let* ((hook-forms (doom--resolve-hook-forms hooks)) + (func-forms ()) + (defn-forms ()) + append-p + local-p + remove-p + forms) + (while (keywordp (car rest)) + (pcase (pop rest) + (:append (setq append-p t)) + (:local (setq local-p t)) + (:remove (setq remove-p t)))) + (let ((first (car-safe (car rest)))) + (cond ((null first) + (setq func-forms rest)) + + ((eq first 'defun) + (setq func-forms (mapcar #'cadr rest) + defn-forms rest)) + + ((memq first '(quote function)) + (setq func-forms + (if (cdr rest) + (mapcar #'doom-unquote rest) + (doom-enlist (doom-unquote (car rest)))))) + + ((setq func-forms (list `(lambda (&rest _) ,@rest))))) + (dolist (hook hook-forms) + (dolist (func func-forms) + (push (if remove-p + `(remove-hook ',hook #',func ,local-p) + `(add-hook ',hook #',func ,append-p ,local-p)) + forms))) + (macroexp-progn + (append defn-forms + (if append-p + (nreverse forms) + forms)))))) + +(defmacro remove-hook! (hooks &rest rest) + "A convenience macro for removing N functions from M hooks. + +Takes the same arguments as `add-hook!'. + +If N and M = 1, there's no benefit to using this macro over `remove-hook'. + +\(fn HOOKS [:append :local] FUNCTIONS)" + (declare (indent defun) (debug t)) + `(add-hook! ,hooks :remove ,@rest)) + +(defmacro setq-hook! (hooks &rest var-vals) + "Sets buffer-local variables on HOOKS. + +\(fn HOOKS &rest [SYM VAL]...)" + (declare (indent 1)) + (macroexp-progn + (cl-loop for (var val hook fn) in (doom--setq-hook-fns hooks var-vals) + collect `(defun ,fn (&rest _) + ,(format "%s = %s" var (pp-to-string val)) + (setq-local ,var ,val)) + collect `(remove-hook ',hook #',fn) ; ensure set order + collect `(add-hook ',hook #',fn)))) + +(defmacro unsetq-hook! (hooks &rest vars) + "Unbind setq hooks on HOOKS for VARS. + +\(fn HOOKS &rest [SYM VAL]...)" + (declare (indent 1)) + (macroexp-progn + (cl-loop for (_var _val hook fn) + in (doom--setq-hook-fns hooks vars 'singles) + collect `(remove-hook ',hook #',fn)))) + +(defmacro load! (filename &optional path noerror) + "Load a file relative to the current executing file (`load-file-name'). + +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." + (let* ((path (or path + (dir!) + (error "Could not detect path to look for '%s' in" + filename))) + (file (if path + `(expand-file-name ,filename ,path) + filename))) + `(condition-case-unless-debug e + (let (file-name-handler-alist) + (load ,file ,noerror 'nomessage)) + (doom-error (signal (car e) (cdr e))) + (error + (let* ((source (file-name-sans-extension ,file)) + (err (cond ((not (featurep 'core)) + (cons 'error (file-name-directory path))) + ((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))))))) + +(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 ,condition + (progn ,@body) + ,(let ((fn (intern (format "doom--delay-form-%s-h" (sxhash (cons condition body)))))) + `(progn + (fset ',fn (lambda (&rest args) + (when ,(or condition t) + (remove-hook 'after-load-functions #',fn) + (unintern ',fn nil) + (ignore args) + ,@body))) + (put ',fn 'permanent-local-hook t) + (add-hook 'after-load-functions #',fn))))) + +(defmacro defer-feature! (feature &optional fn) + "Pretend FEATURE hasn't been loaded yet, until FEATURE-hook or FN runs. + +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 FN 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-a" feature))) + (fn (or fn feature))) + `(progn + (setq features (delq ',feature features)) + (advice-add #',fn :before #',advice-fn) + (defun ,advice-fn (&rest _) + ;; Some plugins (like yasnippet) will invoke a fn early to parse + ;; code, which would prematurely trigger this. In those cases, well + ;; behaved plugins will use `delay-mode-hooks', which we can check for: + (when (and ,(intern (format "%s-hook" fn)) + (not delay-mode-hooks)) + ;; ...Otherwise, announce to the world this package has been loaded, + ;; so `after!' handlers can react. + (provide ',feature) + (advice-remove #',fn #',advice-fn)))))) + +(defmacro quiet! (&rest forms) + "Run FORMS without generating any output. + +This silences calls to `message', `load-file', `write-region' and anything that +writes to `standard-output'." + `(cond (doom-debug-mode ,@forms) + ((not doom-interactive-mode) + (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))) + ((let ((inhibit-message t) + (save-silently t)) + (prog1 ,@forms (message "")))))) + + +;; +;;; Definers + +(defmacro defadvice! (symbol arglist &optional docstring &rest body) + "Define an advice called SYMBOL and add it to PLACES. + +ARGLIST is as in `defun'. WHERE is a keyword as passed to `advice-add', and +PLACE is the function to which to add the advice, like in `advice-add'. +DOCSTRING and BODY are as in `defun'. + +\(fn SYMBOL ARGLIST &optional DOCSTRING &rest [WHERE PLACES...] BODY\)" + (declare (doc-string 3) (indent defun)) + (unless (stringp docstring) + (push docstring body) + (setq docstring nil)) + (let (where-alist) + (while (keywordp (car body)) + (push `(cons ,(pop body) (doom-enlist ,(pop body))) + where-alist)) + `(progn + (defun ,symbol ,arglist ,docstring ,@body) + ,(when where-alist + `(dolist (targets (list ,@(nreverse where-alist))) + (dolist (target (cdr targets)) + (advice-add target (car targets) #',symbol))))))) + +(provide 'core-lib) +;;; core-lib.el ends here diff --git a/.emacs.d/core/core-modules.el b/.emacs.d/core/core-modules.el new file mode 100644 index 0000000..dec2c65 --- /dev/null +++ b/.emacs.d/core/core-modules.el @@ -0,0 +1,603 @@ +;;; 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.") + +(defconst doom-obsolete-modules + '((:feature (version-control (:emacs vc) (:ui vc-gutter)) + (spellcheck (:tools flyspell)) + (syntax-checker (:tools flycheck)) + (evil (:editor evil)) + (snippets (:editor snippets)) + (file-templates (:editor file-templates)) + (workspaces (:ui workspaces)) + (eval (:tools eval)) + (lookup (:tools lookup)) + (debugger (:tools debugger))) + (:tools (rotate-text (:editor rotate-text)) + (vterm (:term vterm)) + (password-store (:tools pass))) + (:emacs (electric-indent (:emacs electric)) + (hideshow (:editor fold)) + (eshell (:term eshell)) + (term (:term term))) + (:ui (doom-modeline (:ui modeline)) + (fci (:ui fill-column)) + (evil-goggles (:ui ophints)) + (tabbar (:ui tabs))) + (:app (email (:email mu4e)) + (notmuch (:email notmuch)))) + "A tree alist that maps deprecated modules to their replacement(s). + +Each entry is a three-level tree. For example: + + (:feature (version-control (:emacs vc) (:ui vc-gutter)) + (spellcheck (:tools flyspell)) + (syntax-checker (:tools flycheck))) + +This marks :feature version-control, :feature spellcheck and :feature +syntax-checker modules obsolete. e.g. If :feature version-control is found in +your `doom!' block, a warning is emitted before replacing it with :emacs vc and +:ui vc-gutter.") + +(defvar doom-inhibit-module-warnings doom-interactive-mode + "If non-nil, don't emit deprecated or missing module warnings at startup.") + +;;; 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) + +(defvar doom--current-module nil) +(defvar doom--current-flags nil) + + +;; +;;; 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 (or force-p (not doom-init-modules-p)) + (setq doom-init-modules-p t + doom-modules nil) + (when (load! "init" doom-private-dir t) + (when doom-modules + (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) + (when doom-interactive-mode + (when doom-modules + (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))))) + + +;; +;;; Module API + +(defun doom-module-p (category module &optional flag) + "Returns t if CATEGORY MODULE is enabled (ie. present in `doom-modules')." + (declare (pure t) (side-effect-free t)) + (let ((plist (gethash (cons category module) doom-modules))) + (and plist + (or (null flag) + (memq flag (plist-get plist :flags))) + 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))) + (if file + (let (file-name-handler-alist) + (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 enabled-only) + "Returns a cons cell (CATEGORY . MODULE) derived from PATH (a file path). +If ENABLED-ONLY, return nil if the containing module isn't enabled." + (if (null path) + (if doom--current-module + (if enabled-only + (and (doom-module-p (car doom--current-module) + (cdr doom--current-module)) + doom--current-module) + doom--current-module) + (ignore-errors + (doom-module-from-path (file!)))) + (let* ((file-name-handler-alist nil) + (path (file-truename (or path (file!))))) + (save-match-data + (cond ((string-match "/modules/\\([^/]+\\)/\\([^/]+\\)\\(?:/.*\\)?$" path) + (when-let* ((category (doom-keyword-intern (match-string 1 path))) + (module (intern (match-string 2 path)))) + (and (or (null enabled-only) + (doom-module-p category module)) + (cons category module)))) + ((file-in-directory-p path doom-core-dir) + (cons :core (intern (file-name-base path)))) + ((file-in-directory-p path doom-private-dir) + (cons :private (intern (file-name-base path))))))))) + +(defun doom-module-load-path (&optional module-dirs) + "Return a list of file paths to activated modules. + +The list is in no particular order and its file paths are absolute. If +MODULE-DIRS is non-nil, include all modules (even disabled ones) available in +those directories. The first returned path is always `doom-private-dir'." + (declare (pure t) (side-effect-free t)) + (append (list doom-private-dir) + (if module-dirs + (doom-files-in (if (listp module-dirs) + module-dirs + doom-modules-dirs) + :type 'dirs + :mindepth 1 + :depth 1) + (cl-loop for plist being the hash-values of (doom-modules) + collect (plist-get plist :path))) + nil)) + +(defun doom-modules (&optional refresh-p) + "Minimally initialize `doom-modules' (a hash table) and return it. +This value is cached. If REFRESH-P, then don't use the cached value." + (or (unless refresh-p doom-modules) + (let (doom-interactive-mode + 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 + +(eval-and-compile + (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 doom-interactive-mode)) + +(defvar doom--deferred-packages-alist '(t)) + +(with-eval-after-load 'use-package-core + ;; Macros are already fontified, no need for this + (font-lock-remove-keywords 'emacs-lisp-mode use-package-font-lock-keywords) + + ;; We define :minor and :magic-minor from the `auto-minor-mode' package here + ;; so we don't have to load `auto-minor-mode' so early. + (dolist (keyword '(:minor :magic-minor)) + (setq use-package-keywords + (use-package-list-insert keyword use-package-keywords :commands))) + + (defalias 'use-package-normalize/:minor #'use-package-normalize-mode) + (defun use-package-handler/:minor (name _ arg rest state) + (use-package-handle-mode name 'auto-minor-mode-alist arg rest state)) + + (defalias 'use-package-normalize/:magic-minor #'use-package-normalize-mode) + (defun use-package-handler/:magic-minor (name _ arg rest state) + (use-package-handle-mode name 'auto-minor-mode-magic-alist arg rest state)) + + ;; Adds two keywords to `use-package' to expand its lazy-loading capabilities: + ;; + ;; :after-call SYMBOL|LIST + ;; :defer-incrementally SYMBOL|LIST|t + ;; + ;; Check out `use-package!'s documentation for more about these two. + (dolist (keyword '(:defer-incrementally :after-call)) + (push keyword use-package-deferring-keywords) + (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 (make-symbol (format "doom--after-call-%s-h" name)))) + (use-package-concat + `((fset ',fn + (lambda (&rest _) + (doom-log "Loading deferred package %s from %s" ',name ',fn) + (condition-case e + ;; If `default-directory' is a directory that doesn't + ;; exist or is unreadable, Emacs throws up file-missing + ;; errors, so we set it to a directory we know exists and + ;; is readable. + (let ((default-directory doom-emacs-dir)) + (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)) + (advice-remove hook #',fn) + (remove-hook hook #',fn)) + (delq! deferral-list doom--deferred-packages-alist) + (unintern ',fn nil))))) + (let (forms) + (dolist (hook hooks forms) + (push (if (string-match-p "-\\(?:functions\\|hook\\)$" (symbol-name hook)) + `(add-hook ',hook #',fn) + `(advice-add #',hook :before #',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 + +(put :if 'lisp-indent-function 2) +(put :when 'lisp-indent-function 'defun) +(put :unless 'lisp-indent-function 'defun) + +(defmacro doom! (&rest modules) + "Bootstraps DOOM Emacs and its modules. + +If the first item in MODULES doesn't satisfy `keywordp', MODULES is evaluated, +otherwise, MODULES is a multiple-property list (a plist where each key can have +multiple, linear values). + +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). + +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)." + `(let ((modules + ,@(if (keywordp (car modules)) + (list (list 'quote modules)) + modules))) + (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) + obsolete category m) + (while modules + (setq m (pop modules)) + (cond ((keywordp m) + (setq category m + obsolete (assq m doom-obsolete-modules))) + ((not category) + (error "No module category specified for %s" m)) + ((and (listp m) (keywordp (car m))) + (pcase (car m) + (:cond + (cl-loop for (cond . mods) in (cdr m) + if (eval cond t) + return (prependq! modules mods))) + (:if (if (eval (cadr m) t) + (push (caddr m) modules) + (prependq! modules (cdddr m)))) + (fn (if (or (eval (cadr m) t) + (eq fn :unless)) + (prependq! modules (cddr m)))))) + ((catch 'doom-modules + (let* ((module (if (listp m) (car m) m)) + (flags (if (listp m) (cdr m)))) + (when-let (new (assq module obsolete)) + (let ((newkeys (cdr new))) + (if (null newkeys) + (message "WARNING %s module was removed" key) + (if (cdr newkeys) + (message "WARNING %s module was removed and split into the %s modules" + (list category module) (mapconcat #'prin1-to-string newkeys ", ")) + (message "WARNING %s module was moved to %s" + (list category module) (car 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))))))) + (unless doom-interactive-mode + (setq doom-inhibit-module-warnings t)) + doom-modules))) + +(defvar doom-disabled-packages) +(defmacro use-package! (name &rest plist) + "Declares and configures a package. + +This is a thin wrapper around `use-package', and is ignored if the NAME package +is disabled by the user (with `package!'). + +See `use-package' to see what properties can be provided. Doom adds support for +two extra properties: + +: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. + +: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. + + NAME is implicitly added if this property is present and non-nil. No need to + specify it. A value of `t' implies NAME." + (declare (indent 1)) + (unless (or (memq name doom-disabled-packages) + ;; At compile-time, use-package will forcibly load packages to + ;; prevent compile-time errors. However, if a Doom user has + ;; disabled packages you get file-missing package errors, so it's + ;; necessary to check for packages at compile time: + (and (bound-and-true-p byte-compile-current-file) + (not (locate-library (symbol-name name))))) + `(use-package ,name ,@plist))) + +(defmacro use-package-hook! (package when &rest body) + "Reconfigures a package's `use-package!' block. + +This macro must be used *before* PACKAGE's `use-package!' block. Often, this +means using it from your DOOMDIR/init.el. + +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 + +WARNINGS: +- The use of this macro is more often than not a code smell. Use it as last + resort. There is almost always a better alternative. +- If you are using this solely for :post-config, stop! `after!' is much better. +- If :pre-init or :pre-config hooks return nil, the original `use-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)) + (unless (memq when '(:pre-init :post-init :pre-config :post-config)) + (error "'%s' isn't a valid hook for use-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))) + (lambda () ,@body) + 'append))) + +(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 (or ,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) + (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 (doom-module-from-path))) + (unless module + (error "(featurep! %s %s %s) couldn't figure out what module it was called from (in %s)" + category module flag (file!))) + (memq category (doom-module-get (car module) (cdr module) :flags))))) + t)) + +(defmacro after! (package &rest body) + "Evaluate BODY after PACKAGE have loaded. + +PACKAGE is a symbol or list of them. These are package names, not modes, +functions or variables. It can be: + +- An unquoted package symbol (the name of a package) + (after! helm BODY...) +- An unquoted list of package symbols (i.e. BODY is evaluated once both magit + and git-gutter have loaded) + (after! (magit git-gutter) BODY...) +- An unquoted, nested list of compound package lists, using any combination of + :or/:any and :and/:all + (after! (:or package-a package-b ...) BODY...) + (after! (:and package-a package-b ...) BODY...) + (after! (:and package-a (:or package-b package-c) ...) BODY...) + Without :or/:any/:and/:all, :and/:all are implied. + +This is a wrapper around `eval-after-load' that: + +1. Suppresses warnings for disabled packages at compile-time +2. No-ops for package that are disabled by the user (via `package!') +3. Supports compound package statements (see below) +4. Prevents eager expansion pulling in autoloaded macros all at once" + (declare (indent defun) (debug t)) + (if (symbolp package) + (unless (memq package (bound-and-true-p doom-disabled-packages)) + (list (if (or (not (bound-and-true-p byte-compile-current-file)) + (require package nil 'noerror)) + #'progn + #'with-no-warnings) + (let ((body (macroexp-progn body))) + `(if (featurep ',package) + ,body + ;; We intentionally avoid `with-eval-after-load' to prevent + ;; eager macro expansion from pulling (or failing to pull) in + ;; autoloaded macros/packages. + (eval-after-load ',package ',body))))) + (let ((p (car package))) + (cond ((not (keywordp p)) + `(after! (:and ,@package) ,@body)) + ((memq p '(:or :any)) + (macroexp-progn + (cl-loop for next in (cdr package) + collect `(after! ,next ,@body)))) + ((memq p '(:and :all)) + (dolist (next (cdr package)) + (setq body `((after! ,next ,@body)))) + (car body)))))) + +;; DEPRECATED +(defmacro def-package! (&rest args) + (make-obsolete 'def-package! 'use-package! "2.0.9") + (message "`def-package!' was renamed to `use-package!'; use that instead.") + `(use-package! ,@args)) + +(defmacro def-package-hook! (&rest args) + (make-obsolete 'def-package-hook! 'use-package-hook! "2.0.9") + (message "`def-package-hook!' was renamed to `use-package-hook!'; use that instead.") + `(use-package-hook! ,@args)) + +(provide 'core-modules) +;;; core-modules.el ends here diff --git a/.emacs.d/core/core-packages.el b/.emacs.d/core/core-packages.el new file mode 100644 index 0000000..7265107 --- /dev/null +++ b/.emacs.d/core/core-packages.el @@ -0,0 +1,329 @@ +;;; core/core-packages.el -*- lexical-binding: t; -*- + +;; Emacs package management is opinionated, and so is Doom. Doom uses `straight' +;; to create a declarative, lazy-loaded and optionally rolling-release package +;; management system. We use `straight' over `package' because the latter is +;; tempermental. ELPA sources suffer downtime occasionally, and often fail at +;; building some packages when GNU Tar is unavailable (e.g. MacOS users start +;; with BSD tar). There are also known gnutls errors in the current stable +;; release of Emacs (26.x) which bork TLS handshakes with ELPA repos (mainly +;; gnu.elpa.org). See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=3434. +;; +;; What's worse, you can only get the latest version of packages through ELPA. +;; In an ecosystem that is constantly changing, this is more frustrating than +;; convenient. Straight (and Doom) can do rolling release, but it is optional +;; (and will eventually be opt-in). +;; +;; ANyhow, interacting with this package management system is done through the +;; bin/doom script included with Doom Emacs. You'll find more about it by +;; running 'doom help' (I highly recommend you add it to your PATH), but here +;; are the highlights: +;; +;; + `bin/doom install`: a wizard that guides you through setting up Doom and +;; your private config for the first time. +;; + `bin/doom refresh`: your go-to command for making sure Doom is in optimal +;; condition. It ensures all unneeded packages are removed, all needed ones +;; are installed, and all metadata associated with them is generated. +;; + `bin/doom upgrade`: upgrades Doom Emacs and your packages to the latest +;; versions. There's also 'bin/doom update' for updating only your packages. +;; +;; How this works is: the system reads packages.el files located in each +;; activated module, your private directory (`doom-private-dir'), and one in +;; `doom-core-dir'. These contain `package!' declarations that tell DOOM what +;; plugins to install and where from. +;; +;; All that said, you can still use package.el's commands, but 'bin/doom +;; refresh' will purge ELPA packages. + +(defvar doom-init-packages-p nil + "If non-nil, Doom's package management system has been initialized.") + +(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 '(straight use-package async gcmh) + "A list of packages that must be installed (and will be auto-installed if +missing) and shouldn't be deleted.") + +(defvar doom-core-package-sources + '((org-elpa :local-repo nil) + (melpa + :type git :host github + :repo "melpa/melpa" + :no-build t) + (gnu-elpa-mirror + :type git :host github + :repo "emacs-straight/gnu-elpa-mirror" + :no-build t) + (emacsmirror-mirror + :type git :host github + :repo "emacs-straight/emacsmirror-mirror" + :no-build t)) + "A list of recipes for straight's recipe repos.") + +(defvar doom-disabled-packages () + "A list of packages that should be ignored by `use-package!' and `after!'.") + + +;; +;;; Package managers + +;; Ensure that, if we do need package.el, it is configured correctly. You really +;; shouldn't be using it, but it may be convenient for quick package testing. +(setq package--init-file-ensured t + package-enable-at-startup nil + package-user-dir (concat doom-local-dir "elpa/") + package-gnupghome-dir (expand-file-name "gpg" package-user-dir) + ;; I omit Marmalade because its packages are manually submitted rather + ;; than pulled, so packages are often out of date with upstream. + package-archives + (let ((proto (if gnutls-verify-error "https" "http"))) + `(("gnu" . ,(concat proto "://elpa.gnu.org/packages/")) + ("melpa" . ,(concat proto "://melpa.org/packages/")) + ("org" . ,(concat proto "://orgmode.org/elpa/"))))) + +;; Don't save `package-selected-packages' to `custom-file' +(defadvice! doom--package-inhibit-custom-file-a (&optional value) + :override #'package--save-selected-packages + (if value (setq package-selected-packages value))) + +;; Refresh package.el the first time you call `package-install' +(add-transient-hook! 'package-install (package-refresh-contents)) + +;;; straight +(setq straight-base-dir doom-local-dir + straight-repository-branch "develop" + straight-cache-autoloads nil ; we already do this, and better. + ;; Doom doesn't encourage you to modify packages in place. Disabling this + ;; makes 'doom refresh' instant (once everything set up), which is much + ;; nicer UX than the several seconds modification checks. + straight-check-for-modifications nil + ;; We handle package.el ourselves (and a little more comprehensively) + straight-enable-package-integration nil + ;; Before switching to straight, `doom-local-dir' would average out at + ;; around 100mb with half Doom's modules at ~230 packages. Afterwards, at + ;; around 1gb. With shallow cloning, that is reduced to ~400mb. This + ;; imposes an issue with packages that require their git history for + ;; certain things to work (like magit and org), but we can deal with that + ;; when we cross that bridge. + straight-vc-git-default-clone-depth 1 + ;; Straight's own emacsmirror mirror is a little smaller and faster. + straight-recipes-emacsmirror-use-mirror t + ;; Prefix declarations are unneeded bulk added to our autoloads file. Best + ;; we just don't have to deal with them at all. + autoload-compute-prefixes nil) + +(defun doom--finalize-straight () + (mapc #'funcall (delq nil (mapcar #'cdr straight--transaction-alist))) + (setq straight--transaction-alist nil)) + +;;; Getting straight to behave in batch mode +(when noninteractive + ;; HACK Remove dired & magit options from prompt, since they're inaccessible + ;; in noninteractive sessions. + (advice-add #'straight-vc-git--popup-raw :override #'straight--popup-raw)) + +;; HACK Replace GUI popup prompts (which hang indefinitely in tty Emacs) with +;; simple prompts. +(defadvice! doom--straight-fallback-to-y-or-n-prompt-a (orig-fn &optional prompt) + :around #'straight-are-you-sure + (if noninteractive + (y-or-n-p (format! "%s" (or prompt ""))) + (funcall orig-fn prompt))) + +(defadvice! doom--straight-fallback-to-tty-prompt-a (orig-fn prompt actions) + "Modifies straight to prompt on the terminal when in noninteractive sessions." + :around #'straight--popup-raw + (if (not noninteractive) + (funcall orig-fn prompt actions) + ;; We can't intercept C-g, so no point displaying any options for this key + ;; Just use C-c + (delq! "C-g" actions 'assoc) + ;; HACK These are associated with opening dired or magit, which isn't + ;; possible in tty Emacs, so... + (delq! "e" actions 'assoc) + (delq! "g" actions 'assoc) + (let ((options (list (lambda () + (let ((doom-format-indent 0)) + (terpri) + (print! (error "Aborted"))) + (kill-emacs))))) + (print! (start "%s") (red prompt)) + (terpri) + (print-group! + (print-group! + (print! " 1) Abort") + (dolist (action actions) + (cl-destructuring-bind (_key desc func) action + (when desc + (push func options) + (print! "%2s) %s" (length options) desc))))) + (terpri) + (let ((options (nreverse options)) + answer fn) + (while + (not + (setq + fn (ignore-errors + (nth (1- (setq answer + (read-number + (format! "How to proceed? (%s) " + (mapconcat #'number-to-string + (number-sequence 1 (length options)) + ", "))))) + options)))) + (print! (warn "%s is not a valid answer, try again.") answer)) + (funcall fn)))))) + + +;; +;;; Bootstrapper + +(defun doom-initialize-packages (&optional force-p) + "Ensures that Doom's package system and straight.el are initialized. + +If FORCE-P is non-nil, do it anyway. + +This ensure `doom-packages' is populated, if isn't aren't already. Use this +before any of straight's or Doom's package management's API to ensure all the +necessary package metadata is initialized and available for them." + (unless doom-init-packages-p + (setq force-p t)) + (when (or force-p (not (bound-and-true-p package--initialized))) + (doom-log "Initializing package.el") + (require 'package) + (package-initialize)) + (when (or force-p (not doom-packages)) + (doom-log "Initializing straight") + (setq doom-init-packages-p t) + (unless (fboundp 'straight--reset-caches) + (doom-ensure-straight) + (require 'straight)) + (straight--reset-caches) + (setq straight-recipe-repositories nil) + (mapc #'straight-use-recipes doom-core-package-sources) + (straight-register-package + `(straight :type git :host github + :repo ,(format "%s/straight.el" straight-repository-user) + :files ("straight*.el") + :branch ,straight-repository-branch + :no-byte-compile t)) + (mapc #'straight-use-package doom-core-packages) + (doom-log "Initializing doom-packages") + (setq doom-disabled-packages nil + doom-packages (doom-package-list)) + (cl-loop for (pkg . plist) in doom-packages + if (plist-get plist :disable) + do (cl-pushnew pkg doom-disabled-packages) + else if (not (plist-get plist :ignore)) + do (with-demoted-errors "Package error: %s" + (straight-register-package + (if-let (recipe (plist-get plist :recipe)) + (cons pkg recipe) + pkg)))) + (unless doom-interactive-mode + (add-hook 'kill-emacs-hook #'doom--finalize-straight)))) + +(defun doom-ensure-straight () + "Ensure `straight' is installed and was compiled with this version of Emacs." + (defvar bootstrap-version) + (let* (;; Force straight to install into ~/.emacs.d/.local/straight instead of + ;; ~/.emacs.d/straight by pretending `doom-local-dir' is our .emacs.d. + (user-emacs-directory straight-base-dir) + (bootstrap-file (doom-path straight-base-dir "straight/repos/straight.el/straight.el")) + (bootstrap-version 5)) + (make-directory (doom-path straight-base-dir "straight/build") 'parents) + (unless (featurep 'straight) + (unless (or (require 'straight nil t) + (file-readable-p bootstrap-file)) + (with-current-buffer + (url-retrieve-synchronously + (format "https://raw.githubusercontent.com/raxod502/straight.el/%s/install.el" + straight-repository-branch) + 'silent 'inhibit-cookies) + (goto-char (point-max)) + (eval-print-last-sexp))) + (load bootstrap-file nil t)))) + + +;; +;;; Module package macros + +(cl-defmacro package! + (name &rest plist &key built-in recipe ignore _disable _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 +populate `doom-packages' with metadata about the packages Doom needs to keep +track of. + +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. + :disable BOOL + Do not install or update this package AND disable all of its `use-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. If set to + 'prefer, will use built-in package if it is present. + +Returns t if package is successfully registered, and nil if it was disabled +elsewhere." + (declare (indent defun)) + (when (and recipe (keywordp (car-safe recipe))) + (plist-put! plist :recipe `(quote ,recipe))) + (when built-in + (when (and (not ignore) (equal built-in '(quote prefer))) + (setq built-in `(locate-library ,(symbol-name name) nil doom--initial-load-path))) + (plist-delete! plist :built-in) + (plist-put! plist :ignore built-in)) + `(let* ((name ',name) + (plist (cdr (assq name doom-packages)))) + (let ((module-list (plist-get plist :modules)) + (module ',(doom-module-from-path))) + (unless (member module module-list) + (plist-put! plist :modules + (append module-list + (list module) + nil)))) + + (doplist! ((prop val) (list ,@plist) plist) + (unless (null val) + (plist-put! plist prop val))) + + ;; Some basic key validation; error if you're not using a valid key + (condition-case e + (cl-destructuring-bind + (&key _local-repo _files _flavor _no-build + _type _repo _host _branch _remote _nonrecursive _fork _depth) + (plist-get plist :recipe)) + (error + (signal 'doom-package-error + (cons ,(symbol-name name) + (error-message-string e))))) + + (setf (alist-get name doom-packages) plist) + (if (not (plist-get plist :disable)) t + (doom-log "Disabling package %S" name) + (cl-pushnew name doom-disabled-packages) + nil))) + +(defmacro disable-packages! (&rest packages) + "A convenience macro for disabling packages in bulk. +Only use this macro in a module's (or your private) packages.el file." + (macroexp-progn + (cl-loop for p in packages + collect `(package! ,p :disable t)))) + +(provide 'core-packages) +;;; core-packages.el ends here diff --git a/.emacs.d/core/core-projects.el b/.emacs.d/core/core-projects.el new file mode 100644 index 0000000..24e6ed3 --- /dev/null +++ b/.emacs.d/core/core-projects.el @@ -0,0 +1,282 @@ +;;; 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 + (or (cl-find-if #'executable-find '("fdfind" "fd")) + "fd") + "name of `fd-find' executable binary") + +(defvar doom-projectile-cache-timer-file (concat doom-cache-dir "projectile.timers") + "Where to save project file cache timers.") + + +;; +;;; Packages + +(use-package! projectile + :after-call after-find-file dired-before-readin-hook minibuffer-setup-hook + :commands (projectile-project-root + projectile-project-name + projectile-project-p + projectile-locate-dominating-file) + :init + (setq projectile-cache-file (concat doom-cache-dir "projectile.cache") + projectile-enable-caching doom-interactive-mode + projectile-files-cache-expire 86400 ; expire after a day + projectile-globally-ignored-files '(".DS_Store" "Icon " "TAGS") + projectile-globally-ignored-file-suffixes '(".elc" ".pyc" ".o") + projectile-kill-buffers-filter 'kill-only-files + projectile-known-projects-file (concat doom-cache-dir "projectile.projects") + projectile-ignored-projects '("~/" "/tmp")) + + (global-set-key [remap evil-jump-to-tag] #'projectile-find-tag) + (global-set-key [remap find-tag] #'projectile-find-tag) + + :config + (projectile-mode +1) + + ;; In the interest of performance, we reduce the number of project root marker + ;; files/directories projectile searches for when resolving the project root. + (setq projectile-project-root-files-bottom-up + (append '(".project" ; doom project marker + ".git") ; Git VCS root dir + (when (executable-find "hg") + '(".hg")) ; Mercurial VCS root dir + (when (executable-find "fossil") + '(".fslckout" ; Fossil VCS root dir + "_FOSSIL_")) ; Fossil VCS root DB on Windows + (when (executable-find "bzr") + '(".bzr")) ; Bazaar VCS root dir + (when (executable-find "darcs") + '("_darcs"))) ; Darcs VCS root dir + ;; This will be filled by other modules. We build this list manually so + ;; projectile doesn't perform so many file checks every time it resolves + ;; a project's root -- particularly when a file has no project. + projectile-project-root-files '("TAGS") + projectile-project-root-files-top-down-recurring '(".svn" "Makefile")) + + (push (abbreviate-file-name doom-local-dir) projectile-globally-ignored-directories) + + ;; Disable commands that won't work, as is, and that Doom already provides a + ;; better alternative for. + (put 'projectile-ag 'disabled "Use +{ivy,helm}/project-search instead") + (put 'projectile-ripgrep 'disabled "Use +{ivy,helm}/project-search instead") + (put 'projectile-grep 'disabled "Use +{ivy,helm}/project-search instead") + + ;; Treat current directory in dired as a "file in a project" and track it + (add-hook 'dired-before-readin-hook #'projectile-track-known-projects-find-file-hook) + + ;; 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. + (add-hook! 'kill-emacs-hook + (defun doom-cleanup-project-cache-h () + "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 (and (bound-and-true-p projectile-projects-cache) + doom-interactive-mode) + (cl-loop with blacklist = (mapcar #'file-truename doom-projectile-cache-blacklist) + for proot in (hash-table-keys projectile-projects-cache) + if (or (not (stringp proot)) + (>= (length (gethash proot projectile-projects-cache)) + 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)))) + + ;; 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) + (doom-log "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))) + + (cond + ;; If fd exists, use it for git and generic projects. fd is a rust program + ;; that is significantly faster than git ls-files or find, and it respects + ;; .gitignore. This is recommended in the projectile docs. + ((executable-find doom-projectile-fd-binary) + (setq projectile-generic-command + (format "%s . --color=never --type f -0 -H -E .git" + doom-projectile-fd-binary) + projectile-git-command projectile-generic-command + projectile-git-submodule-command nil + ;; ensure Windows users get fd's benefits + projectile-indexing-method 'alien)) + + ;; Otherwise, resort to ripgrep, which is also faster than find + ((executable-find "rg") + (setq projectile-generic-command + (concat "rg -0 --files --color=never --hidden" + (cl-loop for dir in projectile-globally-ignored-directories + concat (format " --glob '!%s'" dir))) + projectile-git-command projectile-generic-command + projectile-git-submodule-command nil + ;; ensure Windows users get rg's benefits + projectile-indexing-method 'alien)) + + ;; Fix breakage on windows in git projects with submodules, since Windows + ;; doesn't have tr + ((not (executable-find "tr")) + (setq projectile-git-submodule-command nil))) + + (defadvice! doom--projectile-cache-timers-a () + "Persist `projectile-projects-cache-time' across sessions, so that +`projectile-files-cache-expire' checks won't reset when restarting Emacs." + :before #'projectile-serialize-cache + (projectile-serialize projectile-projects-cache-time doom-projectile-cache-timer-file)) + ;; Restore it + (when (file-readable-p doom-projectile-cache-timer-file) + (setq projectile-projects-cache-time + (projectile-unserialize doom-projectile-cache-timer-file))) + + (defadvice! doom--projectile-default-generic-command-a (orig-fn &rest args) + "If projectile can't tell what kind of project you're in, it issues an error +when using many of projectile's command, e.g. `projectile-compile-command', +`projectile-run-project', `projectile-test-project', and +`projectile-configure-project', for instance. + +This suppresses the error so these commands will still run, but prompt you for +the command instead." + :around #'projectile-default-generic-command + (ignore-errors (apply orig-fn args))) + + ;; Projectile root-searching functions can cause an infinite loop on TRAMP + ;; connections, so disable them. + ;; TODO Is this still necessary? + (defadvice! doom--projectile-locate-dominating-file-a (orig-fn file name) + "Don't traverse the file system if on a remote connection." + :around #'projectile-locate-dominating-file + (when (and (stringp file) + (not (file-remote-p file nil t))) + (funcall orig-fn file name)))) + + +;; +;;; 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.") + +(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 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. + +A project can be enabled through .dir-locals.el too, by setting `doom-project'. + +PLIST may contain any of these properties, which are all checked to see if NAME +should be activated. If they are *all* true, NAME is activated. + + :modes MODES -- if buffers are derived from MODES (one or a list of symbols). + + :files FILES -- if project contains FILES; takes a string or a form comprised + of nested (and ...) and/or (or ...) forms. Each path is relative to the + project root, however, if prefixed with a '.' or '..', it is relative to the + current buffer. + + :match REGEXP -- if file name matches REGEXP + + :when PREDICATE -- if PREDICATE returns true (can be a form or the symbol of a + function) + + :add-hooks HOOKS -- HOOKS is a list of hooks to add this mode's hook. + + :on-load FORM -- FORM to run the first time this project mode is enabled. + + :on-enter FORM -- FORM is run each time the mode is activated. + + :on-exit FORM -- FORM is run each time the mode is disabled. + +Relevant: `doom-project-hook'." + (declare (indent 1)) + (let ((init-var (intern (format "%s-init" name)))) + (macroexp-progn + (append + (when on-load + `((defvar ,init-var nil))) + `((define-minor-mode ,name + "A project minor mode generated by `def-project-mode!'." + :init-value nil + :lighter "" + :keymap (make-sparse-keymap) + (if (not ,name) + ,on-exit + (run-hook-with-args 'doom-project-hook ',name ,name) + ,(when on-load + `(unless ,init-var + ,on-load + (setq ,init-var t))) + ,on-enter)) + (dolist (hook ,add-hooks) + (add-hook ',(intern (format "%s-hook" name)) hook))) + (cond ((or files modes when) + (cl-check-type files (or null list string)) + (let ((fn + `(lambda () + (and (not (bound-and-true-p ,name)) + (and buffer-file-name (not (file-remote-p buffer-file-name nil t))) + ,(or (null match) + `(if buffer-file-name (string-match-p ,match buffer-file-name))) + ,(or (null files) + ;; Wrap this in `eval' to prevent eager expansion + ;; of `project-file-exists-p!' from pulling in + ;; autoloaded files prematurely. + `(eval + '(project-file-exists-p! + ,(if (stringp (car files)) (cons 'and files) files)))) + ,(or when t) + (,name 1))))) + (if modes + `((dolist (mode ,modes) + (let ((hook-name + (intern (format "doom--enable-%s%s-h" ',name + (if (eq mode t) "" (format "-in-%s" mode)))))) + (fset hook-name #',fn) + (if (eq mode t) + (add-to-list 'auto-minor-mode-magic-alist (cons hook-name #',name)) + (add-hook (intern (format "%s-hook" mode)) hook-name))))) + `((add-hook 'change-major-mode-after-body-hook #',fn))))) + (match + `((add-to-list 'auto-minor-mode-alist (cons ,match #',name))))))))) + +(provide 'core-projects) +;;; core-projects.el ends here diff --git a/.emacs.d/core/core-ui.el b/.emacs.d/core/core-ui.el new file mode 100644 index 0000000..fd80d60 --- /dev/null +++ b/.emacs.d/core/core-ui.el @@ -0,0 +1,649 @@ +;;; core-ui.el -*- lexical-binding: t; -*- + +;; +;;; Variables + +(defvar doom-init-theme-p nil + "If non-nil, a theme as been loaded.") + +(defvar doom-theme nil + "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 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-variable-pitch-font nil + "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 + (if IS-MAC + (font-spec :family "Apple Color Emoji") + (font-spec :family "Symbola")) + "Fallback font for unicode glyphs. + +It defaults to Apple Color Emoji on MacOS and Symbola on Linux. 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.") + + +;; +;;; Custom hooks + +(defvar doom-init-ui-hook nil + "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-h () + (let ((gc-cons-threshold most-positive-fixnum)) + (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) + (setq doom--last-window (selected-window)))))) + +(defun doom-run-switch-frame-hooks-h (&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) + (setq doom--last-frame (selected-frame))))) + +(defun doom-run-switch-buffer-hooks-a (orig-fn buffer-or-name &rest args) + (let ((gc-cons-threshold most-positive-fixnum)) + (if (or doom-inhibit-switch-buffer-hooks + (eq (current-buffer) (get-buffer buffer-or-name)) + (and (eq orig-fn #'switch-to-buffer) (car args))) + (apply orig-fn buffer-or-name args) + (let ((doom-inhibit-switch-buffer-hooks t)) + (when-let (buffer (apply orig-fn buffer-or-name args)) + (with-current-buffer (if (windowp buffer) + (window-buffer buffer) + buffer) + (run-hooks 'doom-switch-buffer-hook)) + buffer))))) + +(defun doom-run-switch-to-next-prev-buffer-hooks-a (orig-fn &rest args) + (let ((gc-cons-threshold most-positive-fixnum)) + (if doom-inhibit-switch-buffer-hooks + (apply orig-fn args) + (let ((doom-inhibit-switch-buffer-hooks t)) + (when-let (buffer (apply orig-fn args)) + (with-current-buffer buffer + (run-hooks 'doom-switch-buffer-hook)) + buffer))))) + +(defun doom-protect-fallback-buffer-h () + "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-h () + "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 (eq major-mode 'fundamental-mode) + buffer-read-only + (null buffer-file-name)) + (require 'whitespace) + (set (make-local-variable 'whitespace-style) + (let ((style (if indent-tabs-mode '(indentation) '(tabs tab-mark)))) + (if whitespace-mode + (cl-union style whitespace-active-style) + style))) + (cl-pushnew 'face whitespace-style) + (whitespace-mode +1))) + + +;; +;;; General UX + +;; Simpler confirmation prompt when killing Emacs +(setq confirm-kill-emacs #'doom-quit-p) + +(setq uniquify-buffer-name-style 'forward + ;; no beeping or blinking please + ring-bell-function #'ignore + visible-bell nil) + +;; middle-click paste at point, not at click +(setq mouse-yank-at-point t) + +;; Enable mouse in terminal Emacs +(add-hook 'tty-setup-hook #'xterm-mouse-mode) + + +;; +;;; Scrolling + +(setq hscroll-margin 2 + hscroll-step 1 + scroll-conservatively 10 + scroll-margin 0 + scroll-preserve-screen-position t + ;; Reduce cursor lag by a tiny bit by not auto-adjusting `window-vscroll' + ;; for tall lines. + auto-window-vscroll nil + ;; mouse + mouse-wheel-scroll-amount '(5 ((shift) . 2)) + mouse-wheel-progressive-speed nil) ; don't accelerate scrolling + +;; Remove hscroll-margin in shells, otherwise it causes jumpiness +(setq-hook! '(eshell-mode-hook term-mode-hook) hscroll-margin 0) + +(when IS-MAC + ;; sane trackpad/mouse scroll settings + (setq mac-redisplay-dont-reset-vscroll t + mac-mouse-wheel-smooth-scroll nil)) + + +;; +;;; Cursor + +;; Don't blink the cursor, it's too distracting. +(blink-cursor-mode -1) + +;; Don't blink the paren matching the one at point, it's too distracting. +(setq blink-matching-paren nil) + +(setq visible-cursor nil) + +;; Don't stretch the cursor to fit wide characters, it is disorienting, +;; especially for tabs. +(setq x-stretch-cursor nil) + + +;; +;;; Buffers + +;; Make `next-buffer', `other-buffer', etc. ignore unreal buffers. +(push '(buffer-predicate . doom-buffer-frame-predicate) default-frame-alist) + +(setq confirm-nonexistent-file-or-buffer t) + +(defadvice! doom--switch-to-fallback-buffer-maybe-a (&rest _) + "Switch to `doom-fallback-buffer' if on last real buffer. + +Advice for `kill-current-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-current-buffer'." + :before-until #'kill-current-buffer + (let ((buf (current-buffer))) + (cond ((window-dedicated-p) + (delete-window) + t) + ((eq buf (doom-fallback-buffer)) + (message "Can't kill the fallback buffer.") + t) + ((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))) + (run-hooks 'buffer-list-update-hook)) + t)))) + + +;; +;;; Fringes + +;; Reduce the clutter in the fringes; we'd like to reserve that space for more +;; useful information, like git-gutter and flycheck. +(setq indicate-buffer-boundaries nil + indicate-empty-lines nil) + +;; remove continuation arrow on right fringe +(delq! 'continuation fringe-indicator-alist 'assq) + + +;; +;;; Windows/frames + +;; A simple frame title +(setq frame-title-format '("%b – Doom Emacs") + icon-title-format frame-title-format) + +;; Don't resize emacs in steps, it looks weird. +(setq window-resize-pixelwise t + frame-resize-pixelwise t) + +(unless EMACS27+ ; We already do this in early-init.el + ;; Disable tool and scrollbars; Doom encourages keyboard-centric workflows, so + ;; these are just clutter (the scrollbar also impacts Emacs' performance). + (push '(menu-bar-lines . 0) default-frame-alist) + (push '(tool-bar-lines . 0) default-frame-alist) + (push '(vertical-scroll-bars) default-frame-alist)) + +;; Sets `ns-appearance' and `ns-transparent-titlebar' on GUI frames (and fixes +;; mismatching text color in the frame title) +(when IS-MAC + ;; Curse Lion and its sudden but inevitable fullscreen mode! + ;; NOTE Meaningless to railwaycat's emacs-mac build + (setq ns-use-native-fullscreen nil + ;; Visit files opened outside of Emacs in existing frame, rather than a + ;; new one + ns-pop-up-frames nil) + + ;; Sets ns-transparent-titlebar and ns-appearance frame parameters as is + ;; appropriate for the loaded theme. + (and (or (daemonp) + (display-graphic-p)) + (require 'ns-auto-titlebar nil t) + (ns-auto-titlebar-mode +1)) + + (add-hook! 'after-make-frame-functions + (defun doom-init-menu-bar-in-gui-frames-h (frame) + "On MacOS, the menu bar isn't part of the frame. Disabling it makes MacOS +treat Emacs as a non-application window." + (when (display-graphic-p frame) + (set-frame-parameter frame 'menu-bar-lines 1))))) + +;; The native border "consumes" a pixel of the fringe on righter-most splits, +;; `window-divider' does not. Available since Emacs 25.1. +(setq 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) + +;; Prompt the user for confirmation when deleting a non-empty frame +(global-set-key [remap delete-frame] #'doom/delete-frame) + +;; always avoid GUI +(setq use-dialog-box nil) +;; Don't display floating tooltips; display their contents in the echo-area. +(if (bound-and-true-p tooltip-mode) (tooltip-mode -1)) +;; native linux tooltips are ugly +(when IS-LINUX + (setq x-gtk-use-system-tooltips nil)) + + ;; Favor vertical splits over horizontal ones +(setq split-width-threshold 160 + split-height-threshold nil) + + +;; +;;; Minibuffer + +;; Allow for minibuffer-ception. Sometimes we need another minibuffer command +;; _while_ we're in the minibuffer. +(setq enable-recursive-minibuffers t) + +;; Show current key-sequence in minibuffer, like vim does. Any feedback after +;; typing is better UX than no feedback at all. +(setq echo-keystrokes 0.02) + +;; Expand the minibuffer to fit multi-line text displayed in the echo-area. This +;; doesn't look too great with direnv, however... +(setq resize-mini-windows 'grow-only + ;; But don't let the minibuffer grow beyond this size + max-mini-window-height 0.15) + +;; Typing yes/no is obnoxious when y/n will do +(fset #'yes-or-no-p #'y-or-n-p) + +;; Try really hard to keep the cursor from getting stuck in the read-only prompt +;; portion of the minibuffer. +(setq minibuffer-prompt-properties '(read-only t intangible t cursor-intangible t face minibuffer-prompt)) +(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) + + +;; +;;; Built-in packages + +;;;###package ansi-color +(setq ansi-color-for-comint-mode t) + + +(after! compile + (setq 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) + ;; Handle ansi codes in compilation buffer + (add-hook 'compilation-filter-hook #'doom-apply-ansi-color-to-compilation-buffer-h)) + + +(after! ediff + (setq ediff-diff-options "-w" ; turn off whitespace checking + ediff-split-window-function #'split-window-horizontally + ediff-window-setup-function #'ediff-setup-windows-plain) + + (defvar doom--ediff-saved-wconf nil) + ;; Restore window config after quitting ediff + (add-hook! 'ediff-before-setup-hook + (defun doom-ediff-save-wconf-h () + (setq doom--ediff-saved-wconf (current-window-configuration)))) + (add-hook! '(ediff-quit-hook ediff-suspend-hook) :append + (defun doom-ediff-restore-wconf-h () + (when (window-configuration-p doom--ediff-saved-wconf) + (set-window-configuration doom--ediff-saved-wconf))))) + + +(use-package! goto-addr + :hook (text-mode . goto-address-mode) + :hook (prog-mode . goto-address-prog-mode) + :config + (define-key goto-address-highlight-keymap (kbd "RET") #'goto-address-at-point)) + + +(use-package! hl-line + ;; Highlights the current line + :hook ((prog-mode text-mode conf-mode) . hl-line-mode) + :config + ;; Not having to render the hl-line overlay in multiple buffers offers a tiny + ;; performance boost. I also don't need to see it in other buffers. + (setq hl-line-sticky-flag nil + global-hl-line-sticky-flag nil) + + ;; 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 doom-buffer-hl-line-mode nil) + (add-hook! 'evil-visual-state-entry-hook + (defun doom-disable-hl-line-h () + (when hl-line-mode + (setq-local doom-buffer-hl-line-mode t) + (hl-line-mode -1)))) + (add-hook! 'evil-visual-state-exit-hook + (defun doom-enable-hl-line-maybe-h () + (when doom-buffer-hl-line-mode + (hl-line-mode +1)))))) + + +(use-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) ; I'll bind keys myself + :config (winner-mode +1) + (appendq! winner-boring-buffers + '("*Compile-Log*" "*inferior-lisp*" "*Fuzzy Completions*" + "*Apropos*" "*Help*" "*cvs*" "*Buffer List*" "*Ibuffer*" + "*esh command on file*"))) + + +(use-package! paren + ;; highlight matching delimiters + :after-call after-find-file doom-switch-buffer-hook + :config + (setq show-paren-delay 0.1 + show-paren-highlight-openparen t + show-paren-when-point-inside-paren t + show-paren-when-point-in-periphery t) + (show-paren-mode +1)) + + +;;;###package whitespace +(setq whitespace-line-column nil + whitespace-style + '(face indentation tabs tab-mark spaces space-mark newline newline-mark + trailing lines-tail) + whitespace-display-mappings + '((tab-mark ?\t [?› ?\t]) + (newline-mark ?\n [?¬ ?\n]) + (space-mark ?\ [?·] [?.]))) + + +;; +;;; Third party packages + +(use-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 + (defadvice! doom--disable-all-the-icons-in-tty-a (orig-fn &rest args) + "Return a blank string in tty Emacs, which doesn't support multiple fonts." + :around #'all-the-icons-insert + (if (display-multi-font-p) + (apply orig-fn args) + ""))) + +;;;###package hide-mode-line-mode +(add-hook! '(completion-list-mode-hook Man-mode-hook) + #'hide-mode-line-mode) + +;; Many major modes do no highlighting of number literals, so we do it for them +(use-package! highlight-numbers + :hook ((prog-mode conf-mode) . highlight-numbers-mode) + :config (setq highlight-numbers-generic-regexp "\\_<[[:digit:]]+\\(?:\\.[0-9]*\\)?\\_>")) + +;;;###package image +(setq image-animate-loop t) + +;;;###package rainbow-delimiters +;; Helps us distinguish stacked delimiter pairs, especially in parentheses-drunk +;; languages like Lisp. +(setq rainbow-delimiters-max-face-count 3) + +;;;###package pos-tip +(setq pos-tip-internal-border-width 6 + pos-tip-border-width 1) + + +;; +;;; Line numbers + +;; Explicitly define a width to reduce computation +(setq-default display-line-numbers-width 3) + +;; Show absolute line numbers for narrowed regions +(setq-default display-line-numbers-widen t) + +;; Enable line numbers in most text-editing modes +(add-hook! '(prog-mode-hook text-mode-hook conf-mode-hook) + #'display-line-numbers-mode) + +(defun doom-enable-line-numbers-h () (display-line-numbers-mode +1)) +(defun doom-disable-line-numbers-h () (display-line-numbers-mode -1)) + + +;; +;;; Theme & font + +;; Underline looks a bit better when drawn lower +(setq x-underline-at-descent-line t) + +(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-h'.") + +(defun doom-init-fonts-h () + "Loads `doom-font'." + (cond (doom-font + (cl-pushnew + (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))))) + default-frame-alist + :key #'car :test #'eq)) + ((display-graphic-p) + (setq font-use-system-font t + doom-font (face-attribute 'default :font))))) + +(defun doom-init-extra-fonts-h (&optional frame) + "Loads `doom-variable-pitch-font',`doom-serif-font' and `doom-unicode-font'." + (condition-case e + (with-selected-frame (or frame (selected-frame)) + (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)) + (when (and doom-unicode-font (fboundp 'set-fontset-font)) + (set-fontset-font t 'unicode doom-unicode-font nil 'prepend))) + ((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-h (&optional frame) + "Load the theme specified by `doom-theme' in FRAME." + (when (and doom-theme (not (memq doom-theme custom-enabled-themes))) + (with-selected-frame (or frame (selected-frame)) + (let ((doom--prefer-theme-elc t)) + (load-theme doom-theme t))))) + +(defadvice! doom--run-load-theme-hooks-a (theme &optional _no-confirm no-enable) + "Set up `doom-load-theme-hook' to run after `load-theme' is called." + :after-while #'load-theme + (unless no-enable + (setq doom-theme theme + doom-init-theme-p t) + (run-hooks 'doom-load-theme-hook))) + +(defadvice! doom--prefer-compiled-theme-a (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." + :around #'load-theme + (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))) + + +;; +;;; Bootstrap + +(defun doom-init-ui-h () + "Initialize Doom's user interface by applying all its advice and hooks." + (run-hook-wrapped 'doom-init-ui-hook #'doom-try-run-hook) + + (add-hook 'kill-buffer-query-functions #'doom-protect-fallback-buffer-h) + (add-hook 'after-change-major-mode-hook #'doom-highlight-non-default-indentation-h 'append) + + ;; 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-h) + (add-hook 'focus-in-hook #'doom-run-switch-frame-hooks-h) + (dolist (fn '(switch-to-next-buffer switch-to-prev-buffer)) + (advice-add fn :around #'doom-run-switch-to-next-prev-buffer-hooks-a)) + (dolist (fn '(switch-to-buffer display-buffer)) + (advice-add fn :around #'doom-run-switch-buffer-hooks-a))) + +;; Apply `doom-font' et co +(add-hook 'doom-after-init-modules-hook #'doom-init-fonts-h) +(add-hook 'doom-load-theme-hook #'doom-init-extra-fonts-h) + +;; Apply `doom-theme' +(add-hook (if (daemonp) + 'after-make-frame-functions + 'doom-init-ui-hook) + #'doom-init-theme-h) + +(add-hook 'window-setup-hook #'doom-init-ui-h) + + +;; +;;; Fixes/hacks + +;; Doom doesn't support `customize' and it never will. It's a clumsy interface +;; for something that should be configured from only one place ($DOOMDIR), so we +;; disable them. +(put 'customize 'disabled "Doom doesn't support `customize', configure Emacs from $DOOMDIR/config.el instead") +(put 'customize-themes 'disabled "Set `doom-theme' or use `load-theme' in $DOOMDIR/config.el instead") + +;; doesn't exist in terminal Emacs; we define it to prevent errors +(unless (fboundp 'define-fringe-bitmap) + (fset 'define-fringe-bitmap #'ignore)) + +(after! whitespace + (defun doom-disable-whitespace-mode-in-childframes-a (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-a)) + +;; 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 ,key-command () + (interactive) + (ignore-errors (call-interactively ',command))) + (define-key minibuffer-local-map (kbd ,key) #',key-command)))) +(doom-silence-motion-key backward-delete-char "") +(doom-silence-motion-key delete-char "") + +(provide 'core-ui) +;;; core-ui.el ends here diff --git a/.emacs.d/core/core.el b/.emacs.d/core/core.el new file mode 100644 index 0000000..17330e5 --- /dev/null +++ b/.emacs.d/core/core.el @@ -0,0 +1,553 @@ +;;; core.el --- the heart of the beast -*- lexical-binding: t; -*- + +(when (version< emacs-version "26.1") + (error "Detected Emacs %s. Doom only supports Emacs 26.1 and higher" + emacs-version)) + +(defconst doom-version "2.0.9" + "Current version of Doom Emacs.") + +(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))) + +;; Ensure `doom-core-dir' is in `load-path' +(add-to-list 'load-path (file-name-directory load-file-name)) + +(defvar doom--initial-load-path load-path) +(defvar doom--initial-process-environment process-environment) +(defvar doom--initial-exec-path exec-path) +(defvar doom--initial-file-name-handler-alist file-name-handler-alist) + +;; 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) + +;; Restore `file-name-handler-alist', because it is needed for handling +;; encrypted or compressed files, among other things. +(defun doom-reset-file-handler-alist-h () + (setq file-name-handler-alist doom--initial-file-name-handler-alist)) +(add-hook 'emacs-startup-hook #'doom-reset-file-handler-alist-h) + +;; Load the bare necessities +(require 'core-lib) + +(autoload 'doom-initialize-packages "core-packages") + + +;; +;;; Global variables + +(defvar doom-init-p nil + "Non-nil if Doom has been initialized.") + +(defvar doom-init-time nil + "The time it took, in seconds, for Doom Emacs to initialize.") + +(defvar doom-debug-mode (or (getenv "DEBUG") init-file-debug) + "If non-nil, Doom will log more. + +Use `doom/toggle-debug-mode' to toggle it. The --debug-init flag and setting the +DEBUG envvar will enable this at startup.") + +(defvar doom-interactive-mode (not noninteractive) + "If non-nil, Emacs is in interactive mode.") + +;;; Directories/files +(defconst doom-emacs-dir + (eval-when-compile (file-truename user-emacs-directory)) + "The path to the currently loaded .emacs.d directory. Must end with a slash.") + +(defconst doom-core-dir (concat doom-emacs-dir "core/") + "The root directory of Doom's core files. Must end with a slash.") + +(defconst doom-modules-dir (concat doom-emacs-dir "modules/") + "The root directory for Doom's modules. Must end with a slash.") + +(defconst doom-local-dir + (if-let (localdir (getenv "DOOMLOCALDIR")) + (expand-file-name (file-name-as-directory localdir)) + (concat doom-emacs-dir ".local/")) + "Root directory for local storage. + +Use this as a storage location for this system's installation of Doom Emacs. +These files should not be shared across systems. By default, it is used by +`doom-etc-dir' and `doom-cache-dir'. Must end with a slash.") + +(defconst doom-etc-dir (concat doom-local-dir "etc/") + "Directory for non-volatile local storage. + +Use this for files that don't change much, like server binaries, external +dependencies or long-term shared data. Must end with a slash.") + +(defconst doom-cache-dir (concat doom-local-dir "cache/") + "Directory for volatile local storage. + +Use this for files that change often, like cache files. Must end with a slash.") + +(defconst doom-docs-dir (concat doom-emacs-dir "docs/") + "Where Doom's documentation files are stored. Must end with a slash.") + +(defconst doom-private-dir + (if-let (doomdir (getenv "DOOMDIR")) + (expand-file-name (file-name-as-directory doomdir)) + (or (let ((xdgdir + (expand-file-name "doom/" + (or (getenv "XDG_CONFIG_HOME") + "~/.config")))) + (if (file-directory-p xdgdir) xdgdir)) + "~/.doom.d/")) + "Where your private configuration is placed. + +Defaults to ~/.config/doom, ~/.doom.d or the value of the DOOMDIR envvar; +whichever is found first. Must end in a slash.") + +(defconst doom-autoload-file (concat doom-local-dir "autoloads.el") + "Where `doom-reload-core-autoloads' stores its core autoloads. + +This file is responsible for informing Emacs where to find all of Doom's +autoloaded core functions (in core/autoload/*.el).") + +(defconst doom-package-autoload-file (concat doom-local-dir "autoloads.pkg.el") + "Where `doom-reload-package-autoloads' stores its package autoloads. + +This file is compiled from the autoloads files of all installed packages +combined.") + +(defconst doom-env-file (concat doom-local-dir "env") + "The location of your envvar file, generated by `doom env refresh`. + +This file contains environment variables scraped from your shell environment, +which is loaded at startup (if it exists). This is helpful if Emacs can't +\(easily) be launched from the correct shell session (particularly for MacOS +users).") + +;;; 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) + + +;; +;;; Emacs core configuration + +;; Reduce debug output, well, unless we've asked for it. +(setq debug-on-error doom-debug-mode + jka-compr-verbose doom-debug-mode) + +;; UTF-8 as the default coding system +(when (fboundp 'set-charset-priority) + (set-charset-priority 'unicode)) ; pretty +(prefer-coding-system 'utf-8) ; pretty +(setq locale-coding-system 'utf-8) ; please +;; Except for the clipboard on Windows, where its contents could be in an +;; encoding that's wider than utf-8, so we let Emacs/the OS decide what encoding +;; to use. +(unless IS-WINDOWS + (setq selection-coding-system 'utf-8)) ; with sugar on top + +;; Disable warnings from legacy advice system. They aren't useful, and we can't +;; often do anything about them besides changing packages upstream +(setq ad-redefinition-action 'accept) + +;; Make apropos omnipotent. It's more useful this way. +(setq apropos-do-all t) + +;; Don't make a second case-insensitive pass over `auto-mode-alist'. If it has +;; to, it's our (the user's) failure. One case for all! +(setq auto-mode-case-fold nil) + +;; Make all regexps case-sensitive by default. This favors correctness for +;; programmatical regexp searches and provides a slight performance benefit to +;; font-locking where the keywords don't let-bind `case-fold-search' themselves +;; and are already case-correct. This could break poorly written packages! +(setq-default case-fold-search nil) + +;; Display the bare minimum at startup. We don't need all that noise. The +;; dashboard/empty scratch buffer is good enough. +(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) +(fset #'display-startup-echo-area-message #'ignore) + +;; Emacs "updates" its ui more often than it needs to, so we slow it down +;; slightly, from 0.5s: +(setq idle-update-delay 1) + +;; Emacs is a huge security vulnerability, what with all the dependencies it +;; pulls in from all corners of the globe. Let's at least try to be more +;; discerning. +(setq gnutls-verify-error (getenv "INSECURE") + tls-checktrust gnutls-verify-error + tls-program '("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")) + +;; Emacs stores authinfo in HOME and in plaintext. Let's not do that, mkay? This +;; file usually stores usernames, passwords, and other such treasures for the +;; aspiring malicious third party. +(setq auth-sources (list (expand-file-name "authinfo.gpg" doom-etc-dir) + "~/.authinfo.gpg")) + +;; Emacs on Windows frequently confuses HOME (C:\Users\) and APPDATA, +;; causing `abbreviate-home-dir' to produce incorrect paths. +(when IS-WINDOWS + (setq abbreviated-home-dir "\\`'")) + +;; Don't litter `doom-emacs-dir' +(setq abbrev-file-name (concat doom-local-dir "abbrev.el") + async-byte-compile-log-file (concat doom-etc-dir "async-bytecomp.log") + bookmark-default-file (concat doom-etc-dir "bookmarks") + custom-file (concat doom-private-dir "init.el") + custom-theme-directory (concat doom-private-dir "themes/") + 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/") + 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/") + gamegrid-user-score-file-directory (concat doom-etc-dir "games/")) + +;; HACK Stop sessions from littering the user directory +(defadvice! doom--use-cache-dir-a (session-id) + :override #'emacs-session-filename + (concat doom-cache-dir "emacs-session." session-id)) + + +;; +;;; Optimizations + +;; Disable bidirectional text rendering for a modest performance boost. Of +;; course, this renders Emacs unable to detect/display right-to-left languages +;; (sorry!), but for us left-to-right language speakers/writers, it's a boon. +(setq-default bidi-display-reordering 'left-to-right + bidi-paragraph-direction 'left-to-right) + +;; Reduce rendering/line scan work for Emacs by not rendering cursors or regions +;; in non-focused windows. +(setq-default cursor-in-non-selected-windows nil) +(setq highlight-nonselected-windows nil) + +;; More performant rapid scrolling over unfontified regions. May cause brief +;; spells of inaccurate fontification immediately after scrolling. +(setq fast-but-imprecise-scrolling t) + +;; Resizing the Emacs frame can be a terribly expensive part of changing the +;; font. By inhibiting this, we halve startup times, particularly when we use +;; fonts that are larger than the system default (which would resize the frame). +(setq frame-inhibit-implied-resize t) + +;; Don't ping things that look like domain names. +(setq ffap-machine-p-known 'reject) + +;; Performance on Windows is considerably worse than elsewhere, especially if +;; WSL is involved. We'll need everything we can get. +(when IS-WINDOWS + ;; Reduce the workload when doing file IO + (setq w32-get-true-file-attributes nil) + + ;; Font compacting can be terribly expensive, especially for rendering icon + ;; fonts on Windows. Whether it has a noteable affect on Linux and Mac hasn't + ;; been determined. + (setq inhibit-compacting-font-caches t)) + +;; Remove command line options that aren't relevant to our current OS; means +;; slightly less to process at startup. +(unless IS-MAC (setq command-line-ns-option-alist nil)) +(unless IS-LINUX (setq command-line-x-option-alist nil)) + +;; Adopt a sneaky garbage collection strategy of waiting until idle time to +;; collect; staving off the collector while the user is working. +(when doom-interactive-mode + (add-transient-hook! 'pre-command-hook (gcmh-mode +1)) + (with-eval-after-load 'gcmh + (setq gcmh-idle-delay 10 + gcmh-verbose doom-debug-mode) + (add-hook 'focus-out-hook #'gcmh-idle-garbage-collect))) + + +;; +;;; MODE-local-vars-hook + +;; 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-h () + "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-h) + +;; If the user has disabled `enable-local-variables', then +;; `hack-local-variables-hook' is never triggered, so we trigger it at the end +;; of `after-change-major-mode-hook': +(defun doom-run-local-var-hooks-if-necessary-h () + "Run `doom-run-local-var-hooks-h' if `enable-local-variables' is disabled." + (unless enable-local-variables + (doom-run-local-var-hooks-h))) +(add-hook 'after-change-major-mode-hook + #'doom-run-local-var-hooks-if-necessary-h + 'append) + + +;; +;;; 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-h' from `emacs-startup-hook' or set +`doom-incremental-first-idle-timer' to nil. Incremental loading does not occur +in daemon sessions (they are loaded immediately at startup).") + +(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) + (while packages + (let ((req (pop packages))) + (unless (featurep req) + (doom-log "Incrementally loading %s" req) + (condition-case e + (or (while-no-input + ;; If `default-directory' is a directory that doesn't exist + ;; or is unreadable, Emacs throws up file-missing errors, so + ;; we set it to a directory we know exists and is readable. + (let ((default-directory doom-emacs-dir) + (gc-cons-threshold most-positive-fixnum) + file-name-handler-alist) + (require req nil t)) + t) + (push req packages)) + ((error debug) + (message "Failed to load '%s' package incrementally, because: %s" + req e))) + (if (not packages) + (doom-log "Finished incremental loading") + (run-with-idle-timer doom-incremental-idle-timer + nil #'doom-load-packages-incrementally + packages t) + (setq packages nil))))))) + +(defun doom-load-packages-incrementally-h () + "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 'emacs-startup-hook #'doom-load-packages-incrementally-h) + + +;; +;;; Bootstrap helpers + +(defun doom-try-run-hook (hook) + "Run HOOK (a hook function) with better error handling. +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-display-benchmark-h (&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 %d packages across %d modules in %.03fs" + (- (length load-path) (length doom--initial-load-path)) + (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-load-autoloads-file (file &optional noerror) + "Tries to load FILE (an autoloads file). +Return t on success, nil otherwise (but logs a warning)." + (condition-case e + (load (substring file 0 -3) noerror 'nomessage) + ((debug error) + (message "Autoload file error: %s -> %s" (file-name-nondirectory file) e) + nil))) + +(defun doom-load-envvars-file (file &optional noerror) + "Read and set envvars from FILE. +If NOERROR is non-nil, don't throw an error if the file doesn't exist or is +unreadable. Returns the names of envvars that were changed." + (if (not (file-readable-p file)) + (unless noerror + (signal 'file-error (list "Couldn't read envvar file" file))) + (let (envvars environment) + (with-temp-buffer + (save-excursion + (insert "\n") + (insert-file-contents file)) + (while (re-search-forward "\n *\\([^#= \n]*\\)=" nil t) + (push (match-string 1) envvars) + (push (buffer-substring + (match-beginning 1) + (1- (or (save-excursion + (when (re-search-forward "^\\([^= ]+\\)=" nil t) + (line-beginning-position))) + (point-max)))) + environment))) + (when environment + (setq process-environment + (append (nreverse environment) process-environment) + exec-path + (if (member "PATH" envvars) + (append (parse-colon-path (getenv "PATH")) + (list exec-directory)) + exec-path) + shell-file-name + (if (member "SHELL" envvars) + (setq shell-file-name + (or (getenv "SHELL") shell-file-name)) + shell-file-name)) + envvars)))) + +(defun doom-initialize (&optional force-p noerror) + "Bootstrap Doom, if it hasn't already (or if FORCE-P is non-nil). + +The bootstrap process ensures that the essential directories exist, all core +packages are installed, `doom-autoload-file' and `doom-package-autoload-file' +exist and are loaded, and that `core-packages' is auto-loaded when `package' or +`straight' are. + +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) + + ;; Reset as much state as possible, so `doom-initialize' can be treated like + ;; a reset function. Particularly useful for reloading the config. + (setq-default exec-path doom--initial-exec-path + load-path doom--initial-load-path + process-environment doom--initial-process-environment) + + ;; Load shell environment, optionally generated from 'doom env'. No need to + ;; do so if we're in terminal Emacs, because Emacs will correctly inherit + ;; your shell environment there. + (when (and (or (display-graphic-p) + (daemonp)) + (file-exists-p doom-env-file)) + (doom-load-envvars-file doom-env-file)) + + (require 'core-modules) + (let (;; `doom-autoload-file' tells Emacs where to load all its functions + ;; from. This includes everything in core/autoload/*.el and autoload + ;; files in enabled modules. + (core-autoloads-p (doom-load-autoloads-file doom-autoload-file noerror)) + ;; Loads `doom-package-autoload-file', which loads a concatenated + ;; package autoloads file which caches `load-path', `auto-mode-alist', + ;; `Info-directory-list', and `doom-disabled-packages'. A big + ;; reduction in startup time. + (pkg-autoloads-p (doom-load-autoloads-file doom-package-autoload-file noerror))) + + (if (and core-autoloads-p pkg-autoloads-p (not force-p)) + ;; In case we want to use package.el or straight via M-x + (progn + (with-eval-after-load 'package + (require 'core-packages)) + (with-eval-after-load 'straight + (require 'core-packages) + (doom-initialize-packages))) + + ;; Eagerly load these libraries because we may be in a session that hasn't been + ;; fully initialized (e.g. where autoloads files haven't been generated or + ;; `load-path' populated). + (mapc (doom-rpartial #'load nil (not doom-debug-mode) 'nosuffix) + (file-expand-wildcards (concat doom-core-dir "autoload/*.el"))) + + ;; Create all our core directories to quell file errors + (dolist (dir (list doom-local-dir + doom-etc-dir + doom-cache-dir)) + (unless (file-directory-p dir) + (make-directory dir 'parents))) + + ;; Ensure the package management system (and straight) are ready for + ;; action (and all core packages/repos are installed) + (require 'core-packages) + (doom-initialize-packages force-p)) + + (unless (or (and core-autoloads-p pkg-autoloads-p) + noerror) + (unless core-autoloads-p + (warn "Your Doom core autoloads file is missing")) + (unless pkg-autoloads-p + (warn "Your package autoloads file is missing")) + (signal 'doom-autoload-error (list "Run `bin/doom refresh' to generate them")))) + t)) + +(defun doom-initialize-core () + "Load Doom's core files for an interactive session." + (require 'core-keybinds) + (require 'core-ui) + (require 'core-projects) + (require 'core-editor)) + +(provide 'core) +;;; core.el ends here diff --git a/.emacs.d/core/packages.el b/.emacs.d/core/packages.el new file mode 100644 index 0000000..ff2ee91 --- /dev/null +++ b/.emacs.d/core/packages.el @@ -0,0 +1,40 @@ +;; -*- no-byte-compile: t; -*- +;;; core/packages.el + +;; core.el +(package! dotenv-mode) +(package! auto-minor-mode) + +;; core-ui.el +(package! all-the-icons) +(package! hide-mode-line) +(package! highlight-numbers) +(package! rainbow-delimiters) +(package! restart-emacs) + +;; core-editor.el +(package! better-jumper) +(package! dtrt-indent) +(package! helpful) +(package! ns-auto-titlebar :ignore (not IS-MAC)) +(package! pcre2el) +(package! smartparens) +(package! so-long + :built-in 'prefer + ;; REVIEW so-long is slated to be published to ELPA eventually, but until then + ;; I've created my own mirror for it because git.savannah.gnu.org runs on a + ;; potato. + :recipe (:host github :repo "hlissner/emacs-so-long")) +(package! undo-tree + ;; Version 0.6.5 is on ELPA which lacks a fix we need, so we install 0.6.6 + ;; from emacsmirror/undo-tree instead. + :recipe (:host github :repo "emacsmirror/undo-tree")) +(package! ws-butler) +(package! xclip) + +;; core-projects.el +(package! projectile) + +;; core-keybinds.el +(package! general) +(package! which-key) diff --git a/.emacs.d/core/templates/BUG_REPORT b/.emacs.d/core/templates/BUG_REPORT new file mode 100644 index 0000000..a46909c --- /dev/null +++ b/.emacs.d/core/templates/BUG_REPORT @@ -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 + + + +### Expected behavior + + + +### Steps to reproduce + + + +### Extra details + + + +------------------------------------------------------------------- diff --git a/.emacs.d/core/templates/QUICKSTART_INTRO b/.emacs.d/core/templates/QUICKSTART_INTRO new file mode 100644 index 0000000..8a41034 --- /dev/null +++ b/.emacs.d/core/templates/QUICKSTART_INTRO @@ -0,0 +1,25 @@ +Before you doom yourself, there are a few things you should know: + +1. Whenever you edit your doom! block in ~/.doom.d/init.el or modify your + 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! + +2. If something inexplicably goes wrong, try `bin/doom doctor` + + This will diagnose common issues with your environment and setup, and may + give you clues about what is wrong. + +3. Use `bin/doom upgrade` to update Doom. Doing it any other way may require + additional work. When in doubt, run `bin/doom refresh`. + +4. Check out `bin/doom help` to see what else `bin/doom` can do (and it is + recommended you add ~/.emacs.d/bin to your PATH). + +5. You can find Doom's documentation via `M-x doom/help` or `SPC h D`. + +Have fun! diff --git a/.emacs.d/core/templates/VANILLA_SANDBOX b/.emacs.d/core/templates/VANILLA_SANDBOX new file mode 100644 index 0000000..02a4182 --- /dev/null +++ b/.emacs.d/core/templates/VANILLA_SANDBOX @@ -0,0 +1,12 @@ +;; Welcome to the sandbox! +;; +;; This is a test bed for running Emacs Lisp in another instance of Emacs that +;; has varying amounts of Doom loaded: +;; +;; - vanilla Emacs (nothing loaded) \[doom--run-vanilla-emacs] +;; - vanilla Doom (only Doom core) \[doom--run-vanilla-doom] +;; - Doom + modules - your private config \[doom--run-vanilla-doom+] +;; - Doom + modules + your private config \[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. diff --git a/.emacs.d/core/test/helpers.el b/.emacs.d/core/test/helpers.el new file mode 100644 index 0000000..446ec12 --- /dev/null +++ b/.emacs.d/core/test/helpers.el @@ -0,0 +1,69 @@ +;; -*- lexical-binding: t; no-byte-compile: t; -*- +;;; core/test/helpers.el + +(eval-and-compile + (setq doom-interactive-mode 'test) + (doom-initialize 'force 'noerror) + (require 'buttercup) + (setq split-width-threshold 0 + split-height-threshold 0 + window-min-width 0 + window-min-height 0)) + +;; +;;; Buttercup extensions + +(buttercup-define-matcher-for-binary-function :to-equal-file file-equal-p) + +(buttercup-define-matcher :to-expand-into (form expected) + (cl-destructuring-bind (form expected) + (mapcar #'funcall (list form expected)) + (let ((expanded (macroexpand-1 form))) + (if (equal expanded expected) + (cons t (format "Expected `%S' to not expand to `%S'" + form expected)) + (cons nil (format "Expected `%S' to not expand to `%S', but got `%S' instead" + form expected expanded)))))) + +(buttercup-define-matcher :to-output (form &optional expected-output) + (let ((expected-output (and (functionp expected-output) + (funcall expected-output))) + output) + (with-current-buffer (get-buffer "*Messages*") + (let ((standard-output (current-buffer)) + (start (point))) + (let ((inhibit-message t)) + (funcall form)) + (setq output (buffer-substring-no-properties start (point-max))) + (with-silent-modifications (erase-buffer)))) + (cond ((null expected-output) + (if (string-empty-p output) + (cons nil (format "Expected output %S, but got none" + expected-output)) + (cons t (format "Expected no output, but got %S" + output)))) + ((not (equal expected-output output)) + (cons nil (format "Expected output %S, but got %S instead" + expected-output output))) + ((cons t (format "Expected to not get %S as output" + expected-output)))))) + +(buttercup-define-matcher :to-contain-items (items expected) + (cl-destructuring-bind (items expected) + (mapcar #'funcall (list items expected)) + (if-let (missing (cl-set-difference expected items)) + (cons nil (format "Expected list to contain %S, but it was missing %S" + expected missing)) + (cons t (format "Expected list to not contain %S, but it did: %S" + expected items))))) + + +;; +;;; Helper macros + +(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)))) diff --git a/.emacs.d/core/test/init.el b/.emacs.d/core/test/init.el new file mode 100644 index 0000000..ff1c81b --- /dev/null +++ b/.emacs.d/core/test/init.el @@ -0,0 +1,17 @@ +;;; core/test/init.el -*- lexical-binding: t; no-byte-compile: t; -*- + +;; An init.el for our unit test suites. Do not use this! + +(doom! :completion + company + :ui + doom-dashboard + popup + workspaces + :editor + evil + :tools + pass + :lang + org + web) diff --git a/.emacs.d/core/test/packages.el b/.emacs.d/core/test/packages.el new file mode 100644 index 0000000..a5f2457 --- /dev/null +++ b/.emacs.d/core/test/packages.el @@ -0,0 +1,4 @@ +;; -*- no-byte-compile: t; -*- +;;; core/test/packages.el + +(package! buttercup) diff --git a/.emacs.d/core/test/test-autoload-buffers.el b/.emacs.d/core/test/test-autoload-buffers.el new file mode 100644 index 0000000..f452783 --- /dev/null +++ b/.emacs.d/core/test/test-autoload-buffers.el @@ -0,0 +1,160 @@ +;; -*- no-byte-compile: t; -*- +;;; core/test/test-autoload-buffers.el + +(describe "core/autoload/buffers" + :var (a b c d) + + (require 'core-projects) + (load! "autoload/buffers" doom-core-dir) + + (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 lists" + (describe "doom-buffer-list" + (it "should only see four buffers" + (expect (doom-buffer-list) :to-contain-items (list a b c d))))) + + ;; TODO predicate tests + (xdescribe "predicate functions" + (describe "doom-dired-buffer-p") + (describe "doom-special-buffer-p") + (describe "doom-temp-buffer-p") + (describe "doom-visible-buffer-p") + (describe "doom-buried-buffer-p") + (describe "doom-non-file-visiting-buffer-p") + (describe "doom-dired-buffer-p") + (describe "doom-buffer-frame-predicate")) + + (describe "doom-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-core-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-contain-items (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 "doom-fallback-buffer" + (it "returns a live buffer" + (expect (buffer-live-p (doom-fallback-buffer)))) + + (it "returns the scratch buffer" + (expect (doom-fallback-buffer) :to-equal (get-buffer "*scratch*")))) + + (describe "real buffers" + (before-each + (with-current-buffer b (setq buffer-file-name "x")) + (with-current-buffer c (rename-buffer "*C*"))) + + (describe "doom-mark-buffer-as-real-h" + (with-current-buffer a + (doom-mark-buffer-as-real-h) + (expect (buffer-local-value 'doom-real-buffer-p a)))) + + (describe "doom-set-buffer-real" + (it "sets `doom-real-buffer-p' buffer-locally" + (doom-set-buffer-real a t) + (expect (buffer-local-value 'doom-real-buffer-p a)))) + + (describe "doom-real-buffer-p" + (it "returns t for buffers manually marked real" + (doom-set-buffer-real a t) + (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 "doom-unreal-buffer-p" + (it "returns t for unreal buffers" + (expect (doom-unreal-buffer-p c)) + (expect (doom-unreal-buffer-p d))) + (it "returns nil for real buffers" + (doom-set-buffer-real a t) + (expect (not (doom-unreal-buffer-p a))) + (expect (not (doom-unreal-buffer-p b))))) + + (describe "doom-real-buffer-list" + (it "returns only real buffers" + (expect (doom-real-buffer-list) :to-contain-items (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)) + + (describe "doom-matching-buffers" + (it "can match buffers by regexp" + (expect (doom-matching-buffers "^[ac]$") :to-have-same-items-as (list a c)))) + + (describe "doom-buffers-in-mode" + (it "can match buffers by major-mode" + (expect (doom-buffers-in-mode 'text-mode) :to-have-same-items-as (list b c)))) + + (describe "doom-buried-buffers" + (it "can find all buried buffers" + (expect (doom-buried-buffers) :to-contain-items (list c d)))) + + (describe "doom-visible-buffers" + (it "can find all visible buffers" + (expect (doom-visible-buffers) + :to-have-same-items-as (list a b)))) + + (describe "doom-visible-windows" + (it "can find all visible windows" + (expect (doom-visible-windows) + :to-have-same-items-as + (mapcar #'get-buffer-window (list a b)))))) + + (describe "killing buffers/windows" + (describe "doom-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 + (xdescribe "doom-fixup-windows") + (xdescribe "doom-kill-buffer-fixup-windows") + (xdescribe "doom-kill-buffers-fixup-windows")) + + (xdescribe "commands" + (describe "doom/kill-all-buffers") + (describe "doom/kill-other-buffers") + (describe "doom/kill-matching-buffers") + (describe "doom/kill-buried-buffers") + (describe "doom/kill-project-buffers")))) diff --git a/.emacs.d/core/test/test-autoload-files.el b/.emacs.d/core/test/test-autoload-files.el new file mode 100644 index 0000000..7429eb3 --- /dev/null +++ b/.emacs.d/core/test/test-autoload-files.el @@ -0,0 +1,164 @@ +;; -*- no-byte-compile: t; -*- +;;; core/test/test-autoload-files.el + +(describe "core/autoload/files" + + (load! "autoload/files" doom-core-dir) + + (describe "library" + (describe "file-exists-p!" + (it "is a (quasi) drop-in replacement for `file-exists-p'" + (let ((default-directory doom-emacs-dir) + (init-file "init.el")) + (expect (file-exists-p "init.el")) + (expect (and (file-exists-p! "init.el") + (file-exists-p "init.el"))) + (expect (and (file-exists-p! init-file) + (file-exists-p init-file))) + (expect (and (file-exists-p! doom-emacs-dir) + (file-exists-p doom-emacs-dir))) + (expect (and (not (file-exists-p! "/cant/possibly/exist/please/dont/exist")) + (not (file-exists-p "/cant/possibly/exist/please/dont/exist")))))) + + (it "returns the file path if it exists" + (expect (file-exists-p! "init.example.el" + doom-emacs-dir) + :to-equal (expand-file-name "init.example.el" doom-emacs-dir))) + + (it "understands compound statements" + (let ((default-directory doom-emacs-dir)) + (expect (file-exists-p! (and "init.el" "init.example.el"))) + (expect (file-exists-p! (or "doesnotexist" "init.example.el"))) + (expect (not (file-exists-p! (or "doesnotexist" "DOESNOTEXIST"))))) + (expect (file-exists-p! (and "init.el" "init.example.el") + doom-emacs-dir)) + (expect (file-exists-p! (and "init.el" "init.example.el") + doom-emacs-dir)) + (expect (file-exists-p! (or "doesnotexist" "init.example.el") + doom-emacs-dir)) + (expect (not (file-exists-p! (or "doesnotexist" "DOESNOTEXIST") + doom-emacs-dir)))) + + (it "understands nested compound statements" + (expect (file-exists-p! (and "init.el" "init.example.el" + (or "doesnotexist" "LICENSE")) + doom-emacs-dir)) + (expect (file-exists-p! (and "init.el" "init.example.el" + (and "LICENSE" "README.md" + (or "doesnotexist" + "early-init.el"))) + doom-emacs-dir)) + (expect (file-exists-p! (and "init.el" "init.example.el" + (or "edoesnotexist" "DOESNOTEXIST" + (and "idontexist" + "doanyofusexist?"))) + doom-emacs-dir) + :to-be nil)) + + (it "returns the last form if a compound file check succeeds" + (expect (file-exists-p! (and "init.el" "init.example.el" + (or "doesnotexist" "LICENSE")) + doom-emacs-dir) + :to-equal (expand-file-name "LICENSE" doom-emacs-dir)) + (expect (file-exists-p! (and "init.el" "init.example.el" + (or (or "doesnotexist" "DOESNOTEXIST") + "doanyofusreallyexist" + (or "cantexist" "LICENSE"))) + doom-emacs-dir) + :to-equal (expand-file-name "LICENSE" doom-emacs-dir))) + + (it "disregards the directory argument if given absolute path" + (expect (file-exists-p! "/tmp" "/directory/that/doesnt/exist")) + (expect (file-exists-p! doom-core-dir "/directory/that/doesnt/exist")) + (expect (file-exists-p! (and "/tmp" doom-core-dir) "/directory/that/doesnt/exist")) + (expect (file-exists-p! (or "/tmp" doom-core-dir) "/directory/that/doesnt/exist"))) + + (it "interpolates variables" + (let ((file-1 "init.el") + (file-2 "init.example.el") + (file-3 "LICENSE") + (file-404 "doesnotexistlikenoreally")) + (expect (file-exists-p! file-1 doom-emacs-dir)) + (expect (file-exists-p! (and file-1 file-2) doom-emacs-dir)) + (expect (file-exists-p! (and file-1 (or file-404 file-2)) doom-emacs-dir)) + (expect (file-exists-p! (or (and file-404 file-2) (and file-3 file-1)) + doom-emacs-dir)))) + + (it "interpolates forms" + (cl-letf (((symbol-function 'getfilename) + (lambda () "init.example.el"))) + (expect (file-exists-p! (and (or (if nil "init.el" "doesnotexist") + (getfilename)) + "LICENSE") + doom-emacs-dir) + :to-equal (expand-file-name "LICENSE" doom-emacs-dir))))) + + ;; TODO + (xdescribe "doom-glob") + (xdescribe "doom-path") + (xdescribe "doom-dir") + (xdescribe "doom-files-in") + (xdescribe "doom-file-size") + (xdescribe "doom-directory-size") + (xdescribe "doom-file-cookie-p")) + + (describe "interactive file operations" + :var (src dest projectile-projects-cache-time projectile-projects-cache) + + (require 'core-projects) + (require 'projectile) + + (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)))) + + (xdescribe "sudo {this,find} file" + (before-each + (spy-on 'find-file :and-return-value nil) + (spy-on 'find-alternate-file :and-return-value nil)) + + (describe "doom/sudo-find-file") + (describe "doom/sudo-this-file"))) diff --git a/.emacs.d/core/test/test-autoload-format.el b/.emacs.d/core/test/test-autoload-format.el new file mode 100644 index 0000000..237d263 --- /dev/null +++ b/.emacs.d/core/test/test-autoload-format.el @@ -0,0 +1,44 @@ +;; -*- no-byte-compile: t; -*- +;;; core/test/test-autoload-message.el + +(describe "core/autoload/format" + (describe "format!" + :var (doom-format-backend) + (before-all + (setq doom-format-backend 'ansi)) + + (it "should be a drop-in replacement for `format'" + (expect (format! "Hello %s" "World") + :to-equal "Hello World")) + + (it "supports ansi coloring in noninteractive sessions" + (expect (format! (red "Hello %s") "World") + :to-equal "Hello World")) + + (it "supports text properties in interactive sessions" + (let ((doom-format-backend 'text-properties)) + (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")))) + + (xdescribe "insert!") + (xdescribe "print!") + (xdescribe "print-group!") + (xdescribe "error!") + (xdescribe "user-error!")) diff --git a/.emacs.d/core/test/test-autoload-package.el b/.emacs.d/core/test/test-autoload-package.el new file mode 100644 index 0000000..0292330 --- /dev/null +++ b/.emacs.d/core/test/test-autoload-package.el @@ -0,0 +1,5 @@ +;; -*- no-byte-compile: t; -*- +;;; core/test/test-autoload-package.el +;;;###if nil + +(xdescribe "core/autoload/packages") diff --git a/.emacs.d/core/test/test-core-keybinds.el b/.emacs.d/core/test/test-core-keybinds.el new file mode 100644 index 0000000..52fe723 --- /dev/null +++ b/.emacs.d/core/test/test-core-keybinds.el @@ -0,0 +1,256 @@ +;; -*- no-byte-compile: t; -*- +;;; core/test/test-core-keybinds.el + +(describe "core/keybinds" + (require 'core-keybinds) + + ;; FIXME test against their side effects rather than their implementation + (describe "map!" + :var (doom--map-evil-p states-alist) + (before-each + (setq doom--map-evil-p t + states-alist '((: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 states-alist) + (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 states-alist) + (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)))))))) diff --git a/.emacs.d/core/test/test-core-lib.el b/.emacs.d/core/test/test-core-lib.el new file mode 100644 index 0000000..5391070 --- /dev/null +++ b/.emacs.d/core/test/test-core-lib.el @@ -0,0 +1,277 @@ +;; -*- no-byte-compile: t; -*- +;;; core/test/test-core-lib.el + +(describe "core-lib" + (before-all + (require '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) + (expect (doom-unquote 5) :to-be 5) + (expect (doom-unquote t) :to-be t))) + + (describe "doom-enlist" + (it "returns nil if given nil" + (expect (doom-enlist nil) :to-be nil)) + (it "creates a list out of non-lists" + (expect (doom-enlist 'a) :to-equal '(a))) + (it "returns lists as-is" + (expect (doom-enlist '(a)) :to-equal '(a)))) + + (describe "doom-keyword-intern" + (it "returns a keyword" + (expect (doom-keyword-intern "test") :to-equal :test)) + (it "errors if given anything but a string" + (expect (doom-keyword-intern t) :to-throw 'wrong-type-argument))) + + (describe "doom-keyword-name" + (it "returns the string name of a keyword" + (expect (doom-keyword-name :test) :to-equal "test")) + (it "errors if given anything but a keyword" + (expect (doom-keyword-name "test") :to-throw 'wrong-type-argument))) + + (describe "doom-partial" + (it "returns a closure" + (expect (functionp (doom-partial #'+ 1)))) + (it "returns a partial closure" + (expect (funcall (doom-partial #'+ 1) 2) :to-be 3))) + + (describe "doom-rpartial" + (it "returns a closure" + (expect (functionp (doom-rpartial #'+ 1)))) + (it "returns a partial closure with right-aligned arguments" + (expect (funcall (doom-rpartial #'/ 2) 10) :to-be 5))) + + + ;; --- Sugars ----------------------------- + (describe "lambda!" + (it "returns an interactive function" + (expect (commandp (lambda!))) + (expect (funcall (lambda! 5)) :to-equal 5))) + + (describe "lambda!!" + (it "returns an interactive function with a prefix argument" + (expect (commandp (lambda! #'ignore t))) + (expect (funcall (lambda!! (lambda (arg) + (interactive "P") + arg) + 5)) + :to-equal 5))) + + (describe "file!" + (it "returns the executing file" + (expect (eval-and-compile (file!)) + :to-equal + (eval-and-compile load-file-name)))) + + (describe "dir!" + (it "returns the executing directory" + (expect (eval-and-compile (dir!)) + :to-equal + (eval-and-compile + (directory-file-name (file-name-directory load-file-name)))))) + + (describe "pushnew!" + (it "pushes values onto a list symbol, in order" + (let ((a '(1 2 3))) + (expect (pushnew! a 9 8 7) + :to-equal '(7 8 9 1 2 3)))) + (it "only adds values that aren't already in the list" + (let ((a '(1 symbol 3.14 "test"))) + (expect (pushnew! a "test" 'symbol 3.14 1) + :to-equal '(1 symbol 3.14 "test"))))) + + (describe "prependq!" + (it "prepends a list to a list symbol" + (let ((list '(a b c))) + (expect (prependq! list '(d e f)) + :to-equal '(d e f a b c))))) + + (describe "append!" + (it "appends a list to a list symbol" + (let ((list '(a b c))) + (expect (appendq! list '(d e f)) + :to-equal '(a b c d e f))))) + + (describe "nconcq!" + (it "nconc's a list to a list symbol" + (let ((list '(a b c))) + (expect (nconcq! list '(d e f)) + :to-equal '(a b c d e f))))) + + (describe "delq!" + (it "delete's a symbol from a list" + (let ((list '(a b c))) + (delq! 'b list) + (expect list :to-equal '(a c)))) + (it "delete's an element from an alist by key" + (let ((alist '((a 1) (b 2) (c 3)))) + (delq! 'b alist 'assq) + (expect alist :to-equal '((a 1) (c 3)))))) + + (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 "resolves quoted hooks literally" + (expect '(add-hook! 'fake-mode-hook #'ignore) :to-expand-into + `(add-hook 'fake-mode-hook #'ignore nil nil))) + (it "resolves unquoted modes to their hook variables" + (expect '(add-hook! fake-mode #'ignore) :to-expand-into + `(add-hook 'fake-mode-hook #'ignore nil nil))) + + (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 one-to-many 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! (fake-mode other-mode some-mode) :append #'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! '(fake-mode-hook other-mode-hook some-mode-hook) :append #'(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! 'some-mode-hook :append (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)) + + (it "handles inline defuns as hook symbols" + (add-hook! fake-mode (defun hook-a ())) + (add-hook! 'other-mode-hook + (defun hook-b ()) + (defun hook-c ())) + (expect (car fake-mode-hook) :to-be 'hook-a) + (expect other-mode-hook :to-equal '(hook-b hook-c)))) + + (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)))) + + (describe "(un)setq-hook!" + :var (fake-hook x y z) + (before-each + (setq x 10 y 20 z 30)) + + (it "sets variables buffer-locally" + (setq-hook! 'fake-hook x 1) + (with-temp-buffer + (run-hooks 'fake-hook) + (expect (local-variable-p 'x)) + (expect (= x 1))) + (expect (= x 10))) + + (it "overwrites earlier hooks" + (setq-hook! 'fake-hook x 1 y 0) + (setq-hook! 'fake-hook x 5 y -1) + (with-temp-buffer + (run-hooks 'fake-hook) + (expect (= x 5)) + (expect (= y -1)))) + + (it "unset setq hooks" + (setq-hook! 'fake-hook x 1 y 0) + (unsetq-hook! 'fake-hook y) + (with-temp-buffer + (run-hooks 'fake-hook) + (expect (local-variable-p 'x)) + (expect (= x 1)) + (expect (not (local-variable-p 'y))) + (expect (= y 20)))))) + + (describe "load!" + (before-each + (spy-on 'load :and-return-value t)) + + (it "loads a file relative to the current directory" + (load! "path") + (expect 'load :to-have-been-called) + (expect 'load :to-have-been-called-with + (expand-file-name "path" (eval-when-compile (dir!))) nil 'nomessage)) + + (it "loads a file relative to a specified directory" + (load! "path" doom-etc-dir) + (expect 'load :to-have-been-called-with + (expand-file-name "path" doom-etc-dir) nil 'nomessage))) + + (describe "quiet!" + :var (doom-debug-mode) + (before-each + (setq doom-debug-mode nil)) + + (it "suppresses output from message" + (expect (message "hello world") :to-output "hello world\n") + (expect (message "hello world") :to-output) + (let (doom-interactive-mode) + (expect (quiet! (message "hello world")) :not :to-output)) + (let ((doom-interactive-mode t)) + (expect (quiet! inhibit-message)) + (expect (quiet! save-silently)))) + + (it "suppresses load messages from `load' & `load-file'" + (let ((tmpfile (make-temp-file "test" nil ".el"))) + (with-temp-file tmpfile) + (let (doom-interactive-mode) + (expect (load-file tmpfile) :to-output (format "Loading %s (source)...\n" tmpfile)) + (expect (quiet! (load-file tmpfile)) :not :to-output)) + (delete-file tmpfile))) + + (it "won't suppress output in debug mode" + (let ((doom-debug-mode t) + (tmpfile (make-temp-file "test" nil ".el"))) + (dolist (doom-interactive-mode (list t nil)) + (expect (quiet! (message "hello world")) + :to-output "hello world\n") + (with-temp-file tmpfile) + (expect (quiet! (load-file tmpfile)) + :to-output (format "Loading %s (source)...\n" tmpfile))))))) diff --git a/.emacs.d/core/test/test-core-modules.el b/.emacs.d/core/test/test-core-modules.el new file mode 100644 index 0000000..f8c8966 --- /dev/null +++ b/.emacs.d/core/test/test-core-modules.el @@ -0,0 +1,23 @@ +;; -*- no-byte-compile: t; -*- +;;; core/test/test-core-modules.el + +(xdescribe "core-modules" + (require 'core-modules) + + (describe "doom!") + (describe "doom-modules") + + (describe "doom-module-p") + (describe "doom-module-get") + (describe "doom-module-put") + (describe "doom-module-set") + (describe "doom-module-path") + (describe "doom-module-locate-path") + (describe "doom-module-from-path") + (describe "doom-module-load-path") + + (describe "require!") + (describe "featurep!") + (describe "after!") + (describe "use-package!") + (describe "use-package-hook!")) diff --git a/.emacs.d/core/test/test-core-packages.el b/.emacs.d/core/test/test-core-packages.el new file mode 100644 index 0000000..d4434ad --- /dev/null +++ b/.emacs.d/core/test/test-core-packages.el @@ -0,0 +1,5 @@ +;; -*- no-byte-compile: t; -*- +;;; core/test/test-core-packages.el +;;;###if nil + +(xdescribe "core-packages") diff --git a/.emacs.d/core/test/test-core-projects.el b/.emacs.d/core/test/test-core-projects.el new file mode 100644 index 0000000..c64304f --- /dev/null +++ b/.emacs.d/core/test/test-core-projects.el @@ -0,0 +1,40 @@ +;; -*- no-byte-compile: t; -*- +;;; core/test/test-core-projects.el + +(describe "core/projects" + :var (projectile-enable-caching) + + (require 'core-projects) + (require 'projectile) + + (before-each + (setq projectile-enable-caching nil) + (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-file 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-file + (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"))))))) diff --git a/.emacs.d/core/test/test-core-ui.el b/.emacs.d/core/test/test-core-ui.el new file mode 100644 index 0000000..5079aa9 --- /dev/null +++ b/.emacs.d/core/test/test-core-ui.el @@ -0,0 +1,106 @@ +;; -*- no-byte-compile: t; -*- +;;; ../core/test/test-core-ui.el + +(describe "core/ui" + (before-all + (with-demoted-errors "Import error: %s" + (require 'core-ui))) + + (describe "doom-protect-fallback-buffer-h" + :var (kill-buffer-query-functions) + (before-all + (setq kill-buffer-query-functions '(doom-protect-fallback-buffer-h))) + + (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)))))) + + (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-h) + (add-hook 'focus-in-hook #'doom-run-switch-frame-hooks-h) + (dolist (fn '(switch-to-buffer display-buffer)) + (advice-add fn :around #'doom-run-switch-buffer-hooks-a))) + (after-each + (remove-hook 'buffer-list-update-hook #'doom-run-switch-window-hooks-h) + (remove-hook 'focus-in-hook #'doom-run-switch-frame-hooks-h) + (dolist (fn '(switch-to-buffer display-buffer)) + (advice-remove fn #'doom-run-switch-buffer-hooks-a)) + (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))) + + + (xdescribe "switch-frame" + :var (doom-switch-frame-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)))))) diff --git a/.emacs.d/core/test/test-core.el b/.emacs.d/core/test/test-core.el new file mode 100644 index 0000000..8a0844c --- /dev/null +++ b/.emacs.d/core/test/test-core.el @@ -0,0 +1,127 @@ +;; -*- no-byte-compile: t; -*- +;;; core/test/test-core.el + +(describe "core" + :var (doom-interactive-mode) + (before-each + (setq doom-interactive-mode nil)) + + (describe "initialization" + (describe "doom-initialize" + :var (doom-init-p) + (before-each + (setq doom-init-p nil)) + + (it "initializes once" + (expect (doom-initialize nil 'noerror)) + (expect (not (doom-initialize nil 'noerror))) + (expect (not (doom-initialize nil 'noerror))) + (expect doom-init-p)) + + (it "initializes multiple times, if forced" + (expect (doom-initialize nil 'noerror)) + (expect (not (doom-initialize nil 'noerror))) + (expect (doom-initialize 'force 'noerror))) + + (describe "package initialization" + (before-each + (spy-on 'doom-initialize-packages :and-return-value t)) + + (it "initializes packages if core autoload file doesn't exist" + (let ((doom-autoload-file "doesnotexist")) + (expect (doom-initialize nil 'noerror)) + (expect 'doom-initialize-packages :to-have-been-called)) + + (it "doesn't initialize packages if core autoload file was loaded" + (let ((doom-interactive-mode t)) + (spy-on 'doom-load-autoloads-file :and-return-value t) + (doom-initialize nil 'noerror) + (expect 'doom-load-autoloads-file :to-have-been-called-with doom-package-autoload-file) + (expect 'doom-initialize-packages :to-have-been-called))) + + (it "initializes packages when forced" + (doom-initialize 'force 'noerror) + (expect 'doom-initialize-packages :to-have-been-called))) + + (describe "autoloads files" + (before-each + (spy-on 'doom-load-autoloads-file) + (spy-on 'warn :and-return-value t)) + + (it "loads autoloads files" + (ignore-errors (doom-initialize nil 'noerror)) + (expect 'doom-load-autoloads-file + :to-have-been-called-with doom-autoload-file) + (expect 'doom-load-autoloads-file + :to-have-been-called-with doom-package-autoload-file)) + + (it "throws doom-autoload-error when autoload files don't exist" + (let ((doom-autoload-file "doesnotexist") + (doom-package-autoload-file "doesnotexist")) + (expect (doom-initialize) :to-throw 'doom-autoload-error))))) + + (describe "doom-initialize-core" + (before-each + (spy-on 'require)) + + (it "loads all doom core libraries" + (doom-initialize-core) + (expect 'require :to-have-been-called-with 'core-keybinds) + (expect 'require :to-have-been-called-with 'core-ui) + (expect 'require :to-have-been-called-with 'core-projects) + (expect 'require :to-have-been-called-with 'core-editor)))) + + (describe "doom-load-autoloads-file" + :var (doom-autoload-file doom-alt-autoload-file result) + (before-each + (setq doom-autoload-file (make-temp-file "doom-autoload" nil ".el")) + (with-temp-file doom-autoload-file) + (byte-compile-file doom-autoload-file)) + (after-each + (delete-file doom-autoload-file) + (delete-file (byte-compile-dest-file doom-autoload-file))) + + (it "loads the byte-compiled autoloads file if available" + (doom-load-autoloads-file doom-autoload-file) + (expect (caar load-history) :to-equal-file + (byte-compile-dest-file doom-autoload-file)) + + (delete-file (byte-compile-dest-file doom-autoload-file)) + (doom-load-autoloads-file doom-autoload-file) + (expect (caar load-history) :to-equal-file doom-autoload-file)) + + (it "returns non-nil if successful" + (expect (doom-load-autoloads-file doom-autoload-file))) + + (it "returns nil on failure or error, non-fatally" + (expect (doom-load-autoloads-file "/does/not/exist") :to-be nil))) + + (describe "doom-load-envvars-file" + :var (doom-env-file process-environment) + (before-each + (setq process-environment nil + doom-env-file (make-temp-file "doom-env")) + (with-temp-file doom-env-file + (insert "A=1\nB=2\nC=3\n"))) + (after-each + (delete-file doom-env-file)) + + (it "throws a file-error if file doesn't exist" + (expect (doom-load-envvars-file "/tmp/envvardoesnotexist") + :to-throw 'file-error)) + + (it "to fail silently if NOERROR is non-nil" + (expect (doom-load-envvars-file "/tmp/envvardoesnotexist" 'noerror) + :not :to-throw)) + + (it "returns the new value for `process-environment'" + (expect (doom-load-envvars-file doom-env-file) + :to-have-same-items-as '("A" "B" "C"))) + + (it "alters environment variables" + (dolist (key '("A" "B" "C")) + (expect (getenv key) :not :to-be-truthy)) + (expect (doom-load-envvars-file doom-env-file)) + (expect (getenv "A") :to-equal "1") + (expect (getenv "B") :to-equal "2") + (expect (getenv "C") :to-equal "3")))) diff --git a/.emacs.d/docs/api.org b/.emacs.d/docs/api.org new file mode 100644 index 0000000..7a31a7e --- /dev/null +++ b/.emacs.d/docs/api.org @@ -0,0 +1,569 @@ +#+TITLE: API Demos +#+PROPERTY: header-args:elisp :results pp + +This appendix serves as a reference on how to use Doom Emacs' standard library. +It is integrated into Helpful, in Doom. + +* Table of Contents :TOC_3: +- [[#examples-for-dooms-library][Examples for Doom's library]] + - [[#core-lib][core-lib]] + - [[#add-hook][add-hook!]] + - [[#add-transient-hook][add-transient-hook!]] + - [[#after][after!]] + - [[#appendq][appendq!]] + - [[#custom-set-faces][custom-set-faces!]] + - [[#custom-theme-set-faces][custom-theme-set-faces!]] + - [[#defer-feature][defer-feature!]] + - [[#defer-until][defer-until!]] + - [[#disable-packages][disable-packages!]] + - [[#doom][doom!]] + - [[#file-exists-p][file-exists-p!]] + - [[#lambda][lambda!]] + - [[#lambda-1][lambda!!]] + - [[#letenv][letenv!]] + - [[#load][load!]] + - [[#map][map!]] + - [[#package][package!]] + - [[#pushnew][pushnew!]] + - [[#prependq][prependq!]] + - [[#quiet][quiet!]] + - [[#remove-hook][remove-hook!]] + - [[#setq-hook][setq-hook!]] + - [[#unsetq-hook][unsetq-hook!]] + - [[#use-package][use-package!]] +- [[#interesting-snippets][Interesting snippets]] + - [[#persist-emacs-initial-frame-size-across-sessions][Persist Emacs' initial frame size across sessions]] + - [[#persist-emacs-initial-frame-position-dimensions-andor-full-screen-state-across-sessions][Persist Emacs' initial frame position, dimensions and/or full-screen state across sessions]] + - [[#update-cursor-shape-under-terminal-emacs][Update cursor shape under terminal Emacs]] + +* Examples for Doom's library +** core-lib +*** add-hook! +#+BEGIN_SRC elisp :eval no +;; With only one hook and one function, this is identical to `add-hook'. In that +;; case, use that instead. +(add-hook! 'some-mode-hook #'enable-something) + +;; Adding many-to-many functions to hooks +(add-hook! some-mode #'enable-something #'and-another) +(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) + +;; Appending and local hooks +(add-hook! (one-mode second-mode) :append #'enable-something) +(add-hook! (one-mode second-mode) :local #'enable-something) + +;; With arbitrary forms +(add-hook! (one-mode second-mode) (setq v 5) (setq a 2)) +(add-hook! (one-mode second-mode) :append :local (setq v 5) (setq a 2)) + +;; Inline named hook functions +(add-hook! '(one-mode-hook second-mode-hook) + (defun do-something () + ...) + (defun do-another-thing () + ...)) +#+END_SRC + +*** TODO add-transient-hook! +*** after! +#+BEGIN_SRC elisp :eval no +;;; `after!' will take: + +;; An unquoted package symbol (the name of a package) +(after! helm ...) + +;; An unquoted list of package symbols (i.e. BODY is evaluated once both magit +;; and git-gutter have loaded) +(after! (magit git-gutter) ...) + +;; An unquoted, nested list of compound package lists, using any combination of +;; :or/:any and :and/:all +(after! (:or package-a package-b ...) ...) +(after! (:and package-a package-b ...) ...) +(after! (:and package-a (:or package-b package-c) ...) ...) +;; (Without :or/:any/:and/:all, :and/:all are implied.) + +;; A common mistake is to pass it the names of major or minor modes, e.g. +(after! rustic-mode ...) +(after! python-mode ...) +;; But the code in them will never run! rustic-mode is in the `rustic' package +;; and python-mode is in the `python' package. This is what you want: +(after! rustic ...) +(after! python ...) +#+END_SRC +*** appendq! +#+BEGIN_SRC elisp +(let ((x '(a b c))) + (appendq! x '(c d e)) + x) +#+END_SRC + +#+RESULTS: +: (a b c c d e) + +#+BEGIN_SRC elisp +(let ((x '(a b c)) + (y '(c d e)) + (z '(f g))) + (appendq! x y z '(h)) + x) +#+END_SRC + +#+RESULTS: +: (a b c c d e f g h) + +*** custom-set-faces! +#+BEGIN_SRC elisp :eval no +(custom-set-faces! + '(outline-1 :weight normal) + '(outline-2 :weight normal) + '(outline-3 :weight normal) + '(outline-4 :weight normal) + '(outline-5 :weight normal) + '(outline-6 :weight normal) + '(default :background "red" :weight bold) + '(region :background "red" :weight bold)) + +(custom-set-faces! + '((outline-1 outline-2 outline-3 outline-4 outline-5 outline-6) + :weight normal) + '((default region) + :background "red" :weight bold)) + +(let ((red-bg-faces '(default region))) + (custom-set-faces! + `(,(cl-loop for i from 0 to 6 collect (intern (format "outline-%d" i))) + :weight normal) + `(,red-bg-faces + :background "red" :weight bold))) + +;; If you want to make use of the `doom-themes' package API (e.g. `doom-color', +;; `doom-lighten', `doom-darken', etc.), you must use `custom-set-faces!' +;; *after* the theme has been loaded. e.g. +(load-theme 'doom-one t) +(custom-set-faces! + `(outline-1 :foreground ,(doom-color 'red)) + `(outline-2 :background ,(doom-color 'blue))) +#+END_SRC + +*** custom-theme-set-faces! +#+BEGIN_SRC elisp :eval no +(custom-theme-set-faces! 'doom-one-theme + '(outline-1 :weight normal) + '(outline-2 :weight normal) + '(outline-3 :weight normal) + '(outline-4 :weight normal) + '(outline-5 :weight normal) + '(outline-6 :weight normal) + '(default :background "red" :weight bold) + '(region :background "red" :weight bold)) + +(custom-theme-set-faces! '(doom-one-theme doom-one-light-theme) + '((outline-1 outline-2 outline-3 outline-4 outline-5 outline-6) + :weight normal) + '((default region) + :background "red" :weight bold)) + +(let ((red-bg-faces '(default region))) + (custom-theme-set-faces! '(doom-one-theme doom-one-light-theme) + `(,(cl-loop for i from 0 to 6 collect (intern (format "outline-%d" i))) + :weight normal) + `(,red-bg-faces + :background "red" :weight bold))) + +;; If you want to make use of the `doom-themes' package API (e.g. `doom-color', +;; `doom-lighten', `doom-darken', etc.), you must use `custom-set-faces!' +;; *after* the theme has been loaded. e.g. +(load-theme 'doom-one t) +(custom-theme-set-faces! 'doom-one + `(outline-1 :foreground ,(doom-color 'red)) + `(outline-2 :background ,(doom-color 'blue))) +#+END_SRC + +*** TODO defer-feature! +*** TODO defer-until! +*** disable-packages! +#+BEGIN_SRC elisp :eval no +;; Disable packages enabled by DOOM +(disable-packages! some-package second-package) +#+END_SRC + +*** doom! +#+BEGIN_SRC elisp :eval no +(doom! :completion + company + ivy + ;;helm + + :tools + (:if IS-MAC macos) + docker + lsp + + :lang + (cc +lsp) + (:cond ((string= system-name "work-pc") + python + rust + web) + ((string= system-name "writing-pc") + (org +dragndrop) + ruby)) + (:if IS-LINUX + (web +lsp) + web) + + :config + literate + (default +bindings +smartparens)) +#+END_SRC + +*** file-exists-p! +#+BEGIN_SRC elisp +(file-exists-p! "init.el" doom-emacs-dir) +#+END_SRC + +#+RESULTS: +: /home/hlissner/.emacs.d/init.el + +#+BEGIN_SRC elisp +(file-exists-p! (and (or "doesnotexist" "init.el") + "LICENSE") + doom-emacs-dir) +#+END_SRC + +#+RESULTS: +: /home/hlissner/.emacs.d/LICENSE + +*** lambda! +#+BEGIN_SRC elisp :eval no +(map! "C-j" (lambda! (newline) (indent-according-to-mode))) + +;; The `λ!' short-form alias exists. If you have the snippets module enabled and +;; Doom's default snippets, the 'lam' snippet will expand into 'λ!'. Otherwise, +;; you can use `lambda!'. +(map! "C-j" (λ! (newline) (indent-according-to-mode))) +#+END_SRC +*** lambda!! +When ~newline~ is passed a numerical prefix argument (=C-u 5 M-x newline=), it +inserts N newlines. We can use ~lambda!!~ to easily create a keybinds that bakes +in the prefix arg into the command call: + +#+BEGIN_SRC elisp :eval no +(map! "C-j" (lambda!! #'newline 5)) + +;; The `λ!!' short-form alias exists. If you have the snippets module enabled +;; and Doom's default snippets, a 'lam' snippet is available to expand into +;; 'λ!'. Otherwise, you can use `lambda!!'. +(map! "C-j" (λ!! #'newline 5)) +#+END_SRC + +Or to create aliases for functions that behave differently: + +#+BEGIN_SRC elisp :eval no +(fset 'insert-5-newlines (lambda!! #'newline 5)) + +;; The equivalent of C-u M-x org-global-cycle, which resets the org document to +;; its startup visibility settings. +(fset 'org-reset-global-visibility (lambda!! #'org-global-cycle '(4)) +#+END_SRC +*** letenv! +#+BEGIN_SRC elisp +(letenv! (("SHELL" "/bin/sh")) + (shell-command-to-string "echo $SHELL")) +#+END_SRC + +#+RESULTS: +: "/bin/sh\n" + +*** load! +#+BEGIN_SRC elisp :eval no +;;; Lets say we're in ~/.doom.d/config.el +(load! "lisp/module") ; loads ~/.doom.d/lisp/module.el +(load! "somefile" doom-emacs-dir) ; loads ~/.emacs.d/somefile.el +(load! "anotherfile" doom-private-dir) ; loads ~/.doom.d/anotherfile.el + +;; If you don't want a `load!' call to throw an error if the file doesn't exist: +(load! "~/.maynotexist" nil t) +#+END_SRC + +*** map! +#+BEGIN_SRC elisp :eval no +(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")))) + +(map! (:when (featurep! :completion company) ; Conditional loading + :i "C-@" #'+company/complete + (:prefix "C-x" ; Use a prefix key + :i "C-l" #'+company/whole-lines))) + +(map! (:when (featurep! :lang latex) ; local conditional + (:map LaTeX-mode-map + :localleader ; Use local leader + :desc "View" "v" #'TeX-view)) ; Add which-key description + :leader ; Use leader key from now on + :desc "Eval expression" ";" #'eval-expression) +#+END_SRC + +These are side-by-side comparisons, showing how to bind keys with and without +~map!~: + +#+BEGIN_SRC elisp :eval no +;; bind a global key +(global-set-key (kbd "C-x y") #'do-something) +(map! "C-x y" #'do-something) + +;; bind a key on a keymap +(define-key emacs-lisp-mode (kbd "C-c p") #'do-something) +(map! :map emacs-lisp-mode "C-c p" #'do-something) + +;; unbind a key defined elsewhere +(define-key lua-mode-map (kbd "SPC m b") nil) +(map! :map lua-mode-map "SPC m b" nil) + +;; bind multiple keys +(global-set-key (kbd "C-x x") #'do-something) +(global-set-key (kbd "C-x y") #'do-something-else) +(global-set-key (kbd "C-x z") #'do-another-thing) +(map! "C-x x" #'do-something + "C-x y" #'do-something-else + "C-x z" #'do-another-thing) + +;; bind global keys in normal mode +(evil-define-key* 'normal 'global + (kbd "C-x x") #'do-something + (kbd "C-x y") #'do-something-else + (kbd "C-x z") #'do-another-thing) +(map! :n "C-x x" #'do-something + :n "C-x y" #'do-something-else + :n "C-x z" #'do-another-thing) + +;; or on a deferred keymap +(evil-define-key 'normal emacs-lisp-mode-map + (kbd "C-x x") #'do-something + (kbd "C-x y") #'do-something-else + (kbd "C-x z") #'do-another-thing) +(map! :map emacs-lisp-mode-map + :n "C-x x" #'do-something + :n "C-x y" #'do-something-else + :n "C-x z" #'do-another-thing) + +;; or multiple maps +(dolist (map (list emacs-lisp-mode go-mode-map ivy-minibuffer-map)) + (evil-define-key '(normal insert) map + "a" #'a + "b" #'b + "c" #'c)) +(map! :map (emacs-lisp-mode go-mode-map ivy-minibuffer-map) + :ni "a" #'a + :ni "b" #'b + :ni "c" #'c) + +;; or in multiple states (order of states doesn't matter) +(evil-define-key* '(normal visual) emacs-lisp-mode-map (kbd "C-x x") #'do-something) +(evil-define-key* 'insert emacs-lisp-mode-map (kbd "C-x x") #'do-something-else) +(evil-define-key* '(visual normal insert emacs) emacs-lisp-mode-map (kbd "C-x z") #'do-another-thing) +(map! :map emacs-lisp-mode + :nv "C-x x" #'do-something ; normal+visual + :i "C-x y" #'do-something-else ; insert + :vnie "C-x z" #'do-another-thing) ; visual+normal+insert+emacs + +;; You can nest map! calls: +(evil-define-key* '(normal visual) emacs-lisp-mode-map (kbd "C-x x") #'do-something) +(evil-define-key* 'normal go-lisp-mode-map (kbd "C-x x") #'do-something-else) +(map! (:map emacs-lisp-mode :nv "C-x x" #'do-something) + (:map go-lisp-mode :n "C-x x" #'do-something-else)) +#+END_SRC + +*** package! +#+BEGIN_SRC elisp :eval no +;; To install a package that can be found on ELPA or any of the sources +;; specified in `doom-core-package-sources': +(package! evil) +(package! js2-mode) +(package! rainbow-delimiters) + +;; To disable a package included with Doom (which will no-op all its `after!' +;; and `use-package!' blocks): +(package! evil :disable t) +(package! rainbow-delimiters :disable t) + +;; To install a package from a github repo +(package! so-long :recipe (:host github :repo "hlissner/emacs-so-long")) + +;; If a package is particularly big and comes with submodules you don't need, +;; you can tell the package manager not to clone the repo recursively: +(package! ansible :recipe (:nonrecursive t)) + +;; To install a particular branch, commit or tag: +(package! evil + ;; if :host and :fetcher aren't specified, the package manager will fall back + ;; to evil's default source provided by their (M)ELPA recipes: + :recipe (:commit "e7bc39de2f961505e8e112da8c1b315ae8afce52")) + +(package! evil :recipe (:branch "stable")) + +(package! evil :recipe (:tag "1.2.9")) + +;; If you share your config between two computers, and don't want bin/doom +;; refresh to delete packages used only on one system, use :ignore +(package! evil :ignore (not (equal system-name "my-desktop"))) +#+END_SRC + +*** pushnew! +#+BEGIN_SRC elisp +(let ((list '(a b c))) + (pushnew! list 'c 'd 'e) + list) +#+END_SRC + +#+RESULTS: +: (e d a b c) + +*** prependq! +#+BEGIN_SRC elisp +(let ((x '(a b c))) + (prependq! x '(c d e)) + x) +#+END_SRC + +#+RESULTS: +: (c d e a b c) + +#+BEGIN_SRC elisp +(let ((x '(a b c)) + (y '(c d e)) + (z '(f g))) + (prependq! x y z '(h)) + x) +#+END_SRC + +#+RESULTS: +: (c d e f g h a b c) + +*** quiet! +#+BEGIN_SRC elisp :eval no +;; Enters recentf-mode without extra output +(quiet! (recentf-mode +1)) +#+END_SRC +*** remove-hook! +#+BEGIN_SRC elisp :eval no +;; With only one hook and one function, this is identical to `remove-hook'. In +;; that case, use that instead. +(remove-hook! 'some-mode-hook #'enable-something) + +;; Removing N functions from M hooks +(remove-hook! some-mode #'enable-something #'and-another) +(remove-hook! some-mode #'(enable-something and-another)) +(remove-hook! '(one-mode-hook second-mode-hook) #'enable-something) +(remove-hook! (one-mode second-mode) #'enable-something) + +;; Removing buffer-local hooks +(remove-hook! (one-mode second-mode) :local #'enable-something) + +;; Removing arbitrary forms (must be exactly the same as the definition) +(remove-hook! (one-mode second-mode) (setq v 5) (setq a 2)) +#+END_SRC +*** setq-hook! +#+BEGIN_SRC elisp :eval no +;; Set multiple variables after a hook +(setq-hook! 'markdown-mode-hook + line-spacing 2 + fill-column 80) + +;; Set variables after multiple hooks +(setq-hook! '(eshell-mode-hook term-mode-hook) + hscroll-margin 0) +#+END_SRC + +*** unsetq-hook! +#+BEGIN_SRC elisp :eval no +(unsetq-hook! 'markdown-mode-hook line-spacing) + +;; Removes the following variable hook +(setq-hook! 'markdown-mode-hook line-spacing 2) + +;; Removing N variables from M hooks +(unsetq-hook! some-mode enable-something and-another) +(unsetq-hook! some-mode (enable-something and-another)) +(unsetq-hook! '(one-mode-hook second-mode-hook) enable-something) +(unsetq-hook! (one-mode second-mode) enable-something) +#+END_SRC + +*** use-package! +#+BEGIN_SRC elisp :eval no +;; Use after-call to load package before hook +(use-package! projectile + :after-call (pre-command-hook after-find-file dired-before-readin-hook)) + +;; defer recentf packages one by one +(use-package! recentf + :defer-incrementally easymenu tree-widget timer + :after-call after-find-file) + +;; This is equivalent to :defer-incrementally (abc) +(use-package! abc + :defer-incrementally t) +#+END_SRC +* Interesting snippets +** Persist Emacs' initial frame size across sessions +#+BEGIN_SRC elisp +(let ((display-height (display-pixel-height)) + (display-width (display-pixel-width))) + (add-to-list 'initial-frame-alist + `((left . ,(/ new-frame-width 2)) + (top . ,(/ new-frame-height 2)) + (width . ,(/ display-width 2)) + (height . ,(/ display-height 2))))) +#+END_SRC + +** Persist Emacs' initial frame position, dimensions and/or full-screen state across sessions +#+BEGIN_SRC elisp +;; add to ~/.doom.d/config.el +(when-let (dims (doom-cache-get 'last-frame-size)) + (cl-destructuring-bind ((left . top) width height fullscreen) dims + (setq initial-frame-alist + (append initial-frame-alist + `((left . ,left) + (top . ,top) + (width . ,width) + (height . ,height) + (fullscreen . ,fullscreen)))))) + +(defun save-frame-dimensions () + (doom-cache-set 'last-frame-size + (list (frame-position) + (frame-width) + (frame-height) + (frame-parameter nil 'fullscreen)))) + +(add-hook 'kill-emacs-hook #'save-frame-dimensions) +#+END_SRC + +** Update cursor shape under terminal Emacs +Install [[https://github.com/7696122/evil-terminal-cursor-changer][evil-terminal-cursor-changer]]. The package isn't included in Doom because +it is not maintained, unreasonably buggy, and lacks support for a number of +terminals. Where it fails, it inserts unexpected characters into the buffer. To +uphold the principle of least surprise, an unchanging cursor is less surprising +than unwarranted characters. + +#+BEGIN_SRC elisp +;; ~/.doom.d/packages.el +(package! evil-terminal-cursor-changer) + +;; ~/.doom.d/config.el +(use-package! evil-terminal-cursor-changer + :hook (tty-setup . evil-terminal-cursor-changer-activate)) +#+END_SRC + +Alternatively, an updated version exists at +[[https://github.com/amosbird/evil-terminal-cursor-changer][amosbird/evil-terminal-cursor-changer]], with support for urxvt and tmux. diff --git a/.emacs.d/docs/contributing.org b/.emacs.d/docs/contributing.org new file mode 100644 index 0000000..46feb46 --- /dev/null +++ b/.emacs.d/docs/contributing.org @@ -0,0 +1,170 @@ +#+TITLE: Contributing +#+STARTUP: nofold + +I can't say Doom Emacs is a one man show anymore. It wouldn't have gotten this +far without the help of folks like you! Still, I struggle to maintain it all; +especially the modules I do not use. If Doom has been useful to you, consider +pitching in and helping me out. + +Help can range from reporting bugs, proposing enhancements, submitting code and +documentation, or just spreading the good word. To ensure no toes get stepped on +or wires crossed, this guide was created to help you help us. + +* Table of Contents :TOC_3: +- [[#where-can-i-help][Where can I help?]] +- [[#reporting-issues][Reporting issues]] + - [[#collect-backtraces-of-any-error-messages][Collect backtraces of any error messages]] + - [[#create-a-step-by-step-reproduction-guide][Create a step-by-step reproduction guide]] + - [[#include-information-about-your-doom-install][Include information about your Doom install]] + - [[#debugging-crashes-with-gdb][Debugging crashes with gdb]] +- [[#suggesting-features-keybinds-andor-enhancements][Suggesting features, keybinds and/or enhancements]] +- [[#contributing-code][Contributing code]] + - [[#conventions][Conventions]] + - [[#code-style][Code style]] + - [[#naming-conventions][Naming conventions]] + - [[#commits--prs][Commits & PRs]] + - [[#keybind-conventions][Keybind conventions]] + - [[#your-first-code-contribution][Your first code contribution]] + - [[#submitting-pull-requests][Submitting pull requests]] + - [[#contributing-to-doom-core][Contributing to Doom core]] + - [[#contributing-to-an-existing-module][Contributing to an existing module]] + - [[#contributing-a-new-module][Contributing a new module]] +- [[#contributing-documentation][Contributing documentation]] + - [[#contributing-to-dooms-manual][Contributing to Doom's manual]] + - [[#contributing-module-documentation][Contributing module documentation]] +- [[#other-ways-to-support-doom-emacs][Other ways to support Doom Emacs]] +- [[#special-thanks][Special thanks]] + +* Where can I help? ++ Our [[https://github.com/hlissner/doom-emacs/issues][issue tracker]] has many issues. If you find one that you have an answer to, + please don't hold back! ++ Look for issues tagged [[https://github.com/hlissner/doom-emacs/labels/good%20first%20issue][good first issue]]. These were judged to have a low + barrier of entry. ++ Look for issues tagged [[https://github.com/hlissner/doom-emacs/labels/help%20wanted][help wanted]]. These tend to be a little (or a lot) + harder, and are issues outside my own expertise. ++ If you've encountered a bug, [[https://github.com/hlissner/doom-emacs/issues/new/choose][file a bug report]]. ++ The [[https://github.com/hlissner/doom-emacs/projects/3][development roadmap board]] is a rough timeline of what is being worked on + and when. It will give you some idea of what will change and where you can + redirect your efforts. ++ The [[https://github.com/hlissner/doom-emacs/projects/2][plugins under review board]] lists third party plugins being considered (or + rejected) for inclusion in Doom Emacs. Approved and unclaimed packages are + open for you to implement yourself. ++ The [[https://github.com/hlissner/doom-emacs/projects/5][upstream bugs board]] lists known issues that have external causes, but + affect Doom. If you're feeling adventurous (or are better acquainted with the + cause) perhaps you can address them at the source. + +* TODO Reporting issues +So you've found a problem. Before you fire off that bug report, there are a few +things you should try first: + ++ Make sure your configuration (or Doom Emacs) is *not* byte-compiled. Run ~doom + clean~ to ensure it isn't. *Byte-compilation interferes with debugging!* ++ Run ~bin/doom refresh~ to ensure all plugins are installed and autoload files + generated. ++ Run ~bin/doom doctor~ to diagnose common issues with your system. ++ Check [[file:faq.org::*Common%20Issues][Common Issues]] in the FAQ to see if yours is a known issue. ++ If you happen to know what module(s) are relevant to your issue, check their + documentation (press = h m= to jump to a module's documentation). Your + issue may be documented. ++ If possible, check if the issue can be reproduced in vanilla Emacs (Emacs + without Doom) and/or vanilla Doom (Doom without your private config). To test + this, use ~M-x doom/sandbox~ (bound to = h E=). [[file:getting_started.org::*Use the sandbox][A guide for using the + sandbox can be found in the manual]]. ++ Make sure your issue hasn't already been reported by searching the [[https://github.com/hlissner/doom-emacs/issues][issue + tracker]]. ++ Make sure your issue hasn't been resolved on the =develop= branch of Doom. + +If these suggestions haven't worked for you, it's time [[https://github.com/hlissner/doom-emacs/issues/new/choose][to write a bug report]]. +Please make sure of the following before you submit: + +** TODO Collect backtraces of any error messages + +** TODO Create a step-by-step reproduction guide + +** TODO Include information about your Doom install + +** TODO Debugging crashes with gdb + +* TODO Suggesting features, keybinds and/or enhancements + +* TODO Contributing code +There's much to be done around here! We need bugfixes, new features, and +documentation. If you'd like to convert some caffeine into Emacs Lisp, here are +a few considerations before starting that PR: + +** TODO Conventions +*** TODO Code style +Doom conforms to [[https://github.com/bbatsov/emacs-lisp-style-guide][@bbatsov's emacs-lisp style guide]] with the following +exceptions: + ++ Use ~mapc~ instead of ~seq-do~. ++ No hanging parentheses ++ We use =DEPRECATED= to indicate code that will eventually be removed. + +*** Naming conventions +Doom has a number of naming conventions that it uses in addition to the standard +lisp conventions. Third party packages may use their own conventions as well. + +**** Lisp Naming Conventions +The lisp conventions are simple. Symbols follow ~NAMESPACE-SYMBOLNAME~ for +public variables/functions (e.g. ~bookmark-default-file~ or +~electric-indent-mode~) and ~NAMESPACE--SYMBOLNAME~ for private ones (e.g. +~byte-compile--lexical-environment~ and ~yas--tables~). + +~NAMESPACE~ is usually the name of the containing file or package. E.g. the +~company~ plugin prefixes all its variables/functions with ~company-~. + +**** Doom Naming Conventions ++ ~doom/NAME~ or ~+MODULE/NAME~ :: Denotes a public command designed to be used + interactively, via =M-x= or a keybinding. e.g. ~doom/info~, ~+popup/other~, + ~+ivy/rg~. ++ ~doom:NAME~ :: A public evil operator, motion or command. e.g. ~+evil:align~, + ~+ivy:rg~. ++ ~doom-[-]NAME-h~ or ~+MODULE-[-]NAME-h~ :: A non-interactive function meant to + be used (exclusively) as a hook. e.g. ~+cc-fontify-constants-h~, + ~+flycheck-buffer-h~. ++ ~doom-[-]NAME-a~ or ~+MODULE-[-]NAME-a~ :: Functions designed to be used as + advice for other functions. e.g. ~doom-set-jump-a~, + ~doom--fix-broken-smie-modes-a~, ~+org--babel-lazy-load-library-a~ ++ ~doom-[-]NAME-fn~ or ~+MODULE-[-]NAME-fn~ :: Indicates an [[https://en.wikipedia.org/wiki/Strategy_pattern][strategy]] function. A + good rule of thumb for what makes a strategy function is: is it + interchangeable? Can it be replaced with another function with a matching + signature? e.g. ~+lookup-dumb-jump-backend-fn~, ~+magit-display-buffer-fn~, + ~+workspaces-set-project-action-fn~ ++ ~abc!~ :: A public Doom "autodef" function or macro. An autodef should always + be defined, even if its containing module is disabled (i.e. they will not + throw a void-function error). The purpose of this is to avoid peppering module + configs with conditionals or `after!` blocks before using their APIs. They + should noop if their module is disabled, and should be zero-cost in the case + their module is disabled. + + Autodefs usually serve to configure Doom or a module. e.g. ~after!~, + ~set-company-backends!~, ~set-evil-initial-state!~ + +*** TODO Commits & PRs ++ Target =develop= instead of =master=. The only exception are hotfixes! + +*** TODO Keybind conventions + +** TODO Your first code contribution + +** TODO Submitting pull requests + +** TODO Contributing to Doom core + +** TODO Contributing to an existing module + +** TODO Contributing a new module + +* TODO Contributing documentation +Doom Emacs' documentation is an ongoing effort. If you have suggestions, +improvements, tutorials and/or articles to submit, don't hesitate to get in +contact via our [[https://discord.gg/bcZ6P3y][Discord server]] or [[mailto:henrik@lissner.net][email]]. I appreciate any help I can get! + +** TODO Contributing to Doom's manual + +** TODO Contributing module documentation + +* TODO Other ways to support Doom Emacs + +* TODO Special thanks diff --git a/.emacs.d/docs/faq.org b/.emacs.d/docs/faq.org new file mode 100644 index 0000000..5a3a92c --- /dev/null +++ b/.emacs.d/docs/faq.org @@ -0,0 +1,1131 @@ +#+TITLE: Frequently Asked Questions +#+STARTUP: nofold + +* Table of Contents :TOC: +- [[#general][General]] + - [[#why-is-it-called-doom][Why is it called Doom?]] + - [[#does-doom-work-on-windows][Does Doom work on Windows?]] + - [[#is-doom-only-for-vimmers][Is Doom only for vimmers?]] + - [[#i-am-a-beginner-can-i-use-doom][I am a beginner. Can I use Doom?]] + - [[#how-does-doom-compare-to-spacemacs][How does Doom compare to Spacemacs?]] + - [[#why-such-a-complicated-package-management-system][Why such a complicated package management system?]] + - [[#how-does-doom-start-up-so-quickly][How does Doom start up so quickly?]] + - [[#why-is-startup-time-important-why-not-use-the-daemon][Why is startup time important? Why not use the daemon?]] + - [[#how-do-i-use-doom-alongside-other-emacs-configs][How do I use Doom alongside other Emacs configs?]] + - [[#why-should-i-use-doom-instead-of-rolling-my-own-config][Why should I use Doom instead of rolling my own config?]] + - [[#what-is-the-meaning-behind-dooms-naming-conventions][What is the meaning behind Doom's naming conventions?]] + - [[#how-can-i-contribute-tosupport-doom][How can I contribute to/support Doom?]] + - [[#what-version-of-doom-am-i-running][What version of Doom am I running?]] +- [[#configuration][Configuration]] + - [[#should-i-fork-doom-to-customize-it][Should I fork Doom to customize it?]] + - [[#how-do-i-configure-doom-emacs][How do I configure Doom Emacs?]] + - [[#how-do-i-enable-or-disable-a-doom-module][How do I enable or disable a Doom module?]] + - [[#how-do-i-install-a-package-from-elpa][How do I install a package from ELPA?]] + - [[#how-do-i-install-a-package-from-githubanother-source][How do I install a package from github/another source?]] + - [[#how-do-i-change-where-an-existing-package-is-installed-from][How do I change where an existing package is installed from?]] + - [[#how-do-i-disable-a-package-completely][How do I disable a package completely?]] + - [[#how-do-i-reconfigure-a-package-included-in-doom][How do I reconfigure a package included in Doom?]] + - [[#how-do-i-change-the-theme][How do I change the theme?]] + - [[#how-do-i-change-the-fonts][How do I change the fonts?]] + - [[#how-do-i-bind-my-own-keys-or-change-existing-ones][How do I bind my own keys (or change existing ones)?]] + - [[#how-do-i-get-motions-to-treat-underscores-as-word-delimiters][How do I get motions to treat underscores as word delimiters?]] + - [[#how-do-i-change-the-leaderlocalleader-keys][How do I change the leader/localleader keys?]] + - [[#how-do-i-change-the-style-of-line-numbers-or-disable-them-altogether][How do I change the style of line-numbers (or disable them altogether)?]] + - [[#how-do-i-change-the-behavior-and-appearance-of-popup-windows][How do I change the behavior and appearance of popup windows?]] + - [[#can-doom-be-customized-without-restarting-emacs][Can Doom be customized without restarting Emacs?]] + - [[#can-vimevil-be-removed-for-a-more-vanilla-emacs-experience][Can Vim/Evil be removed for a more vanilla Emacs experience?]] + - [[#should-i-use-make-or-bindoom][Should I use ~make~ or ~bin/doom~?]] + - [[#when-should-and-shouldnt-i-use-bindoom][When should and shouldn't I use ~bin/doom~?]] + - [[#when-to-run-doom-refresh][When to run ~doom refresh~]] + - [[#how-to-suppress-confirmation-prompts-while-bindoom-is-running][How to suppress confirmation prompts while ~bin/doom~ is running]] +- [[#defaults][Defaults]] + - [[#why-ivy-over-helm][Why Ivy over Helm?]] + - [[#why-are-there-no-default-keybinds-for-smartparens-for-evil-users][Why are there no default keybinds for Smartparens (for evil users)?]] + - [[#why-do-non-evil-users-get-expand-region-but-not-evil-users][Why do non-evil users get expand-region, but not evil users?]] + - [[#why-not-use-exec-path-from-shell-instead-of-doom-env][Why not use exec-path-from-shell instead of ~doom env~?]] + - [[#why-wsbutler-over-delete-trailing-whitespace-or-whitespace-cleanup][Why wsbutler over delete-trailing-whitespace or whitespace-cleanup?]] +- [[#common-issues][Common Issues]] + - [[#i-get-the-vanilla-emacs-splash-screen-at-startup][I get the vanilla Emacs splash screen at startup]] + - [[#i-see-a-blank-scratch-buffer-at-startup][I see a blank scratch buffer at startup]] + - [[#strange-or-incorrect-icons-are-displayed-everywhere][Strange (or incorrect) icons are displayed everywhere]] + - [[#void-variable-and-void-function-errors-on-startup][~void-variable~ and ~void-function~ errors on startup]] + - [[#doom-cant-find-my-executablesdoesnt-inherit-the-correct-path][Doom can't find my executables/doesn't inherit the correct ~PATH~]] + - [[#theres-artefacting-on-my-icon-fonts-in-gui-emacs-956][There's artefacting on my icon fonts in GUI Emacs (#956)]] + - [[#the-s-and-s-keys-dont-behave-like-they-do-in-vimevil-1307][The =s= and =S= keys don't behave like they do in vim/evil (#1307)]] + - [[#changes-to-my-config-arent-taking-effect][Changes to my config aren't taking effect]] + - [[#the-frame-goes-black-on-macos-while-in-full-screen-mode][The frame goes black on MacOS, while in full-screen mode]] + - [[#doom-crashes-when][Doom crashes when...]] + - [[#cant-load-my-theme-unable-to-find-theme-file-for-x-errors][Can't load my theme; ~unable to find theme file for X~ errors]] + - [[#tramp-connections-hang-forever-when-connecting][TRAMP connections hang forever when connecting]] +- [[#contributing][Contributing]] + +* General +** Why is it called Doom? +An homage to idsoftware's classic game, whose open sourced code was my first +exposure to programming. + +Also, if you're obsessed enough with a text editor that you write a community +config for it, you're doomed from the get go. + +** Does Doom work on Windows? +Windows support is weak and will generally lag behind Linux/MacOS support, so +your mileage will vary. However, many have reported success installing Doom +Emacs on Windows (using WSL, WSL2 or scoop/chocolatey). You'll find install +instructions for Windows in the [[file:getting_started.org::On Windows][Getting Starting guide]]. + +If you're a Windows user, help us improve our documentation on Windows support! + +** Is Doom only for vimmers? +Henrik is a dyed-in-the-wool vimmer with more than a decade of vim muscle +memory. Vim's is the only paradigm he truly knows, so vimmers will always be his +primary audience. + +That's not to say Doom won't work without evil, only that it is less polished in +that respect. Our growing non-evil userbase are slowly improving the situation +however. We welcome suggestions and PRs to help accommodate a non-evil workflow. + +If you'd still like a go at it, see the [[file:../modules/editor/evil/README.org::Removing%20evil-mode][Removing evil-mode]] section in the +[[file:../modules/editor/evil/README.org][:editor evil]] module's documentation. + +** I am a beginner. Can I use Doom? +If you're new to the terminal, to programming, or Emacs and/or vim, Doom (or +Emacs, for that matter) is a rough place to start. Neither Doom nor Emacs are +particularly beginner friendly. That's not to say it's impossible, or that we +won't help you if you ask, but expect a hefty commitment and a bumpy journey. + +Remember to check out the [[file:index.org][Documentation]] for a guide to getting started. + +** How does Doom compare to Spacemacs? +To paraphrase (and expand upon) a [[https://www.reddit.com/r/emacs/comments/6pa0oq/quickstart_tutorial_for_emacs_newbies_with_doom/dkp1bhd/][reddit answer]] to this question by [[https://github.com/gilbertw1][@gilbertw1]]: + ++ *Doom is lighter than Spacemacs.* Doom starts up faster and is better + optimized, but Spacemacs has more features. ++ *Doom is thinner than Spacemacs.* There are fewer abstractions between you and + vanilla Emacs, and what abstractions do exist are thin by design. This means + there's less to understand and it's easier to hack. ++ *Doom is much more opinionated than Spacemacs.* Doom does not strive to be a + one-size-fits-all, beginner-friendly solution, nor is it configured by + consensus. It is [mostly] the work of one developer and caters to his + vim-slanted tastes. Doom's defaults enforce very particular (albeit optional) + workflows. ++ *Doom lacks manpower.* Bugs stick around longer, documentation is light and + development is at the mercy of it's single maintainer's schedule, health and + whims. ++ *Doom is not beginner friendly.* Spacemacs works out of the box. Your mileage + may vary with Doom; assembly is required! Familiarity with Emacs Lisp (or + programming in general), git and the command line will go a long way to ease + you into Doom. ++ *Doom manages its packages outside of Emacs.* Spacemacs installs (and checks + for packages) on startup or on demand. Doom leaves package management to be + done externally, through the ~bin/doom~ script. This allows for package + management to be scripted on the command line and enables a number of startup + optimizations we wouldn't have otherwise. + +** Why such a complicated package management system? +Doom had +four+ *five* goals for its package management system: + +1. *Scriptability:* package management should be shell-scriptable, so updating + can be automated. +2. *Reach:* allow users to install packages from sources other than ELPA (like + github or gitlab), and from specific commits, branches or tags. Some plugins + are out-of-date through official channels, have changed hands, have a + superior fork, or aren't available in ELPA repos. +3. *Performance:* lazy-loading the package management system is a tremendous + boon to start up speed. Initializing package.el and quelpa (and/or checking + that your packages are installed) every time you start up is expensive. +4. *Organization:* an Emacs configuration grows so quickly, in complexity and + size. A clear separation of concerns (configuration of packages from their + installation) is more organized. +5. *Reproducibility:* /This goal hasn't been implemented yet/, but all our work + up until now is aimed at this goal. Emacs is a tumultuous ecosystem; packages + break left and right, and we rely on hundreds of them. Eventually, we want + package versions to be locked to versions of Doom so that Doom installs are + reproducible. + +** How does Doom start up so quickly? +Doom employs a number of techniques to cut down startup time. Here are its most +effective techniques: + +*** Avoid garbage collection at startup +The GC can easily double startup time, so we suppress it at startup by turning +up ~gc-cons-threshold~ (and perhaps ~gc-cons-percentage~) temporarily: + +#+BEGIN_SRC emacs-lisp +(setq gc-cons-threshold most-positive-fixnum ; 2^61 bytes + gc-cons-percentage 0.6) + +;; ... your emacs config here ... +#+END_SRC + +However, it is important to reset it eventually (as late as possible). Not doing +so will cause garbage collection freezes during long-term interactive use. +Conversely, a ~gc-cons-threshold~ that is too small will cause stuttering. We +use 16mb as our default. + +#+BEGIN_SRC emacs-lisp +(add-hook 'emacs-startup-hook + (lambda () + (setq gc-cons-threshold 16777216 ; 16mb + gc-cons-percentage 0.1))) +#+END_SRC + +It may also be wise to raise ~gc-cons-threshold~ while the minibuffer is active, +so the GC doesn't slow down expensive commands (or completion frameworks, like +helm and ivy). Here is how Doom does it: + +#+BEGIN_SRC emacs-lisp +(defun doom-defer-garbage-collection-h () + (setq gc-cons-threshold most-positive-fixnum)) + +(defun doom-restore-garbage-collection-h () + ;; Defer it so that commands launched immediately after will enjoy the + ;; benefits. + (run-at-time + 1 nil (lambda () (setq gc-cons-threshold doom-gc-cons-threshold)))) + +(add-hook 'minibuffer-setup-hook #'doom-defer-garbage-collection-h) +(add-hook 'minibuffer-exit-hook #'doom-restore-garbage-collection-h) +#+END_SRC + +*** Unset ~file-name-handler-alist~ temporarily +Emacs consults this variable every time a file is read or library loaded, or +when certain functions in the file API are used (like ~expand-file-name~ or +~file-truename~). + +They do so to check if a special handler is needed to read it, but none of these +handlers are necessary for the initialization work we do at startup, so it is +generally safe to disable it (temporarily!): + +#+BEGIN_SRC emacs-lisp +(defvar doom--file-name-handler-alist file-name-handler-alist) +(setq file-name-handler-alist nil) + +;; ... your whole emacs config here ... + +;; Then restore it later: +(setq file-name-handler-alist doom--file-name-handler-alist) + +;; Alternatively, restore it even later: +(add-hook 'emacs-startup-hook + (lambda () + (setq file-name-handler-alist doom--file-name-handler-alist))) +#+END_SRC + +It is important to restore this variable, otherwise you won't be able to use +TRAMP and Emacs will be unable to read compressed/encrypted files. + +*** Cut down on ~load-path~ lookups +Each ~load~ and ~require~ call (without an second argument) costs an O(n) lookup +on ~load-path~. The average Doom config has approximately 260 packages including +dependencies, and around 40 built-in packages. That means a minimum of 300 +entries in ~load-path~ with a worst case of =n=300= for /each/ package load (but +realistically, =n= will be somewhere between =2= and =20=). + +The cost isn't great, but it does add up. There isn't much to do about this, +except be mindful of it where we can: + ++ Paths in Doom's autoloads file are replaced with absolute ones, thus incurring + no lookup cost to lazy load them. ++ The ~load!~ macro is used instead of ~require~ where possible. This builds + paths with string concatenation (which is baked in at compile time, removing + most of the associated cost). ++ ~load-path~ is let-bound to a subset of itself where possible (the + ~doom--initial-load-path~ variable contains the value of ~load-path~ before it + was touched by Doom). + +*** Concatenate package autoloads +When you install a package, a PACKAGE-autoloads.el file is generated. This file +contains a map of autoloaded functions and snippets declared by the package +(that's what those ~;;;###autoload~ comments are for in packages). They tell +Emacs where to find them, when they are eventually called. In your conventional +Emacs config, every single one of these autoloads files are loaded immediately +at startup. + +Since you'll commonly have hundreds of packages, loading hundreds of autoloads +file can hurt startup times. We get around this by concatenating these autoloads +files into one giant one (in =~/.emacs.d/.local/autoloads.pkg.el=) when you run +~doom refresh~. + +Emacs 27+ will introduce a ~package-quickstart~ feature that will do this for +you -- the =straight= package manager does this for you too -- but Doom Emacs +has its own specialized mechanism for doing this, and has tacked a number of +Doom-specific optimizations on top of it. + +*** Lazy load package management system(s) +Initializing package.el or straight.el at startup is expensive. We can save some +time by delaying that initialization until we actually need these libraries (and +only eagerly load them when we're doing package management, e.g. when we run +~doom refresh~). + +Among other things, ~doom refresh~ does a lot for us. It generates concatenated +autoloads files; caches expensive variables like caches ~load-path~, +~Info-directory-list~ and ~auto-mode-alist~; and preforms all your package +management activities there -- far away from your interactive sessions. + +How exactly Doom accomplishes all this is a little complex, so instead, here is +a boiled-down version you can use in your own configs (for package.el, not +straight.el): + +#+BEGIN_SRC emacs-lisp +(defvar cache-file "~/.emacs.d/cache/autoloads") + +(defun initialize () + (unless (load cache-file t t) + (setq package-activated-list nil) + (package-initialize) + (with-temp-buffer + (cl-pushnew doom-core-dir load-path :test #'string=) + (dolist (spec package-alist) + (when-let (desc (cdr spec)) + (let ((file (concat (package--autoloads-file-name desc) ".el"))) + (when (file-readable-p file) + ;; Ensure that the contents of this autoloads file believes they + ;; haven't been moved: + (insert "(let ((load-file-name " (prin1-to-string (abbreviate-file-name file)) "))\n") + (insert-file-contents file) + (save-excursion + ;; Delete forms that modify `load-path' and `auto-mode-alist', we + ;; will set them once, later. + (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))) + ;; Remove unnecessary comment lines and (provide ...) forms + (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"))))) + (prin1 `(setq load-path ',load-path + auto-mode-alist ',auto-mode-alist + Info-directory-list ',Info-directory-list) + (current-buffer)) + (write-file (concat cache-file ".el")) + (byte-compile-file cache-file)))) + +(initialize) +#+END_SRC + +You'll need to delete ~cache-files~ any time you install, remove, or update a +new package, however. In that case you could advise ~package-install~ and +~package-delete~ to call ~initialize~ when they succeed. Or, you could make +~initialize~ interactive and call it manually when you determine it's necessary. +Up to you! + +Note: package.el is sneaky, and will initialize itself if you're not careful. +*Not on my watch, criminal scum!* + +#+BEGIN_SRC emacs-lisp +;; in ~/.emacs.d/init.el (or ~/.emacs.d/early-init.el in Emacs 27) +(setq package-enable-at-startup nil ; don't auto-initialize! + ;; don't add that `custom-set-variables' block to my init.el! + package--init-file-ensured t) +#+END_SRC + +*** Lazy load more than everything +~use-package~ can defer your packages. Using it is a no-brainer, but Doom goes a +little further with lazy loading. There are some massive plugins out there. For +many of them, ordinary lazy loading techniques simply don't work. To name a few: + ++ The =lang/org= module defers loading babel packages until their src blocks are + executed. You no longer need ~org-babel-do-load-languages~ in your config. ++ Company and yasnippet are loaded as late as possible (waiting until the user + opens a non-read-only, file-visiting buffer (that isn't in fundamental-mode)). ++ The =evil-easymotion= package has many keybinds. You'd need to load the + package for them to all take effect, so instead, =gs= is bound to a command + that loads the package and then invisibly populates =gs=, then simulates the + =gs= keypress as though those new keys had always been there. ++ A number of packages are "incrementally" loaded. This is a Doom feature where, + after a few seconds of idle time post-startup, Doom will load packages + piecemeal while Emacs. It will quickly abort if it detects input, as to make + the process as subtle as possible. + + For example, instead of loading =org= (a giant package), it will load these + dependencies, one at a time, before finally loading =org=: + + #+BEGIN_SRC elisp + (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-agenda + org-capture) + #+END_SRC + + This ensures packages load as quickly as possible when you first load an org + file. + +*** +Exploit byte-compilation!+ +It used to be that byte-compilation bought a 40-60% improvement in startup +times, because expensive operations (like ~package-initialize~ or +~exec-path-from-shell~) were evaluated at compile time, but Doom has changed. + +I've since adopted a pre-cache approach (when running ~doom refresh~), which +brings these startup benefits to uncompiled Emacs. This renders byte-compilation +significantly less beneficial for startup time. + +That said, compilation will still benefit Doom's snappiness in general. + +Run ~doom compile :core~ to only compile Doom's core files, or ~doom compile~ to +compile the /entire/ config (=~/.emacs.d= and =~/.doom.d=) -- which may take a +while. + +*** Use [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Lexical-Binding.html][lexical-binding]] everywhere +Add ~;; -*- lexical-binding: t; -*-~ to the top of your elisp files. This can +break code if you've written it to depend on undeclared dynamic variables, but +I've designed Doom not to. + +This buys a small improvement in performance, but every little bit helps. You'll +find more about it in: + ++ [[http://nullprogram.com/blog/2017/01/30/]["How to Write Fast(er) Emacs Lisp."]] ++ [[http://nullprogram.com/blog/2016/12/22/]["Some Performance Advantages of Lexical Scope."]] + +** Why is startup time important? Why not use the daemon? +One of my motivations for a config that starts up fast (aside from the learning +experience) was to shape Emacs into a viable alternative to vim for one-shot +editing in the terminal (without ~-Q~). This also facilitates: + +- Running multiple, independent instances of Emacs (e.g. on a per-project basis, or + for nix-shell users, or to isolate one instance for IRC from an instance for + writing code, etc). +- Quicker restarting of Emacs, to reload package settings or recover from + disastrous errors which can leave Emacs in a broken state. +- Faster integration with "edit in Emacs" solutions (like [[https://github.com/alpha22jp/atomic-chrome][atomic-chrome]]), and + the potential to use them without a running daemon. + +What's more, I don't like using more tools than I need. We should not need a +second program just to make the first run comfortably. + +** How do I use Doom alongside other Emacs configs? +I recommend [[https://github.com/plexus/chemacs][Chemacs]]. You can think of it as a bootloader for Emacs. You'll [[file:getting_started.org::*Alongside other Emacs configs (with Chemacs)][find +instructions on how to use it with Doom in the user manual]]. + +If you only want to try it out without affecting your current config, it is safe +to install Doom anywhere you like. The ~bin/doom~ utility will only address the +config the script is located under. + +You'll still need a separate folder for personal configuration (=~/.doom.d= or +=~/.config/doom= by default), but the =-p PATH= flag (or ~DOOMDIR~ environment +variable) will allow you to use a different location: + +#+BEGIN_SRC bash +# First install Doom somewhere +git clone https://github.com/hlissner/doom-emacs ~/fakehome/doom-emacs +# Then create a place to store our private doom configs. The bin/doom script +# recognizes the DOOMDIR environment variable. +export DOOMDIR=~/fakehome/doom-emacs-config +mkdir -p "$DOOMDIR" + +# Set up Doom for the first time; this may take a while +cd ~/fakehome/doom-emacs +bin/doom install + +# then launch Doom Emacs from this folder with: +bin/doom run +#+END_SRC + +#+begin_quote +Warning: the way ~bin/doom run~ starts Doom bypasses many of its startup +optimizations. Treat it as a convenience for testing Doom, rather than a +permanent entry point. +#+end_quote + +** Why should I use Doom instead of rolling my own config? +If you care about personalizing the software you use on a daily basis, even half +as much as I do, then you probably need professional help, but you also know it +is time consuming. Emacs out-of-the-box is a barren wasteland with archaic +defaults. Building anything out here and getting a feel for it will take /a lot/ +of time. Time that I've already wasted and can never get back. + +Time you could otherwise spend attending your daughter's dance recitals, that +baseball game your son's team almost won last Thursday, or answering the court +summons to fight for custody of your kids. + +Also, Doom's fast yo. + +** What is the meaning behind Doom's naming conventions? +You'll find [[file:contributing.org::*Conventions][an overview of Doom's code conventions]] in the [[file:contributing.org][contributing guide]]. + +** How can I contribute to/support Doom? +Take a look at the [[file:contributing.org][Contributing guide]]. + +** What version of Doom am I running? +You'll find the current version displayed in the modeline on the dashboard. It +can also be retrieved using ~M-x doom/version~ (bound to =SPC h d v= by default) +or ~doom info~ on the command line. + +* Configuration +** Should I fork Doom to customize it? +No. Not unless you have a good reason for doing so (and you're comfortable with +the git-rebase workflow). Your customization can be relegated to =~/.doom.d/= +(or =~/.config/doom/=) entirely. + +If you /must/ modify Doom proper to get something done, it's a code smell. + +Visit the [[file:getting_started.org::*Customize][Customize section]] of [[file:getting_started.org][the Getting Started guide]] for details on how to +do this. + +** How do I configure Doom Emacs? +Canonically, your private config is kept in =~/.doom.d/= or =~/.config/doom/=. +Doom will prioritize =~/.config/doom=, if it exists. This directory is referred +to as your ~$DOOMDIR~. + +Your private config is typically comprised of an =init.el=, =config.el= and +=packages.el= file. Put all your config in =config.el=, install packages by +adding ~package!~ declarations to =packagse.el=, and enable/disable modules in +you ~doom!~ block, which should have been created in your =init.el= when you +first ran ~doom install~. + +Check out the [[file:getting_started.org::Customize][Customize section]] in the [[file:getting_started.org][Getting Started]] guide for details. + +** How do I enable or disable a Doom module? +You'll find your ~doom!~ block in =~/.doom.d/init.el=. This block contains a +list of modules you want enabled and what order to load them in. Disable modules +by commenting them out with semicolons. To enable them, remove those leading +semicolons: + +#+BEGIN_SRC emacs-lisp +(doom! :lang + python ; this is enabled + ;;ruby ; this is disabled + rust) +#+END_SRC + +Remember to run ~bin/doom refresh~ afterwards, on the command line, to sync your +module list with Doom. + +You can find a comprehensive list of modules in the [[file:index.org::*Module list][Module Index]]. + +** How do I install a package from ELPA? +Add a ~package!~ declaration to =~/.doom.d/packages.el= for each package you +want installed. + +#+BEGIN_SRC elisp +(package! winum) +#+END_SRC + +Remember to run ~doom refresh~ afterwards to ensure the package is installed. + +You'll find more information in the "[[file:getting_started.org::*Installing%20packages][Installing packages]]" section of the [[file:getting_started.org][Getting +Started]] guide. + +** How do I install a package from github/another source? +The ~package!~ macro can be passed a MELPA style recipe, allowing you to install +packages from just about anywhere: + +#+BEGIN_SRC elisp +(package! evil :recipe (:host github :repo "hlissner/my-evil-fork")) +#+END_SRC + +Remember to run ~doom refresh~ every time you modify you package list, to ensure +your packages are set up and installed. + +You can find more information about the recipe format [[https://github.com/raxod502/straight.el#the-recipe-format][in the straight.el package +readme]]. + +#+begin_quote +If a MELPA recipe exists for the package you are writing a ~package!~ +declaration for, you may omit keywords and Doom's package manager will fill them +in with values from its original recipe. +#+end_quote + +You'll find more information in the "[[file:getting_started.org::*Installing%20packages%20from%20external%20sources][Installing packages from external sources]]" +section of the [[file:getting_started.org][Getting Started]] guide. + +** How do I change where an existing package is installed from? +~package!~ declarations in your private =packages.el= file have precedence over +modules (even your own). Simply add a new one for that package with the new +recipe. + +You'll find more information in the "[[file:getting_started.org::*Changing%20a%20built-in%20recipe%20for%20a%20package][Changing a built-in recipe for a package]]" +section of the [[file:getting_started.org][Getting Started]] guide. + +** How do I disable a package completely? +With the ~package!~ macro's ~:disable~ property: + +#+BEGIN_SRC elisp +;;; add to DOOMDIR/packages.el +(package! irony :disable t) +#+END_SRC + +Remember to run ~doom refresh~ afterwards to ensure that the package is +uninstalled and disabled. + +You'll find more information in the "[[file:getting_started.org::*Disabling%20packages][Disabling packages]]" section of the [[file:getting_started.org][Getting +Started]] guide. + +** How do I reconfigure a package included in Doom? +~use-package!~ and ~after!~ (wrappers around ~use-package~ and +~eval-after-load~, respectively) are your bread and butter for configuring +packages in Doom. + +#+BEGIN_SRC elisp +;; Takes a feature symbol or a library name (string) +(after! evil + (setq evil-magic nil)) + +;; Takes a major-mode, a quoted hook function or a list of either +(add-hook! python-mode + (setq python-shell-interpreter "bpython")) + +(use-package! hl-todo + ;; if you omit :defer, :hook, :commands, or :after, then the package is loaded + ;; immediately. By using :hook here, the `hl-todo` package won't be loaded + ;; until prog-mode-hook is triggered (by activating a major mode derived from + ;; it, e.g. python-mode) + :hook (prog-mode . hl-todo-mode) + :init + ;; code here will run immediately + :config + ;; code here will run after the package is loaded + (setq hl-todo-highlight-punctuation ":")) + +;; There's also `setq-hook!' for setting variables buffer-locally +(setq-hook! python-mode python-indent-offset 2) +#+END_SRC + +See the "[[file:getting_started.org::*Configuring%20Doom][Configuring Doom]]" section of the [[file:getting_started.org][Getting Started]] guide for more +explanation and examples. + +** How do I change the theme? +There are two ways to load a theme. Both assume the theme is installed and +available. You can either set ~doom-theme~ or manually load a theme with the +~load-theme~ function. + +#+BEGIN_SRC emacs-lisp +;;; add to ~/.doom.d/config.el +(setq doom-theme 'doom-tomorrow-night) +;; or +(load-theme 'doom-tomorrow-night t) +#+END_SRC + +#+begin_quote +At the moment, the only difference between the two is that ~doom-theme~ is +loaded when Emacs has finished initializing at startup and ~load-theme~ loads +the theme immediately. Which you choose depends on your needs, but I recommend +setting ~doom-theme~ because, if I later discover a better way to load themes, I +can easily change how Doom uses ~doom-theme~, but I can't (easily) control how +you use the ~load-theme~ function. +#+end_quote + +*** Installing a third party theme +To install a theme from a third party plugin, say, [[https://github.com/bbatsov/solarized-emacs][solarized]], you need only +install it, then load it: + +#+BEGIN_SRC emacs-lisp +;;; add to ~/.doom.d/packages.el +(package! solarized) + +;;; add to ~/.doom.d/config.el +(setq doom-theme 'solarized-dark) +#+END_SRC + +Don't forget to run ~doom refresh~ after adding that ~package!~ statement to +ensure the package is installed. + +** How do I change the fonts? +Doom exposes five (optional) variables for controlling fonts in Doom, they are: + ++ ~doom-font~ ++ ~doom-variable-pitch-font~ ++ ~doom-serif-font~ ++ ~doom-unicode-font~ ++ ~doom-big-font~ (used for ~doom-big-font-mode~) + +Each of these will accept either a =font-spec=, font string (="Input Mono-12"=), +or [[https://wiki.archlinux.org/index.php/X_Logical_Font_Description][xlfd font string]]. + +e.g. +#+BEGIN_SRC emacs-lisp +;;; Add to ~/.doom.d/config.el +(setq doom-font (font-spec :family "Input Mono Narrow" :size 12 :weight 'semi-light) + doom-variable-pitch-font (font-spec :family "Fira Sans") ; inherits `doom-font''s :size + doom-unicode-font (font-spec :family "Input Mono Narrow" :size 12) + doom-big-font (font-spec :family "Fira Mono" :size 19)) +#+END_SRC + +** How do I bind my own keys (or change existing ones)? +The ~map!~ macro is recommended; it is a convenience macro that wraps around +Emacs' (and evil's) keybinding API, i.e. ~define-key~, ~global-set-key~, +~local-set-key~ and ~evil-define-key~. + +You'll find comprehensive examples of ~map!~'s usage in its documentation (via +=SPC h f map!= or =C-h f map!= -- also found [[file:api.org][in docs/api]]). + +You'll find a more comprehensive example of ~map!~'s usage in +[[file:../modules/config/default/+evil-bindings.el][config/default/+evil-bindings.el]]. + +** How do I get motions to treat underscores as word delimiters? +(This explanation comes from [[https://github.com/emacs-evil/evil#underscore-_-is-not-a-word-character][emacs-evil/evil]]'s readme) + +An underscore "_" is a word character in Vim. This means that word-motions like +=w= skip over underlines in a sequence of letters as if it was a letter itself. +In contrast, in Evil the underscore is often a non-word character like +operators, e.g. =+=. + +The reason is that Evil uses Emacs' definition of a word and this definition +does not often include the underscore. Word characters in Emacs are determined +by the syntax-class of the buffer. The syntax-class usually depends on the +major-mode of this buffer. This has the advantage that the definition of a +"word" may be adapted to the particular type of document being edited. Evil uses +Emacs' definition and does not simply use Vim's definition in order to be +consistent with other Emacs functions. For example, word characters are exactly +those characters that are matched by the regular expression character class +~[:word:]~. + +If you want the underscore to be recognized as word character, you can modify +its entry in the syntax-table: + +#+BEGIN_SRC emacs-lisp +(modify-syntax-entry ?_ "w") +#+END_SRC + +This gives the underscore the word syntax-class. You can use a mode-hook to +modify the syntax-table in all buffers of some mode, e.g.: + +#+BEGIN_SRC emacs-lisp +;; For python +(add-hook! 'python-mode-hook (modify-syntax-entry ?_ "w")) +;; For ruby +(add-hook! 'enh-ruby-mode-hook (modify-syntax-entry ?_ "w")) +;; For Javascript +(add-hook! 'js2-mode-hook (modify-syntax-entry ?_ "w")) +#+END_SRC + +** How do I change the leader/localleader keys? +These variables control what key to use for leader and localleader keys: + ++ For Evil users: + + ~doom-leader-key~ (default: =SPC=) + + ~doom-localleader-key~ (default: =SPC m=) ++ For Emacs and Insert state (evil users), and non-evil users: + + ~doom-leader-alt-key~ (default: =M-SPC= for evil users, =C-c= otherwise) + + ~doom-localleader-alt-key~ (default: =M-SPC m= for evil users, =C-c l= + otherwise) + +e.g. +#+BEGIN_SRC emacs-lisp +;;; add to ~/.doom.d/config.el +(setq doom-leader-key "," + doom-localleader-key "\\") +#+END_SRC + +** How do I change the style of line-numbers (or disable them altogether)? +Doom uses the ~display-line-numbers~ package, which is built into Emacs 26+. + +*** Disabling line numbers entirely +#+BEGIN_SRC elisp +;;; add to ~/.doom.d/config.el +(setq display-line-numbers-type nil) +;; or +(remove-hook! '(prog-mode-hook text-mode-hook conf-mode-hook) + #'display-line-numbers-mode) +#+END_SRC + +*** Switching to relative line numbers (permanently) +To change the style of line numbers, change the value of the +~display-line-numbers-type~ variable. It accepts the following values: + +#+begin_example +t normal line numbers +'relative relative line numbers +'visual relative line numbers in screen space +nil no line numbers +#+end_example + +For example: + +#+BEGIN_SRC elisp +;;; add to ~/.doom.d/config.el +(setq display-line-numbers-type 'relative) +#+END_SRC + +You'll find more precise documentation on the variable through = v +display-line-numbers-type= (== is =SPC h= for evil users, =C-h= +otherwise). + +*** Switching the style of line numbers (temporarily) +Use ~M-x doom/toggle-line-numbers~ (bound to =SPC t l= by default) to cycle +through the available line number styles in the current buffer. + +e.g. =normal -> relative -> visual -> disabled -> normal=. + +** How do I change the behavior and appearance of popup windows? +The =:ui popup= module tries to standardize how Emacs handles "temporary" +windows. It includes a set of default rules that tell Emacs where to open them +(and how big they should be). + +Check out the [[file:../modules/ui/popup/README.org::Configuration][:ui popup module's documentation]] for more on defining your own +rules. + +You'll find more comprehensive documentation on ~set-popup-rule!~ in its +docstring (available through =SPC h f= -- or =C-h f= for non-evil users). + +** Can Doom be customized without restarting Emacs? +Short answer: You can, but you shouldn't. + +Long answer: Restarting Emacs is always your safest bet, but Doom provides a few +tools for experienced Emacs users to skirt around it (most of the time): + +- Evaluate your changes on-the-fly with ~+eval/region~ (bound to the =gr= + operator for evil users) or ~eval-last-sexp~ (bound to =C-x C-e=). Changes + take effect immediately. +- On-the-fly evaluation won't work for all changes. e.g. Changing your ~doom!~ + block (i.e. the list of modules for Doom to enable). + + But rather than running ~doom refresh~ and restarting Emacs, Doom provides + ~M-x doom/reload~ for your convenience (bound to =SPC h r r= and =C-h r r=). + This runs ~doom refresh~, restarts the Doom initialization process and + re-evaluates your personal config. However, this won't clear pre-existing + state; Doom won't unload modules/packages that have already been loaded and it + can't anticipate complications arising from a private config that isn't + idempotent. +- Many ~bin/doom~ commands are available as elisp commands with the ~doom//*~ + prefix. e.g. ~doom//refresh~, ~doom//update~, etc. Feel free to use them, but + consider them highly experimental and subject to change without notice. +- You can quickly restart Emacs and restore the last session with + ~doom/restart-and-restore~ (bound to =SPC q r=). + +** Can Vim/Evil be removed for a more vanilla Emacs experience? +Yes! See the [[file:../modules/editor/evil/README.org::Removing%20evil-mode][Removing evil-mode]] section in [[file:../modules/editor/evil/README.org][:editor evil]]'s documentation. + +** Should I use ~make~ or ~bin/doom~? +~bin/doom~ is recommended. Doom's Makefile (to manage your config, at least) is +deprecated. It forwards to ~bin/doom~ anyway. + +** When should and shouldn't I use ~bin/doom~? +~bin/doom~ is your best friend. It'll keep all your secrets (mostly because it's +a shell script incapable of sentience and thus incapable of retaining, much less +divulging, your secrets to others). + +You can run ~bin/doom help~ to see what it's capable of, but here are some +commands that you may find particularly useful: + ++ ~doom doctor~ :: Diagnose common issues in your environment and list missing + external dependencies for your enabled modules. ++ ~doom refresh~ :: Ensures that all missing packages are installed, orphaned + packages are removed, and metadata properly generated. ++ ~doom install~ :: Install any missing packages. ++ ~doom update~ :: Update all packages that Doom's (enabled) modules use. ++ ~doom env~ :: Regenerates your envvar file, which contains a snapshot of your + shell environment for Doom Emacs to load on startup. You need to run this for + changes to your shell environment to take effect. ++ ~doom purge -g~ :: Purge orphaned packages (i.e. ones that aren't needed + anymore) and regraft your repos. ++ ~doom upgrade~ :: Upgrade Doom to the latest version (then update your + packages). This is equivalent to: + + #+BEGIN_SRC bash + git pull + doom refresh + doom update + #+END_SRC + +** When to run ~doom refresh~ +As a rule of thumb you should run ~doom refresh~ whenever you: + ++ Update Doom with ~git pull~ instead of ~doom upgrade~, ++ Change your ~doom!~ block in =$DOOMDIR/init.el=, ++ Change autoload files in any module (or =$DOOMDIR=), ++ Or change the packages.el file in any module (or =$DOOMDIR=). ++ Install an Emacs package or dependency outside of Emacs (i.e. through your OS + package manager). + +If anything is misbehaving, it's a good idea to run ~doom refresh~ first. ~doom +refresh~ is responsible for regenerating your autoloads file (which tells Doom +where to find lazy-loaded functions and libraries), installing missing packages, +and uninstall orphaned (unneeded) packages. + +** How to suppress confirmation prompts while ~bin/doom~ is running +The ~-y~ and ~--yes~ flags (or the ~YES~ environment variable) will force +~bin/doom~ to auto-accept confirmation prompts: + +#+BEGIN_SRC bash +doom -y update +doom --yes update +YES=1 doom update +#+END_SRC + +* Defaults +** Why Ivy over Helm? +Short answer: I chose ivy because it is the simpler of the two. + +Long answer: Features and performance appear to be the main talking points when +comparing the two, but as far as I'm concerned they are equal in both respects +(not all across the board, but on average). + +Instead, what is important to me is maintainability. As someone who frequently +extends and debugs his editor (and maintains a community config), I frequently +run up against issues with ivy and helm, but spend disproportionally more time +doing so with helm than I do ivy, for little or no gain. Though both frameworks +are excellent, the difference in complexity is also reflected in their plugin +ecosystems; ivy plugins tend to be lighter, simpler, more consistent and +significantly easier to hack if I want to change something. Unless you like helm +/just/ the way it is out of the box, ivy is just the simpler choice. + +And since I dogfood it, Ivy's integration into Doom will always be a step or +three ahead of helm's. + +** Why are there no default keybinds for Smartparens (for evil users)? +Doom only uses smartparens to manage pair "completion" (it does the job better +than electric-{pair,quote}-mode or the multitude of other pair-management +solutions in the Emacs ecosystem at the time of writing). + +None of smartparen's commands have default keybinds for evil users because they +are redundant with motions and text-objects provided by evil/vim. If you +disagree, I recommend trying the =:editor lispy= or =:editor parinfer= modules. + +** Why do non-evil users get expand-region, but not evil users? +~expand-region~ is redundant with and less precise than evil's text objects and +motions. + +- There's a text object for every "step" of expansion that expand-region + provides (and more). To select the word at point = =viw=, symbol at point = + =vio=, line at point = =V=, the block at point (by indentation) = =vii=, the + block at point (by braces) = =vib=, sentence at point = =vis=, paragraph = + =vip=, and so on. +- Selection expansion can be emulated by using text objects consecutively: =viw= + to select a word, followed by =io= to expand to a symbol, then =ib= expands to + the surrounding brackets/parentheses, etc. There is no reverse of this + however; you'd have to restart visual state. + +The expand-region way dictates you start at some point and expand/contract until +you have what you want selected. The vim/evil way would rather you select +exactly what you want from the get go. In the rare event a text object fails +you, a combination of =o= (swaps your cursor between the two ends of the region) +and motion keys can adjust the ends of your selection. + +#+BEGIN_QUOTE +There are also text objects for xml tags (=x=), C-style function arguments +(=a=), angle brackets, and single/double quotes. +#+END_QUOTE + +This is certainly more to remember compared to a pair of expand and contract +commands, but text objects (and motions) are the bread and butter of vim's modal +editing paradigm. Vimmers will feel right at home. To everyone else: mastering +them will have a far-reaching effect on your productivity. I highly recommend +putting in the time to learn them. + +Otherwise, it is trivial to install expand-region and binds keys to it yourself: + +#+BEGIN_SRC elisp +;;; add to ~/.doom.d/packages.el +(package! expand-region) + +;;; add to ~/.doom.d/config.el +(map! :nv "C-=" #'er/contract-region + :nv "C-+" #'er/expand-region) +#+END_SRC + +** Why not use exec-path-from-shell instead of ~doom env~? +In a nutshell, the ~doom env~ approach is a faster and more robust solution. + +1. ~exec-path-from-shell~ must spawn (at least) one process at startup to scrape + your shell environment. This can be arbitrarily slow depending on the user's + shell configuration. A single program (like pyenv or nvm) or config framework + (like oh-my-zsh) could undo all of Doom's startup optimizations in one fell + swoop. + +2. ~exec-path-from-shell~ only scrapes /some/ state from your shell. You have to + be proactive in order to get it to capture all the envvars relevant to your + development environment. + + I'd rather it inherit your shell environment /correctly/ (and /completely/) + or not at all. It frontloads the debugging process rather than hiding it + until it you least want to deal with it. + +That said, if you still want ~exec-path-from-shell~, it is trivial to install +yourself: + +#+BEGIN_SRC emacs-lisp +;;; add to ~/.doom.d/packages.el +(package! exec-path-from-shell) + +;;; add to ~/.doom.d/config.el +(require 'exec-path-from-shell) +(when (display-graphic-p) + (exec-path-from-shell-initialize)) +#+END_SRC +** Why wsbutler over delete-trailing-whitespace or whitespace-cleanup? +TL;DR: =ws-butler= is less imposing. + +Don't be that guy who PRs 99 whitespace adjustments around his one-line +contribution. Don't automate this aggressive behavior by attaching +~delete-trailing-whitespace~ (or ~whitespace-cleanup~) to ~before-save-hook~. If +you have rambunctious colleagues peppering trailing whitespace into your project, +you need to have a talk (with wiffle bats, preferably) rather than play this +passive-aggressive game of whack-a-mole. + +Here at Doom Inc we believe that operations that mutate entire files should +never be automated. Rather, they should be invoked deliberately -- by someone +that is aware of the potential consequences. This is where =ws-butler= comes in. +It only cleans up whitespace /on the lines you've touched/ *and* it leaves +behind virtual whitespace (which is never written to the file, but remains there +so your cursor doesn't get thrown around in all that cleanup work). + +In any case, if you had used =ws-butler= from the beginning, trailing whitespace +and newlines would never be a problem! + +* Common Issues +** I get the vanilla Emacs splash screen at startup +The most common cause for this is a =~/.emacs= file. If it exists, Emacs will +read this file instead of the =~/.emacs.d= directory, ignoring Doom altogether. + +If this isn't the case, try running ~bin/doom doctor~. It can detect a variety +of common issues and may give you some clues as to what is wrong. + +** I see a blank scratch buffer at startup +This commonly means that Emacs can't find your private doom config (in +=~/.doom.d= or =~/.config/doom=). Make sure *only one of these two* folders +exist, and that it has an init.el file with a ~doom!~ block. Running ~doom +install~ will populate your private doom directory with the bare minimum you +need to get going. + +If nothing else works, try running ~bin/doom doctor~. It can detect a variety of +common issues and may give you some clues as to what is wrong. + +** Strange (or incorrect) icons are displayed everywhere +Many of Doom's UI modules use the =all-the-icons= plugin, which uses special +fonts to display icons. These fonts must be installed for them to work properly, +otherwise you'll get a bunch of squares and mismatched icons. When running ~doom +install~, you will be asked whether you want these installed for you or not. + +If you did not accept or need to reinstall those fonts, MacOS and Linux users +can install them with ~M-x all-the-icons-install-fonts~. Windows users will need +to use this command to download the fonts somewhere, then they must install them +manually (e.g. by double-clicking each file in explorer). + +** ~void-variable~ and ~void-function~ errors on startup +The most common culprit for these types of errors are: + +1. An out-of-date autoloads file. To regenerate it, run ~doom refresh~. + + To avoid this issue, remember to run ~doom refresh~ whenever you modify your + ~doom!~ block in =~/.doom.d/init.el=, or add ~package!~ declarations to + =~/.doom.d/packages.el=. Or if you modify =~/.emacs.d/.local= by hand, for + whatever reason. + + See ~doom help refresh~ for details on what this command does and when you + should use it. + +2. Emacs byte-code isn't forward compatible. If you've recently switched to a + newer (or older) version of Emacs, you'll need to either reinstall or + recompile your installed plugins. This can be done by: + + + Running ~doom build~, + + Or deleting =~/.emacs.d/.local/straight= then running ~doom install~ (this + will take a while). + +** Doom can't find my executables/doesn't inherit the correct ~PATH~ +The two most common causes for PATH issues in Doom are: + +1. Your shell configuration doesn't configure ~PATH~ correctly. If ~which + ~ doesn't emit the path you expect on the command line then this is + likely the case. + +2. Your app launcher (rofi, albert, docky, dmenu, sxhkd, etc) is launching Emacs + with the wrong shell, either because it defaults to a different shell from + the one you use or the app launcher itself inherits the wrong environment + because /it/ was launched from the wrong shell. + +3. You're a Mac user launching Emacs from an Emacs.app bundle. MacOS launches + these apps from an isolated environment. + +As long as your shell is properly configured, there is a simple solution to +issues #1 and #3: generate an envvar file by running ~doom env~. This scrapes +your shell environment into a file that is loaded when Doom Emacs starts up. +Check out ~doom help env~ for details on how this works. + +For issue #2, you'll need to investigate your launcher. [[https://discord.gg/bcZ6P3y][Our Discord]] is a good +place to ask about it. + +** There's artefacting on my icon fonts in GUI Emacs ([[https://github.com/hlissner/doom-emacs/issues/956][#956]]) +Check your font rendering settings. Changing the RGBA order to "rgba" will often +fix this issue. See [[https://github.com/hlissner/doom-emacs/issues/956][#956]] for details. + +** The =s= and =S= keys don't behave like they do in vim/evil ([[https://github.com/hlissner/doom-emacs/issues/1307][#1307]]) +This is intentional. =s= and =S= have been replaced by the evil-snipe plugin, +which provides 2-character versions of the f/F motion keys, ala vim-seek or +vim-sneak. + +These keys were changed because they are redundant with =cl= and =cc= +respectively (and the new behavior was deemed more useful). + +If you still want to restore the old behavior, simply disable evil-snipe-mode: + +#+BEGIN_SRC emacs-lisp +;; in ~/.doom.d/config.el +(after! evil-snipe + (evil-snipe-mode -1)) +#+END_SRC + +** Changes to my config aren't taking effect +1. Make sure you don't have both =~/.doom.d= and =~/.config/doom= directories. + Doom will ignore the former if the latter exists. + +2. Remember to run ~doom refresh~ when it is necessary. To get to know when, + exactly, you should run this command, run ~doom help refresh~. + +If neither of these solve your issue, try ~bin/doom doctor~. It will detect a +variety of common issues, and may give you some clues as to what is wrong. + +** The frame goes black on MacOS, while in full-screen mode +There are known issues with childframes and macOS's fullscreen mode. There is no +known fix for this. To work around it, you must either: + +1. Avoid MacOS native fullscreen by maximizing Emacs instead, + +2. Disable childframes (controlled by the =+childframe= flag on the modules that + support it), + +3. Install Emacs via the =emacs-mac= homebrew formula. +** Doom crashes when... +Here are a few common causes for random crashes: + ++ You have enabled ~undo-tree-auto-save-history~. A bloated cache for a + particular file can cause a stack overflow. These caches are stored in + =~/.emacs.d/.local/cache/undo-tree-hist/=. Delete this folder to clear it. ++ On some systems (particularly MacOS), manipulating the fringes or window + margins can cause Emacs to crash. This is most prominent in the Doom Dashboard + (which tries to center its contents), in org-mode buffers (which uses + ~org-indent-mode~ to create virtual indentation), or magit. There is currently + no known fix for this, as it can't be reliably reproduced. Your best bet is to + reinstall/rebuild Emacs or disable the errant plugins/modules. e.g. + + To disable org-indent-mode: + + #+BEGIN_SRC emacs-lisp + (after! org + (setq org-startup-indented nil)) + #+END_SRC + + Or disable the =:ui doom-dashboard= & =:tools magit= modules (see [[https://github.com/hlissner/doom-emacs/issues/1170][#1170]]). + +** Can't load my theme; ~unable to find theme file for X~ errors +This means Emacs can't find the X-theme.el file for the theme you want to load. +Emacs will search for this file in ~custom-theme-load-path~ and +~custom-theme-directory~. There are a couple reasons why it can't be found: + +1. It is generally expected that third party themes will [[https://github.com/hlissner/emacs-doom-themes/blob/master/doom-themes.el#L400-L405][add themselves]] to + ~custom-theme-load-path~, but you will occasionally encounter a theme that + does not. This should be reported upstream. + + In the meantime, you can get around this by eagerly loading the package: + + #+BEGIN_SRC elisp + (require 'third-party-theme) + (setq doom-theme 'third-party) + #+END_SRC +2. You've appended ~-theme~ to the end of your theme's name. + + #+BEGIN_SRC elisp + (setq doom-theme 'third-party-theme) + #+END_SRC + + When you load a theme Emacs searches for ~X-theme.el~. If you set + ~doom-theme~ to ~'third-party-theme~, it will search for + ~third-party-theme-theme.el~. This is rarely intentional. Omit the ~-theme~ + suffix. +3. Did you run ~doom refresh~ after adding your third party theme plugin's + ~package!~ declaration to =~/.doom.d/packages.el=? +** TRAMP connections hang forever when connecting +You'll find solutions [[https://www.emacswiki.org/emacs/TrampMode#toc7][on the emacswiki]]. + +* TODO Contributing diff --git a/.emacs.d/docs/getting_started.org b/.emacs.d/docs/getting_started.org new file mode 100644 index 0000000..c5fa9ad --- /dev/null +++ b/.emacs.d/docs/getting_started.org @@ -0,0 +1,1187 @@ +#+TITLE: Getting Started Guide +#+STARTUP: nofold + +* Table of Contents :TOC_4: +- [[#install][Install]] + - [[#emacs--dependencies][Emacs & dependencies]] + - [[#on-linux][On Linux]] + - [[#arch-linux][Arch Linux:]] + - [[#ubuntu][Ubuntu:]] + - [[#nixos][NixOS]] + - [[#on-macos][On macOS]] + - [[#where-not-to-install-emacs-from][Where *not* to install Emacs from]] + - [[#on-windows][On Windows]] + - [[#chocolatey--scoop][chocolatey / scoop]] + - [[#wsl][WSL]] + - [[#wsl2][WSL2]] + - [[#doom-emacs][Doom Emacs]] + - [[#install-doom-manually][Install Doom Manually]] + - [[#install-doom-alongside-other-configs-with-chemacs][Install Doom alongside other configs (with Chemacs)]] + - [[#externalsystem-dependencies][External/system dependencies]] +- [[#update][Update]] + - [[#doom][Doom]] + - [[#plugins][Plugins]] + - [[#rollback][Rollback]] +- [[#customize][Customize]] + - [[#how-to-enable-or-disable-modules][How to enable or disable modules]] + - [[#package-management][Package management]] + - [[#installing-packages][Installing packages]] + - [[#installing-packages-from-external-sources][Installing packages from external sources]] + - [[#disabling-packages][Disabling packages]] + - [[#changing-a-built-in-recipe-for-a-package][Changing a built-in recipe for a package]] + - [[#usingloading-local-packages][Using/loading local packages]] + - [[#configuring-doom][Configuring Doom]] + - [[#configuring-packages][Configuring packages]] + - [[#reloading-your-config][Reloading your config]] + - [[#binding-keys][Binding keys]] + - [[#doomdir-file-structure][DOOMDIR file structure]] + - [[#writing-your-own-modules][Writing your own modules]] + - [[#structure-of-a-module][Structure of a module]] + - [[#initel][=init.el=]] + - [[#configel][=config.el=]] + - [[#packagesel][=packages.el=]] + - [[#autoloadel-or-autoloadel][=autoload/*.el= OR =autoload.el=]] + - [[#doctorel][=doctor.el=]] + - [[#additional-files][Additional files]] + - [[#load-order][Load order]] + - [[#module-flags][Module flags]] + - [[#testing-for-flags][Testing for flags]] + - [[#module-settings][Module settings]] + - [[#module-cookies][Module cookies]] + - [[#common-mistakes-when-configuring-doom-emacs][Common mistakes when configuring Doom Emacs]] + - [[#packages-are-eagerly-loaded][Packages are eagerly loaded]] + - [[#manual-package-management][Manual package management]] + - [[#using-org-babel-do-load-languages-to-load-your-babel-plugins][Using ~org-babel-do-load-languages~ to load your babel plugins]] + - [[#using-delete-trailing-whitespaces-or-whitespace-cleanup-to-manage-leftover-whitespace][Using ~delete-trailing-whitespaces~ or ~whitespace-cleanup~ to manage leftover whitespace]] +- [[#troubleshoot][Troubleshoot]] + - [[#ive-run-into-an-issue-where-do-i-start][I've run into an issue, where do I start?]] + - [[#looking-up-documentation-and-state-from-within-emacs][Looking up documentation and state from within Emacs]] + - [[#variables-functions-faces-etc][Variables, functions, faces, etc.]] + - [[#for-doom-modules-packages-autodefs-etc][For Doom Modules, packages, autodefs, etc.]] + - [[#how-to-extract-a-backtrace-from-an-error][How to extract a backtrace from an error]] + - [[#enabling-debug-on-error][Enabling ~debug-on-error~]] + - [[#a-backtrace-from-bindoom][A backtrace from ~bin/doom~]] + - [[#evaluating-elisp-on-the-fly][Evaluating Elisp on-the-fly]] + - [[#how-to-determine-the-origin-of-a-bug][How to determine the origin of a bug]] + - [[#testing-in-dooms-sandbox][Testing in Doom's sandbox]] + - [[#opening-the-sandbox][Opening the sandbox]] + - [[#launching-the-sandbox][Launching the sandbox]] + - [[#testing-packages-in-the-sandbox][Testing packages in the sandbox]] + - [[#bisecting-your-private-config][Bisecting your private config]] + - [[#bisecting-doom-emacs][Bisecting Doom Emacs]] + +* Install +To embark on this grand Emacs adventure, you'll need a couple things installed, +including Emacs (shocking, I know), Doom Emacs, the plugins Doom depends on, and +any external tools /they/ depend on as well. + +In summary, you'll be installing: + ++ *git* ++ *Emacs 26.1+* ++ *ripgrep* ++ *all-the-icons fonts* -- unnecessary for exclusive use of terminal Emacs + +And then some optional dependencies that you will likely want, as the will +optimize Doom's performance and stability. + ++ [[https://github.com/sharkdp/fd][fd]] ++ *GNU ls* (BSD ls on macOS/BSD Linux has some limitations) ++ *clang* -- with which to compile certain external dependencies, like the + emacsqlite binary, irony server (requires clang), or vterm module + +The following sections will cover how to install Emacs and these dependencies +across various operating systems. + +#+BEGIN_QUOTE +If any of these install instructions are outdated, or your OS is missing, please +help us by [[https://github.com/hlissner/doom-emacs/issues/new][letting us know]] (or correcting it yourself; pull requests are +welcome). +#+END_QUOTE + +** Emacs & dependencies +*** On Linux +Emacs should be available through your distribution's package manager. +Otherwise, it can be [[https://www.gnu.org/software/emacs/manual/html_node/efaq/Installing-Emacs.html][built from source]]. + +**** Arch Linux: +#+BEGIN_SRC bash +pacman -S git tar clang emacs ripgrep fd +#+END_SRC + +Emacs 27 (HEAD) can be installed through [[https://aur.archlinux.org/packages/emacs-git/][emacs-git]], available on the AUR. + +**** Ubuntu: +#+BEGIN_SRC bash +apt-get install git tar clang ripgrep fd-find +#+END_SRC + +On Ubuntu 18.04, the latest version of Emacs available is 25.3 (and 24.3 on +Ubuntu 16 or 14). Therefore, we have a few extra steps to install 26.1+: + +#+BEGIN_SRC bash +add-apt-repository ppa:kelleyk/emacs +apt-get update +apt-get install emacs26 +#+END_SRC + +**** NixOS +On NixOS Emacs 26.x can be installed via ~nix-env --install emacs~, or more +permanently by adding the following entry to ~etc/nixos/configuration.nix~: + +#+BEGIN_SRC nix +environment.systemPackages = with pkgs; [ + coreutils # basic GNU utilities + git + clang + emacs + ripgrep + fd +]; +#+END_SRC + +*** On macOS +Mac users several options to install Emacs, but only a few of them are +recommended for Doom Emacs (you'll need to [[http://brew.sh/][install Homebrew]] first). To start +with: + +#+BEGIN_SRC bash +brew install git clang ripgrep fd coreutils +#+END_SRC + +As for Emacs, there are several formulas to choose from. There are the best +options, in order from most to least recommended for Doom. + +- [[https://github.com/d12frosted/homebrew-emacs-plus][emacs-plus]] (the safest option): + + #+BEGIN_SRC bash + brew tap d12frosted/emacs-plus + brew install emacs-plus + ln -s /usr/local/opt/emacs-plus/Emacs.app /Applications/Emacs.app + #+END_SRC + +- [[https://formulae.brew.sh/formula/emacs][emacs]] is another acceptable option. + + #+BEGIN_SRC bash + brew install emacs + #+END_SRC + +- [[https://bitbucket.org/mituharu/emacs-mac/overview][emacs-mac]] is also acceptable. It offers slightly better integration into + macOS, with native emojis and better childframe support. However, at the time + of writing, it [[https://github.com/railwaycat/homebrew-emacsmacport/issues/52][lacks multi-tty support]] (which impacts daemon usage). Use it if + you experience crashing or performance issues with emacs-plus. + + #+BEGIN_SRC bash + brew tap railwaycat/emacsmacport + brew install emacs-mac + ln -s /usr/local/opt/emacs-mac/Emacs.app /Applications/Emacs.app + #+END_SRC + +**** Where *not* to install Emacs from +These builds/forks have known compatibility issues with Doom and are likely to +cause you issues later on. Do not use them: + ++ emacsformacosx.com ++ ~brew cask install emacs~ (installs from emacsformacosx.com) ++ AquaMacs ++ XEmacs + +*** On Windows +*Support for Windows is immature,* so your mileage will vary. Some have reported +success with installing Doom via WSL, chocolatey on git-bash or cygwin. + +#+BEGIN_QUOTE +If you manage to get Doom on Windows and found this wasn't enough, or could be +improved, please help us expand this section! +#+END_QUOTE + +**** [[https://chocolatey.org/][chocolatey]] / scoop +Chocolatey is the simplest to get Doom up and running with: + +#+BEGIN_SRC sh +choco install git llvm emacs ripgrep fd +#+END_SRC + +#+begin_quote +You can also use [[https://scoop.sh/][scoop]] by simply replacing ~choco~ with ~scoop~ in the above +snippet to achieve the same result. This hasn't been tested, however. +#+end_quote + +You will also need to [[https://mywindowshub.com/how-to-edit-system-environment-variables-for-a-user-in-windows-10/][add a ~HOME~ system variable]], pointing to +=C:\Users\USERNAME\=, otherwise Emacs will treat +=C:\Users\USERNAME\AppData\Roaming= is your ~HOME~, which causes issues. + +It's also a good idea to add =C:\Users\USERNAME\.emacs.d\bin= to your ~PATH~. + +**** TODO WSL + +**** TODO WSL2 + +** Doom Emacs +The quickest way to get Doom up and running is: + +#+BEGIN_SRC bash +git clone https://github.com/hlissner/doom-emacs ~/.emacs.d +~/.emacs.d/bin/doom install +#+END_SRC + +=doom install= performs the following for you: + +1. It creates your =DOOMDIR= at =~/.doom.d=, if it (or =~/.config/doom=) don't + already exist. +2. Copies =~/.emacs.d/init.example.el= to =$DOOMDIR/init.el=, which contains a + ~doom!~ statement that controls what modules to enable and in what order they + are loaded. +3. Creates dummy config.el and packages.el files in ~$DOOMDIR~. +4. Optionally generates an envvar file (equivalent to using ~doom env~), which + stores your shell environment in an env file that Doom will load at startup. + *This is essential for macOS users!* +5. Installs all dependencies for enabled modules (specified by + =$DOOMDIR/init.el=), +6. And prompts to install the icon fonts required by the [[https://github.com/domtronn/all-the-icons.el][all-the-icons]] package. + +#+BEGIN_QUOTE +You'll find a break down of ~doom install~ into shell commands in the next +section. +#+END_QUOTE + +Consider the =~/.emacs.d/bin/doom= script your new best friend. It performs a +variety of essential functions to help you manage your Doom Emacs configuration, +not least of which is installing or updating it or its plugins. If nothing else, +get to know these four commands: + +- ~doom refresh~: Ensures that Doom is in a proper state to be used (i.e. needed + packages are installed, orphaned packages are removed and necessary metadata + correctly generated). +- ~doom upgrade~: Updates Doom Emacs (if available) and its packages. +- ~doom env~: Generates an "envvar file", which scrapes your shell environment + into a file that is loaded by Doom Emacs at startup. This is especially + necessary for macOS users who open Emacs through an Emacs.app bundle. +- ~doom doctor~: If Doom misbehaves, the doc will diagnose common issues with + your installation and environment. If all else fails, you'll find help on + Doom's [[https://discord.gg/bcZ6P3y][Discord server]] and [[https://github.com/hlissner/doom-emacs/issues][issue tracker]]. + +Run ~doom help ~ for documentation on these commands, or ~doom help~ +for an overview of what the =bin/doom= script is capable of. + +#+begin_quote +I recommend you add =~/.emacs.d/bin= to your ~PATH~ so you can call =doom= +directly, from anywhere. You don't need to be CDed into =~/.emacs.d/bin= to use +it. A quick way to do so is to add this to your .bashrc or .zshrc file: + +~export PATH="$HOME/.emacs.d/bin:$PATH"~ +#+end_quote + +*** Install Doom Manually +If you'd rather install Doom yourself, without the magic of =bin/doom install=, +here is its equivalent in bash shell commands: + +#+BEGIN_SRC bash +git clone https://github.com/hlissner/doom-emacs ~/.emacs.d + +# So we don't have to write ~/.emacs.d/bin/doom every time +export PATH="$HOME/.emacs.d/bin:$PATH" + +# Create a directory for our private config +mkdir ~/.doom.d # or ~/.config/doom + +# The init.example.el file contains an example doom! call, which tells Doom what +# modules to load and in what order. +cp ~/.emacs.d/init.example.el ~/.doom.d/init.el + +# If your ISP or proxy doesn't allow you to install from +# raw.githubusercontent.com, then you'll have to install straight (our package +# manager) manually: +mkdir -p ~/.emacs.d/.local/straight/repos +git clone -b develop https://github.com/raxod502/straight.el ~/.emacs.d/.local/straight/repos/straight.el + +# Edit ~/.doom.d/init.el and adjust the modules list to your liking before +# running this: +doom install + +# If you know Emacs won't be launched from your shell environment (e.g. you're +# on macOS or use an app launcher that doesn't launch programs with the correct +# shell), then creating an envvar file is necessary to ensure Doom inherits your +# shell environment. +# +# If you don't know whether you need this or not, no harm in doing it anyway. +# `doom install` will prompt you to generate an envvar file. If you responded +# no, you can generate it later with the following command: +doom env + +# Install the icon fonts Doom uses +emacs --batch -f all-the-icons-install-fonts +#+END_SRC + +To understand the purpose of the =~/.doom.d= directory and =~/.doom.d/init.el= +file, see the [[#customize][Customize]] section further below. + +*** Install Doom alongside other configs (with Chemacs) +[[https://github.com/plexus/chemacs][Chemacs]] is a bootloader for Emacs; it makes it easy to switch between multiple +Emacs configurations. Here is a quick guide for setting it up with Doom Emacs as +the default config. + +1. First, install Doom somewhere: + + #+BEGIN_SRC sh + git clone https://github.com/hlissner/doom-emacs ~/doom-emacs + ~/doom-emacs/bin/doom install + #+END_SRC + +2. Download [[https://raw.githubusercontent.com/plexus/chemacs/master/.emacs][the Chemacs' startup script]] to =~/.emacs=: + + #+BEGIN_SRC bash + wget -O ~/.emacs https://raw.githubusercontent.com/plexus/chemacs/master/.emacs + #+END_SRC + + #+begin_quote + *WARNING:* the =~/.emacs.d= directory must not exist for this to work. + #+end_quote + +3. Create =~/.emacs-profiles.el= with a list of your Emacs profiles. This file + is structured like a =.dir-locals.el= file. Here is an example with Doom (as + the default), Spacemacs, and Prelude: + + #+BEGIN_SRC emacs-lisp + (("default" . ((user-emacs-directory . "~/doom-emacs"))) + ("spacemacs" . ((user-emacs-directory . "~/spacemacs"))) + ("prelude" . ((user-emacs-directory . "~/prelude")))) + #+END_SRC + +To start Emacs with a specific config, use the =--with-profile= option: + +#+BEGIN_SRC bash +emacs --with-profile spacemacs +#+END_SRC + +If no profile is specified, the =default= profile is used. + +** External/system dependencies +Your system, your rules. There are as many ways to set up a programming +environment as there are dislikes on Youtube Rewind 2018, so Doom entrusts this +task to you, dear user. + +Doom is comprised of modules which provide most of its features, including +language support and integration with external tools. However, some of these +have external dependencies that you must install yourself. You'll find what +modules need what and how to install them in that module's README.org file. If +you find a module without a README file, helps us out by creating one for us! + +~doom doctor~ will provide an overview of missing dependencies (only for the +modules you have enabled) by reporting which ones haven't been installed yet. +Once you know what's missing, have a look at the documentation for that module. + +Use ~M-x doom/help-modules~ (bound to =SPC h d m=) to quickly jump to a module's +documentation from inside Doom. Otherwise, check out the [[file:index.org::*Module list][Module Index]]. + +* Update +Doom is an active project and many of its 300+ plugins are in active development +as well. It is wise to occasionally update them. The following section will go +over how to do so. + +#+begin_quote +*Important: you may encounter errors after up/downgrading Emacs.* Emacs bytecode +is not forward compatible, so you must recompile or reinstall your plugins to +fix this, i.e. + ++ ~doom build~, to rebuild all your installed plugins, ++ Or delete =~/.emacs.d/.local= then ~doom refresh~ to reinstall them +#+end_quote + +** Doom +The =bin/doom= script provides a simple command for upgrading Doom (which will +also update your plugins): + +#+BEGIN_SRC bash +doom upgrade # short version: doom up +#+END_SRC + +If you want to update Doom manually, ~doom upgrade~ is equivalent to: + +#+BEGIN_SRC bash +cd ~/.emacs.d +git pull # updates Doom +doom refresh # refreshes plugins & autoloads +doom update # updates installed plugins +#+END_SRC + +To minimize issues while upgrading, avoid modifying Doom's source files. All +your customization should be kept in your =DOOMDIR= (typically, =~/.doom.d=). +Read the [[#customize][Customize]] section for more on configuring Doom. + +** Plugins +To update /only/ your plugins (i.e. not Doom), run ~doom update~ (short version: +~doom u~). + +** Rollback +The =bin/doom= script doesn't currently offer rollback support for Doom or its +plugins (yet). + +* Customize +Your private configuration is located in =~/.doom.d=, by default (if +=~/.config.d/doom= exists, that will be used instead). This directory is +referred to as your ~$DOOMDIR~ or your "private module". + +~doom install~ will create three files in your DOOMDIR to start you off: + ++ init.el :: This is where you'll find your ~doom!~ block, which controls what + modules are enabled and in what order they are loaded. This is copied from + =~/.emacs.d/init.example.el=. ++ config.el :: This is where the bulk of your private configuration will go. ++ packages.el :: This is where you tell Doom what packages you want to install + and where from. + +** How to enable or disable modules +Every private config starts with a ~doom!~ block, found in =$DOOMDIR/init.el=. +If you followed the Doom installation instructions and ran ~doom install~, this +file should exist and will contain one. + +This block controls what modules are enabled and in what order they are loaded. +To enable a module, add it to this list. To disable it, either remove it or +comment it out (in Emacs Lisp, anything following a semicolon is ignored by the +Elisp interpreter; i.e. it's "commented out"). + +#+BEGIN_SRC emacs-lisp +;; To comment something out, you insert at least one semicolon before it. The +;; Emacs Lisp interpreter will ignore whatever follows. +(doom! :lang + python ; this module is not commented, therefore enabled + ;;javascript ; this module is commented out, therefore disabled + ;;lua ; this module is disabled + ruby ; this module is enabled + php) ; this module is enabled +#+END_SRC + +Some modules have optional features that can be enabled by passing them flags +like so: + +#+BEGIN_SRC emacs-lisp +(doom! :completion + (company +auto) + :lang + (csharp +unity) + (org +attach +babel +capture +export +present +protocol) + (sh +fish)) +#+END_SRC + +Different modules support different flags. To see a quick list of what modules +support what flags in [[file:index.org::*Module list][the Module Index]]. + +*WARNING:* when changing your ~doom!~ block you *must* run =~/.emacs.d/bin/doom +refresh= and restart Emacs for the changes to take effect. This ensures the +needed packages are installed, orphaned packages are removed, and necessary +metadata for your Doom Emacs config has been generated. + +** Package management +Doom's package manager is declarative. Your ~DOOMDIR~ is a module, and modules +may optionally possess a packages.el file, where you may declare what packages +you want to install (and where from) using the ~package!~ macro. It can be used +to: + +1. Install packages (conditionally, even), +2. Disable packages (uninstalling them and disabling their configuration), +3. Or change where a package is installed from. + +If a package is installed via ELPA and does not have a ~package!~ declaration, +Doom will assume the package is unwanted and uninstall it for you next time +~doom refresh~ is executed. + +#+begin_quote +Remember to run ~doom refresh~ after modifying your packages, to ensure they are +installed and properly integrated into Doom. +#+end_quote + +*** Installing packages +To install a package, add a ~package!~ declaration for it to +=DOOMDIR/packages.el=: + +#+BEGIN_SRC emacs-lisp +;; Install a package named "example" from ELPA or MELPA +(package! example) + +;; Tell Doom to install it from a particular archive (e.g. elpa). By default, it +;; will search orgmode.org and melpa.org before searching elpa.gnu.org. See +;; `package-archives' to adjust this order (or to see what values :pin will +;; accept). +(package! example :pin "elpa") + +;; Instruct Doom to install this package once, but never update it when you run +;; `doom update` or `doom upgrade`: +(package! example :freeze t) + +;; Or tell Doom to not manage a particular package at all. +(package! example :ignore t) +#+END_SRC + +~package!~ will return non-nil if the package isn't disabled and is cleared for +install. Use this fact to conditionally install other packages, e.g. + +#+BEGIN_SRC elisp +(when (package! example) + (package! plugin-that-example-depends-on)) +#+END_SRC + +*** Installing packages from external sources +To install a package straight from an external source (like github, gitlab, +etc), you'll need to specify a [[https://github.com/raxod502/straight.el#the-recipe-format][MELPA-style straight recipe]]: + +Here are a few examples: + +#+BEGIN_SRC elisp +;; Install it directly from a github repository. For this to work, the package +;; must have an appropriate .el and must have at least a Package-Version +;; or Version line in its header. +(package! example :recipe (:host github :repo "username/my-example-fork")) + +;; If the source files for a package are in a subdirectory in said repo, you'll +;; need to specify what files to pull in. +(package! example :recipe + (:host github + :repo "username/my-example-fork" + :files ("*.el" "src/lisp/*.el"))) + +;; To grab a particular commit: +(package! example :recipe + (:host gitlab + :repo "username/my-example-fork" + :branch "develop")) + +;; If a package has a default recipe on MELPA or emacsmirror, you may omit +;; keywords and the recipe will inherit from their original. +(package! example :recipe (:branch "develop")) + +;; If the repo pulls in many unneeded submodules, you can disable recursive cloning +(package! example :recipe (:nonrecursive t)) +#+END_SRC + +*** Disabling packages +The ~package!~ macro possesses a ~:disable~ property. + +#+BEGIN_SRC emacs-lisp +(package! irony :disable t) +(package! rtags :disable t) +#+END_SRC + +Once a package is disabled, ~use-packages!~ and ~after!~ blocks for it will be +ignored, and the package will be removed the next time you run ~doom refresh~. +Use this to disable undesirable packages included with the built-in modules. + +Alternatively, the ~disable-packages!~ macro exists for more concisely disabling +multiple packages: + +#+BEGIN_SRC elisp +(disable-packages! irony rtags) +#+END_SRC + +*** Changing a built-in recipe for a package +If a module installs package X, but you'd like to install it from somewhere else +(say, a superior fork or a fork with a bugfix), simple add a ~package!~ +declaration for it in your =DOOMDIR/packages.el=. Your private declarations +always have precedence over modules (even your own modules). + +#+BEGIN_SRC elisp +;; modules/editor/evil/packages.el +(package! evil) ; installs from MELPA + +;; DOOMDIR/packages.el +(package! evil :recipe (:host github :repo "username/my-evil-fork")) +#+END_SRC + +You will need to run ~doom refresh~ for this change to take effect. + +*** TODO Using/loading local packages + +** Configuring Doom +*** Configuring packages +If your configuration needs are simple, the ~use-package!~, ~after!~, +~add-hook!~ and ~setq-hook!~ emacros can help you reconfigure packages: + +#+BEGIN_SRC emacs-lisp +;;; ~/.doom.d/config.el (example) +(setq doom-font (font-spec :family "Fira Mono" :size 12)) + +;; Takes a feature symbol or a library name (string) +(after! evil + (setq evil-magic nil)) + +;; Takes a major-mode, a quoted hook function or a list of either +(add-hook! python-mode + (setq python-shell-interpreter "bpython")) + +;; These are equivalent +(setq-hook! 'python-mode-hook python-indent-offset 2) +(setq-hook! python-mode python-indent-offset 2) + +(use-package! hl-todo + ;; if you omit :defer, :hook, :commands, or :after, then the package is loaded + ;; immediately. By using :hook here, the `hl-todo` package won't be loaded + ;; until prog-mode-hook is triggered (by activating a major mode derived from + ;; it, e.g. python-mode) + :hook (prog-mode . hl-todo-mode) + :init + ;; code here will run immediately + :config + ;; code here will run after the package is loaded + (setq hl-todo-highlight-punctuation ":")) +#+END_SRC + +For more flexibility, the ~use-package-hook!~ is another option, but should be +considered a last resort (because there is usually a better way). It allows you +to disable, append/prepend to and/or overwrite Doom's ~use-package!~ blocks. +These are powered by ~use-package~'s inject-hooks under the hood. + +~use-package-hook!~ *must be used before that package's ~use-package!~ block*. +Therefore it must be used from your private init.el file. + +#+BEGIN_SRC emacs-lisp +;;; ~/.doom.d/init.el (example) +;; If a :pre-init / :pre-config hook returns nil, it overwrites that package's +;; original :init / :config block. Exploit this to overwrite Doom's config. +(use-package-hook! doom-themes + :pre-config + (setq doom-neotree-file-icons t) + nil) + +;; ...otherwise, make sure they always return non-nil! +(use-package-hook! evil + :pre-init + (setq evil-magic nil) + t) + +;; `use-package-hook' also has :post-init and :post-config hooks +#+END_SRC + +*** Reloading your config +You may find it helpful to have your changes take effect immediately. For things +that don't require a complete restart of Doom Emacs (like changing your enabled +modules or installed packages), you can evaluate Emacs Lisp code on-the-fly. + ++ Evil users can use the =gr= operator to evaluate a segment of code. The return + value is displayed in the minibuffer or in a popup (if the result is large + enough to warrant one). + + =gr= works for most languages, but using it on Elisp is a special case; it's + executed within your current session of Emacs. You can use this to modify + Emacs' state on the fly. ++ Non-evil users can use =C-x C-e= to run ~eval-last-sexp~, as well as ~M-x + +eval/buffer-or-region~ (on =SPC c e=). ++ Another option is to open a scratch buffer with =SPC x=, change its major mode + (~M-x emacs-lisp-mode~), and use the above keys to evaluate your code. ++ An ielm REPL is available by pressing =SPC o r= + (~+eval/open-repl-other-window~). ++ There's also =M-:= or =SPC ;=, which invokes ~eval-expression~, which you can + use to run elisp code inline. + +While all this is helpful for reconfiguring your running Emacs session, it can +also be helpful for debugging. + +*** TODO Binding keys ++ define-key ++ global-set-key ++ map! ++ unmap! ++ define-key! + +*** TODO DOOMDIR file structure + +** Writing your own modules +Modules are made up of several files, all of which are optional. This is a +comprehensive list of what they are: + +#+begin_example +modules/ + category/ + module/ + test/*.el + autoload/*.el + autoload.el + init.el + config.el + packages.el + doctor.el +#+end_example + +By default, doom looks for modules in two places: =.emacs.d/modules/= where doom's +own modules are located and =$DOOMDIR/modules/= where you can define your +own private modules. +*** Structure of a module +**** =init.el= +This file is loaded first, before anything else, but after Doom core is loaded. + +Use this file to: + ++ Configure Emacs or perform setup/teardown operations that must be set before + other modules are (or this module is) loaded. Tampering with ~load-path~, for + instance. ++ Reconfigure packages defined in Doom modules with ~use-package-hook!~ (as a + last resort, when ~after!~ and hooks aren't enough). ++ To change the behavior of ~bin/doom~. + +Do *not* use this file to: + ++ Configure packages with ~use-package!~ or ~after!~ ++ Preform expensive or error-prone operations; these files are evaluated + whenever ~bin/doom~ is used. + +**** =config.el= +This file is the heart of every module. + +Code in this file should expect that dependencies (in =packages.el=) are +installed and available, but shouldn't make assumptions about what /modules/ are +activated (use ~featurep!~ for this). + +Packages should be configured using ~after!~ or ~use-package!~. + +#+BEGIN_SRC emacs-lisp +;; from modules/completion/company/config.el +(use-package! company + :commands (company-mode global-company-mode company-complete + company-complete-common company-manual-begin company-grab-line) + :config + (setq company-idle-delay nil + company-tooltip-limit 10 + company-dabbrev-downcase nil + company-dabbrev-ignore-case nil) + [...]) +#+END_SRC + +#+begin_quote +For anyone already familiar with ~use-package~, ~use-package!~ is merely a thin +wrapper around it. It supports all the same keywords and can be used in much the +same way. +#+end_quote + +**** =packages.el= +This file is where package declarations belong. It's also a good place to look +if you want to see what packages a module manages (and where they are installed +from). + +A =packages.el= file shouldn't contain complex logic. Mostly conditional +statements and ~package!~, ~disable-packages!~ or ~depend-on!~ calls. It +shouldn't produce side effects and should be deterministic. Because this file +gets evaluated in an environment isolated from your interactive session, code +within should make no assumptions about the current session. + +The ~package!~ macro is the star of the show in =packages.el= files: + +#+BEGIN_SRC emacs-lisp +;; from modules/lang/org/packages.el +(package! org-bullets) + +;; from modules/tools/rotate-text/packages.el +(package! rotate-text :recipe (:host github :repo "debug-ito/rotate-text.el")) +#+END_SRC + +Its ~:recipe~ property accepts [[https://github.com/melpa/melpa#recipe-format][a MELPA recipe]], which provides a lot of control +over where to fetch a package, including specific commit, tags or branches: + +#+BEGIN_SRC emacs-lisp +(package! rotate-text + :recipe (:host github + :repo "debug-ito/rotate-text.el" + :commit "1a2b3c4d")) +#+END_SRC + +You can also use this ~package!~ to disable other packages: + +#+BEGIN_SRC emacs-lisp +;; Uninstalls evil, keeps it uninstalled, and tells Doom to ignore any +;; use-package! and after! blocks for it +(package! evil :disable t) + +;; disable-packages! can be used to disable multiple packages in one statement +(disable-packages! evil evil-snipe evil-escape) +#+END_SRC + +**** =autoload/*.el= OR =autoload.el= +Functions marked with an autoload cookie (~;;;###autoload~) in these files will +be lazy loaded. + +When you run ~bin/doom autoloads~, Doom scans these files to populate autoload file +in =~/.emacs.d/.local/autoloads.el=, which will tell Emacs where to find these +functions when they are called. + +For example: + +#+BEGIN_SRC emacs-lisp +;; from modules/lang/org/autoload/org.el +;;;###autoload +(defun +org/toggle-checkbox () + (interactive) + [...]) + +;; from modules/lang/org/autoload/evil.el +;;;###autoload (autoload '+org:attach "lang/org/autoload/evil" nil t) +(evil-define-command +org:attach (&optional uri) + (interactive "") + [...]) +#+END_SRC + +**** =doctor.el= +This file is used by ~make doctor~, and should test for all that module's +dependencies. If it is missing one, it should use the ~warn!~, ~error!~ and +~explain!~ macros to inform the user why it's a problem and, ideally, a way to +fix it. + +For example, the ~:lang cc~ module's doctor checks to see if the irony server is +installed: + +#+BEGIN_SRC emacs-lisp +;; from lang/cc/doctor.el +(require 'irony) +(unless (file-directory-p irony-server-install-prefix) + (warn! "Irony server isn't installed. Run M-x irony-install-server")) +#+END_SRC + +**** Additional files +Sometimes, it is preferable that a module's config.el file be split up into +multiple files. The convention is to name these additional files with a leading +=+=, e.g. =modules/feature/version-control/+git.el=. + +There is no syntactical or functional significance to this convention. +Directories do not have to follow this convention, nor do files within those +directories. + +These additional files are *not* loaded automatically. You will need to use the +~load!~ macro to do so: + +#+BEGIN_SRC emacs-lisp +;; from modules/feature/version-control/config.el +(load! +git) +#+END_SRC + +The ~load!~ macro will try to load a =+git.el= relative to the current file. + +*** Load order +Module files are loaded in a precise order: + +#+BEGIN_SRC sh +~/.emacs.d/early-init.el # in Emacs 27+ only +~/.emacs.d/init.el +$DOOMDIR/init.el +{~/.emacs.d,$DOOMDIR}/modules/*/*/init.el +{~/.emacs.d,$DOOMDIR}/modules/*/*/config.el +$DOOMDIR/config.el +#+END_SRC + +*** Module flags +In the code examples of the previous section, you may have noticed something odd +about that haskell entry: ~(haskell +intero)~. ~+intero~ is a module flag. You +may specify these for any module that supports them. Unsupported flags are +ignored. + +You can find out what flags a module supports by looking at its documentation (a +README.org in the module's directory; which can be jumped to quickly with ~M-x +doom/describe-module~). + +For example, the haskell module supports the ~+intero~ and ~+dante~ flags, which +represent the two Haskell backends available to Emacs. You may choose one or the +other (or neither, or both) by specifying the appropriate flags in you ~doom!~ +block: + +#+BEGIN_SRC emacs-lisp +(doom! :lang (haskell +dante)) +#+END_SRC + +You may specify as many flags are you like: + +#+BEGIN_SRC emacs-lisp +(doom! :lang (org +attach +babel +capture +export +present)) +#+END_SRC + +#+begin_quote +=+flagname= is simply a naming convention and has no syntactical or functional +significance. +#+end_quote + +**** Testing for flags +Modules are free to interpret flags however they like. If you are writing your +own module(s), you can test for flags using the ~featurep! MODULE SUBMODULE +&optional FLAG~ macro: + +#+BEGIN_SRC emacs-lisp +(when (featurep! :lang haskell +dante) + [...]) +#+END_SRC + +The first two arguments if ~featurep!~ may be skipped if it is used from inside +a module. For example: + +#+BEGIN_SRC emacs-lisp +;; In modules/lang/haskell/config.el +(when (featurep! +dante) ; same as (featurep! :lang haskell +dante) + [...]) +#+END_SRC + +*** Module settings +Some modules expose settings that can be configured from other modules. Use ~M-x +doom/help-autdefs~ (=SPC h d a= or =C-h d a=) to see what is available and how +to use them. + +An example would be the ~set-company-backend!~ function that the =:completion +company= module exposes. It lets you register company completion backends with +certain major modes. For instance: + +#+BEGIN_SRC emacs-lisp +(set-company-backend! 'python-mode '(company-anaconda)) +#+END_SRC + +You'll find what settings a module exposes in its documentation (remember to use +~M-x doom/help-modules~ on =SPC h d m= or =C-h d m=). +*** Module cookies +There is a special syntax available to module files called module cookies. Like +autoload cookies (~;;;###autoload~), module files may have ~;;;###if FORM~ at or +near the top of the file. FORM is read by ~doom refresh~ and ~doom compile~ to +determine whether or not to ignore this file. + +If FORM returns nil, the file won't be scanned for autoloads nor will it be +byte-compiled. Use this to prevent errors that may occur if that file contains +(for example) calls to functions that won't exist if a certain feature isn't +available to that module, e.g. + +#+BEGIN_SRC emacs-lisp +;;;###if (featurep! +intero) +#+END_SRC + +#+BEGIN_SRC emacs-lisp +;;;###if (not (featurep 'evil-mode)) +#+END_SRC + +Remember that these run in a limited, non-interactive sub-session, so do not +call anything that wouldn't be available in a Doom session without any modules +enabled. +** Common mistakes when configuring Doom Emacs +Having helped many users configure Doom, I've spotted a few recurring oversights +that I will list here, in the hopes that it will help you avoid the same +mistakes: + +*** Packages are eagerly loaded +Using ~use-package!~ without a deferring keyword (one of: ~:defer :after +:commands :defer-incrementally :after-call~) will load the package immediately. +This can cause other packages to be pulled in and loaded, which will compromise +many of Doom's startup optimizations. + +This is usually by accident. Choosing which keyword to use depends on the +needs of the package, so there is no simple answer to this. + +*** Manual package management +A lot of Emacs documentation and help will contain advice to install packages +with package.el's API (e.g. ~package-install~) or with use-package's ~:ensure~ +keyword). You are free to do this, if it is your preference, but otherwise, Doom +has its own package management system. + +Migrating ~use-package~ code to Doom is usually a case of removing the ~:ensure~ +keyword and adding a ~(package! PACKAGENAME)~ to =~/.doom.d/packages.el= (and +running ~doom refresh~ to sync your config). + +*** Using ~org-babel-do-load-languages~ to load your babel plugins +You don't need ~org-babel-do-load-languages~. Doom lazy loads babel plugins +based on the language name in ~#+BEGIN_SRC~ blocks needed. As long as the babel +plugin is installed and the plugin is named after its language (e.g. +~#+BEGIN_SRC rust~ will load ~ob-rust~), you don't need to do anything else. + +There may be some special cases, however. Doom tries to handle a couple of them +(e.g. with ob-jupyter, ob-ipython and ob-async). If you are experiencing errors +while trying to use a certain language in org src blocks, check out the [[file:../modules/lang/org/README.org][:lang +org module documentation]] for details on how to add support for it. + +*** Using ~delete-trailing-whitespaces~ or ~whitespace-cleanup~ to manage leftover whitespace +#+BEGIN_SRC elisp +(add-hook 'after-save-hook #'delete-trailing-whitespace) +;; or +(add-hook 'after-save-hook #'whitespace-cleanup) +#+END_SRC + +These two lines are a common sight in Emacs configs, but they are unnecessary +for Doom Emacs. We already use the more sophisticated =wsbutler= to manage +extraneous whitespace. However, you might have the impression that it isn't +working. That's because =wsbutler= works in two unusual ways, meant to be less +imposing than its alternatives: + +1. It only cleans up trailing whitespace /on lines that you've touched/ (but + always strips newlines at EOF). + + Why do this? Because I believe file-wide reformatting should be a deliberate + act (and not blindly automated). If it is necessary, chances are you're + working on somebody else's project -- or with other people, but here, large + scale whitespace changes could cause problems or simply be rude. We don't + endorse PRs that are 1% contribution and 99% whitespace! + + However, if it's truly deliberate, ~M-x delete-trailing-whitespaces~ and ~M-x + whitespace-cleanup~ are available to be called =deliberately=, instead. + +2. =wsbutler= replaces trailing whitespace and newlines with *virtual* + whitespace. This is whitespace that only exists in the Emacs buffer, but + isn't actually written to the file. + + Why do this? Because you might have wanted to use that space for something in + your current editing session, and it would be inconvenient for the editor to + delete it before you got to it. + + If you use it, it's there. If you don't, it isn't written to the file. + +* Troubleshoot +When problems arise, and they will, you will need to debug them. Fortunately, +Emacs (and Doom) provide you with tools to make this easier. I recommend +becoming acquainted with them. They will be yours (and our) best tool for +understanding the problem. + +** I've run into an issue, where do I start? +Before you file a bug report, there are a number of things you should try first: + ++ You'll find [[file:faq.org::Common%20Issues][a list of common issues & errors in the FAQ]]. That is a good place + to start. You can access and search this FAQ from inside Doom with =SPC h d f= + (or =C-h d f= for non-evil users). + ++ Run ~doom doctor~ to diagnose any common issues with your environment or + config. + ++ Run ~doom refresh~ to ensure the problem isn't caused by missing packages or + outdated autoloads files. + ++ See if your issue is mentioned in the Common Issues section below. + ++ Search Doom's issue tracker to see if your issue is mentioned there. + ++ Ask for help on [[https://discord.gg/bcZ6P3y][our Discord server]]. This may not be immediately available to + everyone, so I won't fault you for skipping this step, but you'll sometimes + find help there quicker. In many cases, Henrik fixes issues. + +** Looking up documentation and state from within Emacs +... + +*** Variables, functions, faces, etc. +Emacs is a Lisp interpreter whose state you can access on-the-fly with tools +provided to you by Emacs itself. They're available on the =SPC h= prefix by +default. Use them to debug your sessions. + +Here are some of the more important ones: + ++ ~describe-variable~ (=SPC h v=) ++ ~describe-function~ (=SPC h f=) ++ ~describe-face~ (=SPC h F=) ++ ~describe-bindings~ (=SPC h b=) ++ ~describe-key~ (=SPC h k=) ++ ~describe-char~ (=SPC h '=) ++ ~find-library~ (=SPC h P=) + +You can also evaluate code with ~eval-expression~ (=M-;= or =SPC ;=). + +*** TODO For Doom Modules, packages, autodefs, etc. ++ ~doom/open-news~ (=SPC h n=) :: + ... ++ ~doom/open-manual~ (=SPC h D=) :: + ... ++ ~doom/describe-module~ (=SPC h d=) :: + Jumps to a module's documentation. ++ ~doom/describe-autodefs~ (=SPC h A=) :: + Jumps to the documentation for an autodef function/macro. These are special + functions that are always defined, whether or not their containing modules + are enabled. ++ ~doom/describe-package~ (=SPC h p=) :: + Look up packages that are installed, by whom (what modules) and where jump + to all the places it is being configured. ++ ~doom/info~ :: + ... + +** How to extract a backtrace from an error +If you encounter an error while using Doom Emacs, you're probably about to head +off and file a bug report (or request help on [[https://discord.gg/bcZ6P3y][our Discord server]]). Before you +do, please generate a backtrace to include with it. + +To do so you must enable ~debug-on-error~ then recreate the error. + +*** Enabling ~debug-on-error~ +There are three ways to enable ~debug-on-error~: + +1. Start Emacs with ~emacs --debug-init~. Use this for errors that occur at + startup. +2. Evil users can press =SPC h d d= and non-evil users can press =C-h d d=. +3. If the above don't work, there's always: ~M-x toggle-debug-on-error~ + +Now that ~debug-on-error~ is on, recreate the error. A window should pop up with +a backtrace. + +*** A backtrace from ~bin/doom~ +If the error you've encountered is emitted from ~bin/doom~, you can re-run the +same command with the ~-d~ or ~--debug~ switches to force it to emit a backtrace +when an error occurs. The ~DEBUG~ environment variable will work to. + +#+BEGIN_SRC sh +doom -d refresh +doom --debug install +DEBUG=1 doom update +#+END_SRC + +#+BEGIN_QUOTE +Note: switch order is important. ~-d~ / ~--debug~ /must/ come right after ~doom~ +and before the subcommand. This will be fixed eventually. +#+END_QUOTE + +** Evaluating Elisp on-the-fly +Often, you may find it helpful for debugging to evaluate some Emacs Lisp. Here +are couple things you can do: + ++ Use =M-;= (bound to ~eval-expression~), ++ =SPC x= will open a scratch buffer. ~M-x emacs-lisp-mode~ will change it to + the appropriate major mode, then use ~+eval:region~ (=gr=) and ~+eval:buffer~ + (=gR=) to evaluate code, + +** How to determine the origin of a bug +** Testing in Doom's sandbox +"The sandbox" is one of Doom Emacs' features; it is a test bed for running elisp +in a fresh instance of Emacs with varying amounts of Doom loaded (none at all, +all of it, or somewhere in between). This can be helpful for isolating bugs to +determine who you should report a bug to. + +If you can recreate a bug in vanilla Emacs than it should be reported to the +developers of the relevant plugins or, perhaps, the Emacs devs themselves. + +Otherwise, it is best to bring it up on the Doom Emacs issue list, rather than +confusing and inundating the Emacs community with Doom-specific issues. + +*** Opening the sandbox +There are three common ways to access the sandbox: + ++ =SPC h E= (for evil users) ++ =C-h E= (for non-evil users) ++ ~M-x doom/sandbox~ + +Doing any of the above will pop up a ~*doom:sandbox*~ window. What you enter +into this buffer will be executed in the new instance of Emacs when you decide +to launch it. + +*** Launching the sandbox +You have four options when it comes to launching the sandbox: + +- =C-c C-c= :: This launches "vanilla Emacs". Vanilla means nothing is loaded; + purely Emacs and nothing else. If you can reproduce an error here, then the + issue likely lies in the plugin(s) you are testing or in Emacs itself. +- =C-c C-d= :: This launches "vanilla Doom", which is vanilla Emacs plus Doom's + core. This does not load your private config, nor any of Doom's (or your) + modules. +- =C-c C-p= :: This launches "vanilla Doom+". That is, Doom core plus the + modules that you have specified in the ~doom!~ block of your private config + (in =~/.doom.d/init.el=). This *does not* load your private config, however. +- =C-c C-f= :: This launches "full Doom". It loads Doom's core, your enabled + modules, and your private config. This instance should be identical to the + instance you launched it from. + +#+BEGIN_QUOTE +All new instances will inherit your ~load-path~ so you can access any packages +you have installed. +#+END_QUOTE +*** Testing packages in the sandbox +Instances of Emacs launched from the sandbox have inherited your ~load-path~. +This means you can load packages -- even in Vanilla Emacs -- without worrying +about installing or setting them up. Just ~(require PACKAGE)~ and launch the +sandbox. e.g. + +#+BEGIN_SRC elisp +(require 'magit) +(find-file "~/some/file/in/a/repo") +(call-interactively #'magit-status) +#+END_SRC + +** TODO Bisecting your private config +** TODO Bisecting Doom Emacs diff --git a/.emacs.d/docs/index.org b/.emacs.d/docs/index.org new file mode 100644 index 0000000..1904832 --- /dev/null +++ b/.emacs.d/docs/index.org @@ -0,0 +1,276 @@ +#+TITLE: Doom Emacs Documentation +#+STARTUP: nofold + +Doom Emacs is a configuration for [[https://www.gnu.org/software/emacs/][GNU Emacs]] written by a stubborn, +shell-dwelling, and melodramatic ex-vimmer. It is designed to be a foundation +for your own Emacs configuration or a resource for enthusiasts to learn more +about our favorite OS. + +#+begin_quote +Github fails to render org links to sub-sections, so it is recommended that you +view the documentation from within Doom Emacs by pressing = d h= (== +is =SPC h= for evil users and =C-h= for vanilla users) or searching it with += d /=. +#+end_quote + +* Table of Contents :TOC: +- [[#release-notes][Release Notes]] +- [[#documentation][Documentation]] + - [[#getting-started][Getting Started]] + - [[#frequently-asked-questions][Frequently Asked Questions]] + - [[#contributing][Contributing]] + - [[#workflow-tips-tricks--tutorials][Workflow Tips, Tricks & Tutorials]] +- [[#community-resources][Community Resources]] + - [[#asking-for-help][Asking for help]] + - [[#project-roadmap][Project roadmap]] + - [[#tutorials--guides][Tutorials & guides]] +- [[#module-list][Module list]] + - [[#app][:app]] + - [[#completion][:completion]] + - [[#config][:config]] + - [[#editor][:editor]] + - [[#emacs][:emacs]] + - [[#email][:email]] + - [[#input][:input]] + - [[#lang][:lang]] + - [[#term][:term]] + - [[#tools][:tools]] + - [[#ui][:ui]] + +* TODO Release Notes + +* Documentation +** [[file:getting_started.org][Getting Started]] +- [[file:getting_started.org::*Install][Install]] - How to install Emacs, Doom and its plugins +- [[file:getting_started.org::*Update][Update]] - Keep Doom and its packages up-to-date +- [[file:getting_started.org::*Customize][Customize]] - A primer on customizing and reconfiguring Doom +- [[file:getting_started.org::*Troubleshoot][Troubleshoot]] - How to debug Emacs & Doom, find help or look up documentation + +** [[file:faq.org][Frequently Asked Questions]] +- [[file:faq.org::*General][General]] +- [[file:faq.org::*Configuration][Configuration]] +- [[file:faq.org::*Defaults][Defaults]] +- [[file:faq.org::Common Issues][Common Issues]] +- [[file:faq.org::Contributing][Contributing]] + +** TODO [[file:contributing.org][Contributing]] +- [[file:contributing.org::*Where can I help?][Where to get help]] +- Writing an effective bug report +- Suggesting features, keybinds or enhancements +- Contributing code +- Contributing documentation +- Other ways to support Doom Emacs +- Special thanks + +** TODO [[file:workflow.org][Workflow Tips, Tricks & Tutorials]] + +* Community Resources +** Asking for help +- [[https://discord.gg/bcZ6P3y][Our Discord server]] +- [[https://github.com/hlissner/doom-emacs/issues][Our issue tracker]] + +** Project roadmap +- [[https://github.com/hlissner/doom-emacs/projects/3][Development roadmap]] - A timeline outlining what's being worked on and when it + is expected to be done. +- [[https://github.com/hlissner/doom-emacs/projects/2][Plugins under review]] - A sitrep on third party plugins that we've considered, + rejected, or awaiting integration into Doom. +- [[https://github.com/hlissner/doom-emacs/projects/5][Upstream bugs]] - Tracks issues originating from plugins and external programs + that Doom relies on. + +** Tutorials & guides ++ *Doom Emacs* + - [[https://noelwelsh.com/posts/2019-01-10-doom-emacs.html][Noel's crash course on Doom Emacs]] + - [[https://medium.com/@aria_39488/getting-started-with-doom-emacs-a-great-transition-from-vim-to-emacs-9bab8e0d8458][Getting Started with Doom Emacs -- a great transition from Vim to Emacs]] + - [[https://medium.com/@aria_39488/the-niceties-of-evil-in-doom-emacs-cabb46a9446b][The Niceties of evil in Doom Emacs]] + - [[https://www.youtube.com/playlist?list=PLhXZp00uXBk4np17N39WvB80zgxlZfVwj][DoomCasts (youtube series)]] + - [[https://www.youtube.com/watch?v=GK3fij-D1G8][Org-mode, literate programming in (Doom) Emacs]] ++ *Emacs & Emacs Lisp* + - [[https://www.gnu.org/software/emacs/manual/html_node/elisp/index.html][The Official Emacs manual]] + - A variety of Emacs resources - https://github.com/ema2159/awesome-emacs + - Quick crash courses on Emacs Lisp's syntax for programmers: + - https://learnxinyminutes.com/docs/elisp/ + - http://steve-yegge.blogspot.com/2008/01/emergency-elisp.html + - Workflows for customizing Emacs and its packages (and its C/C++ modes): + - https://david.rothlis.net/emacs/customize_c.html ++ *Tools in Emacs* + - [[https://www.emacswiki.org/emacs/Calc_Tutorials_by_Andrew_Hyatt][How to use M-x calc]] ++ *Vim & Evil* + - [[https://gist.github.com/dmsul/8bb08c686b70d5a68da0e2cb81cd857f][A crash course on modal editing and Ex commands]] + +* Module list +** :app +Application modules are complex and opinionated modules that transform Emacs +toward a specific purpose. They may have additional dependencies and should be +loaded last, before =:config= modules. + ++ [[file:../modules/app/calendar/README.org][calendar]] - TODO ++ [[file:../modules/app/irc/README.org][irc]] - how neckbeards socialize ++ rss =+org= - an RSS client in Emacs ++ [[file:../modules/app/twitter/README.org][twitter]] - A twitter client for Emacs ++ [[file:../modules/app/write/README.org][write]] =+wordnut +langtool= - Transforms emacs into an IDE for writers, and for + writing fiction, notes, papers and so on. + +** :completion +Modules that provide new interfaces or frameworks for completion, including code +completion. + ++ [[file:../modules/completion/company/README.org][company]] =+childframe +tng= - 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:../modules/completion/ivy/README.org][ivy]] =+fuzzy +prescient +childframe= - /The/ search engine for love and life + +** :config +Modules that configure Emacs one way or another, or focus on making it easier +for you to customize it yourself. It is best to load these last. + ++ literate - For users with literate configs. This will tangle+compile a + config.org in your ~doom-private-dir~ when it changes. ++ [[file:../modules/config/default/README.org][default]] =+bindings +smartparens= - 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. + +** :editor +Modules that affect and augment your ability to manipulate or insert text. + ++ [[file:../modules/editor/evil/README.org][evil]] =+everywhere= - transforms Emacs into Vim ++ [[file:../modules/editor/file-templates/README.org][file-templates]] - Auto-inserted templates in blank new files ++ [[file:../modules/editor/fold/README.org][fold]] - universal code folding ++ format =+onsave= - TODO ++ god - run Emacs commands without modifier keys ++ [[file:../modules/editor/lispy/README.org][lispy]] - TODO ++ multiple-cursors - TODO ++ [[file:../modules/editor/objed/README.org][objed]] - TODO ++ [[file:../modules/editor/parinfer/README.org][parinfer]] - TODO ++ rotate-text - TODO ++ [[file:../modules/editor/snippets/README.org][snippets]] - Snippet expansion for lazy typists ++ [[file:../modules/editor/word-wrap/README.org][word-wrap]] - soft wrapping with language-aware indent + +** :emacs +Modules that reconfigure or augment packages or features built into Emacs. + ++ [[file:../modules/emacs/dired/README.org][dired]] =+ranger +icons= - TODO ++ electric - TODO ++ [[file:../modules/emacs/ibuffer/README.org][ibuffer]] =+icons= - TODO ++ vc - TODO + +** :email ++ [[file:../modules/email/mu4e/README.org][mu4e]] =+gmail= - TODO ++ notmuch - TODO ++ wanderlust =+gmail= - TODO + +** :input ++ [[file:../modules/input/chinese/README.org][chinese]] - TODO ++ [[file:../modules/input/japanese/README.org][japanese]] - TODO + +** :lang +Modules that bring support for a language or group of languages to Emacs. + ++ [[file:../modules/lang/agda/README.org][agda]] - TODO ++ assembly - TODO ++ [[file:../modules/lang/cc/README.org][cc]] =+lsp= - TODO ++ clojure - TODO ++ common-lisp - TODO ++ [[file:../modules/lang/coq/README.org][coq]] - TODO ++ crystal - TODO ++ [[file:../modules/lang/csharp/README.org][csharp]] - TODO ++ data - TODO ++ [[file:../modules/lang/elixir/README.org][elixir]] =+lsp= - TODO ++ elm - TODO ++ emacs-lisp - TODO ++ erlang - TODO ++ [[file:../modules/lang/ess/README.org][ess]] - TODO ++ [[file:../modules/lang/faust/README.org][faust]] - TODO ++ [[file:../modules/lang/fsharp/README.org][fsharp]] - TODO ++ [[file:../modules/lang/go/README.org][go]] =+lsp= - TODO ++ [[file:../modules/lang/haskell/README.org][haskell]] =+dante +intero +lsp= - TODO ++ hy - TODO ++ [[file:../modules/lang/idris/README.org][idris]] - TODO ++ java =+meghanada +lsp= - TODO ++ [[file:../modules/lang/javascript/README.org][javascript]] =+lsp= - JavaScript, TypeScript, and CoffeeScript support ++ julia - TODO ++ kotlin =+lsp+= - TODO ++ [[file:../modules/lang/latex/README.org][latex]] - TODO ++ lean - TODO ++ [[file:../modules/lang/ledger/README.org][ledger]] - TODO ++ lua =+moonscript= - TODO ++ [[file:../modules/lang/markdown/README.org][markdown]] =+grip= - TODO ++ [[file:../modules/lang/nim/README.org][nim]] - TODO ++ nix - TODO ++ [[file:../modules/lang/ocaml/README.org][ocaml]] =+lsp= - TODO ++ [[file:../modules/lang/org/README.org][org]] =+dragndrop +gnuplot +hugo +ipython +pandoc +pomodoro +present= - TODO ++ [[file:../modules/lang/perl/README.org][perl]] - TODO ++ [[file:../modules/lang/php/README.org][php]] =+lsp= - TODO ++ plantuml - TODO ++ purescript - TODO ++ [[file:../modules/lang/python/README.org][python]] =+lsp +pyenv +conda= - TODO ++ qt - TODO ++ racket - TODO ++ [[file:../modules/lang/rest/README.org][rest]] - TODO ++ ruby =+lsp +rvm +rbenv= - TODO ++ [[file:../modules/lang/rust/README.org][rust]] =+lsp= - TODO ++ scala =+lsp= - TODO ++ [[file:../modules/lang/scheme/README.org][scheme]] - TODO ++ [[file:../modules/lang/sh/README.org][sh]] =+fish +lsp= - TODO ++ [[file:../modules/lang/solidity/README.org][solidity]] - TODO ++ swift =+lsp= - TODO ++ terra - TODO ++ web =+lsp= - HTML and CSS (SCSS/SASS/LESS/Stylus) support. + +** :term +Modules that offer terminal emulation. + ++ eshell - TODO ++ shell - TODO ++ term - TODO ++ [[file:../modules/term/vterm/README.org][vterm]] - TODO + +** :tools +Small modules that give Emacs access to external tools & services. + ++ ansible - TODO ++ debugger - A (nigh-)universal debugger in Emacs ++ [[file:../modules/tools/direnv/README.org][direnv]] - TODO ++ [[file:../modules/tools/docker/README.org][docker]] - TODO ++ [[file:../modules/tools/editorconfig/README.org][editorconfig]] - TODO ++ [[file:../modules/tools/ein/README.org][ein]] - TODO ++ [[file:../modules/tools/eval/README.org][eval]] =+overlay= - REPL & code evaluation support for a variety of languages ++ flycheck - Live error/warning highlights ++ flyspell - Spell checking ++ gist - TODO ++ [[file:../modules/tools/lookup/README.org][lookup]] =+docsets= - Universal jump-to & documentation lookup backend ++ [[file:../modules/tools/lsp/README.org][lsp]] - TODO ++ macos - TODO ++ magit - TODO ++ make - TODO ++ pass - TODO ++ pdf - TODO ++ prodigy - TODO ++ rgb - TODO ++ [[file:../modules/tools/terraform/README.org][terraform]] ++ tmux - TODO ++ upload - TODO + +** :ui +Aesthetic modules that affect the Emacs interface or user experience. + ++ [[file:../modules/ui/deft/README.org][deft]] - TODO ++ [[file:../modules/ui/doom/README.org][doom]] - TODO ++ [[file:../modules/ui/doom-dashboard/README.org][doom-dashboard]] - TODO ++ [[file:../modules/ui/doom-quit/README.org][doom-quit]] - TODO ++ fill-column - TODO ++ [[file:../modules/ui/hl-todo/README.org][hl-todo]] - TODO ++ [[file:../modules/ui/hydra/README.org][hydra]] - TODO ++ indent-guides - TODO ++ [[file:../modules/ui/modeline/README.org][modeline]] - TODO ++ [[file:../modules/ui/nav-flash/README.org][nav-flash]] - TODO ++ [[file:../modules/ui/neotree/README.org][neotree]] - TODO ++ [[file:../modules/ui/ophints/README.org][ophints]] - TODO ++ [[file:../modules/ui/popup/README.org][popup]] =+all +defaults= - Makes temporary/disposable windows less intrusive ++ pretty-code - TODO ++ [[file:../modules/ui/tabs/README.org][tabs]] - TODO ++ treemacs - TODO ++ [[file:../modules/ui/unicode/README.org][unicode]] - TODO ++ vc-gutter - TODO ++ vi-tilde-fringe - TODO ++ [[file:../modules/ui/window-select/README.org][window-select]] =+switch-window +numbers= - TODO ++ [[file:../modules/ui/workspaces/README.org][workspaces]] - Isolated workspaces diff --git a/.emacs.d/docs/workflow.org b/.emacs.d/docs/workflow.org new file mode 100644 index 0000000..50d3bd1 --- /dev/null +++ b/.emacs.d/docs/workflow.org @@ -0,0 +1,73 @@ +#+TITLE: Workflow tips, tricks & tutorials +#+STARTUP: nofold + +This page is a WIP. + +* Table of Contents :TOC: +- [[#day-1-in-doom-emacs][Day 1 in Doom Emacs]] +- [[#an-introduction-to-modal-editing-with-evil][An introduction to modal editing with Evil]] + - [[#an-introduction-to-vim][An introduction to Vim]] + - [[#getting-around-the-buffer][Getting around the buffer]] + - [[#operating-with-operators][Operating with operators]] + - [[#get-what-you-want-with-text-objects][Get what you want with text objects]] + - [[#ways-to-edit-multiple-places-at-once][Ways to edit multiple places at once]] + - [[#pipe-text-through-ex-commands-and-programs][Pipe text through ex commands and programs]] + - [[#transposingswapping-text][Transposing/swapping text]] +- [[#managing-your-projects][Managing your projects]] +- [[#search--replace][Search & replace]] + - [[#project-wide-text-search][Project-wide text search]] + - [[#search--replace-1][Search & replace]] +- [[#how-to-get-around-your-projects][How to get around your projects]] + - [[#looking-up-definitionreferences][Looking up definition/references]] + - [[#looking-up-documentation][Looking up documentation]] + - [[#projectfile-navigation-getting-around-quickly][Project/file navigation; getting around quickly]] + - [[#getting-around-slowly][Getting around slowly]] + - [[#alternating-files-eg-scss-css-h-c][Alternating files (e.g. .scss<->.css, .h<->.c)]] +- [[#looking-things-up-online-or-in-documentation][Looking things up online or in documentation]] +- [[#creating-and-using-snippets][Creating and using snippets]] +- [[#how-to-use-workspaces][How to use workspaces]] +- [[#version-control-with-magit-forge--git-gutter][Version control with Magit, Forge & git-gutter]] +- [[#how-to-write--debug-emacs-lisp][How to write & debug Emacs Lisp]] +- [[#on-the-fly-code-evaluation--repls][On-the-fly code evaluation & REPLs]] +- [[#using-emacs-for][Using Emacs for...]] + - [[#writing-fiction][Writing fiction]] + - [[#writing-papers][Writing papers]] + - [[#composing-music][Composing music]] + - [[#game-development][Game development]] + - [[#web-development][Web development]] + - [[#machine-learning][Machine learning]] + - [[#as-an-x-client-twitter-email-rss-reader-etc][As an X client (twitter, email, RSS reader, etc)]] + +* TODO Day 1 in Doom Emacs +* TODO An introduction to modal editing with Evil +** TODO An introduction to Vim +** TODO Getting around the buffer +** TODO Operating with operators +** TODO Get what you want with text objects +** TODO Ways to edit multiple places at once +** TODO Pipe text through ex commands and programs +** TODO Transposing/swapping text +* TODO Managing your projects +* TODO Search & replace +** TODO Project-wide text search +** TODO Search & replace +* TODO How to get around your projects +** TODO Looking up definition/references +** TODO Looking up documentation +** TODO Project/file navigation; getting around quickly +** TODO Getting around slowly +** TODO Alternating files (e.g. .scss<->.css, .h<->.c) +* TODO Looking things up online or in documentation +* TODO Creating and using snippets +* TODO How to use workspaces +* TODO Version control with Magit, Forge & git-gutter +* TODO How to write & debug Emacs Lisp +* TODO On-the-fly code evaluation & REPLs +* TODO Using Emacs for... +** TODO Writing fiction +** TODO Writing papers +** TODO Composing music +** TODO Game development +** TODO Web development +** TODO Machine learning +** TODO As an X client (twitter, email, RSS reader, etc) diff --git a/.emacs.d/early-init.el b/.emacs.d/early-init.el new file mode 100644 index 0000000..25005c3 --- /dev/null +++ b/.emacs.d/early-init.el @@ -0,0 +1,27 @@ +;;; 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 most-positive-fixnum) + +;; In Emacs 27+, package initialization occurs 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 disabling these UI elements early. +(push '(menu-bar-lines . 0) default-frame-alist) +(push '(tool-bar-lines . 0) default-frame-alist) +(push '(vertical-scroll-bars) default-frame-alist) + +;; Resizing the Emacs frame can be a terribly expensive part of changing the +;; font. By inhibiting this, we easily halve startup times with fonts that are +;; larger than the system default. +(setq frame-inhibit-implied-resize t) + +;; Ignore X resources; its settings would be redundant with the other settings +;; in this file and can conflict with later config (particularly where the +;; cursor color is concerned). +(advice-add #'x-apply-session-resources :override #'ignore) diff --git a/.emacs.d/init.el b/.emacs.d/init.el old mode 100755 new mode 100644 index 6a77857..ce908b1 --- a/.emacs.d/init.el +++ b/.emacs.d/init.el @@ -1,105 +1,56 @@ -;; ____ _____ -;; | _ \_ _| Derek Taylor (DistroTube) -;; | | | || | http://www.youtube.com/c/DistroTube -;; | |_| || | http://www.gitlab.com/dwt1/ -;; |____/ |_| -;; -;; A customized config for Emacs (https://www.gnu.org/software/emacs/) -;; Modified by Derek Taylor (http://www.gitlab.com/dwt1/) +;;; init.el -*- lexical-binding: t; -*- +;; +;; Author: Henrik Lissner +;; URL: https://github.com/hlissner/doom-emacs +;; +;; ================= =============== =============== ======== ======== +;; \\ . . . . . . .\\ //. . . . . . .\\ //. . . . . . .\\ \\. . .\\// . . // +;; ||. . ._____. . .|| ||. . ._____. . .|| ||. . ._____. . .|| || . . .\/ . . .|| +;; || . .|| ||. . || || . .|| ||. . || || . .|| ||. . || ||. . . . . . . || +;; ||. . || || . .|| ||. . || || . .|| ||. . || || . .|| || . | . . . . .|| +;; || . .|| ||. _-|| ||-_ .|| ||. . || || . .|| ||. _-|| ||-_.|\ . . . . || +;; ||. . || ||-' || || `-|| || . .|| ||. . || ||-' || || `|\_ . .|. .|| +;; || . _|| || || || || ||_ . || || . _|| || || || |\ `-_/| . || +;; ||_-' || .|/ || || \|. || `-_|| ||_-' || .|/ || || | \ / |-_.|| +;; || ||_-' || || `-_|| || || ||_-' || || | \ / | `|| +;; || `' || || `' || || `' || || | \ / | || +;; || .===' `===. .==='.`===. .===' /==. | \/ | || +;; || .==' \_|-_ `===. .===' _|_ `===. .===' _-|/ `== \/ | || +;; || .==' _-' `-_ `=' _-' `-_ `=' _-' `-_ /| \/ | || +;; || .==' _-' '-__\._-' '-_./__-' `' |. /| | || +;; ||.==' _-' `' | /==.|| +;; ==' _-' \/ `== +;; \ _-' `-_ / +;; `'' ``' +;; +;; These demons are not part of GNU Emacs. +;; +;;; License: MIT -(require 'package) -(require 'erc) -(add-to-list 'load-path "~/.emacs.d/evil") -(setq evil-want-integration t) ;; This is optional since it's already set to t by default. -(setq evil-want-keybinding nil) -(require 'evil) -(evil-mode 1) -(add-to-list 'load-path "~/emacs-libvterm") -(require 'vterm) -(custom-set-variables - ;; custom-set-variables was added by Custom. - ;; If you edit it by hand, you could mess it up, so be careful. - ;; Your init file should contain only one such instance. - ;; If there is more than one, they won't work right. - '(blink-cursor-delay 0.5) - '(blink-cursor-interval 0.5) - '(blink-cursor-mode t) - '(display-line-numbers-type (quote relative)) - '(global-display-line-numbers-mode t) - '(menu-bar-mode nil) - '(minimap-window-location (quote right)) - '(package-archives - (quote - (("gnu" . "http://elpa.gnu.org/packages/") - ("melpa-stable" . "http://stable.melpa.org/packages/")))) - '(package-selected-packages - (quote - (ipython-shell-send evil-collection goto-chg multi-term org-bullets undo-tree rainbow-mode pdf-tools emojify minimap lua-mode haskell-mode ##))) - '(scroll-bar-mode nil) - '(send-mail-function (quote mailclient-send-it)) - '(tool-bar-mode nil) - '(tooltip-mode nil)) -(package-initialize) -(add-to-list 'load-path "/home/dt/.emacs.d/elpa/which-key-3.3.0/which-key.el") -(require 'which-key) -(which-key-mode) -(add-to-list 'load-path "/usr/local/share/emacs/site-lisp/mu4e") -(require 'mu4e) +;; A big contributor to startup times is garbage collection. We up the gc +;; threshold to temporarily prevent it from running, then reset it later with +;; `doom-restore-garbage-collection-h'. Not resetting it will cause +;; stuttering/freezes. +(setq gc-cons-threshold most-positive-fixnum) -(setq mu4e-get-mail-command "mbsync -c ~/.emacs.d/mu4e/.mbsyncrc -a" - mu4e-update-interval 300) +;; In noninteractive sessions, prioritize non-byte-compiled source files to +;; prevent the use of stale byte-code. Otherwise, it saves us a little IO time +;; to skip the mtime checks on every *.elc file. +(setq load-prefer-newer noninteractive) -;; Fields that are auto-filled in email -(setq - user-mail-address "derek@distrotube.com" - user-full-name "Derek Taylor" - mu4e-compose-signature - (concat - "Derek Taylor\n" - "http://www.youtube.com/DistroTube\n")) +(let (file-name-handler-alist) + ;; Ensure Doom is running out of this file's directory + (setq user-emacs-directory (file-name-directory load-file-name))) -;; sending mail -- replace USERNAME with your gmail username -;; also, make sure the gnutls command line utils are installed -;; package 'gnutls-bin' in Debian/Ubuntu -(require 'smtpmail) -(setq message-send-mail-function 'smtpmail-send-it - starttls-use-gnutls t - smtpmail-starttls-credentials '(("smtp.1and1.com" 587 nil nil)) - smtpmail-auth-credentials - '(("smtp.1and1.com" 587 "derek@distrotube.com" nil)) - smtpmail-default-smtp-server "smtp.1and1.com" - smtpmail-smtp-server "smtp.1and1.com" - smtpmail-smtp-service 587) -;; don't keep message buffers around -(setq message-kill-buffer-on-exit t) +;; Load the heart of Doom Emacs +(load (concat user-emacs-directory "core/core") + nil 'nomessage) -(require 'rainbow-mode) - (use-package rainbow-mode - :ensure t - :config - (setq rainbow-x-colors nil) - (add-hook 'prog-mode-hook 'rainbow-mode)) -(add-to-list 'custom-theme-load-path "~/.emacs.d/themes") -(load-theme 'dracula t) -(add-hook 'after-init-hook #'global-emojify-mode) -(custom-set-faces - ;; custom-set-faces was added by Custom. - ;; If you edit it by hand, you could mess it up, so be careful. - ;; Your init file should contain only one such instance. - ;; If there is more than one, they won't work right. - '(default ((t (:family "Mononoki Nerd Font Mono" :foundry "UKWN" :slant normal :weight normal :height 120 :width normal))))) -(when (require 'evil-collection nil t) - (evil-collection-init)) -(require 'org-bullets) -;; (setq org-bullets-bullet-list '("☯" "○" "✸" "✿" "~")) -(add-hook 'org-mode-hook (lambda () (org-bullets-mode 1))) - -;; minimal rendering by default in eww -(setq-default shr-inhibit-images t)` -(setq-default shr-use-fonts nil) - -;;; Keybindings -(global-set-key (kbd "C->") 'indent-rigidly-right-to-tab-stop) ; Indent selection by one tab length -(global-set-key (kbd "C-<") 'indent-rigidly-left-to-tab-stop) ; De-indent selection by one tab length -(put 'upcase-region 'disabled nil) -(put 'downcase-region 'disabled nil) +;; And let 'er rip! +(doom-initialize) +(if noninteractive + (doom-initialize-packages) + (doom-initialize-core) + (doom-initialize-modules) + (add-hook 'window-setup-hook #'doom-display-benchmark-h) + (add-to-list 'command-switch-alist (cons "--restore" #'doom-restore-session-handler))) diff --git a/.emacs.d/init.example.el b/.emacs.d/init.example.el new file mode 100644 index 0000000..52398c6 --- /dev/null +++ b/.emacs.d/init.example.el @@ -0,0 +1,171 @@ +;;; init.el -*- lexical-binding: t; -*- + +;; Copy this file to ~/.doom.d/init.el or ~/.config/doom/init.el ('doom install' +;; 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! :input + ;;chinese + ;;japanese + + :completion + company ; the ultimate code completion backend + ;;helm ; the *other* search engine for love and life + ;;ido ; the other *other* search engine... + ivy ; a search engine for love and life + + :ui + ;;deft ; notational velocity for Emacs + doom ; what makes DOOM look the way it does + doom-dashboard ; a nifty splash screen for Emacs + doom-quit ; DOOM quit-message prompts when you quit Emacs + ;;fill-column ; a `fill-column' indicator + hl-todo ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW + ;;hydra + ;;indent-guides ; highlighted indent columns + modeline ; snazzy, Atom-inspired modeline, plus API + nav-flash ; blink the current line after jumping + ;;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 + ;;tabs ; an 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 ; visually switch windows + workspaces ; tab emulation, persistence & separate workspaces + + :editor + (evil +everywhere); come to the dark side, we have cookies + file-templates ; auto-snippets for empty files + fold ; (nigh) universal code folding + ;;(format +onsave) ; automated prettiness + ;;god ; run Emacs commands without modifier keys + ;;lispy ; vim for lisp, for people who don't like vim + multiple-cursors ; editing in many places at once + ;;objed ; text object editing for the innocent + ;;parinfer ; turn lisp into python, sort of + rotate-text ; cycle region at point between text candidates + snippets ; my elves. They type so I don't have to + ;;word-wrap ; soft wrapping with language-aware indent + + :emacs + dired ; making dired pretty [functional] + electric ; smarter, keyword-based electric-indent + ibuffer ; interactive buffer management + vc ; version-control and Emacs, sitting in a tree + + :term + ;;eshell ; a consistent, cross-platform shell (WIP) + ;;shell ; a terminal REPL for Emacs + ;;term ; terminals in Emacs + ;;vterm ; another terminals in Emacs + + :tools + ;;ansible + ;;debugger ; FIXME stepping through code, to help you add bugs + ;;direnv + ;;docker + ;;editorconfig ; let someone else argue about tabs vs spaces + ;;ein ; tame Jupyter notebooks with emacs + (eval +overlay) ; run code, run (also, repls) + flycheck ; tasing you for every semicolon you forget + ;;flyspell ; tasing you for misspelling mispelling + ;;gist ; interacting with github gists + (lookup ; helps you navigate your code and documentation + +docsets) ; ...or in Dash docsets locally + ;;lsp + ;;macos ; MacOS-specific commands + magit ; a git porcelain for Emacs + ;;make ; run make tasks from Emacs + ;;pass ; 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 + + :lang + ;;agda ; types of types of types of types... + ;;assembly ; assembly for fun or debugging + ;;cc ; 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? + emacs-lisp ; drown in parentheses + ;;erlang ; an elegant language for a more civilized age + ;;ess ; emacs speaks statistics + ;;faust ; dsp, but you get to keep your soul + ;;fsharp ; ML stands for Microsoft's Language + ;;go ; the hipster dialect + ;;(haskell +dante) ; 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 + ;;lean + ;;factor + ;;ledger ; an accounting system in Emacs + ;;lua ; one-based indices? one-based indices + markdown ; writing docs for people to ignore + ;;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 + +dragndrop ; drag & drop files/images into org buffers + ;;+hugo ; use Emacs for hugo blogging + +ipython ; ipython/jupyter support for babel + +pandoc ; export-with-pandoc support + ;;+pomodoro ; be fruitful with the tomato technique + +present) ; using org-mode for presentations + ;;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 + ;;rst ; ReST in peace + ;;ruby ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"} + ;;rust ; Fe2O3.unwrap().unwrap().unwrap().unwrap() + ;;scala ; java, but good + ;;scheme ; a fully conniving family of lisps + sh ; 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 + + :email + ;;(mu4e +gmail) + ;;notmuch + ;;(wanderlust +gmail) + + :app + ;;calendar + ;;irc ; how neckbeards socialize + ;;(rss +org) ; emacs as an RSS reader + ;;twitter ; twitter client https://twitter.com/vnought + ;;write ; emacs for writers (fiction, notes, papers, etc.) + + :config + ;;literate + (default +bindings +smartparens)) diff --git a/.emacs.d/modules/app/calendar/README.org b/.emacs.d/modules/app/calendar/README.org new file mode 100644 index 0000000..23ff680 --- /dev/null +++ b/.emacs.d/modules/app/calendar/README.org @@ -0,0 +1,66 @@ +#+TITLE: app/calendar +#+DATE: January 13, 2018 +#+SINCE: v2.1 +#+STARTUP: inlineimages + +* Table of Contents :TOC: +- [[#description][Description]] + - [[#module-flags][Module Flags]] + - [[#packages][Packages]] +- [[#configuration][Configuration]] + - [[#changing-calendar-sources][Changing calendar sources]] + - [[#synchronizing-org-and-google-calendar][Synchronizing Org and Google Calendar]] + +* Description +This module adds a calendar view for Emacs, with org and google calendar sync +support. + +** Module Flags +This module provides no flags. + +** Packages ++ [[https://github.com/kiwanami/emacs-calfw][calfw]] ++ [[https://github.com/kiwanami/emacs-calfw][calfw-org]] ++ [[https://github.com/myuhe/org-gcal.el][org-gcal]] + +* Configuration +** Changing calendar sources +By defining your own calendar commands, you can control what sources to pull +calendar data from: + +#+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 + +To control what org files ~clfw:org-create-source~ will use, ~let~-bind +~org-agenda-files~ around a call to ~+calendar/open-calendar~ like so: + +#+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 + +The [[https://github.com/kiwanami/emacs-calfw][kiwanami/emacs-calfw]] project readme contains more examples. + +** Synchronizing Org and Google Calendar +The [[https://github.com/myuhe/org-gcal.el][myuhe/org-gcal.el]] project README contains more detailed instructions on how +to link your calendar with Google calendars. diff --git a/.emacs.d/modules/app/calendar/autoload.el b/.emacs.d/modules/app/calendar/autoload.el new file mode 100644 index 0000000..f642c18 --- /dev/null +++ b/.emacs.d/modules/app/calendar/autoload.el @@ -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! :ui 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! :ui 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 (face-foreground 'default)) ; orgmode source + ))) + +;;;###autoload +(defun +calendar-cfw:render-button-a (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)) diff --git a/.emacs.d/modules/app/calendar/config.el b/.emacs.d/modules/app/calendar/config.el new file mode 100644 index 0000000..66a2629 --- /dev/null +++ b/.emacs.d/modules/app/calendar/config.el @@ -0,0 +1,51 @@ +;;; app/calendar/config.el -*- lexical-binding: t; -*- + +(defvar +calendar-open-function #'+calendar/open-calendar + "TODO") + + +;; +;; Packages + +(use-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-h) + (add-hook 'cfw:calendar-mode-hook 'hide-mode-line-mode) + + (advice-add #'cfw:render-button :override #'+calendar-cfw:render-button-a)) + + +(use-package! calfw-org + :commands (cfw:open-org-calendar + cfw:org-create-source + cfw:open-org-calendar-withkevin + my-open-calendar)) + + +(use-package! org-gcal + :commands (org-gcal-sync + org-gcal-fetch + org-gcal-post-at-point + org-gcal-delete-at-point) + :config + ;; hack to avoid the deferred.el error + (defun org-gcal--notify (title mes) + (message "org-gcal::%s - %s" title mes))) + + +;; (use-package! alert) diff --git a/.emacs.d/modules/app/calendar/packages.el b/.emacs.d/modules/app/calendar/packages.el new file mode 100644 index 0000000..41bd347 --- /dev/null +++ b/.emacs.d/modules/app/calendar/packages.el @@ -0,0 +1,6 @@ +;; -*- no-byte-compile: t; -*- +;;; app/calendar/packages.el + +(package! calfw) +(package! calfw-org) +(package! org-gcal) diff --git a/.emacs.d/modules/app/irc/README.org b/.emacs.d/modules/app/irc/README.org new file mode 100644 index 0000000..f2d56d0 --- /dev/null +++ b/.emacs.d/modules/app/irc/README.org @@ -0,0 +1,141 @@ +#+TITLE: app/irc +#+DATE: June 11, 2017 +#+SINCE: v2.0.3 +#+STARTUP: inlineimages + +* Table of Contents :TOC: +- [[#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 requires =gnutls-cli= or =openssl= for secure connections. + +* 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-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: + +** 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/pass/README.org][:tools pass]] module you get an elisp API through which to access your +password store. + +~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-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: + +#+BEGIN_SRC emacs-lisp :tangle no +(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. + +#+BEGIN_SRC emacs-lisp :tangle no +(setq auth-sources '("~/.authinfo.gpg")) + +(defun my-fetch-password (&rest params) + (require 'auth-source) + (let ((match (car (apply #'auth-source-search params)))) + (if match + (let ((secret (plist-get match :secret))) + (if (functionp secret) + (funcall secret) + secret)) + (error "Password not found for %S" params)))) + +(defun my-nickserv-password (server) + (my-fetch-password :user "forcer" :host "irc.freenode.net")) + +(set-irc-server! "chat.freenode.net" + '(:tls t + :port 6697 + :nick "doom" + :sasl-password my-nickserver-password + :channels ("#emacs"))) +#+END_SRC + +* TODO Troubleshooting diff --git a/.emacs.d/modules/app/irc/autoload/irc.el b/.emacs.d/modules/app/irc/autoload/irc.el new file mode 100644 index 0000000..8ff58ee --- /dev/null +++ b/.emacs.d/modules/app/irc/autoload/irc.el @@ -0,0 +1,123 @@ +;;; app/irc/autoload/email.el -*- lexical-binding: t; -*- + +(defvar +irc--workspace-name "*IRC*") + +(defun +irc-setup-wconf (&optional inhibit-workspace) + (unless inhibit-workspace + (+workspace-switch +irc--workspace-name t)) + (let ((buffers (doom-buffers-in-mode 'circe-mode nil t))) + (if buffers + (ignore (switch-to-buffer (car buffers))) + (require 'circe) + (delete-other-windows) + (switch-to-buffer (doom-fallback-buffer)) + t))) + +;;;###autoload +(defun =irc (&optional inhibit-workspace) + "Connect to IRC and auto-connect to all registered networks. + +If INHIBIT-WORKSPACE (the universal argument) is non-nil, don't spawn a new +workspace for it." + (interactive "P") + (cond ((and (featurep! :ui 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) + "Connect to a specific registered server. + +If INHIBIT-WORKSPACE (the universal argument) is non-nil, don't spawn a new +workspace for it." + (interactive "P") + (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." + (interactive) + (if (y-or-n-p "Really kill IRC session?") + (let (circe-channel-killed-confirmation + circe-server-killed-confirmation) + (when +irc--defer-timer + (cancel-timer +irc--defer-timer)) + (disable-circe-notifications) + (mapc #'kill-buffer (doom-buffers-in-mode 'circe-mode (buffer-list) t)) + (when (equal (+workspace-current-name) +irc--workspace-name) + (+workspace/delete +irc--workspace-name))) + (message "Aborted"))) + +;;;###autoload +(defun +irc/ivy-jump-to-channel (&optional this-server) + "Jump to an open channel or server buffer with ivy. If THIS-SERVER (universal +argument) is non-nil only show channels in current server." + (interactive "P") + (if (not (circe-server-buffers)) + (message "No circe buffers available") + (when (and this-server (not circe-server-buffer)) + (setq this-server nil)) + (ivy-read (format "Jump to%s: " (if this-server (format " (%s)" (buffer-name circe-server-buffer)) "")) + (cl-loop with servers = (if this-server (list circe-server-buffer) (circe-server-buffers)) + with current-buffer = (current-buffer) + for server in servers + collect (buffer-name server) + nconc + (with-current-buffer server + (cl-loop for buf in (circe-server-chat-buffers) + unless (eq buf current-buffer) + collect (format " %s" (buffer-name buf))))) + :action #'+irc--ivy-switch-to-buffer-action + :preselect (buffer-name (current-buffer)) + :keymap ivy-switch-buffer-map + :caller '+irc/ivy-jump-to-channel))) + +(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))) + + +;; +;;; Hooks/fns + +;;;###autoload +(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)))) + +;;;###autoload +(defun +irc--add-circe-buffer-to-persp-h () + (when (bound-and-true-p persp-mode) + (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))))) diff --git a/.emacs.d/modules/app/irc/autoload/settings.el b/.emacs.d/modules/app/irc/autoload/settings.el new file mode 100644 index 0000000..59615a7 --- /dev/null +++ b/.emacs.d/modules/app/irc/autoload/settings.el @@ -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))) diff --git a/.emacs.d/modules/app/irc/config.el b/.emacs.d/modules/app/irc/config.el new file mode 100644 index 0000000..eda3563 --- /dev/null +++ b/.emacs.d/modules/app/irc/config.el @@ -0,0 +1,235 @@ +;;; app/irc/config.el -*- lexical-binding: t; -*- + +(defvar +irc-left-padding 13 + "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.") + +(defvar +irc-scroll-to-bottom-on-commands + '(self-insert-command yank hilit-yank) + "If these commands are called pre prompt the buffer will scroll to `point-max'.") + +(defvar +irc-disconnect-hook nil + "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") + "Nicks listed have `circe-fool-face' applied and will not be tracked.") + +(defvar +irc-time-stamp-format "%H:%M" + "The format of time stamps. + +See `format-time-string' for a full description of available +formatting directives. ") + +(defvar +irc-notifications-watch-strings nil + "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.") + +(defvar +irc--defer-timer nil) + +(defsubst +irc--pad (left right) + (format (format "%%%ds | %%s" +irc-left-padding) + (concat "*** " left) right)) + + +;; +;; Packages + +(use-package! circe + :commands circe circe-server-buffers + :init (setq circe-network-defaults nil) + :config + (setq circe-default-quit-message nil + circe-default-part-message nil + circe-use-cycle-completion t + circe-reduce-lurker-spam t + + circe-format-say (format "{nick:+%ss} │ {body}" +irc-left-padding) + 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 + (+irc--pad "Join" "{nick} ({userinfo}) joined {channel}") + circe-format-server-join + (+irc--pad "Join" "{nick} ({userinfo})") + circe-format-server-part + (+irc--pad "Part" "{nick} ({userhost}) left {channel}: {reason}") + circe-format-server-quit + (+irc--pad "Quit" "{nick} ({userhost}) left IRC: {reason}]") + circe-format-server-quit-channel + (+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 + (+irc--pad "Nick" "You are now known as {new-nick} ({old-nick})") + circe-format-server-nick-change-self + (+irc--pad "Nick" "{old-nick} ({userhost}) is now known as {new-nick}") + circe-format-server-mode-change + (+irc--pad "Mode" "{change} on {target} by {setter} ({userhost})") + circe-format-server-lurker-activity + (+irc--pad "Lurk" "{nick} joined {joindelta} ago")) + + (add-hook 'doom-real-buffer-functions #'+circe-buffer-p) + (add-hook 'circe-channel-mode-hook #'turn-on-visual-line-mode) + (add-hook 'circe-mode-hook #'+irc--add-circe-buffer-to-persp-h) + + (defadvice! +irc--circe-run-disconnect-hook-a (&rest _) + "Runs `+irc-disconnect-hook' after circe disconnects." + :after #'circe--irc-conn-disconnected + (run-hooks '+irc-disconnect-hook)) + + (add-hook! 'lui-pre-output-hook + (defun +irc-circe-truncate-nicks-h () + "Truncate long nicknames in chat output non-destructively." + (when-let (beg (text-property-any (point-min) (point-max) 'lui-format-argument 'nick)) + (goto-char beg) + (let ((end (next-single-property-change beg 'lui-format-argument)) + (nick (plist-get (plist-get (text-properties-at beg) 'lui-keywords) + :nick))) + (when (> (length nick) +irc-left-padding) + (compose-region (+ beg +irc-left-padding -1) end + +irc-truncate-nick-char)))))) + + (add-hook! 'circe-message-option-functions + (defun +irc-circe-message-option-bot-h (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)))))) + + ;; 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))) + + +(use-package! circe-color-nicks + :hook (circe-channel-mode . enable-circe-color-nicks) + :config + (setq circe-color-nicks-min-constrast-ratio 4.5 + circe-color-nicks-everywhere t)) + + +(use-package! circe-new-day-notifier + :after circe + :config + (enable-circe-new-day-notifier) + (setq circe-new-day-notifier-format-message + (+irc--pad "Day" "Date changed [{day}]"))) + + +(use-package! circe-notifications + :commands enable-circe-notifications + :init + (if +irc-defer-notifications + (add-hook! 'circe-server-connected-hook + (setq +irc--defer-timer + (run-at-time +irc-defer-notifications nil + #'enable-circe-notifications))) + (add-hook 'circe-server-connected-hook #'enable-circe-notifications)) + :config + (setq circe-notifications-watch-strings +irc-notifications-watch-strings + circe-notifications-emacs-focused nil + circe-notifications-alert-style + (cond (IS-MAC 'osx-notifier) + (IS-LINUX 'libnotify) + (circe-notifications-alert-style)))) + + +(use-package! lui + :commands lui-mode + :config + (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-h () + "Ensure entering insert mode will put us at the prompt, unless editing +after prompt marker." + (when (> (marker-position lui-input-marker) (point)) + (goto-char (point-max)))) + + (add-hook! 'lui-mode-hook + (add-hook 'evil-insert-state-entry-hook #'+irc-evil-insert-h + nil 'local)) + + (mapc (lambda (cmd) (push cmd +irc-scroll-to-bottom-on-commands)) + '(evil-paste-after evil-paste-before evil-open-above evil-open-below))) + + + (defun +irc-preinput-scroll-to-bottom-h () + "Go to the end of the buffer in all windows showing it. +Courtesy of esh-mode.el" + (when (memq this-command +irc-scroll-to-bottom-on-commands) + (let* ((selected (selected-window)) + (current (current-buffer))) + (when (> (marker-position lui-input-marker) (point)) + (walk-windows + (function + (lambda (window) + (when (eq (window-buffer window) current) + (select-window window) + (goto-char (point-max)) + (select-window selected)))) + nil t))))) + + (add-hook! 'lui-mode-hook + (add-hook 'pre-command-hook #'+irc-preinput-scroll-to-bottom-h nil t)) + + ;; enable a horizontal line marking the last read message + (add-hook 'lui-mode-hook #'enable-lui-track-bar) + + (add-hook! 'lui-mode-hook + (defun +irc-init-lui-margins-h () + (setq lui-time-stamp-position 'right-margin + lui-time-stamp-format +irc-time-stamp-format + right-margin-width (length (format-time-string lui-time-stamp-format)))) + (defun +irc-init-lui-wrapping-a () + (setq fringes-outside-margins t + word-wrap t + wrap-prefix (make-string (+ +irc-left-padding 3) ? ))))) + + +(use-package! lui-logging + :after lui + :config (enable-lui-logging)) + + +(use-package! lui-autopaste + :hook (circe-channel-mode . enable-lui-autopaste)) diff --git a/.emacs.d/modules/app/irc/packages.el b/.emacs.d/modules/app/irc/packages.el new file mode 100644 index 0000000..58b486c --- /dev/null +++ b/.emacs.d/modules/app/irc/packages.el @@ -0,0 +1,5 @@ +;; -*- no-byte-compile: t; -*- +;;; app/irc/packages.el + +(package! circe) +(package! circe-notifications) diff --git a/.emacs.d/modules/app/regex/autoload/export.el b/.emacs.d/modules/app/regex/autoload/export.el new file mode 100644 index 0000000..90fbff7 --- /dev/null +++ b/.emacs.d/modules/app/regex/autoload/export.el @@ -0,0 +1,14 @@ +;;; app/regex/autoload/export.el + +;;;###autoload +(defun +regex/export () (interactive)) ; TODO +regex/export + + +;; +(defun +regex-export-python ()) ; import (re|regex) + +(defun +regex-export-php ()) ; preg_(match(_all)?|replace) + +(defun +regex-export-ruby ()) ; %r[.+] + +(defun +regex-export-js ()) ; /.+/ diff --git a/.emacs.d/modules/app/regex/autoload/regex.el b/.emacs.d/modules/app/regex/autoload/regex.el new file mode 100644 index 0000000..0df6b79 --- /dev/null +++ b/.emacs.d/modules/app/regex/autoload/regex.el @@ -0,0 +1,272 @@ +;;; app/regex/autoload/regex.el + +(defvar +regex--text-buffer nil) +(defvar +regex--text-replace-buffer nil) +(defvar +regex--expr-buffer nil) +(defvar +regex--expr-replace-buffer nil) +(defvar +regex--groups-buffer nil) +(defvar +regex--replace-buffer nil) + +;; +(defface +regex-match-0-face + `((t (:foreground "Black" :background ,(doom-color 'magenta) :bold t))) + "TODO" + :group 'faces) + +(defface +regex-match-1-face + `((t (:foreground "Black" :background ,(doom-color 'blue) :bold t))) + "TODO" + :group 'faces) + +(defface +regex-match-2-face + `((t (:foreground "Black" :background ,(doom-color 'green) :bold t))) + "TODO" + :group 'faces) + +(defvar +regex-faces + '(+regex-match-0-face +regex-match-1-face +regex-match-2-face) + "TODO") + +;; +(defvar +regex-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\C-c\C-c" #'+regex-update-buffers) + (define-key map "\C-c\C-r" #'=regex/replace) + (define-key map "\C-c\C-k" #'+regex/quit) + (define-key map [remap kill-current-buffer] #'+regex/quit) + (define-key map [remap kill-buffer] #'+regex/quit) + map) + "TODO") + +;;;###autoload +(define-minor-mode +regex-mode + "TODO" + :init-value nil + :global nil + :lighter "" + :keymap +regex-mode-map + (if +regex-mode + (add-hook 'after-change-functions #'+regex-update-buffers nil t) + (remove-hook 'after-change-functions #'+regex-update-buffers t))) + +;;;###autoload +(defun =regex (&optional dummy-text) + "Start the Regex IDE." + (interactive "P") + (unless (buffer-live-p +regex--expr-buffer) + (condition-case ex + (progn + (setq +regex--expr-buffer (get-buffer-create "*doom-regex*") + +regex--text-buffer (if dummy-text (get-buffer-create "*doom-regex-text*") (current-buffer)) + +regex--groups-buffer (get-buffer-create "*doom-regex-groups*")) + (when dummy-text + (+workspace-switch +regex-workspace-name t) + (switch-to-buffer +regex--text-buffer) + (with-current-buffer +regex--text-buffer + (insert +regex-dummy-text))) + (pop-to-buffer +regex--groups-buffer) + (pop-to-buffer +regex--expr-buffer) + (with-current-buffer +regex--expr-buffer + (conf-mode) + (rainbow-delimiters-mode +1) + (doom/toggle-line-numbers +1) + (setq-local require-final-newline nil) + (+regex-mode +1) + (text-scale-set 3))) + ('error + (+regex/quit) + (error "Failed to open the Regexp IDE: %s" ex))))) + +;;;###autoload +(defun =regex/replace () + (interactive) + (unless (buffer-live-p +regex--replace-buffer) + (let (text) + (=regex t) + (with-selected-window (get-buffer-window +regex--text-buffer) + (setq text (buffer-string)) + (select-window (split-window-right)) + (switch-to-buffer (get-buffer-create "*doom-regex-text-repl*")) + (erase-buffer) + (insert text) + (read-only-mode +1) + (setq +regex--text-replace-buffer (current-buffer))) + (with-current-buffer +regex--expr-buffer + (select-window (split-window-right)) + (switch-to-buffer (get-buffer-create "*doom-regex-repl*")) + (conf-mode) + (rainbow-delimiters-mode +1) + (doom/toggle-line-numbers -1) + (setq-local require-final-newline nil) + (setq mode-line-format nil + +regex--expr-replace-buffer (current-buffer)) + (+regex-mode +1) + (text-scale-set 3))))) + +;;;###autoload +(defun +regex/quit () + "TODO" + (interactive) + (when (and +regex--text-buffer (buffer-live-p +regex--text-buffer)) + (with-current-buffer +regex--text-buffer + (+regex-mode -1) + (remove-overlays nil nil 'category '+regex)) + (when (equal (buffer-name +regex--text-buffer) "*doom-regex-text*") + (kill-buffer +regex--text-buffer))) + (when (equal (+workspace-current-name) +regex-workspace-name) + (+workspace/delete +regex-workspace-name)) + (mapc (lambda (bufname) + (let ((buf (symbol-value bufname))) + (when (and buf (buffer-live-p buf)) + (kill-buffer buf) + (set bufname nil)))) + (list '+regex--text-replace-buffer + '+regex--expr-replace-buffer + '+regex--expr-buffer + '+regex--groups-buffer + '+regex--replace-buffer))) + +(defun +regex--expr () + (when (buffer-live-p +regex--expr-buffer) + (with-current-buffer +regex--expr-buffer + (string-trim (buffer-string))))) + +(defun +regex--expr-replace () + (when (buffer-live-p +regex--expr-replace-buffer) + (with-current-buffer +regex--expr-replace-buffer + (string-trim (buffer-string))))) + +;;;###autoload +(defun +regex-update-buffers (&optional beg end len) + (interactive) + (let* ((inhibit-read-only t) + (regex (or (+regex--expr) "")) + (replace (or (+regex--expr-replace) "")) + (str (or (with-current-buffer +regex--text-buffer (buffer-string)) ""))) + (with-current-buffer +regex--groups-buffer + (erase-buffer)) + (with-current-buffer +regex--text-buffer + (remove-overlays nil nil 'category '+regex) + (when (> (length regex) 0) + (save-excursion + (goto-char (point-min)) + (pcase +regex-default-backend + ('emacs (+regex-backend-emacs regex replace str)) + ('perl (+regex-backend-perl regex replace str)))))) + (with-current-buffer +regex--groups-buffer + (goto-char (point-min))))) + + +;; --- backends --------------------------- + +(defun +regex--render-perl (regex text) + "From " + (with-temp-buffer + (insert (format "@lines = ; +$line = join(\"\", @lines); +print \"(\"; +while ($line =~ m/%s/gm) { + print \"(\", length($`), \" \", length($&), \" \"; + for $i (1 .. 20) { + if ($$i) { + my $group = $$i; + $group =~ s/([\\\\\"])/\\\\\\1/g; + print \"(\", $i, \" . \\\"\", $group, \"\\\") \"; + } + } + print \")\"; +} +print \")\"; +__DATA__ +%s" (replace-regexp-in-string "/" "\\/" regex nil t) text)) + (call-process-region (point-min) (point-max) "perl" t t) + (goto-char (point-min)) + (read (current-buffer)))) + +(defun +regex--replace-perl (regex replace text) + (unless (or (string-empty-p regex) + (string-empty-p replace) + (string-empty-p text)) + (with-temp-buffer + (insert (format "@lines = ; +$line = join(\"\", @lines); +$line =~ s/%s/%s/gm; +print $line; +__DATA__ +%s" (replace-regexp-in-string "/" "\\/" regex nil t) (replace-regexp-in-string "/" "\\/" replace nil t) text)) + (call-process-region (point-min) (point-max) "perl" t t) + (buffer-string)))) + +;;;###autoload +(defun +regex-backend-perl (regex replace str) + "TODO" + (cl-assert (stringp regex)) + (cl-assert (stringp replace)) + (cl-assert (stringp str)) + (let ((i 0) + (results (+regex--render-perl regex str))) + (when (buffer-live-p +regex--text-replace-buffer) + (let ((replace (+regex--replace-perl regex replace str))) + (with-current-buffer +regex--text-replace-buffer + (erase-buffer) + (insert + (if (and (listp results) + replace + (not (string-empty-p replace))) + replace + str))))) + (dolist (result (if (listp results) results)) + (let* ((offset (nth 0 result)) + (length (nth 1 result)) + (matches (nthcdr 2 result)) + (ov (make-overlay (1+ offset) (+ offset length 1)))) + (overlay-put ov 'face (nth (mod i 3) +regex-faces)) + (overlay-put ov 'category '+regex) + (cl-incf i) + (let* ((match-zero (buffer-substring (1+ offset) (+ offset length 1))) + (line (format "Match: %s\n" (propertize match-zero 'face 'font-lock-string-face)))) + (with-current-buffer +regex--groups-buffer + (insert line))) + (dolist (match matches) + (with-current-buffer +regex--groups-buffer + (goto-char (point-max)) + (insert (format "Group %d: %s\n" + (propertize (car match) 'face 'font-lock-constant-face) + (propertize (cdr match) 'face 'font-lock-string-face))))) + (with-current-buffer +regex--groups-buffer + (insert ?\n)))))) + +;;;###autoload +(defun +regex-backend-emacs (regex replace str) + "TODO" + (cl-assert (stringp regex)) + (cl-assert (stringp replace)) + (cl-assert (stringp str)) + (let ((i 0) + pos) + (when (buffer-live-p +regex--text-replace-buffer) + (with-current-buffer +regex--text-replace-buffer + (erase-buffer) + (insert str) + (when (and (listp results) (string-empty-p replace)) + (replace-regexp regex replace)))) + (while (and (setq pos (point)) + (re-search-forward regex nil t)) + (if (= (point) pos) + (forward-char 1) + (let ((ov (make-overlay (match-beginning 0) (match-end 0)))) + (overlay-put ov 'face (nth (mod i 3) +regex-faces)) + (overlay-put ov 'category '+regex)) + (cl-incf i) + (dotimes (i 10) + (when-let (text (match-string i)) + (save-match-data + (with-current-buffer +regex--groups-buffer + (goto-char (point-max)) + (insert + (format "Group %d: %s\n" + (propertize i 'face 'font-lock-constant-face) + (propertize text 'face 'font-lock-string-face))))))) + (with-current-buffer +regex--groups-buffer + (insert ?\n)))))) + diff --git a/.emacs.d/modules/app/regex/config.el b/.emacs.d/modules/app/regex/config.el new file mode 100644 index 0000000..595d619 --- /dev/null +++ b/.emacs.d/modules/app/regex/config.el @@ -0,0 +1,51 @@ +;;; app/regex/config.el + +;; Often, I find myself writing regular expressions that could terrify seasoned +;; programmers (or little children). To hone my regex fu, I need a regex +;; playground. Sure, there's regexr.com, but don't be silly, that's not Emacs. +;; +;; Sadly, the Emacs' regex syntax is niche and lacks support for a few +;; questionably useful features, like lookaround assertions, conditionals, case +;; modifiers or backreferences, among others. No, I want PCRE. I am going to +;; have my cake and eat it too, damn it! +;; +;; Workflow: +;; + Invoke `=regex' (if opened with C-u, opens in separate workspace with a +;; dummy text buffer). +;; + A regex window will popup up. Any matches will be highlighted in the +;; original buffer. +;; + C-c C-k to close it +;; + TODO C-c C-e to export to various langauges +;; +;; WARNING: THIS IS A WORK IN PROGRESS + +(defvar +regex-workspace-name "*regex*" + "TODO") + +(defvar +regex-default-backend 'perl + "The backend used to process regular expressions. +The `emacs' backend handles regular expressions directly. +The `perl' backend talks to a perl subprocess to do the handling.") + +(defvar +regex-dummy-text + "Welcome to DOOM Emacs, proudly hosted by the demons of hell! + +Edit the Expression & Text to see matches. Roll over matches or the expression +for details. Undo mistakes with ctrl-z. Save Favorites & Share expressions with +friends or the Community. Explore your results with Tools. A full Reference & +Help is available in the Library, or watch the video Tutorial. + +Sample text for testing: +abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ +0123456789 _+-.,!@#$%^&*();\/|<>\"' +12345 -98.7 3.141 .6180 9,000 +42 +555.123.4567 +1-(800)-555-2468 +foo@demo.net bar.ba@test.co.uk +www.demo.com http://foo.co.uk/ +http://regexr.com/foo.html?q=bar +https://mediatemple.net" + "TODO") + +(set-popup-rules! + '(("^\\*doom-regex\\*$" :size 4 :quit nil) + ("^\\*doom-regex-groups" :side 'left :size 28 :select nil :quit nil))) diff --git a/.emacs.d/modules/app/rss/autoload.el b/.emacs.d/modules/app/rss/autoload.el new file mode 100644 index 0000000..603cbed --- /dev/null +++ b/.emacs.d/modules/app/rss/autoload.el @@ -0,0 +1,117 @@ +;;; app/rss/autoload.el -*- lexical-binding: t; -*- + +;;;###autoload +(defalias '=rss #'elfeed + "Activate (or switch to) `elfeed' in its workspace.") + +;;;###autoload +(defun +rss/delete-pane () + "Delete the *elfeed-entry* split pane." + (interactive) + (let* ((buf (get-buffer "*elfeed-entry*")) + (window (get-buffer-window buf))) + (delete-window window) + (when (buffer-live-p buf) + (kill-buffer buf)))) + +;;;###autoload +(defun +rss/open (entry) + "Display the currently selected item in a buffer." + (interactive (list (elfeed-search-selected :ignore-region))) + (when (elfeed-entry-p entry) + (elfeed-untag entry 'unread) + (elfeed-search-update-entry entry) + (elfeed-show-entry entry))) + +;;;###autoload +(defun +rss/next () + "Show the next item in the elfeed-search buffer." + (interactive) + (funcall elfeed-show-entry-delete) + (with-current-buffer (elfeed-search-buffer) + (forward-line) + (call-interactively '+rss/open))) + +;;;###autoload +(defun +rss/previous () + "Show the previous item in the elfeed-search buffer." + (interactive) + (funcall elfeed-show-entry-delete) + (with-current-buffer (elfeed-search-buffer) + (forward-line -1) + (call-interactively '+rss/open))) + + +;; +;; Hooks + +;;;###autoload +(defun +rss-elfeed-wrap-h () + "Enhances an elfeed entry's readability by wrapping it to a width of +`fill-column'." + (let ((inhibit-read-only t) + (inhibit-modification-hooks t)) + (setq-local truncate-lines nil) + (setq-local shr-use-fonts nil) + (setq-local shr-width 85) + (set-buffer-modified-p nil))) + +;;;###autoload +(defun +rss-cleanup-h () + "Clean up after an elfeed session. Kills all elfeed and elfeed-org files." + (interactive) + ;; `delete-file-projectile-remove-from-cache' slows down `elfeed-db-compact' + ;; tremendously, so we disable the projectile cache: + (let (projectile-enable-caching) + (elfeed-db-compact)) + (let ((buf (previous-buffer))) + (when (or (null buf) (not (doom-real-buffer-p buf))) + (switch-to-buffer (doom-fallback-buffer)))) + (let ((search-buffers (doom-buffers-in-mode 'elfeed-search-mode)) + (show-buffers (doom-buffers-in-mode 'elfeed-show-mode)) + kill-buffer-query-functions) + (dolist (file +rss-elfeed-files) + (when-let (buf (get-file-buffer (expand-file-name file org-directory))) + (kill-buffer buf))) + (dolist (b search-buffers) + (with-current-buffer b + (remove-hook 'kill-buffer-hook #'+rss-cleanup-h :local) + (kill-buffer b))) + (mapc #'kill-buffer show-buffers))) + + +;; +;; Functions + +;;;###autoload +(defun +rss-dead-feeds (&optional years) + "Return a list of feeds that haven't posted anything in YEARS." + (let* ((years (or years 1.0)) + (living-feeds (make-hash-table :test 'equal)) + (seconds (* years 365.0 24 60 60)) + (threshold (- (float-time) seconds))) + (with-elfeed-db-visit (entry feed) + (let ((date (elfeed-entry-date entry))) + (when (> date threshold) + (setf (gethash (elfeed-feed-url feed) living-feeds) t)))) + (cl-loop for url in (elfeed-feed-list) + unless (gethash url living-feeds) + collect url))) + +;;;###autoload +(defun +rss-put-sliced-image-fn (spec alt &optional flags) + "TODO" + (cl-letf (((symbol-function #'insert-image) + (lambda (image &optional alt _area _slice) + (let ((height (cdr (image-size image t)))) + (insert-sliced-image image alt nil (max 1 (/ height 20.0)) 1))))) + (shr-put-image spec alt flags))) + +;;;###autoload +(defun +rss-render-image-tag-without-underline-fn (dom &optional url) + "TODO" + (let ((start (point))) + (shr-tag-img dom url) + ;; And remove underlines in case images are links, otherwise we get an + ;; underline beneath every slice. + (put-text-property start (point) 'face '(:underline nil)))) diff --git a/.emacs.d/modules/app/rss/config.el b/.emacs.d/modules/app/rss/config.el new file mode 100644 index 0000000..7e1af21 --- /dev/null +++ b/.emacs.d/modules/app/rss/config.el @@ -0,0 +1,74 @@ +;;; app/rss/config.el -*- lexical-binding: t; -*- + +;; This is an opinionated workflow that turns Emacs into an RSS reader, inspired +;; by apps Reeder and Readkit. It can be invoked via `=rss'. Otherwise, if you +;; don't care for the UI you can invoke elfeed directly with `elfeed'. + +(defvar +rss-elfeed-files (list "elfeed.org") + "Where to look for elfeed.org files, relative to `org-directory'. Can be +absolute paths.") + +(defvar +rss-split-direction 'below + "What direction to pop up the entry buffer in elfeed.") + +(defvar +rss-enable-sliced-images t + "Automatically slice images shown in elfeed-show-mode buffers, making them +easier to scroll through.") + + +;; +;; Packages + +(use-package! elfeed + :commands elfeed + :config + (setq elfeed-search-filter "@2-week-ago " + elfeed-db-directory (concat doom-local-dir "elfeed/db/") + elfeed-enclosure-default-dir (concat doom-local-dir "elfeed/enclosures/") + elfeed-show-entry-switch #'pop-to-buffer + elfeed-show-entry-delete #'+rss/delete-pane + shr-max-image-proportion 0.8) + + (set-popup-rule! "^\\*elfeed-entry" + :size 0.75 :actions '(display-buffer-below-selected) + :select t :quit nil :ttl t) + + (make-directory elfeed-db-directory t) + + ;; Ensure elfeed buffers are treated as real + (add-hook! 'doom-real-buffer-functions + (defun +rss-buffer-p (buf) + (string-match-p "^\\*elfeed" (buffer-name buf)))) + + ;; Enhance readability of a post + (add-hook 'elfeed-show-mode-hook #'+rss-elfeed-wrap-h) + (add-hook! 'elfeed-search-mode-hook + (add-hook 'kill-buffer-hook #'+rss-cleanup-h nil 'local)) + + ;; Large images are annoying to scroll through, because scrolling follows the + ;; cursor, so we force shr to insert images in slices. + (when +rss-enable-sliced-images + (setq-hook! 'elfeed-show-mode-hook + shr-put-image-function #'+rss-put-sliced-image-fn + shr-external-rendering-functions '((img . +rss-render-image-tag-without-underline-fn)))) + + ;; Keybindings + (after! elfeed-show + (define-key! elfeed-show-mode-map + [remap next-buffer] #'+rss/next + [remap previous-buffer] #'+rss/previous)) + (when (featurep! :editor evil +everywhere) + (evil-define-key 'normal elfeed-search-mode-map + "q" #'elfeed-kill-buffer + "r" #'elfeed-search-update--force + (kbd "M-RET") #'elfeed-search-browse-url))) + + +(use-package! elfeed-org + :when (featurep! +org) + :after elfeed + :config + (let ((default-directory org-directory)) + (setq rmh-elfeed-org-files + (mapcar #'expand-file-name +rss-elfeed-files))) + (elfeed-org)) diff --git a/.emacs.d/modules/app/rss/packages.el b/.emacs.d/modules/app/rss/packages.el new file mode 100644 index 0000000..b1356ab --- /dev/null +++ b/.emacs.d/modules/app/rss/packages.el @@ -0,0 +1,5 @@ +;; -*- no-byte-compile: t; -*- +;;; app/rss/packages.el + +(package! elfeed) +(package! elfeed-org) diff --git a/.emacs.d/modules/app/twitter/README.org b/.emacs.d/modules/app/twitter/README.org new file mode 100644 index 0000000..38d9694 --- /dev/null +++ b/.emacs.d/modules/app/twitter/README.org @@ -0,0 +1,96 @@ +#+TITLE: app/twitter +#+DATE: October 11, 2019 +#+SINCE: v2.0 +#+STARTUP: inlineimages + +* Table of Contents :TOC_3:noexport: +- [[#description][Description]] + - [[#module-flags][Module Flags]] + - [[#plugins][Plugins]] + - [[#hacks][Hacks]] +- [[#prerequisites][Prerequisites]] +- [[#features][Features]] +- [[#configuration][Configuration]] +- [[#troubleshooting][Troubleshooting]] +- [[#appendix][Appendix]] + - [[#commands--keybindings][Commands & Keybindings]] + +* Description +Enjoy twitter from emacs. + ++ View various timelines side by side, e.g. user's timeline, home, etc. ++ Post new tweets ++ Send direct messages ++ Retweet ++ Follow and un-follow users ++ Favorite tweets + +** Module Flags +This module provides no flags. + +** Plugins ++ [[https://github.com/hayamiz/twittering-mode][twittering-mode]] ++ [[https://github.com/abo-abo/avy][avy]] + +** TODO Hacks +{A list of internal modifications to included packages} + +* Prerequisites + ++ For SSL connection (required by Twitter's API), one of the followings is required: + + [[http://curl.haxx.se/][cURL]] + + [[http://www.gnu.org/software/wget/][GNU Wget]] + + [[http://www.openssl.org/][OpenSSL]] + + [[http://www.gnu.org/software/gnutls/][GnuTLS]] ++ [[http://www.gnupg.org/][GnuPG]] is required for keeping the OAuth token encrypted in local storage. ++ ~twittering-icon-mode~ converts retrieved icons into XPM by default. To + achieve this and for displaying icons in formats that are not supported by + Emacs as well as for resizing icon images, [[http://www.imagemagick.org/][ImageMagick]] is required. + + To build emacs with ImageMagick support the ~--with-imagemagick~ flag needs to + be passed to the ~configure~ script, e.g. ~./configure --with-imagemagick~. + For detailed instruction on how to build Emacs from source please refer to + [[https://git.savannah.gnu.org/cgit/emacs.git/tree/INSTALL][INSTALL]] in Emacs' savannah repository. ++ For keeping retrieved icons in local storage, [[http://www.gzip.org/][gzip]] is required. + +* TODO Features +An in-depth list of features, how to use them, and their dependencies. + +* TODO Configuration +How to configure this module, including common problems and how to address them. + +* Troubleshooting +Currently ~twittering-mode~ binds =SPC=, breaking its functionality as a leader +key. To work around this issue you may use =M-SPC= instead when in +~twittering-mode~. + +* Appendix +** Commands & Keybindings +Here is a list of available commands and their default keybindings (defined in +[[./config.el][config.el]]). + +| command | key / ex command | description | +|---------------------+------------------+-------------------------------------------------------------| +| ~+twitter/quit~ | =q= | Close current window | +| ~+twitter/quit-all~ | =Q= | Close all twitter windows and buffers, and delete workspace | + +And when ~:editor evil +everywhere~ is active: + +| command | key / ex command | description | +|--------------------------------------------------+------------------+------------------------------------------------------------------| +| ~twittering-favorite~ | =f= | Favorite/Like a tweet | +| ~twittering-unfavorite~ | =F= | Un-favorite/Un-like a tweet | +| ~twittering-follow~ | =C-f= | Follow user | +| ~twittering-unfollow~ | =C-F= | Un-follow user | +| ~twittering-delete-status~ | =d= | Delete a tweet | +| ~twittering-retweet~ | =r= | Retweet | +| ~twittering-toggle-or-retrieve-replied-statuses~ | =R= | Toggle or retrieve replies | +| ~twittering-update-status-interactive~ | =o= | Update tweets | +| ~+twitter/ace-link~ | =O= | Open some visible link from a ~twittering-mode~ buffer using ace | +| ~twittering-search~ | =/= | Search | +| ~twittering-goto-next-status~ | =J= | Go to next tweet | +| ~twittering-goto-previous-status~ | =K= | Go to previous tweet | +| ~twittering-goto-first-status~ | =gg= | Go to first tweet | +| ~twittering-goto-last-status~ | =G= | Go to last tweet | +| ~twittering-goto-next-status-of-user~ | =gj= | Go to next tweet of user | +| ~twittering-goto-previous-status-of-user)))~ | =gk= | Go to previous tweet of user | diff --git a/.emacs.d/modules/app/twitter/autoload.el b/.emacs.d/modules/app/twitter/autoload.el new file mode 100644 index 0000000..1116633 --- /dev/null +++ b/.emacs.d/modules/app/twitter/autoload.el @@ -0,0 +1,103 @@ +;;; app/twitter/autoload.el -*- lexical-binding: t; -*- + +(defvar +twitter-workspace-name "*Twitter*" + "The name to use for the twitter workspace.") + +;;;###autoload +(defun +twitter-display-buffer-fn (buf) + "A replacement display-buffer command for `twittering-pop-to-buffer-function' +that works with the feature/popup module." + (let ((win (selected-window))) + (display-buffer buf) + ;; This is required because the new window generated by `pop-to-buffer' + ;; may hide the region following the current position. + (twittering-ensure-whole-of-status-is-visible win))) + +;;;###autoload +(defun +twitter-buffer-p (buf) + "Return non-nil if BUF is a `twittering-mode' buffer." + (eq 'twittering-mode (buffer-local-value 'major-mode buf))) + + +;; +;; Commands + +(defvar +twitter--old-wconf nil) +;;;###autoload +(defun =twitter (arg) + "Opens a workspace dedicated to `twittering-mode'." + (interactive "P") + (condition-case _ + (progn + (if (and (not arg) (featurep! :ui workspaces)) + (+workspace/new +twitter-workspace-name) + (setq +twitter--old-wconf (current-window-configuration)) + (delete-other-windows) + (switch-to-buffer (doom-fallback-buffer))) + (call-interactively #'twit) + (unless (get-buffer (car twittering-initial-timeline-spec-string)) + (error "Failed to open twitter")) + (switch-to-buffer (car twittering-initial-timeline-spec-string)) + (dolist (name (cdr twittering-initial-timeline-spec-string)) + (split-window-horizontally) + (switch-to-buffer name)) + (balance-windows) + (call-interactively #'+twitter/rerender-all)) + ('error (+twitter/quit-all)))) + +;;;###autoload +(defun +twitter/quit () + "Close the current `twitter-mode' buffer." + (interactive) + (when (eq major-mode 'twittering-mode) + (twittering-kill-buffer) + (cond ((one-window-p) (+twitter/quit-all)) + ((featurep! :ui workspaces) + (+workspace/close-window-or-workspace)) + ((delete-window))))) + +;;;###autoload +(defun +twitter/quit-all () + "Close all open `twitter-mode' buffers and the associated workspace, if any." + (interactive) + (when (featurep! :ui workspaces) + (+workspace/delete +twitter-workspace-name)) + (when +twitter--old-wconf + (set-window-configuration +twitter--old-wconf) + (setq +twitter--old-wconf nil)) + (dolist (buf (doom-buffers-in-mode 'twittering-mode (buffer-list) t)) + (twittering-kill-buffer buf))) + +;;;###autoload +(defun +twitter/rerender-all () + "Rerender all `twittering-mode' buffers." + (interactive) + (dolist (buf (doom-buffers-in-mode 'twittering-mode (buffer-list) t)) + (with-current-buffer buf + (twittering-rerender-timeline-all buf) + (setq-local line-spacing 0.2) + (goto-char (point-min))))) + +;;;###autoload +(defun +twitter/ace-link () + "Open a visible link, username or hashtag in a `twittering-mode' buffer." + (interactive) + (require 'avy) + (let ((pt (avy-with +twitter/ace-link + (avy--process + (+twitter--collect-links) + (avy--style-fn avy-style))))) + (when (number-or-marker-p pt) + (goto-char pt) + (let ((uri (get-text-property (point) 'uri))) + (if uri (browse-url uri)))))) + +(defun +twitter--collect-links () + (let ((end (window-end)) + points) + (save-excursion + (goto-char (window-start)) + (while (and (< (point) end) + (ignore-errors (twittering-goto-next-thing) t)) + (push (point) points)) + (nreverse points)))) diff --git a/.emacs.d/modules/app/twitter/config.el b/.emacs.d/modules/app/twitter/config.el new file mode 100644 index 0000000..db8849a --- /dev/null +++ b/.emacs.d/modules/app/twitter/config.el @@ -0,0 +1,79 @@ +;;; app/twitter/config.el -*- lexical-binding: t; -*- + +(use-package! twittering-mode + :commands twit + :config + (setq twittering-private-info-file + (expand-file-name "twittering-mode.gpg" doom-etc-dir) + twittering-use-master-password t + twittering-request-confirmation-on-posting t + ;; twittering-icon-mode t + ;; twittering-use-icon-storage t + ;; twittering-icon-storage-file (concat doom-cache-dir "twittering-mode-icons.gz") + ;; twittering-convert-fix-size 12 + twittering-timeline-header "" + twittering-timeline-footer "" + twittering-edit-skeleton 'inherit-any + twittering-status-format "%FACE[font-lock-function-name-face]{ @%s} %FACE[italic]{%@} %FACE[error]{%FIELD-IF-NONZERO[❤ %d]{favorite_count}} %FACE[warning]{%FIELD-IF-NONZERO[↺ %d]{retweet_count}} +%FOLD[ ]{%FILL{%t}%QT{ +%FOLD[ ]{%FACE[font-lock-function-name-face]{@%s}\t%FACE[shadow]{%@} +%FOLD[ ]{%FILL{%t}} +}}} + +%FACE[twitter-divider]{ } +" + ;; twittering-timeline-spec-alias '() + twittering-initial-timeline-spec-string + '(":home" ":mentions" ":direct_messages")) + + (set-popup-rule! "^\\*twittering-edit" :size 15 :ttl nil :quit nil :select t) + + (defface twitter-divider + '((((background dark)) (:underline (:color "#141519"))) + (((background light)) (:underline (:color "#d3d3d3")))) + "The vertical divider between tweets." + :group 'twittering-mode) + + (add-hook 'doom-real-buffer-functions #'+twitter-buffer-p) + (when (featurep! :ui popup) + (setq twittering-pop-to-buffer-function #'+twitter-display-buffer-fn)) + + (after! solaire-mode + (add-hook 'twittering-mode-hook #'solaire-mode)) + + ;; Custom header-line for twitter buffers + (add-hook! 'twittering-mode-hook + (defun +twitter-switch-mode-and-header-line-h () + (setq header-line-format mode-line-format + mode-line-format nil))) + + ;; `epa--decode-coding-string' isn't defined in later versions of Emacs 27 + (unless (fboundp 'epa--decode-coding-string) + (defalias 'epa--decode-coding-string #'decode-coding-string)) + + (define-key! twittering-mode-map + "q" #'+twitter/quit + "Q" #'+twitter/quit-all + [remap twittering-kill-buffer] #'+twitter/quit + [remap delete-window] #'+twitter/quit + [remap +workspace/close-window-or-workspace] #'+twitter/quit) + (when (featurep! :editor evil +everywhere) + (define-key! twittering-mode-map + [remap evil-window-delete] #'+twitter/quit + "f" #'twittering-favorite + "F" #'twittering-unfavorite + "\C-f" #'twittering-follow + "\C-F" #'twittering-unfollow + "d" #'twittering-delete-status + "r" #'twittering-retweet + "R" #'twittering-toggle-or-retrieve-replied-statuses + "o" #'twittering-update-status-interactive + "O" #'+twitter/ace-link + "/" #'twittering-search + "J" #'twittering-goto-next-status + "K" #'twittering-goto-previous-status + "g" nil + "gg" #'twittering-goto-first-status + "G" #'twittering-goto-last-status + "gj" #'twittering-goto-next-status-of-user + "gk" #'twittering-goto-previous-status-of-user))) diff --git a/.emacs.d/modules/app/twitter/packages.el b/.emacs.d/modules/app/twitter/packages.el new file mode 100644 index 0000000..85b1da5 --- /dev/null +++ b/.emacs.d/modules/app/twitter/packages.el @@ -0,0 +1,5 @@ +;; -*- no-byte-compile: t; -*- +;;; app/twitter/packages.el + +(package! twittering-mode) +(package! avy) diff --git a/.emacs.d/modules/app/write/README.org b/.emacs.d/modules/app/write/README.org new file mode 100644 index 0000000..dc8159f --- /dev/null +++ b/.emacs.d/modules/app/write/README.org @@ -0,0 +1,131 @@ +#+TITLE: app/write +#+DATE: October 10, 2019 +#+SINCE: v1.3 +#+STARTUP: inlineimages + +* Table of Contents :TOC_3:noexport: +- [[#description][Description]] + - [[#module-flags][Module Flags]] + - [[#plugins][Plugins]] +- [[#prerequisites][Prerequisites]] + - [[#language-tool][Language Tool]] + - [[#wordnut][Wordnut]] +- [[#features][Features]] + - [[#m-x-write-mode][~M-x +write-mode~]] + - [[#language-tool-langtool][Language Tool ~+langtool~]] + - [[#commands][Commands]] + - [[#wordnut-wordnut][Wordnut ~+wordnut~]] + - [[#commands-1][Commands]] + - [[#synosaurus][Synosaurus]] + - [[#commands-2][Commands]] +- [[#configuration][Configuration]] + - [[#mixed-pitch-mode][mixed-pitch-mode]] +- [[#appendix][Appendix]] + - [[#minor-modes][Minor modes]] + - [[#commands-3][Commands]] + +* Description +Adds word processing tools and the ~+write-mode~ minor mode, which converts +Emacs into a more comfortable writing environment. + +** Module Flags +This module provides two module flags: + +- ~+langtool~ Enables language tool integration. +- ~+wordnut~ Enables wordnet integration. + +** Plugins ++ [[https://github.com/hpdeifel/synosaurus][synosaurus]] ++ [[https://gitlab.com/jabranham/mixed-pitch][mixed-pitch]] ++ [[https://github.com/joostkremers/visual-fill-column][visual-fill-column]] ++ [[https://github.com/mhayashi1120/Emacs-langtool][langtool]]* (=+langtool=) ++ [[https://github.com/gromnitsky/wordnut][wordnut]]* (=+wordnut=) + +* Prerequisites +** Language Tool +Either download and deploy it from https://languagetool.org/ or install it +through your OS package manager: + +#+BEGIN_SRC sh +# MacOS/Homebrew users: +brew install languagetool + +# Arch Linux users: +sudo pacman -S languagetool +#+END_SRC + +This module tries to guess the location of languagetool-commandline.jar. If you +get a warning that Doom =couldn't find languagetool-commandline.jar=, you will +need to find langaugetool-commandline.jar and set ~langtool-language-tool-jar~ +to its path. + +** Wordnut +This requires =wordnet= to be installed, which should be available through your +OS package manager: + +#+BEGIN_SRC sh +# MacOS/Homebrew users: +brew install wordnet + +# Arch Linux users: +sudo pacaur -S wordnet # on the AUR +#+END_SRC + +* Features +** ~M-x +write-mode~ +Write mode makes Emacs a more comfortable writing environment by: + +- Centering the buffer (with ~visual-fill-column-mode~), ala distraction-free + mode from other text editors. +- Soft-wrapping long text lines with ~visual-line-mode~. +- Enabling ~mixed-pitch-mode~, allowing fixed-width and variable-pitch fonts to + co-exist in one buffer. For example, a monospace font for SRC blocks and Arial + for everything else. +- In org-mode: + - Turns on ~org-indent-mode~ + - Turns on ~+org-pretty-mode~ + +** Language Tool ~+langtool~ +[[https://www.languagetool.org/][Language Tool]] is a polyglot proofreader service that checks for grammar and +stylistic issues in your writing. This requires Java 1.8+. + +#+begin_quote +This requires Java 1.8+ +#+end_quote + +*** Commands +- ~langtool-check~ +- ~langtool-correct-buffer~ + +** Wordnut ~+wordnut~ +Wordnut provides a searchable dictionary frontend for Emacs. This requires +~wordnet~, which should be available in your OS's package manager. + +*** Commands +- ~wordnut-search~ +- ~wordnut-lookup-curent-word~ + +** Synosaurus +Synosaurus provides a service for looking up synonyms. It requires an internet +connection. + +*** Commands +- ~synosaurus-lookup~ +- ~synosaurus-choose-and-replace~ + +* Configuration +** mixed-pitch-mode +To configure which faces are displayed with fixed-pitch fonts in +~mixed-pitch-mode~, look into ~mixed-pitch-fixed-pitch-faces~. + +* Appendix +** Minor modes +- ~+write-mode~ +- ~mixed-pitch-mode~ +** Commands +- ~langtool-check~ +- ~langtool-correct-buffer~ +- ~synosaurus-choose-and-replace~ +- ~synosaurus-lookup~ +- ~wordnut-lookup-curent-word~ +- ~wordnut-search~ diff --git a/.emacs.d/modules/app/write/autoload.el b/.emacs.d/modules/app/write/autoload.el new file mode 100644 index 0000000..f1fa846 --- /dev/null +++ b/.emacs.d/modules/app/write/autoload.el @@ -0,0 +1,43 @@ +;;; app/write/autoload.el -*- lexical-binding: t; -*- + +;;;###autoload +(defvar +write-mode-map (make-sparse-keymap) + "TODO") + +;;;###autoload +(define-minor-mode +write-mode + "Turns Emacs into a more comfortable writing environment and word processor." + :init-value nil + :keymap +write-mode-map + (setq-local visual-fill-column-center-text t) + (when +write-text-scale + (text-scale-set (if +write-mode 2 0))) + (when +write-line-spacing + (setq-local line-spacing +write-line-spacing))) + +;;;###autoload +(defun +write-init-org-mode-h () + "Initializes `org-mode' specific settings for `+write-mode'." + (when (eq major-mode 'org-mode) + (+org-pretty-mode (if +write-mode +1 -1)))) + +;;;###autoload +(defun +write-init-line-numbers-h () + (display-line-numbers-mode (if +write-mode +1 -1))) + +;;;###autoload +(defun +write-init-mixed-pitch-h () + (mixed-pitch-mode (if +write-mode +1 -1))) + +;;;###autoload +(defun +write-init-visual-fill-column-h () + (visual-fill-column-mode (if +write-mode +1 -1))) + +;;;###autoload +(add-hook! '+write-mode-hook + #'(flyspell-mode + visual-line-mode + +write-init-mixed-pitch-h + +write-init-visual-fill-column-h + +write-init-line-numbers-h + +write-init-org-mode-h)) diff --git a/.emacs.d/modules/app/write/config.el b/.emacs.d/modules/app/write/config.el new file mode 100644 index 0000000..e42620d --- /dev/null +++ b/.emacs.d/modules/app/write/config.el @@ -0,0 +1,56 @@ +;;; app/write/config.el -*- lexical-binding: t; -*- + +(defvar +write-text-scale nil + "What to scale the text up to in `+write-mode'. Uses `text-scale-set'.") + +(defvar +write-line-spacing nil + "What to set `line-spacing' in `+write-mode'.") + +;; +;; Packages + +(use-package! langtool + :when (featurep! +langtool) + :commands (langtool-check + langtool-check-done + langtool-show-message-at-point + langtool-correct-buffer) + :init (setq langtool-default-language "en-US") + :config + (unless langtool-language-tool-jar + (setq langtool-language-tool-jar + (cond (IS-MAC + (locate-file "libexec/languagetool-commandline.jar" + (doom-files-in "/usr/local/Cellar/languagetool" + :type 'dirs + :depth 2))) + (IS-LINUX + "/usr/share/java/languagetool/languagetool-commandline.jar"))))) + + +;; `synosaurus' +(setq synosaurus-choose-method 'default) + + +;; `mixed-pitch' +(after! mixed-pitch + (setq mixed-pitch-fixed-pitch-faces + (append mixed-pitch-fixed-pitch-faces + '(org-todo-keyword-todo + org-todo-keyword-habt + org-todo-keyword-done + org-todo-keyword-wait + org-todo-keyword-kill + org-todo-keyword-outd + org-todo + org-indent + line-number + line-number-current-line + org-special-keyword + org-date + org-property-value + org-special-keyword + org-property-value + org-ref-cite-face + org-tag + font-lock-comment-face)))) diff --git a/.emacs.d/modules/app/write/doctor.el b/.emacs.d/modules/app/write/doctor.el new file mode 100644 index 0000000..655adea --- /dev/null +++ b/.emacs.d/modules/app/write/doctor.el @@ -0,0 +1,7 @@ +;; -*- lexical-binding: t; no-byte-compile: t; -*- +;;; app/write/doctor.el + +(when (featurep! +langtool) + (require 'langtool) + (unless (file-exists-p langtool-language-tool-jar) + (warn! "Couldn't find languagetool-commandline.jar"))) diff --git a/.emacs.d/modules/app/write/packages.el b/.emacs.d/modules/app/write/packages.el new file mode 100644 index 0000000..4827e98 --- /dev/null +++ b/.emacs.d/modules/app/write/packages.el @@ -0,0 +1,12 @@ +;; -*- no-byte-compile: t; -*- +;;; app/write/packages.el + +(package! synosaurus) +(package! mixed-pitch) + +(when (featurep! +langtool) + (package! langtool)) +(when (featurep! +wordnut) + (package! wordnut)) + +(package! visual-fill-column) diff --git a/.emacs.d/modules/completion/company/README.org b/.emacs.d/modules/completion/company/README.org new file mode 100644 index 0000000..1ca8d72 --- /dev/null +++ b/.emacs.d/modules/completion/company/README.org @@ -0,0 +1,131 @@ +#+TITLE: completion/company +#+DATE: February 19, 2017 +#+SINCE: v2.0 +#+STARTUP: inlineimages + +* Table of Contents :TOC_3:noexport: +- [[#description][Description]] + - [[#module-flags][Module Flags]] + - [[#plugins][Plugins]] +- [[#prerequisites][Prerequisites]] +- [[#features][Features]] + - [[#code-completion][Code completion]] + - [[#vim-esque-omni-completion-prefix-c-x][Vim-esque omni-completion prefix (C-x)]] +- [[#configuration][Configuration]] + - [[#enable-company-backends-in-certain-modes][Enable company backend(s) in certain modes]] +- [[#troubleshooting][Troubleshooting]] + - [[#x-mode-doesnt-have-code-completion-support-or-requires-extra-setup][X-mode doesn't have code completion support or requires extra setup.]] + - [[#no-backends-or-the-incorrect-ones-have-been-registered-for-x-mode][No backends (or the incorrect ones) have been registered for X-mode.]] + +* Description +This module provides code completion, powered by [[https://github.com/company-mode/company-mode][company-mode]]. It is required +for code completion in many of Doom's :lang modules. + +https://assets.doomemacs.org/completion/company/overlay.png + +** Module Flags ++ =+childframe= Enables displaying completion candidates in a child frame, + rather than an overlay or tooltip (among with other UI enhancements). *This + requires GUI Emacs 26.1+ and is incompatible with the =+tng= flag* ++ =+tng= Enables completion using only ~TAB~. Pressing ~TAB~ will select the + next completion suggestion, while ~S-TAB~ will select the previous one. *This + is incompatible with the =+childframe= flag* + +** Plugins ++ [[https://github.com/company-mode/company-mode][company-mode]] ++ [[https://github.com/hlissner/emacs-company-dict][company-dict]] ++ [[https://github.com/raxod502/prescient.el][company-prescient]] ++ [[https://github.com/sebastiencs/company-box][company-box]]* (=+childframe=) + +* Prerequisites +This module has no direct prerequisites. + +However, some major modes may require additional setup for code completion to +work in them. Some major modes may have no completion support at all. Check that +major mode's module's documentation for details. + +* Features +** Code completion +By default, completion is triggered after a short idle period or with the +=C-SPC= key. While the popup is visible, the following keys are available: + +| Keybind | Description | +|---------+------------------------------------------| +| =C-n= | Go to next candidate | +| =C-p= | Go to previous candidate | +| =C-j= | (evil) Go to next candidate | +| =C-k= | (evil) Go to previous candidate | +| =C-h= | Display documentation (if available) | +| =C-u= | Move to previous page of candidates | +| =C-d= | Move to next page of candidates | +| =C-s= | Filter candidates | +| =C-S-s= | Search candidates with helm/ivy | +| =C-SPC= | Complete common | +| =TAB= | Complete common or select next candidate | +| =S-TAB= | Select previous candidate | + +** Vim-esque omni-completion prefix (C-x) +In the spirit of Vim's omni-completion, the following insert mode keybinds are +available to evil users to access specific company backends: + +| Keybind | Description | +|-----------+-----------------------------------| +| =C-x C-]= | Complete etags | +| =C-x C-f= | Complete file path | +| =C-x C-k= | Complete from dictionary/keyword | +| =C-x C-l= | Complete full line | +| =C-x C-o= | Invoke complete-at-point function | +| =C-x C-n= | Complete next symbol at point | +| =C-x C-p= | Complete previous symbol at point | +| =C-x C-s= | Complete snippet | +| =C-x s= | Complete spelling suggestions | + +* Configuration +** Enable company backend(s) in certain modes +The ~set-company-backend!~ function exists for setting ~company-backends~ +buffer-locally in MODES, which is either a major-mode symbol, a minor-mode +symbol, or a list of either. BACKENDS are prepended to ~company-backends~ for +those modes. + +#+BEGIN_SRC emacs-lisp +(after! js2-mode + (set-company-backend! 'js2-mode 'company-tide 'company-yasnippet)) + +(after! sh-script + (set-company-backend! 'sh-mode + '(company-shell :with company-yasnippet))) + +(after! cc-mode + (set-company-backend! 'c-mode + '(:separate company-irony-c-headers company-irony))) +#+END_SRC + +To unset the backends for a particular mode, pass ~nil~ to it: + +#+BEGIN_SRC emacs-lisp +(after! sh-script + (set-company-backend! 'sh-mode nil)) +#+END_SRC + +* Troubleshooting +If code completion isn't working for you, consider the following common causes +before you file a bug report: + +** X-mode doesn't have code completion support or requires extra setup. +There is no guarantee your language mode will have completion support. + +Some, like ~lua-mode~, don't have completion support in Emacs at all. Others may +requires additional setup to get code completion working. For instance, +~go-mode~ requires ~guru~ to be installed on your system, and ~enh-ruby-mode~ +requires that you have a Robe server running (~M-x robe-start~). + +Check the relevant module's documentation for this kind of information. + +** No backends (or the incorrect ones) have been registered for X-mode. +Doom expects every mode to have an explicit list of company-backends (and as +short a list as possible). This may mean you aren't getting all the completion +you want or any at all. + +Check the value of ~company-backends~ (=SPC h v company-backends=) from that +mode to see what backends are available. Check the [[*Assigning company backend(s) to modes][Configuration section]] for +details on changing what backends are available for that mode. diff --git a/.emacs.d/modules/completion/company/autoload.el b/.emacs.d/modules/completion/company/autoload.el new file mode 100644 index 0000000..a37627d --- /dev/null +++ b/.emacs.d/modules/completion/company/autoload.el @@ -0,0 +1,156 @@ +;;; completion/company/autoload.el -*- lexical-binding: t; -*- + +;;;###autoload +(defvar +company-backend-alist + '((text-mode company-dabbrev company-yasnippet company-ispell) + (prog-mode company-capf company-yasnippet) + (conf-mode company-capf company-dabbrev-code company-yasnippet)) + "An alist matching modes to company backends. The backends for any mode is +built from this.") + +;;;###autodef +(defun set-company-backend! (modes &rest backends) + "Prepends BACKENDS (in order) to `company-backends' in MODES. + +MODES should be one symbol or a list of them, representing major or minor modes. +This will overwrite backends for MODES on consecutive uses. + +If the car of BACKENDS is nil, unset the backends for MODES. + +Examples: + + (set-company-backend! 'js2-mode + 'company-tide 'company-yasnippet) + + (set-company-backend! 'sh-mode + '(company-shell :with company-yasnippet)) + + (set-company-backend! '(c-mode c++-mode) + '(:separate company-irony-c-headers company-irony)) + + (set-company-backend! 'sh-mode nil) ; unsets backends for sh-mode" + (declare (indent defun)) + (dolist (mode (doom-enlist modes)) + (if (null (car backends)) + (setq +company-backend-alist + (delq (assq mode +company-backend-alist) + +company-backend-alist)) + (setf (alist-get mode +company-backend-alist) + backends)))) + + +;; +;;; Library + +(defun +company--backends () + (let (backends) + (let ((mode major-mode) + (modes (list major-mode))) + (while (setq mode (get mode 'derived-mode-parent)) + (push mode modes)) + (dolist (mode modes) + (dolist (backend (append (cdr (assq mode +company-backend-alist)) + (default-value 'company-backends))) + (push backend backends))) + (delete-dups + (append (cl-loop for (mode . backends) in +company-backend-alist + if (or (eq major-mode mode) ; major modes + (and (boundp mode) + (symbol-value mode))) ; minor modes + append backends) + (nreverse backends)))))) + + +;; +;;; Hooks + +;;;###autoload +(defun +company-init-backends-h () + "Set `company-backends' for the current buffer." + (if (not company-mode) + (remove-hook 'change-major-mode-after-body-hook #'+company-init-backends-h 'local) + (unless (eq major-mode 'fundamental-mode) + (setq-local company-backends (+company--backends))) + (add-hook 'change-major-mode-after-body-hook #'+company-init-backends-h nil 'local))) + +(put '+company-init-backends-h 'permanent-local-hook t) + + +;; +;;; Commands + +;;;###autoload +(defun +company-has-completion-p () + "Return non-nil if a completion candidate exists at point." + (and (company-manual-begin) + (= company-candidates-length 1))) + +;;;###autoload +(defun +company/toggle-auto-completion () + "Toggle as-you-type code completion." + (interactive) + (require 'company) + (setq company-idle-delay (unless company-idle-delay 0.2)) + (message "Auto completion %s" + (if company-idle-delay "enabled" "disabled"))) + +;;;###autoload +(defun +company/complete () + "Bring up the completion popup. If only one result, complete it." + (interactive) + (require 'company) + (when (ignore-errors + (/= (point) + (cdr (bounds-of-thing-at-point 'symbol)))) + (save-excursion (insert " "))) + (when (and (company-manual-begin) + (= company-candidates-length 1)) + (company-complete-common))) + +;;;###autoload +(defun +company/dabbrev () + "Invokes `company-dabbrev-code' in prog-mode buffers and `company-dabbrev' +everywhere else." + (interactive) + (call-interactively + (if (derived-mode-p 'prog-mode) + #'company-dabbrev-code + #'company-dabbrev))) + +;;;###autoload +(defun +company/whole-lines (command &optional arg &rest ignored) + "`company-mode' completion backend that completes whole-lines, akin to vim's +C-x C-l." + (interactive (list 'interactive)) + (require 'company) + (pcase command + (`interactive (company-begin-backend '+company/whole-lines)) + (`prefix (company-grab-line "^[\t\s]*\\(.+\\)" 1)) + (`candidates + (all-completions + arg + (delete-dups + (split-string + (replace-regexp-in-string + "^[\t\s]+" "" + (concat (buffer-substring-no-properties (point-min) (line-beginning-position)) + (buffer-substring-no-properties (line-end-position) (point-max)))) + "\\(\r\n\\|[\n\r]\\)" t)))))) + +;;;###autoload +(defun +company/dict-or-keywords () + "`company-mode' completion combining `company-dict' and `company-keywords'." + (interactive) + (require 'company-dict) + (require 'company-keywords) + (let ((company-backends '((company-keywords company-dict)))) + (call-interactively #'company-complete))) + +;;;###autoload +(defun +company/dabbrev-code-previous () + "TODO" + (interactive) + (require 'company-dabbrev) + (let ((company-selection-wrap-around t)) + (call-interactively #'+company/dabbrev) + (company-select-previous-or-abort))) diff --git a/.emacs.d/modules/completion/company/config.el b/.emacs.d/modules/completion/company/config.el new file mode 100644 index 0000000..a98bcca --- /dev/null +++ b/.emacs.d/modules/completion/company/config.el @@ -0,0 +1,133 @@ +;;; completion/company/config.el -*- lexical-binding: t; -*- + +(use-package! company + :commands company-complete-common company-manual-begin company-grab-line + :after-call pre-command-hook after-find-file + :init + (setq company-minimum-prefix-length 2 + company-tooltip-limit 14 + company-dabbrev-downcase nil + company-dabbrev-ignore-case nil + company-dabbrev-code-other-buffers t + company-tooltip-align-annotations t + company-require-match 'never + company-global-modes + '(not erc-mode message-mode help-mode gud-mode eshell-mode) + company-backends '(company-capf) + company-frontends + '(company-pseudo-tooltip-frontend + company-echo-metadata-frontend)) + :config + (when (featurep! :editor evil) + (add-hook 'company-mode-hook #'evil-normalize-keymaps) + + ;; Allow users to switch between backends on the fly. E.g. C-x C-s followed + ;; by C-x C-n, will switch from `company-yasnippet' to + ;; `company-dabbrev-code'. + (defadvice! +company--abort-previous-a (&rest _) + :before #'company-begin-backend + (company-abort))) + + (add-hook 'company-mode-hook #'+company-init-backends-h) + (global-company-mode +1)) + + +(use-package! company-tng + :when (featurep! +tng) + :after-call post-self-insert-hook + :config + (add-to-list 'company-frontends 'company-tng-frontend) + (define-key! company-active-map + "RET" nil + [return] nil + "TAB" #'company-select-next + [tab] #'company-select-next + [backtab] #'company-select-previous)) + + +;; +;; Packages + +(after! company-files + (pushnew! company-files--regexps + "file:\\(\\(?:\\.\\{1,2\\}/\\|~/\\|/\\)[^\]\n]*\\)")) + + +(use-package! company-prescient + :hook (company-mode . company-prescient-mode) + :config + ;; NOTE prescient config duplicated with `ivy' + (setq prescient-save-file (concat doom-cache-dir "prescient-save.el")) + (prescient-persist-mode +1)) + + +(use-package! company-box + :when (featurep! +childframe) + :hook (company-mode . company-box-mode) + :config + (setq company-box-show-single-candidate t + company-box-backends-colors nil + company-box-max-candidates 50 + company-box-icons-alist 'company-box-icons-all-the-icons + company-box-icons-functions + '(+company-box-icons--yasnippet-fn + company-box-icons--lsp + +company-box-icons--elisp-fn + company-box-icons--acphp) + company-box-icons-all-the-icons + `((Unknown . ,(all-the-icons-material "find_in_page" :height 0.8 :face 'all-the-icons-purple)) + (Text . ,(all-the-icons-material "text_fields" :height 0.8 :face 'all-the-icons-green)) + (Method . ,(all-the-icons-material "functions" :height 0.8 :face 'all-the-icons-red)) + (Function . ,(all-the-icons-material "functions" :height 0.8 :face 'all-the-icons-red)) + (Constructor . ,(all-the-icons-material "functions" :height 0.8 :face 'all-the-icons-red)) + (Field . ,(all-the-icons-material "functions" :height 0.8 :face 'all-the-icons-red)) + (Variable . ,(all-the-icons-material "adjust" :height 0.8 :face 'all-the-icons-blue)) + (Class . ,(all-the-icons-material "class" :height 0.8 :face 'all-the-icons-red)) + (Interface . ,(all-the-icons-material "settings_input_component" :height 0.8 :face 'all-the-icons-red)) + (Module . ,(all-the-icons-material "view_module" :height 0.8 :face 'all-the-icons-red)) + (Property . ,(all-the-icons-material "settings" :height 0.8 :face 'all-the-icons-red)) + (Unit . ,(all-the-icons-material "straighten" :height 0.8 :face 'all-the-icons-red)) + (Value . ,(all-the-icons-material "filter_1" :height 0.8 :face 'all-the-icons-red)) + (Enum . ,(all-the-icons-material "plus_one" :height 0.8 :face 'all-the-icons-red)) + (Keyword . ,(all-the-icons-material "filter_center_focus" :height 0.8 :face 'all-the-icons-red)) + (Snippet . ,(all-the-icons-material "short_text" :height 0.8 :face 'all-the-icons-red)) + (Color . ,(all-the-icons-material "color_lens" :height 0.8 :face 'all-the-icons-red)) + (File . ,(all-the-icons-material "insert_drive_file" :height 0.8 :face 'all-the-icons-red)) + (Reference . ,(all-the-icons-material "collections_bookmark" :height 0.8 :face 'all-the-icons-red)) + (Folder . ,(all-the-icons-material "folder" :height 0.8 :face 'all-the-icons-red)) + (EnumMember . ,(all-the-icons-material "people" :height 0.8 :face 'all-the-icons-red)) + (Constant . ,(all-the-icons-material "pause_circle_filled" :height 0.8 :face 'all-the-icons-red)) + (Struct . ,(all-the-icons-material "streetview" :height 0.8 :face 'all-the-icons-red)) + (Event . ,(all-the-icons-material "event" :height 0.8 :face 'all-the-icons-red)) + (Operator . ,(all-the-icons-material "control_point" :height 0.8 :face 'all-the-icons-red)) + (TypeParameter . ,(all-the-icons-material "class" :height 0.8 :face 'all-the-icons-red)) + ;; (Template . ,(company-box-icons-image "Template.png")))) + (Yasnippet . ,(all-the-icons-material "short_text" :height 0.8 :face 'all-the-icons-green)) + (ElispFunction . ,(all-the-icons-material "functions" :height 0.8 :face 'all-the-icons-red)) + (ElispVariable . ,(all-the-icons-material "check_circle" :height 0.8 :face 'all-the-icons-blue)) + (ElispFeature . ,(all-the-icons-material "stars" :height 0.8 :face 'all-the-icons-orange)) + (ElispFace . ,(all-the-icons-material "format_paint" :height 0.8 :face 'all-the-icons-pink)))) + + (defun +company-box-icons--yasnippet-fn (candidate) + (when (get-text-property 0 'yas-annotation candidate) + 'Yasnippet)) + + (defun +company-box-icons--elisp-fn (candidate) + (when (derived-mode-p 'emacs-lisp-mode) + (let ((sym (intern candidate))) + (cond ((fboundp sym) 'ElispFunction) + ((boundp sym) 'ElispVariable) + ((featurep sym) 'ElispFeature) + ((facep sym) 'ElispFace)))))) + + +(use-package! company-dict + :defer t + :config + (setq company-dict-dir (expand-file-name "dicts" doom-private-dir)) + (add-hook! 'doom-project-hook + (defun +company-enable-project-dicts-h (mode &rest _) + "Enable per-project dictionaries." + (if (symbol-value mode) + (add-to-list 'company-dict-minor-mode-list mode nil #'eq) + (setq company-dict-minor-mode-list (delq mode company-dict-minor-mode-list)))))) diff --git a/.emacs.d/modules/completion/company/packages.el b/.emacs.d/modules/completion/company/packages.el new file mode 100644 index 0000000..91b9e88 --- /dev/null +++ b/.emacs.d/modules/completion/company/packages.el @@ -0,0 +1,8 @@ +;; -*- no-byte-compile: t; -*- +;;; completion/company/packages.el + +(package! company) +(package! company-dict) +(package! company-prescient) +(when (featurep! +childframe) + (package! company-box)) diff --git a/.emacs.d/modules/completion/company/test/test-company.el b/.emacs.d/modules/completion/company/test/test-company.el new file mode 100644 index 0000000..9c6148d --- /dev/null +++ b/.emacs.d/modules/completion/company/test/test-company.el @@ -0,0 +1,75 @@ +;; -*- lexical-binding: t; no-byte-compile: t; -*- +;;; completion/company/test/test-company.el + +(describe "completion/company" + (before-all + (load! "../autoload")) + + (describe ":company-backend" + :var (a +company-backend-alist backends) + (before-each + (setq-default company-backends '(t)) + (setq +company-backend-alist nil + a (get-buffer-create "x")) + (fset 'backends + (lambda (mode) + (let ((major-mode mode)) + (+company--backends)))) + (set-buffer a) + (spy-on 'require)) + (after-each + (kill-buffer a)) + + ;; + (it "sets backends for a major mode" + (set-company-backend! 'text-mode 'a) + (expect (backends 'text-mode) :to-equal '(a t))) + + (it "sets backends for a derived-mode" + (set-company-backend! 'prog-mode 'a) + (expect (backends 'prog-mode) :to-equal '(a t)) + (expect (backends 'emacs-lisp-mode) :to-equal '(a t))) + + (it "sets multiple backends for exact major modes" + (set-company-backend! '(text-mode emacs-lisp-mode) 'a 'b) + (expect (backends 'text-mode) :to-equal (backends 'emacs-lisp-mode))) + + (it "sets cumulative backends" + (set-company-backend! 'prog-mode '(a b c)) + (set-company-backend! 'emacs-lisp-mode 'd 'e) + (expect (backends 'emacs-lisp-mode) :to-equal '(d e (a b c) t))) + + (it "sets cumulative backends with a minor mode" + (set-company-backend! 'prog-mode '(a b c)) + (set-company-backend! 'emacs-lisp-mode 'd 'e) + (set-company-backend! 'some-minor-mode 'x 'y) + (setq-local some-minor-mode t) + (expect (backends 'emacs-lisp-mode) :to-equal '(x y d e (a b c) t))) + + (it "overwrites past backends" + (set-company-backend! 'text-mode 'old 'backends) + (set-company-backend! 'text-mode 'new 'backends) + (expect (backends 'text-mode) :to-equal '(new backends t))) + + (it "unsets past backends" + (set-company-backend! 'text-mode 'old) + (set-company-backend! 'text-mode nil) + (expect (backends 'text-mode) :to-equal (default-value 'company-backends))) + + (it "unsets past parent backends" + (set-company-backend! 'prog-mode 'old) + (set-company-backend! 'emacs-lisp-mode 'child) + (set-company-backend! 'prog-mode nil) + (expect (backends 'emacs-lisp-mode) :to-equal '(child t))) + + (it "overwrites past cumulative backends" + (set-company-backend! 'prog-mode 'base) + (set-company-backend! 'emacs-lisp-mode 'old) + (set-company-backend! 'emacs-lisp-mode 'new) + (expect (backends 'emacs-lisp-mode) :to-equal '(new base t))) + + (it "overwrites past parent backends" + (set-company-backend! 'prog-mode 'base) + (set-company-backend! 'emacs-lisp-mode 'child) + (set-company-backend! 'prog-mode 'new) + (expect (backends 'emacs-lisp-mode) :to-equal '(child new t))))) diff --git a/.emacs.d/modules/completion/helm/autoload/evil.el b/.emacs.d/modules/completion/helm/autoload/evil.el new file mode 100644 index 0000000..fbcf0a9 --- /dev/null +++ b/.emacs.d/modules/completion/helm/autoload/evil.el @@ -0,0 +1,42 @@ +;;; completion/helm/autoload/evil.el -*- lexical-binding: t; -*- +;;;###if (featurep! :editor evil) + +;;;###autoload (autoload '+helm:project-search "completion/helm/autoload/evil" nil t) +(evil-define-command +helm:project-search (all-files-p query) + "Ex interface for `+helm/grep'" + (interactive "") + (+helm/project-search all-files-p query)) + +;;;###autoload (autoload '+helm:project-search-from-cwd "completion/helm/autoload/evil" nil t) +(evil-define-command +helm:project-search-from-cwd (query &optional recurse-p) + "Ex interface for `+helm/grep-from-cwd'." + (interactive "") + (+helm/project-search-from-cwd (not recurse-p) query)) + +;;;###autoload +(defun +helm--set-prompt-display (pos) + "TODO" + (let (beg state region-active m) + (with-selected-window (minibuffer-window) + (setq beg (save-excursion (vertical-motion 0 (helm-window)) (point)) + state evil-state + region-active (region-active-p) + m (mark t))) + (when region-active + (setq m (- m beg)) + ;; Increment pos to handle the space before prompt (i.e `pref'). + (put-text-property (1+ (min m pos)) (+ 2 (max m pos)) + 'face + (list :background (face-background 'region)) + header-line-format)) + (put-text-property + ;; Increment pos to handle the space before prompt (i.e `pref'). + (+ 1 pos) (+ 2 pos) + 'face + (if (eq state 'insert) + 'underline + ;; Don't just use 'cursor, this can hide the current character. + (list :inverse-video t + :foreground (face-background 'cursor) + :background (face-background 'default))) + header-line-format))) diff --git a/.emacs.d/modules/completion/helm/autoload/helm.el b/.emacs.d/modules/completion/helm/autoload/helm.el new file mode 100644 index 0000000..ec741c0 --- /dev/null +++ b/.emacs.d/modules/completion/helm/autoload/helm.el @@ -0,0 +1,96 @@ +;;; completion/helm/autoload/helm.el -*- lexical-binding: t; -*- + +;;;###autoload +(defun +helm/projectile-find-file () + "Call `helm-find-files' if called from HOME, otherwise +`helm-projectile-find-file'." + (interactive) + (call-interactively + (if (or (file-equal-p default-directory "~") + (if-let* ((proot (doom-project-root))) + (file-equal-p proot "~") + t)) + #'helm-find-files + #'helm-projectile-find-file))) + +;;;###autoload +(defun +helm/workspace-buffer-list () + "A version of `helm-buffers-list' with its buffer list restricted to the +current workspace." + (interactive) + (unless (featurep! :ui workspaces) + (user-error "This command requires the :ui workspaces module")) + (with-no-warnings + (with-persp-buffer-list nil (helm-buffers-list)))) + +;;;###autoload +(defun +helm/workspace-mini () + "A version of `helm-mini' with its buffer list restricted to the current +workspace." + (interactive) + (unless (featurep! :ui workspaces) + (user-error "This command requires the :ui workspaces module")) + (with-no-warnings + (with-persp-buffer-list nil (helm-mini)))) + + +;; +;;; Project search + +;;;###autoload +(cl-defun +helm-file-search (&key query in all-files (recursive t)) + "Conduct a file search using ripgrep. + +:query STRING + Determines the initial input to search for. +:in PATH + Sets what directory to base the search out of. Defaults to the current + project's root. +:recursive BOOL + Whether or not to search files recursively from the base directory." + (declare (indent defun)) + (unless (executable-find "rg") + (user-error "Couldn't find ripgrep in your PATH")) + (require 'helm-rg) + (let ((helm-rg-default-directory (or in (doom-project-root) default-directory)) + (helm-rg-default-extra-args + (delq nil (list (when all-files "-z -uu") + (unless recursive "--maxdepth 1"))))) + (helm-rg (or query + (when (use-region-p) + (let ((beg (or (bound-and-true-p evil-visual-beginning) (region-beginning))) + (end (or (bound-and-true-p evil-visual-end) (region-end)))) + (when (> (abs (- end beg)) 1) + (buffer-substring-no-properties beg end)))) + "")))) + +;;;###autoload +(defun +helm/project-search (&optional arg initial-query directory) + "Performs a project search from the project root with ripgrep. + +ARG (universal argument), include all files, even hidden or compressed ones, in +the search." + (interactive "P") + (+helm-file-search + :query initial-query + :in directory + :all-files (and (not (null arg)) + (listp arg)))) + +;;;###autoload +(defun +helm/project-search-from-cwd (&optional arg initial-query) + "Performs a project search recursively from the current directory. + +If ARG (universal argument), include all files, even hidden or compressed ones." + (interactive "P") + (+helm-file-search + :query initial-query + :in default-directory + :all-files (and (not (null arg)) + (listp arg)))) + +;;;###autoload +(defun +helm/jump-list () + "TODO" + (interactive) + (error "not implemented yet")) diff --git a/.emacs.d/modules/completion/helm/autoload/posframe.el b/.emacs.d/modules/completion/helm/autoload/posframe.el new file mode 100644 index 0000000..25ac3fd --- /dev/null +++ b/.emacs.d/modules/completion/helm/autoload/posframe.el @@ -0,0 +1,59 @@ +;;; completion/helm/autoload/posframe.el -*- lexical-binding: t; -*- + +;;;###autoload +(defun +helm-poshandler-frame-center-near-bottom-fn (info) + "Display the child frame in the center of the frame, slightly closer to the +bottom, which is easier on the eyes on big displays." + (let ((parent-frame (plist-get info :parent-frame)) + (pos (posframe-poshandler-frame-center info))) + (cons (car pos) + (truncate (/ (frame-pixel-height parent-frame) + 2))))) + +(defvar +helm--posframe-buffer nil) +;;;###autoload +(defun +helm-posframe-display-fn (buffer &optional _resume) + "TODO" + (setq helm--buffer-in-new-frame-p t) + (let ((solaire-p (bound-and-true-p solaire-mode)) + (params (copy-sequence +helm-posframe-parameters))) + (let-alist params + (require 'posframe) + (posframe-show + (setq +helm--posframe-buffer buffer) + :position (point) + :poshandler +helm-posframe-handler + :width + (max (cl-typecase .width + (integer .width) + (float (truncate (* (frame-width) .width))) + (function (funcall .width)) + (t 0)) + .min-width) + :height + (max (cl-typecase .height + (integer .height) + (float (truncate (* (frame-height) .height))) + (function (funcall .height)) + (t 0)) + .min-height) + :override-parameters + (dolist (p '(width height min-width min-height) params) + (setq params (delq (assq p params) params))))) + ;; + (unless (or (null +helm-posframe-text-scale) + (= +helm-posframe-text-scale 0)) + (with-current-buffer buffer + (when (and (featurep 'solaire-mode) + (not solaire-p)) + (solaire-mode +1)) + (text-scale-set +helm-posframe-text-scale))))) + +;;;###autoload +(defun +helm-posframe-cleanup-h () + "TODO" + ;; Ensure focus is properly returned to the underlying window. This gives the + ;; modeline a chance to refresh. + (switch-to-buffer +helm--posframe-buffer t)) + +(add-hook 'helm-cleanup-hook #'+helm-posframe-cleanup-h) diff --git a/.emacs.d/modules/completion/helm/config.el b/.emacs.d/modules/completion/helm/config.el new file mode 100644 index 0000000..84b68f7 --- /dev/null +++ b/.emacs.d/modules/completion/helm/config.el @@ -0,0 +1,185 @@ +;;; completion/helm/config.el -*- lexical-binding: t; -*- + +;; Posframe (requires +childframe) +(defvar +helm-posframe-handler #'+helm-poshandler-frame-center-near-bottom-fn + "The function that determines the location of the childframe. It should return +a cons cell representing the X and Y coordinates. See +`posframe-poshandler-frame-center' as a reference.") + +(defvar +helm-posframe-text-scale 1 + "The text-scale to use in the helm childframe. Set to nil for no scaling. Can +be negative.") + +(defvar +helm-posframe-parameters + '((internal-border-width . 8) + (width . 0.5) + (height . 0.35) + (min-width . 80) + (min-height . 16)) + "TODO") + + +;; +;;; Packages + +(use-package! helm-mode + :defer t + :after-call pre-command-hook + :init + (map! [remap apropos] #'helm-apropos + [remap find-library] #'helm-locate-library + [remap bookmark-jump] #'helm-bookmarks + [remap execute-extended-command] #'helm-M-x + [remap find-file] #'helm-find-files + [remap locate] #'helm-locate + [remap imenu] #'helm-semantic-or-imenu + [remap noop-show-kill-ring] #'helm-show-kill-ring + [remap persp-switch-to-buffer] #'+helm/workspace-mini + [remap switch-to-buffer] #'helm-buffers-list + [remap projectile-find-file] #'+helm/projectile-find-file + [remap projectile-recentf] #'helm-projectile-recentf + [remap projectile-switch-project] #'helm-projectile-switch-project + [remap projectile-switch-to-buffer] #'helm-projectile-switch-to-buffer + [remap recentf-open-files] #'helm-recentf + [remap yank-pop] #'helm-show-kill-ring) + :config + (helm-mode +1) + ;; helm is too heavy for `find-file-at-point' + (add-to-list 'helm-completing-read-handlers-alist (cons #'find-file-at-point nil))) + + +(use-package! helm + :after helm-mode + :preface + (setq helm-candidate-number-limit 50 + ;; Remove extraineous helm UI elements + helm-display-header-line nil + helm-mode-line-string nil + helm-ff-auto-update-initial-value nil + helm-find-files-doc-header nil + ;; Don't override evil-ex's completion + helm-mode-handle-completion-in-region nil + ;; Default helm window sizes + helm-display-buffer-default-width nil + helm-display-buffer-default-height 0.25 + ;; When calling `helm-semantic-or-imenu', don't immediately jump to + ;; symbol at point + helm-imenu-execute-action-at-once-if-one nil + ;; disable special behavior for left/right, M-left/right keys. + helm-ff-lynx-style-map nil) + + (when (featurep! :editor evil +everywhere) + (setq helm-default-prompt-display-function #'+helm--set-prompt-display)) + + :init + (when (featurep! +childframe) + (setq helm-display-function #'+helm-posframe-display-fn)) + + (let ((fuzzy (featurep! +fuzzy))) + (setq helm-M-x-fuzzy-match fuzzy + helm-apropos-fuzzy-match fuzzy + helm-apropos-fuzzy-match fuzzy + helm-bookmark-show-location fuzzy + helm-buffers-fuzzy-matching fuzzy + helm-completion-in-region-fuzzy-match fuzzy + helm-completion-in-region-fuzzy-match fuzzy + helm-ff-fuzzy-matching fuzzy + helm-file-cache-fuzzy-match fuzzy + helm-flx-for-helm-locate fuzzy + helm-imenu-fuzzy-match fuzzy + helm-lisp-fuzzy-completion fuzzy + helm-locate-fuzzy-match fuzzy + helm-mode-fuzzy-match fuzzy + helm-projectile-fuzzy-match fuzzy + helm-recentf-fuzzy-match fuzzy + helm-semantic-fuzzy-match fuzzy)) + + :config + (set-popup-rule! "^\\*helm" :vslot -100 :size 0.22 :ttl nil) + + ;; HACK Doom doesn't support these commands, which invite the user to install + ;; the package via ELPA. Force them to use +helm/* instead, because they work + ;; out of the box. + (advice-add #'helm-projectile-rg :override #'+helm/project-search) + (advice-add #'helm-projectile-ag :override #'+helm/project-search) + (advice-add #'helm-projectile-grep :override #'+helm/project-search) + + ;; Hide the modeline + (defun +helm--hide-mode-line (&rest _) + (with-current-buffer (helm-buffer-get) + (unless helm-mode-line-string + (hide-mode-line-mode +1)))) + (add-hook 'helm-after-initialize-hook #'+helm--hide-mode-line) + (advice-add #'helm-display-mode-line :override #'+helm--hide-mode-line) + (advice-add #'helm-ag-show-status-default-mode-line :override #'ignore) + + ;; Use helpful instead of describe-* to display documentation + (dolist (fn '(helm-describe-variable helm-describe-function)) + (advice-add fn :around #'doom-use-helpful-a))) + + +(use-package! helm-flx + :when (featurep! +fuzzy) + :hook (helm-mode . helm-flx-mode) + :config (helm-flx-mode +1)) + + +(after! helm-rg + (setq helm-rg-display-buffer-normal-method #'pop-to-buffer) + (set-popup-rule! "^helm-rg-" :ttl nil :select t :size 0.45) + (map! :map helm-rg-map + "C-c C-e" #'helm-rg--bounce) + (map! :map helm-rg--bounce-mode-map + "q" #'kill-current-buffer + "C-c C-c" (λ! (helm-rg--bounce-dump) (kill-current-buffer)) + "C-x C-c" #'helm-rg--bounce-dump-current-file + "C-c C-k" #'kill-current-buffer)) + + +;;;###package helm-bookmark +(setq helm-bookmark-show-location t) + + +(after! helm-files + (setq helm-boring-file-regexp-list + (append (list "\\.projects$" "\\.DS_Store$") + helm-boring-file-regexp-list))) + + +(defvar helm-generic-files-map (make-sparse-keymap)) +(after! helm-locate + (when (and IS-MAC + (null helm-locate-command) + (executable-find "mdfind")) + (setq helm-locate-command "mdfind -name %s")) + (set-keymap-parent helm-generic-files-map helm-map)) + + +(use-package! helm-org + :when (featurep! :lang org) + :defer t + :init + (after! helm-mode + (pushnew! helm-completing-read-handlers-alist + '(org-capture . helm-org-completing-read-tags) + '(org-set-tags . helm-org-completing-read-tags)))) + + +(use-package! helm-projectile + :commands (helm-projectile-find-file + helm-projectile-recentf + helm-projectile-switch-project + helm-projectile-switch-to-buffer) + :init + (setq projectile-completion-system 'helm) + (defvar helm-projectile-find-file-map (make-sparse-keymap)) + :config + (set-keymap-parent helm-projectile-find-file-map helm-map)) + + +(setq ivy-height 20) ; for `swiper-isearch' +(after! swiper-helm + (setq swiper-helm-display-function + (lambda (buf &optional _resume) (pop-to-buffer buf))) + (global-set-key [remap swiper] #'swiper-helm) + (add-to-list 'swiper-font-lock-exclude #'+doom-dashboard-mode nil #'eq)) diff --git a/.emacs.d/modules/completion/helm/packages.el b/.emacs.d/modules/completion/helm/packages.el new file mode 100644 index 0000000..4bfb434 --- /dev/null +++ b/.emacs.d/modules/completion/helm/packages.el @@ -0,0 +1,16 @@ +;; -*- no-byte-compile: t; -*- +;;; completion/helm/packages.el + +(package! helm) +(package! helm-rg) +(package! helm-c-yasnippet) +(package! helm-company) +(package! helm-describe-modes :recipe (:host github :repo "emacs-helm/helm-describe-modes")) +(package! helm-projectile) +(package! swiper-helm) +(when (featurep! +fuzzy) + (package! helm-flx)) +(when (featurep! +childframe) + (package! posframe)) +(when (featurep! :lang org) + (package! helm-org)) diff --git a/.emacs.d/modules/completion/ido/config.el b/.emacs.d/modules/completion/ido/config.el new file mode 100644 index 0000000..39e53d9 --- /dev/null +++ b/.emacs.d/modules/completion/ido/config.el @@ -0,0 +1,59 @@ +;;; completion/ido/config.el -*- lexical-binding: t; -*- + +(defun +ido-init-h () + (setq ido-ignore-buffers + '("\\` " "^\\*ESS\\*" "^\\*Messages\\*" "^\\*Help\\*" "^\\*Buffer" + "^\\*.*Completions\\*$" "^\\*Ediff" "^\\*tramp" "^\\*cvs-" + "_region_" " output\\*$" "^TAGS$" "^\*Ido") + ido-use-faces nil + ido-confirm-unique-completion t + ido-case-fold t + ido-enable-tramp-completion nil + ido-enable-flex-matching t + ido-create-new-buffer 'always + ido-enable-tramp-completion t + ido-enable-last-directory-history t + ido-save-directory-list-file (concat doom-cache-dir "ido.last")) + + (unless (member "\\`.DS_Store$" ido-ignore-files) + (push "\\`.DS_Store$" ido-ignore-files) + (push "Icon\\?$" ido-ignore-files)) + + (define-key! (ido-common-completion-map ido-completion-map ido-file-completion-map) + "\C-n" #'ido-next-match + "\C-p" #'ido-prev-match + "\C-w" #'ido-delete-backward-word-updir + ;; Go to $HOME with ~ + "~" (λ! (if (looking-back "/" (point-min)) + (insert "~/") + (call-interactively #'self-insert-command)))) + + (defadvice! +ido--sort-mtime-a () + "Sort ido filelist by mtime instead of alphabetically." + :override #'ido-sort-mtime + (setq ido-temp-list + (sort ido-temp-list + (lambda (a b) + (time-less-p + (sixth (file-attributes (concat ido-current-directory b))) + (sixth (file-attributes (concat ido-current-directory a))))))) + (ido-to-end ;; move . files to end (again) + (cl-loop for x in ido-temp-list + if (char-equal (string-to-char x) ?.) + collect x))) + (add-hook! '(ido-make-file-list-hook ido-make-dir-list-hook) + #'ido-sort-mtime) + + ;; + (ido-mode 1) + (ido-everywhere 1) + (ido-ubiquitous-mode 1) + (ido-vertical-mode 1) + (flx-ido-mode +1) + (crm-custom-mode +1) + + ;; + (remove-hook 'ido-setup-hook #'+ido-init-h)) + +;; +(add-hook 'ido-setup-hook #'+ido-init-h) diff --git a/.emacs.d/modules/completion/ido/packages.el b/.emacs.d/modules/completion/ido/packages.el new file mode 100644 index 0000000..3686654 --- /dev/null +++ b/.emacs.d/modules/completion/ido/packages.el @@ -0,0 +1,7 @@ +;; -*- no-byte-compile: t; -*- +;;; completion/ido/packages.el + +(package! flx-ido) +(package! ido-completing-read+) +(package! ido-vertical-mode) +(package! crm-custom) diff --git a/.emacs.d/modules/completion/ivy/README.org b/.emacs.d/modules/completion/ivy/README.org new file mode 100644 index 0000000..b21b92c --- /dev/null +++ b/.emacs.d/modules/completion/ivy/README.org @@ -0,0 +1,194 @@ +#+TITLE: completion/ivy +#+DATE: February 13, 2017 +#+SINCE: v2.0 +#+STARTUP: inlineimages + +* Table of Contents :TOC_3:noexport: +- [[#description][Description]] + - [[#module-flags][Module Flags]] + - [[#plugins][Plugins]] + - [[#hacks][Hacks]] +- [[#prerequisites][Prerequisites]] + - [[#install][Install]] + - [[#macos][MacOS]] + - [[#arch-linux][Arch Linux]] + - [[#opensuse][openSUSE]] +- [[#features][Features]] + - [[#jump-to-navigation][Jump-to navigation]] + - [[#project-search--replace][Project search & replace]] + - [[#in-buffer-searching][In-buffer searching]] + - [[#ivy-integration-for-various-completing-commands][Ivy integration for various completing commands]] + - [[#general][General]] + - [[#jump-to-files-buffers-or-projects][Jump to files, buffers or projects)]] + - [[#search][Search]] +- [[#configuration][Configuration]] + - [[#enable-fuzzynon-fuzzy-search-for-specific-commands][Enable fuzzy/non-fuzzy search for specific commands]] + - [[#change-the-position-of-the-ivy-childframe][Change the position of the ivy childframe]] +- [[#troubleshooting][Troubleshooting]] + +* Description +This module provides Ivy integration for a variety of Emacs commands, as well as +a unified interface for project search and replace, powered by ripgrep. + +#+begin_quote +I prefer ivy over ido for its flexibility. I prefer ivy over helm because it's +lighter, simpler and faster in many cases. +#+end_quote + +** Module Flags ++ =+fuzzy= Enables fuzzy completion for Ivy searches. ++ =+prescient= Enables prescient filtering and sorting for Ivy searches. ++ =+childframe= Causes Ivy to display in a floating child frame, above Emacs. ++ =+icons= Enables file icons for switch-{buffer,project}/find-file counsel + commands. + +** Plugins ++ [[https://github.com/abo-abo/swiper][ivy]] ++ [[https://github.com/abo-abo/swiper][counsel]] ++ [[https://github.com/ericdanan/counsel-projectile][counsel-projectile]] ++ [[https://github.com/abo-abo/swiper][swiper]] ++ [[https://github.com/abo-abo/swiper][ivy-hydra]] ++ [[https://github.com/yevgnen/ivy-rich][ivy-rich]] ++ [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]] ++ [[https://github.com/DarwinAwardWinner/amx][amx]] ++ [[https://github.com/lewang/flx][flx]]* (=+fuzzy=) ++ [[https://github.com/raxod502/prescient.el][prescient]]* (=+prescient=) ++ [[https://github.com/tumashu/ivy-posframe][ivy-posframe]]* (=+childframe=) ++ [[https://github.com/asok/all-the-icons-ivy][all-the-icons-ivy]]* (=+icons=) + +** Hacks ++ Functions with ivy/counsel equivalents have been globally remapped (like + ~find-file~ => ~counsel-find-file~). So a keybinding to ~find-file~ will + invoke ~counsel-find-file~ instead. ++ ~counsel-[arp]g~'s 3-character limit was reduced to 1 (mainly for the ex + command) + +* Prerequisites +This module depends on: + ++ [[https://github.com/BurntSushi/ripgrep][ripgrep]] (rg) + +** Install +*** MacOS +#+BEGIN_SRC sh +brew install ripgrep +#+END_SRC + +*** Arch Linux +#+BEGIN_SRC sh :dir /sudo:: +sudo pacman --needed --noconfirm -S ripgrep +#+END_SRC + +*** openSUSE +#+BEGIN_SRC sh :dir /sudo:: +sudo zypper install ripgrep +#+END_SRC + +* Features +Ivy and its ilk are large plugins. Covering everything about them is outside of +this documentation's scope, so only Doom-specific Ivy features are listed here: + +** Jump-to navigation +Inspired by Sublime Text's jump-to-anywhere, CtrlP/Unite in Vim, and Textmate's +Command-T, this module provides similar functionality by bringing ~projectile~ +and ~ivy~ together. + +https://assets.doomemacs.org/completion/ivy/projectile.png + +| Keybind | Description | +|----------------------+-------------------------------------| +| =SPC p f=, =SPC SPC= | Jump to file in project | +| =SPC f f=, =SPC .= | Jump to file from current directory | +| =SPC s i= | Jump to symbol in file | + +** Project search & replace +This module provides interactive text search and replace using ripgrep. + +| Keybind | Description | +|-----------+--------------------------| +| =SPC s p= | Search project | +| =SPC s P= | Search another project | +| =SPC s d= | Search this directory | +| =SPC s D= | Search another directory | + +https://assets.doomemacs.org/completion/ivy/search.png + +Prefixing these keys with the universal argument (=SPC u= for evil users; =C-u= +otherwise) changes the behavior of these commands, instructing the underlying +search engine to include ignored files. + +This module also provides Ex Commands for evil users: + +| Ex command | Description | +|------------------------+------------------------------------------------------------------| +| ~:pg[rep][!] [QUERY]~ | Search project (if ~!~, include hidden files) | +| ~:pg[rep]d[!] [QUERY]~ | Search from current directory (if ~!~, don't search recursively) | + +The optional `!` is equivalent to the universal argument for the previous +commands. + +----- + +These keybindings are available while a search is active: + +| Keybind | Description | +|-----------+-----------------------------------------------| +| =C-c C-o= | Open a buffer with your search results | +| =C-c C-e= | Open a writable buffer of your search results | +| =C-SPC= | Preview the current candidate | +| =M-RET= | Open the selected candidate in other-window | + +Changes to the resulting wgrep buffer (opened by =C-c C-e=) can be committed +with =C-c C-c= and aborted with =C-c C-k= (alternatively =ZZ= and =ZQ=, for evil +users). + +https://assets.doomemacs.org/completion/ivy/search-replace.png + +** In-buffer searching +The =swiper= package provides an interactive buffer search powered by ivy. It +can be invoked with: + ++ =SPC s s= (~swiper-isearch~) ++ =SPC s S= (~swiper-isearch-thing-at-point~) ++ =SPC s b= (~swiper~) ++ ~:sw[iper] [QUERY]~ + +https://assets.doomemacs.org/completion/ivy/swiper.png + +A wgrep buffer can be opened from swiper with =C-c C-e=. + +** Ivy integration for various completing commands +*** General +| Keybind | Description | +|----------------+---------------------------| +| =M-x=, =SPC := | Smarter, smex-powered M-x | +| =SPC '= | Resume last ivy session | + +*** Jump to files, buffers or projects) +| Keybind | Description | +|----------------------+---------------------------------------| +| =SPC RET= | Find bookmark | +| =SPC f f=, =SPC .= | Browse from current directory | +| =SPC p f=, =SPC SPC= | Find file in project | +| =SPC f r= | Find recently opened file | +| =SPC p p= | Open another project | +| =SPC b b=, =SPC ,= | Switch to buffer in current workspace | +| =SPC b B=, =SPC <= | Switch to buffer | + +*** Search +| Keybind | Description | +|-----------+-------------------------------------------| +| =SPC p t= | List all TODO/FIXMEs in project | +| =SPC s b= | Search the current buffer | +| =SPC s d= | Search this directory | +| =SPC s D= | Search another directory | +| =SPC s i= | Search for symbol in current buffer | +| =SPC s p= | Search project | +| =SPC s P= | Search another project | +| =SPC s s= | Search the current buffer (incrementally) | + +* Configuration +** TODO Enable fuzzy/non-fuzzy search for specific commands +** TODO Change the position of the ivy childframe + +* TODO Troubleshooting diff --git a/.emacs.d/modules/completion/ivy/autoload/evil.el b/.emacs.d/modules/completion/ivy/autoload/evil.el new file mode 100644 index 0000000..8a81255 --- /dev/null +++ b/.emacs.d/modules/completion/ivy/autoload/evil.el @@ -0,0 +1,14 @@ +;; completion/ivy/autoload/evil.el -*- lexical-binding: t; -*- +;;;###if (featurep! :editor evil) + +;;;###autoload (autoload '+ivy:project-search "completion/ivy/autoload/evil" nil t) +(evil-define-command +ivy:project-search (query &optional all-files-p) + "Ex interface for `+ivy/project-search'." + (interactive "") + (+ivy/project-search all-files-p query)) + +;;;###autoload (autoload '+ivy:project-search-from-cwd "completion/ivy/autoload/evil" nil t) +(evil-define-command +ivy:project-search-from-cwd (query &optional recurse-p) + "Ex interface for `+ivy/project-search-from-cwd'." + (interactive "") + (+ivy/project-search-from-cwd (not recurse-p) query)) diff --git a/.emacs.d/modules/completion/ivy/autoload/hydras.el b/.emacs.d/modules/completion/ivy/autoload/hydras.el new file mode 100644 index 0000000..ee71f51 --- /dev/null +++ b/.emacs.d/modules/completion/ivy/autoload/hydras.el @@ -0,0 +1,33 @@ +;;; completion/ivy/autoload/hydras.el -*- lexical-binding: t; -*- +;;;###if (featurep! :ui hydra) + +(eval-when-compile (require 'ivy-hydra)) + +;;;###autoload (autoload 'hydra-ivy/body "completion/ivy/autoload/hydras" nil nil) +(defhydra+ hydra-ivy (:hint nil :color pink) + " + Move ^^^^^^^^^^ | Call ^^^^ | Cancel^^ | Options^^ | Action _w_/_s_/_a_: %s(ivy-action-name) +----------^^^^^^^^^^-+--------------^^^^-+-------^^-+--------^^-+--------------------------------- + _g_ ^ ^ _k_ ^ ^ _u_ | _f_orward _o_ccur | _i_nsert | _c_alling: %-7s(if ivy-calling \"on\" \"off\") _C_ase-fold: %-10`ivy-case-fold-search + ^↨^ _h_ ^+^ _l_ ^↕^ | _RET_ done ^^ | _q_uit | _m_atcher: %-7s(ivy--matcher-desc) _t_runcate: %-11`truncate-lines + _G_ ^ ^ _j_ ^ ^ _d_ | _TAB_ alt-done ^^ | ^ ^ | _<_/_>_: shrink/grow +" + ;; arrows + ("l" ivy-alt-done) + ("h" ivy-backward-delete-char) + ("g" ivy-beginning-of-buffer) + ("G" ivy-end-of-buffer) + ("d" ivy-scroll-up-command) + ("u" ivy-scroll-down-command) + ("e" ivy-scroll-down-command) + ;; actions + ("q" keyboard-escape-quit :exit t) + ("" keyboard-escape-quit :exit t) + ("TAB" ivy-alt-done :exit nil) + ("RET" ivy-done :exit t) + ("C-SPC" ivy-call-and-recenter :exit nil) + ("f" ivy-call) + ("c" ivy-toggle-calling) + ("m" ivy-toggle-fuzzy) + ("t" (setq truncate-lines (not truncate-lines))) + ("o" ivy-occur :exit t)) diff --git a/.emacs.d/modules/completion/ivy/autoload/ivy.el b/.emacs.d/modules/completion/ivy/autoload/ivy.el new file mode 100644 index 0000000..5a55cd5 --- /dev/null +++ b/.emacs.d/modules/completion/ivy/autoload/ivy.el @@ -0,0 +1,366 @@ +;;; completion/ivy/autoload/ivy.el -*- lexical-binding: t; -*- + +(defun +ivy--is-workspace-buffer-p (buffer) + (let ((buffer (car buffer))) + (when (stringp buffer) + (setq buffer (get-buffer buffer))) + (+workspace-contains-buffer-p buffer))) + +(defun +ivy--is-workspace-other-buffer-p (buffer) + (let ((buffer (car buffer))) + (when (stringp buffer) + (setq buffer (get-buffer buffer))) + (and (not (eq buffer (current-buffer))) + (+workspace-contains-buffer-p buffer)))) + +;;;###autoload +(defun +ivy-standard-search (str) + "TODO" + (funcall +ivy-standard-search-fn str)) + +;;;###autoload +(defun +ivy-alternative-search (str) + "TODO" + (funcall +ivy-alternative-search-fn str)) + +;;;###autoload +(defun +ivy-rich-buffer-name (candidate) + "Display the buffer name. + +Buffers that are considered unreal (see `doom-real-buffer-p') are dimmed with +`+ivy-buffer-unreal-face'." + (let ((b (get-buffer candidate))) + (when (null uniquify-buffer-name-style) + (setq candidate (replace-regexp-in-string "<[0-9]+>$" "" candidate))) + (cond ((ignore-errors + (file-remote-p + (buffer-local-value 'default-directory b))) + (ivy-append-face candidate 'ivy-remote)) + ((doom-unreal-buffer-p b) + (ivy-append-face candidate +ivy-buffer-unreal-face)) + ((not (buffer-file-name b)) + (ivy-append-face candidate 'ivy-subdir)) + ((buffer-modified-p b) + (ivy-append-face candidate 'ivy-modified-buffer)) + (candidate)))) + +;;;###autoload +(defun +ivy-rich-buffer-icon (candidate) + "Display the icon for CANDIDATE buffer." + ;; NOTE This is inspired by `all-the-icons-ivy-buffer-transformer', minus the + ;; buffer name and extra padding as those are handled by `ivy-rich'. + (propertize "\t" 'display + (if-let* ((buffer (get-buffer candidate)) + (mode (buffer-local-value 'major-mode buffer))) + (or + (all-the-icons-ivy--icon-for-mode mode) + (all-the-icons-ivy--icon-for-mode (get mode 'derived-mode-parent)) + (funcall + all-the-icons-ivy-family-fallback-for-buffer + all-the-icons-ivy-name-fallback-for-buffer)) + (all-the-icons-icon-for-file candidate)))) + +;;;###autoload +(defun +ivy-rich-describe-variable-transformer (cand) + "Previews the value of the variable in the minibuffer" + (let* ((sym (intern cand)) + (val (and (boundp sym) (symbol-value sym))) + (print-level 3)) + (replace-regexp-in-string + "[\n\t\^[\^M\^@\^G]" " " + (cond ((booleanp val) + (propertize (format "%s" val) 'face + (if (null val) + 'font-lock-comment-face + 'success))) + ((symbolp val) + (propertize (format "'%s" val) + 'face 'highlight-quoted-symbol)) + ((keymapp val) + (propertize "" 'face 'font-lock-constant-face)) + ((listp val) + (prin1-to-string val)) + ((stringp val) + (propertize (format "%S" val) 'face 'font-lock-string-face)) + ((numberp val) + (propertize (format "%s" val) 'face 'highlight-numbers-number)) + ((format "%s" val))) + t))) + + +;; +;; Library + +(defun +ivy--switch-buffer-preview () + (let (ivy-use-virtual-buffers ivy--virtual-buffers) + (counsel--switch-buffer-update-fn))) + +(defalias '+ivy--switch-buffer-preview-all #'counsel--switch-buffer-update-fn) +(defalias '+ivy--switch-buffer-unwind #'counsel--switch-buffer-unwind) + +(defun +ivy--switch-buffer (workspace other) + (let ((current (not other)) + prompt action filter update unwind) + (cond ((and workspace current) + (setq prompt "Switch to workspace buffer: " + action #'ivy--switch-buffer-action + filter #'+ivy--is-workspace-other-buffer-p)) + (workspace + (setq prompt "Switch to workspace buffer in other window: " + action #'ivy--switch-buffer-other-window-action + filter #'+ivy--is-workspace-buffer-p)) + (current + (setq prompt "Switch to buffer: " + action #'ivy--switch-buffer-action)) + ((setq prompt "Switch to buffer in other window: " + action #'ivy--switch-buffer-other-window-action))) + (when +ivy-buffer-preview + (cond ((not (and ivy-use-virtual-buffers + (eq +ivy-buffer-preview 'everything))) + (setq update #'+ivy--switch-buffer-preview + unwind #'+ivy--switch-buffer-unwind)) + ((setq update #'+ivy--switch-buffer-preview-all + unwind #'+ivy--switch-buffer-unwind)))) + (ivy-read prompt 'internal-complete-buffer + :action action + :predicate filter + :update-fn update + :unwind unwind + :preselect (buffer-name (other-buffer (current-buffer))) + :matcher #'ivy--switch-buffer-matcher + :keymap ivy-switch-buffer-map + ;; NOTE A clever disguise, needed for virtual buffers. + :caller #'ivy-switch-buffer))) + +;;;###autoload +(defun +ivy/switch-workspace-buffer (&optional arg) + "Switch to another buffer within the current workspace. + +If ARG (universal argument), open selection in other-window." + (interactive "P") + (+ivy--switch-buffer t arg)) + +;;;###autoload +(defun +ivy/switch-workspace-buffer-other-window () + "Switch another window to a buffer within the current workspace." + (interactive) + (+ivy--switch-buffer t t)) + +;;;###autoload +(defun +ivy/switch-buffer () + "Switch to another buffer." + (interactive) + (+ivy--switch-buffer nil nil)) + +;;;###autoload +(defun +ivy/switch-buffer-other-window () + "Switch to another buffer in another window." + (interactive) + (+ivy--switch-buffer nil t)) + +;;;###autoload +(defun +ivy/woccur () + "Invoke a wgrep buffer on the current ivy results, if supported." + (interactive) + (unless (window-minibuffer-p) + (user-error "No completion session is active")) + (require 'wgrep) + (let ((caller (ivy-state-caller ivy-last))) + (if-let (occur-fn (plist-get +ivy-edit-functions caller)) + (ivy-exit-with-action + (lambda (_) (funcall occur-fn))) + (if-let (occur-fn (plist-get ivy--occurs-list caller)) + (let ((buffer (generate-new-buffer + (format "*ivy-occur%s \"%s\"*" + (if caller (concat " " (prin1-to-string caller)) "") + ivy-text)))) + (with-current-buffer buffer + (let ((inhibit-read-only t)) + (erase-buffer) + (funcall occur-fn)) + (setf (ivy-state-text ivy-last) ivy-text) + (setq ivy-occur-last ivy-last) + (setq-local ivy--directory ivy--directory)) + (ivy-exit-with-action + `(lambda (_) + (pop-to-buffer ,buffer) + (ivy-wgrep-change-to-wgrep-mode)))) + (user-error "%S doesn't support wgrep" caller))))) + +;;;###autoload +(defun +ivy-yas-prompt (prompt choices &optional display-fn) + (yas-completing-prompt prompt choices display-fn #'ivy-completing-read)) + +;;;###autoload +(defun +ivy-git-grep-other-window-action (x) + "Opens the current candidate in another window." + (when (string-match "\\`\\(.*?\\):\\([0-9]+\\):\\(.*\\)\\'" x) + (select-window + (with-ivy-window + (let ((file-name (match-string-no-properties 1 x)) + (line-number (match-string-no-properties 2 x))) + (find-file-other-window (expand-file-name file-name (ivy-state-directory ivy-last))) + (goto-char (point-min)) + (forward-line (1- (string-to-number line-number))) + (re-search-forward (ivy--regex ivy-text t) (line-end-position) t) + (run-hooks 'counsel-grep-post-action-hook) + (selected-window)))))) + +;;;###autoload +(defun +ivy-confirm-delete-file (x) + (dired-delete-file x 'confirm-each-subdirectory)) + + +;; +;;; File searching + +;;;###autoload +(defun +ivy/projectile-find-file () + "A more sensible `counsel-projectile-find-file', which will revert to +`counsel-find-file' if invoked from $HOME, `counsel-file-jump' if invoked from a +non-project, `projectile-find-file' if in a big project (more than +`ivy-sort-max-size' files), or `counsel-projectile-find-file' otherwise. + +The point of this is to avoid Emacs locking up indexing massive file trees." + (interactive) + (call-interactively + (cond ((or (file-equal-p default-directory "~") + (when-let (proot (doom-project-root)) + (file-equal-p proot "~"))) + #'counsel-find-file) + + ((doom-project-p) + (let ((files (projectile-current-project-files))) + (if (<= (length files) ivy-sort-max-size) + #'counsel-projectile-find-file + #'projectile-find-file))) + + (#'counsel-file-jump)))) + +;;;###autoload +(cl-defun +ivy-file-search (&key query in all-files (recursive t)) + "Conduct a file search using ripgrep. + +:query STRING + Determines the initial input to search for. +:in PATH + Sets what directory to base the search out of. Defaults to the current + project's root. +:recursive BOOL + Whether or not to search files recursively from the base directory." + (declare (indent defun)) + (unless (executable-find "rg") + (user-error "Couldn't find ripgrep in your PATH")) + (require 'counsel) + (let* ((ivy-more-chars-alist '((t . 1))) + (project-root (or (doom-project-root) default-directory)) + (directory (or in project-root)) + (default-directory directory) + (args (concat (if all-files " -uu") + (unless recursive " --maxdepth 1")))) + (counsel-rg + (or (if query query) + (when (use-region-p) + (let ((beg (or (bound-and-true-p evil-visual-beginning) (region-beginning))) + (end (or (bound-and-true-p evil-visual-end) (region-end)))) + (when (> (abs (- end beg)) 1) + (let ((query (buffer-substring-no-properties beg end))) + ;; Escape characters that are special to ivy searches + (replace-regexp-in-string "[! |]" (lambda (substr) + (cond ((and (string= substr " ") + (not (featurep! +fuzzy))) + " ") + ((string= substr "|") + "\\\\\\\\|") + ((concat "\\\\" substr)))) + (rxt-quote-pcre query))))))) + directory args + (format "rg%s %s" + args + (cond ((equal directory default-directory) + "./") + ((equal directory project-root) + (projectile-project-name)) + ((file-relative-name directory project-root))))))) + +;;;###autoload +(defun +ivy/project-search (&optional arg initial-query directory) + "Performs a live project search from the project root using ripgrep. + +If ARG (universal argument), include all files, even hidden or compressed ones, +in the search." + (interactive "P") + (+ivy-file-search :query initial-query :in directory :all-files arg)) + +;;;###autoload +(defun +ivy/project-search-from-cwd (&optional arg initial-query) + "Performs a project search recursively from the current directory. + +If ARG (universal argument), include all files, even hidden or compressed ones." + (interactive "P") + (+ivy/project-search arg initial-query default-directory)) + + +;; +;;; Wrappers around `counsel-compile' + +;;;###autoload +(defun +ivy/compile () + "Execute a compile command from the current buffer's directory." + (interactive) + (counsel-compile default-directory)) + +;;;###autoload +(defun +ivy/project-compile () + "Execute a compile command from the current project's root." + (interactive) + (counsel-compile (projectile-project-root))) + +;;;###autoload +(defun +ivy/jump-list () + "Go to an entry in evil's (or better-jumper's) jumplist." + (interactive) + ;; REVIEW Refactor me + (let (buffers) + (unwind-protect + (ivy-read "jumplist: " + (nreverse + (delete-dups + (delq + nil + (mapcar (lambda (mark) + (when mark + (cl-destructuring-bind (path pt _id) mark + (let ((buf (get-file-buffer path))) + (unless buf + (push (setq buf (find-file-noselect path t)) + buffers)) + (with-current-buffer buf + (goto-char pt) + (font-lock-fontify-region (line-beginning-position) (line-end-position)) + (cons (format "%s:%d: %s" + (buffer-name) + (line-number-at-pos) + (string-trim-right (or (thing-at-point 'line) ""))) + (point-marker))))))) + (cddr (better-jumper-jump-list-struct-ring + (better-jumper-get-jumps (better-jumper--get-current-context)))))))) + :sort nil + :require-match t + :action (lambda (cand) + (let ((mark (cdr cand))) + (delq! (marker-buffer mark) buffers) + (mapc #'kill-buffer buffers) + (setq buffers nil) + (with-current-buffer (switch-to-buffer (marker-buffer mark)) + (goto-char (marker-position mark))))) + :caller '+ivy/jump-list) + (mapc #'kill-buffer buffers)))) + +;;;###autoload +(defun +ivy/git-grep-other-window-action () + "Open the current counsel-{ag,rg,git-grep} candidate in other-window." + (interactive) + (ivy-set-action #'+ivy-git-grep-other-window-action) + (setq ivy-exit 'done) + (exit-minibuffer)) diff --git a/.emacs.d/modules/completion/ivy/autoload/posframe.el b/.emacs.d/modules/completion/ivy/autoload/posframe.el new file mode 100644 index 0000000..eb080af --- /dev/null +++ b/.emacs.d/modules/completion/ivy/autoload/posframe.el @@ -0,0 +1,16 @@ +;;; completion/ivy/autoload/posframe.el -*- lexical-binding: t; -*- +;;;###if (featurep! +childframe) + +;;;###autoload +(defun +ivy-display-at-frame-center-near-bottom-fn (str) + "TODO" + (ivy-posframe--display str #'+ivy-poshandler-frame-center-near-bottom-fn)) + +;;;###autoload +(defun +ivy-poshandler-frame-center-near-bottom-fn (info) + "TODO" + (let ((parent-frame (plist-get info :parent-frame)) + (pos (posframe-poshandler-frame-center info))) + (cons (car pos) + (truncate (/ (frame-pixel-height parent-frame) 2))))) + diff --git a/.emacs.d/modules/completion/ivy/config.el b/.emacs.d/modules/completion/ivy/config.el new file mode 100644 index 0000000..5c9e856 --- /dev/null +++ b/.emacs.d/modules/completion/ivy/config.el @@ -0,0 +1,350 @@ +;;; completion/ivy/config.el -*- lexical-binding: t; -*- + +(defvar +ivy-buffer-preview nil + "If non-nil, preview buffers while switching, à la `counsel-switch-buffer'. + +When nil, don't preview anything. +When non-nil, preview non-virtual buffers. +When 'everything, also preview virtual buffers") + +(defvar +ivy-buffer-unreal-face 'font-lock-comment-face + "The face for unreal buffers in `ivy-switch-to-buffer'.") + +(defvar +ivy-edit-functions nil + "A plist mapping ivy/counsel commands to commands that generate an editable +results buffer.") + +(defvar +ivy-standard-search-fn + (if (featurep! +prescient) + #'+ivy-prescient-non-fuzzy + #'ivy--regex-plus) + "Function to use for non-fuzzy search commands. +This uses the standard search algorithm ivy uses (or a variant of it).") + +(defvar +ivy-alternative-search-fn + (cond ((featurep! +prescient) #'ivy-prescient-re-builder) + ((featurep! +fuzzy) #'ivy--regex-fuzzy) + ;; Ignore order for non-fuzzy searches by default + (#'ivy--regex-ignore-order)) + "Function to use for fuzzy search commands. +This uses a search algorithm other than ivy's default.") + + +;; +;;; Packages + +(use-package! ivy + :after-call pre-command-hook + :init + (setq ivy-re-builders-alist + `((counsel-rg . +ivy-standard-search) + (swiper . +ivy-standard-search) + (swiper-isearch . +ivy-standard-search) + (t . +ivy-alternative-search))) + + (define-key! + [remap switch-to-buffer] #'+ivy/switch-buffer + [remap switch-to-buffer-other-window] #'+ivy/switch-buffer-other-window + [remap persp-switch-to-buffer] #'+ivy/switch-workspace-buffer + [remap evil-show-jumps] #'+ivy/jump-list) + :config + ;; Counsel changes a lot of ivy's state at startup; to control for that, we + ;; need to load it as early as possible. Some packages (like `ivy-prescient') + ;; require this. + (require 'counsel nil t) + + (setq ivy-height 17 + ivy-wrap t + ivy-fixed-height-minibuffer t + projectile-completion-system 'ivy + ;; disable magic slash on non-match + ivy-magic-slash-non-match-action nil + ;; don't show recent files in switch-buffer + ivy-use-virtual-buffers nil + ;; ...but if that ever changes, show their full path + ivy-virtual-abbreviate 'full + ;; don't quit minibuffer on delete-error + ivy-on-del-error-function #'ignore + ;; enable ability to select prompt (alternative to `ivy-immediate-done') + ivy-use-selectable-prompt t) + + ;; Highlight each ivy candidate including the following newline, so that it + ;; extends to the right edge of the window + (setf (alist-get 't ivy-format-functions-alist) + #'ivy-format-function-line) + + ;; Integrate `ivy' with `better-jumper'; ensure a jump point is registered + ;; before jumping to new locations with ivy + (setf (alist-get 't ivy-hooks-alist) + (lambda () + (with-ivy-window + (setq +ivy--origin (point-marker))))) + + (add-hook! 'minibuffer-exit-hook + (defun +ivy--set-jump-point-maybe-h () + (and (markerp (bound-and-true-p +ivy--origin)) + (not (equal (ignore-errors (with-ivy-window (point-marker))) + +ivy--origin)) + (with-current-buffer (marker-buffer +ivy--origin) + (better-jumper-set-jump +ivy--origin))) + (setq +ivy--origin nil))) + + (after! yasnippet + (add-hook 'yas-prompt-functions #'+ivy-yas-prompt)) + + (defadvice! +ivy--inhibit-completion-in-region-a (orig-fn &rest args) + "`ivy-completion-in-region' struggles with completing certain +evil-ex-specific constructs, so we disable it solely in evil-ex." + :around #'evil-ex + (let ((completion-in-region-function #'completion--in-region)) + (apply orig-fn args))) + + (define-key ivy-minibuffer-map (kbd "C-c C-e") #'+ivy/woccur) + + (ivy-mode +1) + + (use-package! ivy-hydra + :commands (ivy-dispatching-done ivy--matcher-desc ivy-hydra/body) + :init + (define-key! ivy-minibuffer-map + "C-o" #'ivy-dispatching-done + "M-o" #'hydra-ivy/body) + :config + ;; ivy-hydra rebinds this, so we have to do so again + (define-key ivy-minibuffer-map (kbd "M-o") #'hydra-ivy/body))) + + +(use-package! ivy-rich + :after ivy + :config + (setq ivy-rich-parse-remote-buffer nil) + + (when (featurep! +icons) + (cl-pushnew '(+ivy-rich-buffer-icon) + (cadr (plist-get ivy-rich-display-transformers-list + 'ivy-switch-buffer)))) + + ;; Include variable value in `counsel-describe-variable' + (plist-put! ivy-rich-display-transformers-list + 'counsel-describe-variable + '(:columns + ((counsel-describe-variable-transformer (:width 40)) ; the original transformer + (+ivy-rich-describe-variable-transformer (:width 50)) + (ivy-rich-counsel-variable-docstring (:face font-lock-doc-face))))) + + ;; Remove built-in coloring of buffer list; we do our own + (setq ivy-switch-buffer-faces-alist nil) + (ivy-set-display-transformer 'internal-complete-buffer nil) + + ;; Highlight buffers differently based on whether they're in the same project + ;; as the current project or not. + (let* ((plist (plist-get ivy-rich-display-transformers-list 'ivy-switch-buffer)) + (switch-buffer-alist (assq 'ivy-rich-candidate (plist-get plist :columns)))) + (when switch-buffer-alist + (setcar switch-buffer-alist '+ivy-rich-buffer-name))) + + ;; Apply switch buffer transformers to `counsel-projectile-switch-to-buffer' as well + (plist-put! ivy-rich-display-transformers-list + 'counsel-projectile-switch-to-buffer + (plist-get ivy-rich-display-transformers-list 'ivy-switch-buffer)) + + (ivy-rich-mode +1)) + + +(use-package! all-the-icons-ivy + :when (featurep! +icons) + :after ivy + :config + ;; `all-the-icons-ivy' is incompatible with ivy-rich's switch-buffer + ;; modifications, so we disable them and merge them ourselves + (setq all-the-icons-ivy-buffer-commands nil) + + (all-the-icons-ivy-setup) + (after! counsel-projectile + (let ((all-the-icons-ivy-file-commands '(counsel-projectile + counsel-projectile-find-file + counsel-projectile-find-dir))) + (all-the-icons-ivy-setup)))) + + +(use-package! counsel + :defer t + :init + (define-key! + [remap apropos] #'counsel-apropos + [remap bookmark-jump] #'counsel-bookmark + [remap describe-face] #'counsel-faces + [remap describe-function] #'counsel-describe-function + [remap describe-variable] #'counsel-describe-variable + [remap describe-bindings] #'counsel-descbinds + [remap set-variable] #'counsel-set-variable + [remap execute-extended-command] #'counsel-M-x + [remap find-file] #'counsel-find-file + [remap find-library] #'counsel-find-library + [remap info-lookup-symbol] #'counsel-info-lookup-symbol + [remap imenu] #'counsel-imenu + [remap recentf-open-files] #'counsel-recentf + [remap swiper] #'counsel-grep-or-swiper + [remap evil-ex-registers] #'counsel-evil-registers + [remap evil-show-marks] #'counsel-mark-ring + [remap yank-pop] #'counsel-yank-pop + [remap load-theme] #'counsel-load-theme + [remap locate] #'counsel-locate + [remap unicode-chars-list-chars] #'counsel-unicode-char + [remap compile] #'+ivy/compile + [remap projectile-compile-project] #'+ivy/project-compile) + :config + (set-popup-rule! "^\\*ivy-occur" :size 0.35 :ttl 0 :quit nil) + + ;; Don't use ^ as initial input. Set this here because `counsel' defines more + ;; of its own, on top of the defaults. + (setq ivy-initial-inputs-alist nil) + + ;; REVIEW Move this somewhere else and perhaps generalize this so both + ;; ivy/helm users can enjoy it. + (defadvice! +ivy--counsel-file-jump-use-fd-rg-a (args) + "Change `counsel-file-jump' to use fd or ripgrep, if they are available." + :override #'counsel--find-return-list + (cl-destructuring-bind (find-program . args) + (cond ((executable-find "fd") + (cons "fd" (list "-t" "f" "-E" ".git"))) + ((executable-find "rg") + (cons "rg" (list "--files" "--hidden" "--no-messages"))) + ((cons find-program args))) + (unless (listp args) + (user-error "`counsel-file-jump-args' is a list now, please customize accordingly.")) + (counsel--call + (cons find-program args) + (lambda () + (goto-char (point-min)) + (let ((offset (if (member find-program '("fd" "rg")) 0 2)) + files) + (while (< (point) (point-max)) + (push (buffer-substring + (+ offset (line-beginning-position)) (line-end-position)) files) + (forward-line 1)) + (nreverse files)))))) + + ;; Integrate with `helpful' + (setq counsel-describe-function-function #'helpful-callable + counsel-describe-variable-function #'helpful-variable) + + ;; Make `counsel-compile' projectile-aware (if you prefer it over + ;; `+ivy/compile' and `+ivy/project-compile') + (add-to-list 'counsel-compile-root-functions #'projectile-project-root) + (after! savehist + ;; Persist `counsel-compile' history + (add-to-list 'savehist-additional-variables 'counsel-compile-history)) + + ;; Use spotlight on mac for `counsel-locate' by default, since it doesn't need + ;; any additional setup. + (when IS-MAC + (setq counsel-locate-cmd #'counsel-locate-cmd-mdfind)) + + ;; Don't mess with font-locking on the dashboard; it causes breakages + (add-to-list 'swiper-font-lock-exclude #'+doom-dashboard-mode) + + ;; Record in jumplist when opening files via counsel-{ag,rg,pt,git-grep} + (add-hook 'counsel-grep-post-action-hook #'better-jumper-set-jump) + + ;; Configure `counsel-find-file' + (setq counsel-find-file-ignore-regexp "\\(?:^[#.]\\)\\|\\(?:[#~]$\\)\\|\\(?:^Icon?\\)") + (ivy-add-actions + 'counsel-find-file + '(("p" (lambda (path) (with-ivy-window (insert (file-relative-name path default-directory)))) + "insert relative path") + ("P" (lambda (path) (with-ivy-window (insert path))) + "insert absolute path") + ("l" (lambda (path) (with-ivy-window (insert (format "[[./%s]]" (file-relative-name path default-directory))))) + "insert relative org-link") + ("L" (lambda (path) (with-ivy-window (insert (format "[[%s]]" path)))) + "Insert absolute org-link"))) + + (ivy-add-actions + 'counsel-ag ; also applies to `counsel-rg' + '(("O" +ivy-git-grep-other-window-action "open in other window")))) + + +(use-package! counsel-projectile + :defer t + :init + (define-key! + [remap projectile-find-file] #'+ivy/projectile-find-file + [remap projectile-find-dir] #'counsel-projectile-find-dir + [remap projectile-switch-to-buffer] #'counsel-projectile-switch-to-buffer + [remap projectile-grep] #'counsel-projectile-grep + [remap projectile-ag] #'counsel-projectile-ag + [remap projectile-switch-project] #'counsel-projectile-switch-project) + :config + ;; A more sensible `counsel-projectile-find-file' that reverts to + ;; `counsel-find-file' if invoked from $HOME, `counsel-file-jump' if invoked + ;; from a non-project, `projectile-find-file' if in a big project (more than + ;; `ivy-sort-max-size' files), or `counsel-projectile-find-file' otherwise. + (setf (alist-get 'projectile-find-file counsel-projectile-key-bindings) + #'+ivy/projectile-find-file) + + ;; no highlighting visited files; slows down the filtering + (ivy-set-display-transformer #'counsel-projectile-find-file nil)) + + +(use-package! wgrep + :commands wgrep-change-to-wgrep-mode + :config (setq wgrep-auto-save-buffer t)) + + +(use-package! ivy-posframe + :when (featurep! +childframe) + :hook (ivy-mode . ivy-posframe-mode) + :config + (setq ivy-fixed-height-minibuffer nil + ivy-posframe-border-width 10 + ivy-posframe-parameters + `((min-width . 90) + (min-height . ,ivy-height))) + + ;; default to posframe display function + (setf (alist-get t ivy-posframe-display-functions-alist) + #'+ivy-display-at-frame-center-near-bottom-fn) + + ;; posframe doesn't work well with async sources (the posframe will + ;; occasionally stop responding/redrawing), and causes violent resizing of the + ;; posframe. + (dolist (fn '(swiper counsel-rg counsel-grep counsel-git-grep)) + (setf (alist-get fn ivy-posframe-display-functions-alist) + #'ivy-display-function-fallback))) + + +(use-package! flx + :when (featurep! +fuzzy) + :unless (featurep! +prescient) + :defer t ; is loaded by ivy + :init (setq ivy-flx-limit 10000)) + + +(use-package! ivy-prescient + :hook (ivy-mode . ivy-prescient-mode) + :when (featurep! +prescient) + :init + (setq prescient-filter-method + (if (featurep! +fuzzy) + '(literal regexp initialism fuzzy) + '(literal regexp initialism)) + ivy-prescient-enable-filtering nil ; we do this ourselves + ivy-prescient-retain-classic-highlighting t) + + :config + (defun +ivy-prescient-non-fuzzy (str) + (let ((prescient-filter-method '(literal regexp))) + (ivy-prescient-re-builder str))) + + ;; NOTE prescient config duplicated with `company' + (setq prescient-save-file (concat doom-cache-dir "prescient-save.el")) + (prescient-persist-mode +1)) + + +;;;###package swiper +(setq swiper-action-recenter t) + + +;;;###package amx +(setq amx-save-file (concat doom-cache-dir "amx-items")) ; used by `counsel-M-x' diff --git a/.emacs.d/modules/completion/ivy/doctor.el b/.emacs.d/modules/completion/ivy/doctor.el new file mode 100644 index 0000000..a79d37c --- /dev/null +++ b/.emacs.d/modules/completion/ivy/doctor.el @@ -0,0 +1,3 @@ +;; -*- lexical-binding: t; no-byte-compile: t; -*- +;;; completion/ivy/doctor.el + diff --git a/.emacs.d/modules/completion/ivy/packages.el b/.emacs.d/modules/completion/ivy/packages.el new file mode 100644 index 0000000..5571641 --- /dev/null +++ b/.emacs.d/modules/completion/ivy/packages.el @@ -0,0 +1,22 @@ +;; -*- no-byte-compile: t; -*- +;;; completion/ivy/packages.el + +(package! amx) +(package! ivy) +(package! counsel) +(package! counsel-projectile) +(package! swiper) +(package! ivy-hydra) +(package! ivy-rich) +(package! wgrep) + +(if (featurep! +prescient) + (package! ivy-prescient) + (when (featurep! +fuzzy) + (package! flx))) + +(when (featurep! +childframe) + (package! ivy-posframe)) + +(when (featurep! +icons) + (package! all-the-icons-ivy)) diff --git a/.emacs.d/modules/config/default/+emacs-bindings.el b/.emacs.d/modules/config/default/+emacs-bindings.el new file mode 100644 index 0000000..ee5bde2 --- /dev/null +++ b/.emacs.d/modules/config/default/+emacs-bindings.el @@ -0,0 +1,412 @@ +;;; config/default/+emacs-bindings.el -*- lexical-binding: t; -*- + +;; Sensible deafult key bindings for non-evil users +(setq doom-leader-alt-key "C-c" + doom-localleader-alt-key "C-c l") + +;; persp-mode and projectile in different prefixes +(setq persp-keymap-prefix (kbd "C-c w")) +(after! projectile + (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)) + + +;; +;;; Autoloads + +(autoload 'org-capture-goto-target "org-capture" nil t) + + +;; +;;; Leader keys + +(map! :leader + :desc "Evaluate line/region" "e" #'+eval/line-or-region + + (:prefix ("l" . "")) ; bound locally + (:prefix ("!" . "checkers")) ; bound by flycheck + + ;;; f --- file + (:prefix-map ("f" . "file") + :desc "Open project editorconfig" "." #'editorconfig-find-current-editorconfig + :desc "Find other file" "a" #'projectile-find-other-file + :desc "Browse private config" "c" #'doom/open-private-config + :desc "Find file in private config" "C" #'doom/find-file-in-private-config + :desc "Find directory" "d" #'dired + :desc "Find file in emacs.d" "e" #'+default/find-in-emacsd + :desc "Browse emacs.d" "E" #'+default/browse-emacsd + :desc "Find file from here" "f" (if (fboundp 'counsel-file-jump) #'counsel-file-jump #'find-file) + :desc "Find file in other project" "F" #'doom/browse-in-other-project + :desc "Delete this file" "K" #'doom/delete-this-file + :desc "Move this file" "m" #'doom/move-this-file + :desc "Find file in project" "p" #'projectile-find-file + :desc "Find file in other project" "P" #'doom/find-file-in-other-project + :desc "Recent files" "r" #'recentf-open-files + :desc "Recent project files" "R" #'projectile-recentf + :desc "Sudo this file" "s" #'doom/sudo-this-file + :desc "Sudo find file" "S" #'doom/sudo-find-file + :desc "Yank filename" "y" #'+default/yank-buffer-filename + :desc "Open scratch buffer" "x" #'doom/open-scratch-buffer + :desc "Open project scratch buffer" "X" #'doom/switch-to-scratch-buffer) + + ;;; g --- lookup + (:when (featurep! :tools lookup) + (:prefix-map ("g" . "lookup") + "k" #'+lookup/documentation + "d" #'+lookup/definition + "D" #'+lookup/references + "f" #'+lookup/file + "o" #'+lookup/online-select + "i" #'+lookup/in-docsets + "I" #'+lookup/in-all-docsets)) + + ;;; o --- org + "o" nil ; we need to unbind it first as Org claims this + (:prefix-map ("o". "org") + :desc "Do what I mean" "o" #'+org/dwim-at-point + :desc "Display inline images" "i" #'org-display-inline-images + :desc "Search notes for symbol" "." #'+default/search-notes-for-symbol-at-point + (:prefix ("a" . "org agenda") + :desc "Agenda" "a" #'org-agenda + :desc "Todo list" "t" #'org-todo-list + :desc "Tags view" "m" #'org-tags-view + :desc "View search" "v" #'org-search-view) + :desc "Browse notes" "f" #'+default/browse-notes + :desc "Search org-directory" "s" #'+default/org-notes-search + :desc "Switch org buffers" "b" #'org-switchb + :desc "Capture" "c" #'org-capture + :desc "Goto capture" "C" #'org-capture-goto-target + :desc "Link store" "l" #'org-store-link + :desc "Sync org caldav" "S" #'org-caldav-sync) + + ;;; p --- project + (:prefix ("p" . "project") + :desc "Find file in other project" "F" #'doom/find-file-in-other-project + :desc "Search project" "s" #'+default/search-project + :desc "List project tasks" "t" #'magit-todos-list + :desc "Open project scratch buffer" "x" #'doom/open-project-scratch-buffer + :desc "Switch to project scratch buffer" "X" #'doom/switch-to-project-scratch-buffer + ;; later expanded by projectile + (:prefix ("4" . "in other window")) + (:prefix ("5" . "in other frame"))) + + ;;; q --- quit/restart + (:prefix-map ("q" . "quit/restart") + :desc "Quit Emacs" "q" #'kill-emacs + :desc "Save and quit Emacs" "Q" #'save-buffers-kill-terminal + (:when (featurep! :ui workspaces) + :desc "Quit Emacs & forget session" "X" #'+workspace/kill-session-and-quit) + :desc "Restart & restore Emacs" "r" #'doom/restart-and-restore + :desc "Restart Emacs" "R" #'doom/restart) + + ;;; & --- snippets + (:prefix-map ("&" . "snippets") + :desc "New snippet" "n" #'yas-new-snippet + :desc "Insert snippet" "i" #'yas-insert-snippet + :desc "Find global snippet" "/" #'yas-visit-snippet-file + :desc "Reload snippets" "r" #'yas-reload-all + :desc "Create Temp Template" "c" #'aya-create + :desc "Use Temp Template" "e" #'aya-expand) + + (:prefix-map ("t" . "terminal") + (:when (featurep! :term term) + :desc "Toggle term popup" "t" #'+term/toggle + :desc "Open term here" "T" #'+term/here) + (:when (featurep! :term vterm) + :desc "Toggle vterm popup" "t" #'+vterm/toggle + :desc "Open vterm here" "T" #'+vterm/here) + (:when (featurep! :term eshell) + :desc "Toggle eshell popup" "t" #'+eshell/toggle + :desc "Open eshell here" "T" #'+eshell/here)) + + ;;; v --- versioning + (:prefix-map ("v" . "versioning") + :desc "Git revert file" "R" #'vc-revert + :desc "Kill link to remote" "y" #'+vc/browse-at-remote-kill-file-or-region + :desc "Kill link to homepage" "Y" #'+vc/browse-at-remote-kill-homepage + (:when (featurep! :ui vc-gutter) + :desc "Git revert hunk" "r" #'git-gutter:revert-hunk + :desc "Git stage hunk" "s" #'git-gutter:stage-hunk + :desc "Git time machine" "t" #'git-timemachine-toggle + :desc "Jump to next hunk" "n" #'git-gutter:next-hunk + :desc "Jump to previous hunk" "p" #'git-gutter:previous-hunk) + (:when (featurep! :tools magit) + :desc "Magit dispatch" "/" #'magit-dispatch + :desc "Forge dispatch" "'" #'forge-dispatch + :desc "Magit status" "g" #'magit-status + :desc "Magit file delete" "x" #'magit-file-delete + :desc "Magit blame" "B" #'magit-blame-addition + :desc "Magit clone" "C" #'magit-clone + :desc "Magit fetch" "F" #'magit-fetch + :desc "Magit buffer log" "L" #'magit-log + :desc "Git stage file" "S" #'magit-stage-file + :desc "Git unstage file" "U" #'magit-unstage-file + (:prefix ("f" . "find") + :desc "Find file" "f" #'magit-find-file + :desc "Find gitconfig file" "g" #'magit-find-git-config-file + :desc "Find commit" "c" #'magit-show-commit + :desc "Find issue" "i" #'forge-visit-issue + :desc "Find pull request" "p" #'forge-visit-pullreq) + (:prefix ("o" . "open in browser") + :desc "Browse file or region" "." #'+vc/browse-at-remote-file-or-region + :desc "Browse homepage" "h" #'+vc/browse-at-remote-homepage + :desc "Browse remote" "r" #'forge-browse-remote + :desc "Browse commit" "c" #'forge-browse-commit + :desc "Browse an issue" "i" #'forge-browse-issue + :desc "Browse a pull request" "p" #'forge-browse-pullreq + :desc "Browse issues" "I" #'forge-browse-issues + :desc "Browse pull requests" "P" #'forge-browse-pullreqs) + (:prefix ("l" . "list") + (:when (featurep! :tools gist) + :desc "List gists" "g" #'+gist:list) + :desc "List repositories" "r" #'magit-list-repositories + :desc "List submodules" "s" #'magit-list-submodules + :desc "List issues" "i" #'forge-list-issues + :desc "List pull requests" "p" #'forge-list-pullreqs + :desc "List notifications" "n" #'forge-list-notifications) + (:prefix ("c" . "create") + :desc "Initialize repo" "r" #'magit-init + :desc "Clone repo" "R" #'magit-clone + :desc "Commit" "c" #'magit-commit-create + :desc "Fixup" "f" #'magit-commit-fixup + :desc "Issue" "i" #'forge-create-issue + :desc "Pull request" "p" #'forge-create-pullreq))) + + ;;; w --- workspaces/windows + (:prefix-map ("w" . "workspaces/windows") + :desc "Autosave session" "a" #'doom/quicksave-session + :desc "Display workspaces" "d" #'+workspace/display + :desc "Rename workspace" "r" #'+workspace/rename + :desc "Create workspace" "c" #'+workspace/new + :desc "Delete workspace" "k" #'+workspace/delete + :desc "Save session" "s" #'doom/save-session + :desc "Save workspace" "S" #'+workspace/save + :desc "Load session" "l" #'doom/load-session + :desc "Load last autosaved session" "L" #'doom/quickload-session + :desc "Switch to other workspace" "o" #'+workspace/other + :desc "Undo window config" "u" #'winner-undo + :desc "Redo window config" "U" #'winner-redo + :desc "Switch to left workspace" "p" #'+workspace/switch-left + :desc "Switch to right workspace" "n" #'+workspace/switch-right + :desc "Switch to" "w" #'+workspace/switch-to + :desc "Switch to workspace 1" "1" #'+workspace/switch-to-0 + :desc "Switch to workspace 2" "2" #'+workspace/switch-to-1 + :desc "Switch to workspace 3" "3" #'+workspace/switch-to-2 + :desc "Switch to workspace 4" "4" #'+workspace/switch-to-3 + :desc "Switch to workspace 5" "5" #'+workspace/switch-to-4 + :desc "Switch to workspace 6" "6" #'+workspace/switch-to-5 + :desc "Switch to workspace 7" "7" #'+workspace/switch-to-6 + :desc "Switch to workspace 8" "8" #'+workspace/switch-to-7 + :desc "Switch to workspace 9" "9" #'+workspace/switch-to-8 + :desc "Switch to last workspace" "0" #'+workspace/switch-to-final) + + ;;; m --- multiple cursors + (:when (featurep! :editor multiple-cursors) + (:prefix-map ("m" . "multiple cursors") + :desc "Edit lines" "l" #'mc/edit-lines + :desc "Mark next" "n" #'mc/mark-next-like-this + :desc "Unmark next" "N" #'mc/unmark-next-like-this + :desc "Mark previous" "p" #'mc/mark-previous-like-this + :desc "Unmark previous" "P" #'mc/unmark-previous-like-this + :desc "Mark all" "t" #'mc/mark-all-like-this + :desc "Mark all DWIM" "m" #'mc/mark-all-like-this-dwim + :desc "Edit line endings" "e" #'mc/edit-ends-of-lines + :desc "Edit line starts" "a" #'mc/edit-beginnings-of-lines + :desc "Mark tag" "s" #'mc/mark-sgml-tag-pair + :desc "Mark in defun" "d" #'mc/mark-all-like-this-in-defun + :desc "Add cursor w/mouse" "" #'mc/add-cursor-on-click)) + + ;; APPs + ;;; M --- mu4e + (:when (featurep! :email mu4e) + (:prefix-map ("M" . "mu4e") + :desc "Open email app" "M" #'=mu4e + :desc "Compose email" "c" #'+mu4e/compose)) + + ;;; I --- IRC + (:when (featurep! :app irc) + (:prefix-map ("I" . "irc") + :desc "Open irc app" "I" #'=irc + :desc "Next unread buffer" "a" #'tracking-next-buffer + :desc "Quit irc" "q" #'+irc/quit + :desc "Reconnect all" "r" #'circe-reconnect-all + :desc "Send message" "s" #'+irc/send-message + (:when (featurep! :completion ivy) + :desc "Jump to channel" "j" #'irc/ivy-jump-to-channel))) + + ;;; T --- twitter + (:when (featurep! :app twitter) + (:prefix-map ("T" . "twitter") + :desc "Open twitter app" "T" #'=twitter + :desc "Quit twitter" "q" #'+twitter/quit + :desc "Rerender twits" "r" #'+twitter/rerender-all + :desc "Ace link" "l" #'+twitter/ace-link))) + + +;; +;;; Global & plugin keybinds + +(map! "C-'" #'imenu + + ;;; Text scaling + [C-mouse-4] #'text-scale-increase + [C-mouse-5] #'text-scale-decrease + [C-down-mouse-2] (λ! (text-scale-set 0)) + "M-+" #'doom/reset-font-size + "M-=" #'doom/increase-font-size + "M--" #'doom/decrease-font-size + + ;;; newlines + [remap newline] #'newline-and-indent + "C-j" #'+default/newline + + ;;; search + (:when (featurep! :completion ivy) + "C-S-s" #'swiper + "C-S-r" #'ivy-resume) + (:when (featurep! :completion helm) + "C-S-s" #'swiper-helm + "C-S-r" #'helm-resume) + + ;;; objed + (:when (featurep! :editor objed +manual) + "M-SPC" #'objed-activate) + + ;;; buffer management + "C-x b" #'persp-switch-to-buffer + (:when (featurep! :completion ivy) + "C-x 4 b" #'+ivy/switch-workspace-buffer-other-window) + "C-x C-b" #'ibuffer-list-buffers + "C-x B" #'switch-to-buffer + "C-x 4 B" #'switch-to-buffer-other-window + "C-x K" #'doom/kill-this-buffer-in-all-windows + + ;;; company-mode + "C-;" #'+company/complete + (:after company + :map company-active-map + "C-o" #'company-search-kill-others + "C-n" #'company-select-next + "C-p" #'company-select-previous + "C-h" #'company-quickhelp-manual-begin + "C-S-h" #'company-show-doc-buffer + "C-s" #'company-search-candidates + "M-s" #'company-filter-candidates + "" #'company-complete-common-or-cycle + [tab] #'company-complete-common-or-cycle + [backtab] #'company-select-previous + "C-RET" #'counsel-company + :map company-search-map + "C-n" #'company-search-repeat-forward + "C-p" #'company-search-repeat-backward + "C-s" (λ! (company-search-abort) (company-filter-candidates))) + + ;;; ein notebooks + (:after ein:notebook-multilang + :map ein:notebook-multilang-mode-map + "C-c h" #'+ein/hydra/body) + + ;;; expand-region + "C-=" #'er/expand-region + "C--" #'er/contract-region + + ;;; flycheck + (:after flycheck + :map flycheck-error-list-mode-map + "C-n" #'flycheck-error-list-next-error + "C-p" #'flycheck-error-list-previous-error + "RET" #'flycheck-error-list-goto-error) + + ;;; help and info + (:after help-mode + :map help-mode-map + "o" #'ace-link-help + ">" #'help-go-forward + "<" #'help-go-back + "n" #'forward-button + "p" #'backward-button) + (:after helpful + :map helpful-mode-map + "o" #'ace-link-help) + (:after apropos + :map apropos-mode-map + "o" #'ace-link-help + "n" #'forward-button + "p" #'backward-button) + (:after info + :map Info-mode-map + "o" #'ace-link-info) + + ;;; ivy & counsel + (:when (featurep! :completion ivy) + (:after ivy + :map ivy-minibuffer-map + "TAB" #'ivy-alt-done + "C-g" #'keyboard-escape-quit) + (:after counsel + :map counsel-ag-map + "C-SPC" #'ivy-call-and-recenter ; preview + "M-RET" #'+ivy/git-grep-other-window-action) + "C-M-y" #'counsel-yank-pop) + + ;;; neotree + (:when (featurep! :ui neotree) + "" #'+neotree/open + "" #'+neotree/find-this-file + (:after neotree + :map neotree-mode-map + "q" #'neotree-hide + "RET" #'neotree-enter + "SPC" #'neotree-quick-look + "v" #'neotree-enter-vertical-split + "s" #'neotree-enter-horizontal-split + "c" #'neotree-create-node + "D" #'neotree-delete-node + "g" #'neotree-refresh + "r" #'neotree-rename-node + "R" #'neotree-refresh + "h" #'+neotree/collapse-or-up + "l" #'+neotree/expand-or-open + "n" #'neotree-next-line + "p" #'neotree-previous-line + "N" #'neotree-select-next-sibling-node + "P" #'neotree-select-previous-sibling-node)) + + ;;; popups + (:when (featurep! :ui popup) + "C-x p" #'+popup/other + "C-`" #'+popup/toggle + "C-~" #'+popup/raise) + + ;;; repl + "C-c C-z" #'+eval/open-repl-other-window + "C-c C-S-z" #'+eval/open-repl-same-window + + ;;; smartparens + (:after smartparens + :map smartparens-mode-map + "C-M-a" #'sp-beginning-of-sexp + "C-M-e" #'sp-end-of-sexp + "C-M-f" #'sp-forward-sexp + "C-M-b" #'sp-backward-sexp + "C-M-d" #'sp-splice-sexp + "C-M-k" #'sp-kill-sexp + "C-M-t" #'sp-transpose-sexp + "C-" #'sp-forward-slurp-sexp + "M-" #'sp-forward-barf-sexp + "C-" #'sp-backward-slurp-sexp + "M-" #'sp-backward-barf-sexp) + + ;;; treemacs + (:when (featurep! :ui treemacs) + "" #'+treemacs/toggle + "" #'+treemacs/find-file) + + ;;; yasnippet + (:after yasnippet + :map yas-keymap ; keymap while editing an inserted snippet + "C-e" #'+snippets/goto-end-of-field + "C-a" #'+snippets/goto-start-of-field + "" #'yas-prev-field + "" #'+snippets/delete-to-start-of-field + [backspace] #'+snippets/delete-backward-char + [delete] #'+snippets/delete-forward-char-or-field)) diff --git a/.emacs.d/modules/config/default/+emacs.el b/.emacs.d/modules/config/default/+emacs.el new file mode 100644 index 0000000..56981ee --- /dev/null +++ b/.emacs.d/modules/config/default/+emacs.el @@ -0,0 +1,26 @@ +;;; config/default/+emacs.el -*- lexical-binding: t; -*- + +(require 'projectile) ; we need its keybinds immediately + + +;; +;;; Reasonable defaults + +(setq shift-select-mode t) +(delete-selection-mode +1) + +(use-package! expand-region + :commands (er/contract-region er/mark-symbol er/mark-word) + :config + (defadvice! doom--quit-expand-region-a () + "Properly abort an expand-region region." + :before '(evil-escape doom/escape) + (when (memq last-command '(er/expand-region er/contract-region)) + (er/contract-region 0)))) + + +;; +;;; Keybinds + +(when (featurep! +bindings) + (load! "+emacs-bindings")) diff --git a/.emacs.d/modules/config/default/+evil-bindings.el b/.emacs.d/modules/config/default/+evil-bindings.el new file mode 100644 index 0000000..009833c --- /dev/null +++ b/.emacs.d/modules/config/default/+evil-bindings.el @@ -0,0 +1,624 @@ +;;; config/default/+bindings.el -*- lexical-binding: t; -*- + +(when (featurep! :editor evil +everywhere) + ;; Have C-u behave similarly to `doom/backward-to-bol-or-indent'. + ;; NOTE SPC u replaces C-u as the universal argument. + (map! :i "C-u" #'doom/backward-kill-to-bol-and-indent + :i "C-w" #'backward-kill-word + ;; Vimmish ex motion keys + :i "C-b" #'backward-word + :i "C-f" #'forward-word) + + ;; Minibuffer + (define-key! evil-ex-completion-map + "C-a" #'move-beginning-of-line + "C-b" #'backward-word + "C-s" (if (featurep! :completion ivy) + #'counsel-minibuffer-history + #'helm-minibuffer-history)) + + (define-key! :keymaps +default-minibuffer-maps + [escape] #'abort-recursive-edit + "C-a" #'move-beginning-of-line + "C-b" #'backward-word + "C-f" #'forward-word + "C-r" #'evil-paste-from-register + "C-u" #'doom/backward-kill-to-bol-and-indent + "C-v" #'yank + "C-w" #'backward-kill-word + "C-z" (λ! (ignore-errors (call-interactively #'undo))) + ;; Scrolling lines + "C-j" #'next-line + "C-k" #'previous-line + "C-S-j" #'scroll-up-command + "C-S-k" #'scroll-down-command) + + (define-key! read-expression-map + "C-j" #'next-line-or-history-element + "C-k" #'previous-line-or-history-element)) + + +;; +;;; Global keybindings + +;; Smart tab, these will only work in GUI Emacs +(map! :i [tab] (general-predicate-dispatch nil ; fall back to nearest keymap + (and (featurep! :editor snippets) + (bound-and-true-p yas-minor-mode) + (yas-maybe-expand-abbrev-key-filter 'yas-expand)) + #'yas-expand + (and (featurep! :completion company +tng) + (+company-has-completion-p)) + #'+company/complete) + :n [tab] (general-predicate-dispatch nil + (and (featurep! :editor fold) + (save-excursion (end-of-line) (invisible-p (point)))) + #'+fold/toggle + (fboundp 'evil-jump-item) + #'evil-jump-item) + :v [tab] (general-predicate-dispatch nil + (and (bound-and-true-p yas-minor-mode) + (or (eq evil-visual-selection 'line) + (not (memq (char-after) (list ?\( ?\[ ?\{ ?\} ?\] ?\)))))) + #'yas-insert-snippet + (fboundp 'evil-jump-item) + #'evil-jump-item) + + ;; Smarter newlines + :i [remap newline] #'newline-and-indent ; auto-indent on newline + :i "C-j" #'+default/newline ; default behavior + + (:after help :map help-mode-map + :n "o" #'ace-link-help) + (:after helpful :map helpful-mode-map + :n "o" #'ace-link-help) + (:after info :map Info-mode-map + :n "o" #'ace-link-info) + (:after apropos :map apropos-mode-map + :n "o" #'ace-link-help + :n "TAB" #'forward-button + :n [tab] #'forward-button + :n [backtab] #'backward-button) + (:after view :map view-mode-map + [escape] #'View-quit-all) + (:after man :map Man-mode-map + :n "q" #'kill-current-buffer) + + :m "gs" #'+evil/easymotion ; lazy-load `evil-easymotion' + (:after org + :map org-mode-map + :m "gsh" #'+org/goto-visible) + + (:when (featurep! :editor multiple-cursors) + :prefix "gz" + :nv "d" #'evil-mc-make-and-goto-next-match + :nv "D" #'evil-mc-make-and-goto-prev-match + :nv "j" #'evil-mc-make-cursor-move-next-line + :nv "k" #'evil-mc-make-cursor-move-prev-line + :nv "m" #'evil-mc-make-all-cursors + :nv "n" #'evil-mc-make-and-goto-next-cursor + :nv "N" #'evil-mc-make-and-goto-last-cursor + :nv "p" #'evil-mc-make-and-goto-prev-cursor + :nv "P" #'evil-mc-make-and-goto-first-cursor + :nv "q" #'evil-mc-undo-all-cursors + :nv "t" #'+multiple-cursors/evil-mc-toggle-cursors + :nv "u" #'evil-mc-undo-last-added-cursor + :nv "z" #'+multiple-cursors/evil-mc-make-cursor-here + :v "I" #'evil-mc-make-cursor-in-visual-selection-beg + :v "A" #'evil-mc-make-cursor-in-visual-selection-end) + + ;; misc + :n "C-S-f" #'toggle-frame-fullscreen + :n "C-+" #'doom/reset-font-size + ;; Buffer-local font resizing + :n "C-=" #'text-scale-increase + :n "C--" #'text-scale-decrease + ;; Frame-local font resizing + :n "M-C-=" #'doom/increase-font-size + :n "M-C--" #'doom/decrease-font-size) + + +;; +;;; Module keybinds + +;;; :completion +(map! (:when (featurep! :completion company) + :i "C-@" #'+company/complete + :i "C-SPC" #'+company/complete + (:after company + (:map company-active-map + "C-w" nil ; don't interfere with `evil-delete-backward-word' + "C-n" #'company-select-next + "C-p" #'company-select-previous + "C-j" #'company-select-next + "C-k" #'company-select-previous + "C-h" #'company-show-doc-buffer + "C-u" #'company-previous-page + "C-d" #'company-next-page + "C-s" #'company-filter-candidates + "C-S-s" (cond ((featurep! :completion helm) #'helm-company) + ((featurep! :completion ivy) #'counsel-company)) + "C-SPC" #'company-complete-common + "TAB" #'company-complete-common-or-cycle + [tab] #'company-complete-common-or-cycle + [backtab] #'company-select-previous + [f1] nil) + (:map company-search-map ; applies to `company-filter-map' too + "C-n" #'company-select-next-or-abort + "C-p" #'company-select-previous-or-abort + "C-j" #'company-select-next-or-abort + "C-k" #'company-select-previous-or-abort + "C-s" (λ! (company-search-abort) (company-filter-candidates)) + "ESC" #'company-search-abort)) + ;; TAB auto-completion in term buffers + (:after comint :map comint-mode-map + "TAB" #'company-complete + [tab] #'company-complete)) + + (:when (featurep! :completion ivy) + (:after ivy + :map ivy-minibuffer-map + "C-SPC" #'ivy-call-and-recenter ; preview file + "C-l" #'ivy-alt-done + "C-v" #'yank) + (:after counsel + :map counsel-ag-map + "C-SPC" #'ivy-call-and-recenter ; preview + "C-l" #'ivy-done + [C-return] #'+ivy/git-grep-other-window-action)) + + (:when (featurep! :completion helm) + (:after helm :map helm-map + [left] #'left-char + [right] #'right-char + "C-S-f" #'helm-previous-page + "C-S-n" #'helm-next-source + "C-S-p" #'helm-previous-source + "C-S-j" #'helm-next-source + "C-S-k" #'helm-previous-source + "C-j" #'helm-next-line + "C-k" #'helm-previous-line + "C-u" #'helm-delete-minibuffer-contents + "C-s" #'helm-minibuffer-history + ;; Swap TAB and C-z + "TAB" #'helm-execute-persistent-action + [tab] #'helm-execute-persistent-action + "C-z" #'helm-select-action) + (:after helm-ag :map helm-ag-map + "C--" #'+helm-do-ag-decrease-context + "C-=" #'+helm-do-ag-increase-context + [left] nil + [right] nil) + (:after helm-files :map (helm-find-files-map helm-read-file-map) + [C-return] #'helm-ff-run-switch-other-window + "C-w" #'helm-find-files-up-one-level) + (:after helm-locate :map helm-generic-files-map + [C-return] #'helm-ff-run-switch-other-window) + (:after helm-buffers :map helm-buffer-map + [C-return] #'helm-buffer-switch-other-window) + (:after helm-occur :map helm-occur-map + [C-return] #'helm-occur-run-goto-line-ow) + (:after helm-grep :map helm-grep-map + [C-return] #'helm-grep-run-other-window-action))) + +;;; :ui +(map! (:when (featurep! :ui popup) + :n "C-`" #'+popup/toggle + :n "C-~" #'+popup/raise + :g "C-x p" #'+popup/other) + + (:when (featurep! :ui workspaces) + :n "C-t" #'+workspace/new + :n "C-S-t" #'+workspace/display + :g "M-1" #'+workspace/switch-to-0 + :g "M-2" #'+workspace/switch-to-1 + :g "M-3" #'+workspace/switch-to-2 + :g "M-4" #'+workspace/switch-to-3 + :g "M-5" #'+workspace/switch-to-4 + :g "M-6" #'+workspace/switch-to-5 + :g "M-7" #'+workspace/switch-to-6 + :g "M-8" #'+workspace/switch-to-7 + :g "M-9" #'+workspace/switch-to-8 + :g "M-0" #'+workspace/switch-to-final + (:when IS-MAC + :g "s-t" #'+workspace/new + :g "s-T" #'+workspace/display + :n "s-1" #'+workspace/switch-to-0 + :n "s-2" #'+workspace/switch-to-1 + :n "s-3" #'+workspace/switch-to-2 + :n "s-4" #'+workspace/switch-to-3 + :n "s-5" #'+workspace/switch-to-4 + :n "s-6" #'+workspace/switch-to-5 + :n "s-7" #'+workspace/switch-to-6 + :n "s-8" #'+workspace/switch-to-7 + :n "s-9" #'+workspace/switch-to-8 + :n "s-0" #'+workspace/switch-to-final))) + +;;; :editor +(map! (:when (featurep! :editor format) + :n "gQ" #'+format:region) + + (:when (featurep! :editor rotate-text) + :n "!" #'rotate-text) + + (:when (featurep! :editor multiple-cursors) + ;; evil-multiedit + :v "R" #'evil-multiedit-match-all + :n "M-d" #'evil-multiedit-match-symbol-and-next + :n "M-D" #'evil-multiedit-match-symbol-and-prev + :v "M-d" #'evil-multiedit-match-and-next + :v "M-D" #'evil-multiedit-match-and-prev + :nv "C-M-d" #'evil-multiedit-restore + (:after evil-multiedit + (:map evil-multiedit-state-map + "M-d" #'evil-multiedit-match-and-next + "M-D" #'evil-multiedit-match-and-prev + "RET" #'evil-multiedit-toggle-or-restrict-region + [return] #'evil-multiedit-toggle-or-restrict-region))) + + (:when (featurep! :editor snippets) + ;; auto-yasnippet + :i [C-tab] #'aya-expand + :nv [C-tab] #'aya-create)) + +;;; :tools +(when (featurep! :tools eval) + (map! "M-r" #'+eval/buffer)) + + +;; +;;; + +(map! :leader + :desc "Eval expression" ";" #'pp-eval-expression + :desc "M-x" ":" #'execute-extended-command + :desc "Pop up scratch buffer" "x" #'doom/open-scratch-buffer + :desc "Org Capture" "X" #'org-capture + + ;; C-u is used by evil + :desc "Universal argument" "u" #'universal-argument + :desc "window" "w" evil-window-map + :desc "help" "h" help-map + + (:when (featurep! :ui popup) + :desc "Toggle last popup" "~" #'+popup/toggle) + :desc "Find file" "." #'find-file + + :desc "Switch buffer" "," #'switch-to-buffer + (:when (featurep! :ui workspaces) + :desc "Switch workspace buffer" "," #'persp-switch-to-buffer + :desc "Switch buffer" "<" #'switch-to-buffer) + + :desc "Switch to last buffer" "`" #'evil-switch-to-windows-last-buffer + :desc "Resume last search" "'" + (cond ((featurep! :completion ivy) #'ivy-resume) + ((featurep! :completion helm) #'helm-resume)) + + :desc "Search for symbol in project" "*" #'+default/search-project-for-symbol-at-point + + :desc "Find file in project" "SPC" #'projectile-find-file + :desc "Jump to bookmark" "RET" #'bookmark-jump + + ;;; TAB --- workspace + (:when (featurep! :ui workspaces) + (:prefix-map ("TAB" . "workspace") + :desc "Display tab bar" "TAB" #'+workspace/display + :desc "Switch workspace" "." #'+workspace/switch-to + :desc "Switch to last workspace" "`" #'+workspace/other + :desc "New workspace" "n" #'+workspace/new + :desc "Load workspace from file" "l" #'+workspace/load + :desc "Save workspace to file" "s" #'+workspace/save + :desc "Delete session" "x" #'+workspace/kill-session + :desc "Delete this workspace" "d" #'+workspace/delete + :desc "Rename workspace" "r" #'+workspace/rename + :desc "Restore last session" "R" #'+workspace/restore-last-session + :desc "Next workspace" "]" #'+workspace/switch-right + :desc "Previous workspace" "[" #'+workspace/switch-left + :desc "Switch to 1st workspace" "1" #'+workspace/switch-to-0 + :desc "Switch to 2nd workspace" "2" #'+workspace/switch-to-1 + :desc "Switch to 3rd workspace" "3" #'+workspace/switch-to-2 + :desc "Switch to 4th workspace" "4" #'+workspace/switch-to-3 + :desc "Switch to 5th workspace" "5" #'+workspace/switch-to-4 + :desc "Switch to 6th workspace" "6" #'+workspace/switch-to-5 + :desc "Switch to 7th workspace" "7" #'+workspace/switch-to-6 + :desc "Switch to 8th workspace" "8" #'+workspace/switch-to-7 + :desc "Switch to 9th workspace" "9" #'+workspace/switch-to-8 + :desc "Switch to final workspace" "0" #'+workspace/switch-to-final)) + + ;;; b --- buffer + (:prefix-map ("b" . "buffer") + :desc "Toggle narrowing" "-" #'doom/toggle-narrow-buffer + :desc "Previous buffer" "[" #'previous-buffer + :desc "Next buffer" "]" #'next-buffer + (:when (featurep! :ui workspaces) + :desc "Switch workspace buffer" "b" #'persp-switch-to-buffer + :desc "Switch buffer" "B" #'switch-to-buffer) + (:unless (featurep! :ui workspaces) + :desc "Switch buffer" "b" #'switch-to-buffer) + :desc "Kill buffer" "d" #'kill-current-buffer + :desc "ibuffer" "i" #'ibuffer + :desc "Kill buffer" "k" #'kill-current-buffer + :desc "Kill all buffers" "K" #'doom/kill-all-buffers + :desc "Switch to last buffer" "l" #'evil-switch-to-windows-last-buffer + :desc "Next buffer" "n" #'next-buffer + :desc "New empty buffer" "N" #'evil-buffer-new + :desc "Kill other buffers" "O" #'doom/kill-other-buffers + :desc "Previous buffer" "p" #'previous-buffer + :desc "Revert buffer" "r" #'revert-buffer + :desc "Save buffer" "s" #'basic-save-buffer + :desc "Save all buffers" "S" #'evil-write-all + :desc "Pop up scratch buffer" "x" #'doom/open-scratch-buffer + :desc "Switch to scratch buffer" "X" #'doom/switch-to-scratch-buffer + :desc "Bury buffer" "z" #'bury-buffer + :desc "Kill buried buffers" "Z" #'doom/kill-buried-buffers) + + ;;; c --- code + (:prefix-map ("c" . "code") + :desc "Compile" "c" #'compile + :desc "Recompile" "C" #'recompile + :desc "Jump to definition" "d" #'+lookup/definition + :desc "Jump to references" "D" #'+lookup/references + :desc "Evaluate buffer/region" "e" #'+eval/buffer-or-region + :desc "Evaluate & replace region" "E" #'+eval:replace-region + :desc "Format buffer/region" "f" #'+format/region-or-buffer + :desc "LSP Format buffer/region" "F" #'+default/lsp-format-region-or-buffer + :desc "LSP Organize imports" "i" #'lsp-organize-imports + :desc "Jump to documentation" "k" #'+lookup/documentation + :desc "LSP Rename" "r" #'lsp-rename + :desc "Send to repl" "s" #'+eval/send-region-to-repl + :desc "Delete trailing whitespace" "w" #'delete-trailing-whitespace + :desc "Delete trailing newlines" "W" #'doom/delete-trailing-newlines + :desc "List errors" "x" #'flymake-show-diagnostics-buffer + (:when (featurep! :tools flycheck) + :desc "List errors" "x" #'flycheck-list-errors)) + + ;;; f --- file + (:prefix-map ("f" . "file") + :desc "Open project editorconfig" "c" #'editorconfig-find-current-editorconfig + :desc "Copy this file" "C" #'doom/copy-this-file + :desc "Find directory" "d" #'dired + :desc "Delete this file" "D" #'doom/delete-this-file + :desc "Find file in emacs.d" "e" #'+default/find-in-emacsd + :desc "Browse emacs.d" "E" #'+default/browse-emacsd + :desc "Find file" "f" #'find-file + :desc "Find file from here" "F" #'+default/find-file-under-here + :desc "Locate file" "l" #'locate + :desc "Find file in private config" "p" #'doom/find-file-in-private-config + :desc "Browse private config" "P" #'doom/open-private-config + :desc "Recent files" "r" #'recentf-open-files + :desc "Rename/move file" "R" #'doom/move-this-file + :desc "Save file" "s" #'save-buffer + :desc "Save file as..." "S" #'write-file + :desc "Sudo find file" "u" #'doom/sudo-find-file + :desc "Sudo this file" "U" #'doom/sudo-this-file + :desc "Yank filename" "y" #'+default/yank-buffer-filename) + + ;;; g --- git + (:prefix-map ("g" . "git") + :desc "Git revert file" "R" #'vc-revert + :desc "Copy link to remote" "y" #'+vc/browse-at-remote-kill-file-or-region + :desc "Copy link to homepage" "Y" #'+vc/browse-at-remote-kill-homepage + (:when (featurep! :ui vc-gutter) + :desc "Git revert hunk" "r" #'git-gutter:revert-hunk + :desc "Git stage hunk" "s" #'git-gutter:stage-hunk + :desc "Git time machine" "t" #'git-timemachine-toggle + :desc "Jump to next hunk" "]" #'git-gutter:next-hunk + :desc "Jump to previous hunk" "[" #'git-gutter:previous-hunk) + (:when (featurep! :tools magit) + :desc "Magit dispatch" "/" #'magit-dispatch + :desc "Forge dispatch" "'" #'forge-dispatch + :desc "Magit switch branch" "b" #'magit-branch-checkout + :desc "Magit status" "g" #'magit-status + :desc "Magit file delete" "D" #'magit-file-delete + :desc "Magit blame" "B" #'magit-blame-addition + :desc "Magit clone" "C" #'magit-clone + :desc "Magit fetch" "F" #'magit-fetch + :desc "Magit buffer log" "L" #'magit-log + :desc "Git stage file" "S" #'magit-stage-file + :desc "Git unstage file" "U" #'magit-unstage-file + (:prefix ("f" . "find") + :desc "Find file" "f" #'magit-find-file + :desc "Find gitconfig file" "g" #'magit-find-git-config-file + :desc "Find commit" "c" #'magit-show-commit + :desc "Find issue" "i" #'forge-visit-issue + :desc "Find pull request" "p" #'forge-visit-pullreq) + (:prefix ("o" . "open in browser") + :desc "Browse file or region" "o" #'+vc/browse-at-remote-file-or-region + :desc "Browse homepage" "h" #'+vc/browse-at-remote-homepage + :desc "Browse remote" "r" #'forge-browse-remote + :desc "Browse commit" "c" #'forge-browse-commit + :desc "Browse an issue" "i" #'forge-browse-issue + :desc "Browse a pull request" "p" #'forge-browse-pullreq + :desc "Browse issues" "I" #'forge-browse-issues + :desc "Browse pull requests" "P" #'forge-browse-pullreqs) + (:prefix ("l" . "list") + (:when (featurep! :tools gist) + :desc "List gists" "g" #'+gist:list) + :desc "List repositories" "r" #'magit-list-repositories + :desc "List submodules" "s" #'magit-list-submodules + :desc "List issues" "i" #'forge-list-issues + :desc "List pull requests" "p" #'forge-list-pullreqs + :desc "List notifications" "n" #'forge-list-notifications) + (:prefix ("c" . "create") + :desc "Initialize repo" "r" #'magit-init + :desc "Clone repo" "R" #'magit-clone + :desc "Commit" "c" #'magit-commit-create + :desc "Fixup" "f" #'magit-commit-fixup + :desc "Branch" "b" #'magit-branch-and-checkout + :desc "Issue" "i" #'forge-create-issue + :desc "Pull request" "p" #'forge-create-pullreq))) + + ;;; i --- insert + (:prefix-map ("i" . "insert") + :desc "Current file name" "f" #'+default/insert-file-path + :desc "Current file path" "F" (λ!! #'+default/insert-file-path t) + :desc "Evil ex path" "p" (λ! (evil-ex "R!echo ")) + :desc "From evil register" "r" #'evil-ex-registers + :desc "Snippet" "s" #'yas-insert-snippet + :desc "Unicode" "u" #'unicode-chars-list-chars + :desc "From clipboard" "y" #'+default/yank-pop) + + ;;; n --- notes + (:prefix-map ("n" . "notes") + :desc "Search notes for symbol" "*" #'+default/search-notes-for-symbol-at-point + :desc "Org agenda" "a" #'org-agenda + :desc "Toggle org-clock" "c" #'+org/toggle-clock + :desc "Cancel org-clock" "C" #'org-clock-cancel + :desc "Open deft" "d" #'deft + :desc "Find file in notes" "f" #'+default/find-in-notes + :desc "Browse notes" "F" #'+default/browse-notes + :desc "Org store link" "l" #'org-store-link + :desc "Tags search" "m" #'org-tags-view + :desc "Org capture" "n" #'org-capture + :desc "Active org-clock" "o" #'org-clock-goto + :desc "Todo list" "t" #'org-todo-list + :desc "Search notes" "s" #'+default/org-notes-search + :desc "Search org agenda headlines" "S" #'+default/org-notes-headlines + :desc "View search" "v" #'org-search-view + :desc "Org export to clipboard" "y" #'+org/export-to-clipboard + :desc "Org export to clipboard as RTF" "Y" #'+org/export-to-clipboard-as-rich-text + + (:when (featurep! :lang org +journal) + (:prefix ("j" . "journal") + :desc "New Entry" "j" #'org-journal-new-entry + :desc "Search Forever" "s" #'org-journal-search-forever))) + + ;;; o --- open + (:prefix-map ("o" . "open") + :desc "Org agenda" "A" #'org-agenda + (:prefix ("a" . "org agenda") + :desc "Agenda" "a" #'org-agenda + :desc "Todo list" "t" #'org-todo-list + :desc "Tags search" "m" #'org-tags-view + :desc "View search" "v" #'org-search-view) + :desc "Default browser" "b" #'browse-url-of-file + :desc "Start debugger" "d" #'+debugger/start + :desc "New frame" "f" #'make-frame + :desc "REPL" "r" #'+eval/open-repl-other-window + :desc "REPL (same window)" "R" #'+eval/open-repl-same-window + :desc "Dired" "-" #'dired-jump + (:when (featurep! :ui neotree) + :desc "Project sidebar" "p" #'+neotree/open + :desc "Find file in project sidebar" "P" #'+neotree/find-this-file) + (:when (featurep! :ui treemacs) + :desc "Project sidebar" "p" #'+treemacs/toggle + :desc "Find file in project sidebar" "P" #'+treemacs/find-file) + (:when (featurep! :term shell) + :desc "Toggle shell popup" "t" #'+shell/toggle + :desc "Open shell here" "T" #'+shell/here) + (:when (featurep! :term term) + :desc "Toggle terminal popup" "t" #'+term/toggle + :desc "Open terminal here" "T" #'+term/here) + (:when (featurep! :term vterm) + :desc "Toggle vterm popup" "t" #'+vterm/toggle + :desc "Open vterm here" "T" #'+vterm/here) + (:when (featurep! :term eshell) + :desc "Toggle eshell popup" "e" #'+eshell/toggle + :desc "Open eshell here" "E" #'+eshell/here) + (:when (featurep! :tools macos) + :desc "Reveal in Finder" "o" #'+macos/reveal-in-finder + :desc "Reveal project in Finder" "O" #'+macos/reveal-project-in-finder + :desc "Send to Transmit" "u" #'+macos/send-to-transmit + :desc "Send project to Transmit" "U" #'+macos/send-project-to-transmit + :desc "Send to Launchbar" "l" #'+macos/send-to-launchbar + :desc "Send project to Launchbar" "L" #'+macos/send-project-to-launchbar) + (:when (featurep! :tools docker) + :desc "Docker" "D" #'docker)) + + ;;; p --- project + (:prefix-map ("p" . "project") + :desc "Browse project" "." #'+default/browse-project + :desc "Browse other project" ">" #'doom/browse-in-other-project + :desc "Run cmd in project root" "!" #'projectile-run-shell-command-in-root + :desc "Add new project" "a" #'projectile-add-known-project + :desc "Switch to project buffer" "b" #'projectile-switch-to-buffer + :desc "Compile in project" "c" #'projectile-compile-project + :desc "Repeat last command" "C" #'projectile-repeat-last-command + :desc "Remove known project" "d" #'projectile-remove-known-project + :desc "Edit project .dir-locals" "e" #'projectile-edit-dir-locals + :desc "Find file in project" "f" #'projectile-find-file + :desc "Find file in other project" "F" #'doom/find-file-in-other-project + :desc "Configure project" "g" #'projectile-configure-project + :desc "Invalidate project cache" "i" #'projectile-invalidate-cache + :desc "Kill project buffers" "k" #'projectile-kill-buffers + :desc "Find other file" "o" #'projectile-find-other-file + :desc "Switch project" "p" #'projectile-switch-project + :desc "Find recent project files" "r" #'projectile-recentf + :desc "Run project" "R" #'projectile-run-project + :desc "Save project files" "s" #'projectile-save-project-buffers + :desc "Pop up scratch buffer" "x" #'doom/open-project-scratch-buffer + :desc "Switch to scratch buffer" "X" #'doom/switch-to-project-scratch-buffer + :desc "List project tasks" "t" #'magit-todos-list + :desc "Test project" "T" #'projectile-test-project) + + ;;; q --- quit/session + (:prefix-map ("q" . "quit/session") + :desc "Restart emacs server" "d" #'+default/restart-server + :desc "Delete frame" "f" #'delete-frame + :desc "Clear current frame" "F" #'doom/kill-all-buffers + :desc "Kill Emacs (and daemon)" "K" #'save-buffers-kill-emacs + :desc "Quit Emacs" "q" #'save-buffers-kill-terminal + :desc "Quit Emacs without saving" "Q" #'evil-quit-all-with-error-code + :desc "Quick save current session" "s" #'doom/quicksave-session + :desc "Restore last session" "l" #'doom/quickload-session + :desc "Save session to file" "S" #'doom/save-session + :desc "Restore session from file" "L" #'doom/load-session + :desc "Restart & restore Emacs" "r" #'doom/restart-and-restore + :desc "Restart Emacs" "R" #'doom/restart) + + ;;; r --- remote + (:when (featurep! :tools upload) + (:prefix-map ("r" . "remote") + :desc "Upload local" "u" #'ssh-deploy-upload-handler + :desc "Upload local (force)" "U" #'ssh-deploy-upload-handler-forced + :desc "Download remote" "d" #'ssh-deploy-download-handler + :desc "Diff local & remote" "D" #'ssh-deploy-diff-handler + :desc "Browse remote files" "." #'ssh-deploy-browse-remote-handler + :desc "Detect remote changes" ">" #'ssh-deploy-remote-changes-handler)) + + ;;; s --- search + (:prefix-map ("s" . "search") + :desc "Search buffer" "b" #'swiper + :desc "Search current directory" "d" #'+default/search-cwd + :desc "Search other directory" "D" #'+default/search-other-cwd + :desc "Locate file" "f" #'locate + :desc "Jump to symbol" "i" #'imenu + :desc "Jump to visible link" "l" #'ace-link + :desc "Jump to link" "L" #'ffap-menu + :desc "Jump list" "j" #'evil-show-jumps + :desc "Jump to mark" "m" #'evil-show-marks + :desc "Look up online" "o" #'+lookup/online + :desc "Look up online (w/ prompt)" "O" #'+lookup/online-select + :desc "Look up in local docsets" "k" #'+lookup/in-docsets + :desc "Look up in all docsets" "K" #'+lookup/in-all-docsets + :desc "Search project" "p" #'+default/search-project + :desc "Search other project" "P" #'+default/search-other-project + :desc "Search buffer" "s" #'swiper-isearch + :desc "Search buffer for thing at point" "S" #'swiper-isearch-thing-at-point) + + ;;; t --- toggle + (:prefix-map ("t" . "toggle") + :desc "Big mode" "b" #'doom-big-font-mode + :desc "Flymake" "f" #'flymake-mode + (:when (featurep! :tools flycheck) + :desc "Flycheck" "f" #'flycheck-mode) + :desc "Frame fullscreen" "F" #'toggle-frame-fullscreen + :desc "Evil goggles" "g" #'evil-goggles-mode + (:when (featurep! :ui indent-guides) + :desc "Indent guides" "i" #'highlight-indent-guides-mode) + :desc "Indent style" "I" #'doom/toggle-indent-style + :desc "Line numbers" "l" #'doom/toggle-line-numbers + (:when (featurep! :lang org +present) + :desc "org-tree-slide mode" "p" #'+org-present/start) + :desc "Read-only mode" "r" #'read-only-mode + (:when (featurep! :tools flyspell) + :desc "Flyspell" "s" #'flyspell-mode) + (:when (featurep! :lang org +pomodoro) + :desc "Pomodoro timer" "t" #'org-pomodoro) + :desc "Word-wrap mode" "w" #'+word-wrap-mode)) + +(after! which-key + (let ((prefix-re (regexp-opt (list doom-leader-key doom-leader-alt-key)))) + (cl-pushnew `((,(format "\\`\\(?:C-w\\|%s w\\) m\\'" prefix-re)) + nil . "maximize") + which-key-replacement-alist))) diff --git a/.emacs.d/modules/config/default/+evil.el b/.emacs.d/modules/config/default/+evil.el new file mode 100644 index 0000000..afb6745 --- /dev/null +++ b/.emacs.d/modules/config/default/+evil.el @@ -0,0 +1,21 @@ +;;; config/default/+evil.el -*- lexical-binding: t; -*- + +(defun +default-disable-delete-selection-mode-h () + (delete-selection-mode -1)) +(add-hook 'evil-insert-state-entry-hook #'delete-selection-mode) +(add-hook 'evil-insert-state-exit-hook #'+default-disable-delete-selection-mode-h) + + +;; +;;; Keybindings + +;; This section is dedicated to "fixing" certain keys so that they behave +;; sensibly (and consistently with similar contexts). + +;; Make SPC u SPC u [...] possible (#747) +(map! :map universal-argument-map + :prefix doom-leader-key "u" #'universal-argument-more + :prefix doom-leader-alt-key "u" #'universal-argument-more) + +(when (featurep! +bindings) + (load! "+evil-bindings")) diff --git a/.emacs.d/modules/config/default/README.org b/.emacs.d/modules/config/default/README.org new file mode 100644 index 0000000..1d97889 --- /dev/null +++ b/.emacs.d/modules/config/default/README.org @@ -0,0 +1,46 @@ +#+TITLE: :config default + +This module provides a set of reasonable defaults, including: + ++ A Spacemacs-esque keybinding scheme ++ Extra Ex commands for evil-mode users ++ A yasnippet snippets library tailored to Doom emacs ++ A configuration for (almost) universally repeating searches with =;= and =,= + +#+begin_quote +The defaults module is intended as a "reasonable-defaults" module, but also as a +reference for your own private modules. You'll find [[https://github.com/hlissner/doom-emacs-private][my private module in a +separate repo]]. + +Refer to the [[https://github.com/hlissner/doom-emacs/wiki/Customization][Customization page]] on the wiki for details on starting your own +private module. +#+end_quote + +* Table of Contents :TOC: +- [[#install][Install]] +- [[#configuration][Configuration]] + - [[#im-not-an-evil-user][I'm not an evil user...]] +- [[#appendix][Appendix]] + - [[#commands][Commands]] + - [[#hacks][Hacks]] + +* Install +This module has no external dependencies. + +* Configuration +** I'm not an evil user... +That's fine. All evil configuration is ignored if =:editor evil= is disabled. + +* Appendix +** Commands ++ ~+default/browse-project~ ++ ~+default/browse-templates~ ++ ~+default/find-in-templates~ ++ ~+default/browse-emacsd~ ++ ~+default/find-in-emacsd~ ++ ~+default/browse-notes~ ++ ~+default/find-in-notes~ ++ ~+default/find-in-snippets~ +** Hacks ++ ~epa-pinentry-mode~ is set to ~'loopback~, forcing gpg-agent to use the Emacs + minibuffer when prompting for your passphrase. *Only works with GPG 2.1+!* diff --git a/.emacs.d/modules/config/default/autoload/default.el b/.emacs.d/modules/config/default/autoload/default.el new file mode 100644 index 0000000..28d2cae --- /dev/null +++ b/.emacs.d/modules/config/default/autoload/default.el @@ -0,0 +1,347 @@ +;; config/default/autoload/default.el -*- lexical-binding: t; -*- + +;;;###autoload +(defun +default/yank-buffer-filename () + "Copy the current buffer's path to the kill ring." + (interactive) + (if-let* ((filename (or buffer-file-name (bound-and-true-p list-buffers-directory)))) + (message (kill-new (abbreviate-file-name filename))) + (error "Couldn't find filename in current buffer"))) + +;;;###autoload +(defun +default/browse-project () + (interactive) (doom-project-browse (doom-project-root))) +;; NOTE No need for find-in-project, use `projectile-find-file' + +;;;###autoload +(defun +default/browse-templates () + (interactive) (doom-project-browse +file-templates-dir)) +;;;###autoload +(defun +default/find-in-templates () + (interactive) (doom-project-find-file +file-templates-dir)) + +;;;###autoload +(defun +default/browse-emacsd () + (interactive) (doom-project-browse doom-emacs-dir)) +;;;###autoload +(defun +default/find-in-emacsd () + (interactive) (doom-project-find-file doom-emacs-dir)) + +;;;###autoload +(defun +default/browse-notes () + (interactive) (doom-project-browse org-directory)) +;;;###autoload +(defun +default/find-in-notes () + (interactive) (doom-project-find-file org-directory)) + +;;;###autoload +(defun +default/compile (arg) + "Runs `compile' from the root of the current project. + +If a compilation window is already open, recompile that instead. + +If ARG (universal argument), runs `compile' from the current directory." + (interactive "P") + (if (and (bound-and-true-p compilation-in-progress) + (buffer-live-p compilation-last-buffer)) + (recompile) + (call-interactively + (if arg + #'projectile-compile-project + #'compile)))) + +;;;###autoload +(defun +default/man-or-woman () + "Invoke `man' if man is installed, otherwise use `woman'." + (interactive) + (call-interactively + (if (executable-find "man") + #'man + #'woman))) + +;;;###autoload +(defalias '+default/newline #'newline) + +;;;###autoload +(defun +default/new-buffer () + "TODO" + (interactive) + (if (featurep! 'evil) + (call-interactively #'evil-buffer-new) + (let ((buffer (generate-new-buffer "*new*"))) + (set-window-buffer nil buffer) + (with-current-buffer buffer + (funcall (default-value 'major-mode)))))) + +;;;###autoload +(defun +default/newline-above () + "Insert an indented new line before the current one." + (interactive) + (if (featurep 'evil) + (call-interactively 'evil-open-above) + (beginning-of-line) + (save-excursion (newline)) + (indent-according-to-mode))) + +;;;###autoload +(defun +default/newline-below () + "Insert an indented new line after the current one." + (interactive) + (if (featurep 'evil) + (call-interactively 'evil-open-below) + (end-of-line) + (newline-and-indent))) + +;;;###autoload +(defun +default/yank-pop () + "Interactively select what text to insert from the kill ring." + (interactive) + (call-interactively + (cond ((fboundp 'counsel-yank-pop) #'counsel-yank-pop) + ((fboundp 'helm-show-kill-ring) #'helm-show-kill-ring) + ((error "No kill-ring search backend available. Enable ivy or helm!"))))) + +;;;###autoload +(defun +default--newline-indent-and-continue-comments-a () + "A replacement for `newline-and-indent'. + +Continues comments if executed from a commented line, with special support for +languages with weak native comment continuation support (like C-family +languages)." + (interactive) + (if (and (sp-point-in-comment) + comment-line-break-function) + (funcall comment-line-break-function nil) + (delete-horizontal-space t) + (newline nil t) + (indent-according-to-mode))) + +(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* ((context (ignore-errors (sp-get-thing))) + (op (plist-get context :op)) + (cl (plist-get context :cl)) + open-len close-len) + (cond ;; When in strings (sp acts weird with quotes; this is the fix) + ;; Also, skip closing delimiters + ((and op cl + (string= op cl) + (and (string= (char-to-string (or (char-before) 0)) op) + (setq open-len (length op))) + (and (string= (char-to-string (or (char-after) 0)) cl) + (setq close-len (length cl)))) + (delete-char (- open-len)) + (delete-char close-len)) + + ;; Delete up to the nearest tab column IF only whitespace between + ;; point and bol. + ((and (not indent-tabs-mode) + (not (bolp)) + (not (sp-point-in-string)) + (save-excursion (>= (- (skip-chars-backward " \t")) tab-width))) + (let ((movement (% (current-column) tab-width))) + (when (= movement 0) + (setq movement tab-width)) + (delete-char (- movement))) + (unless (memq (char-before) (list ?\n ?\ )) + (insert " "))) + + ;; Otherwise do a regular delete + ((delete-char -1))))) + +;;;###autoload +(defun +default--delete-backward-char-a (n &optional killflag) + "Same as `delete-backward-char', but preforms these additional checks: + ++ If point is surrounded by (balanced) whitespace and a brace delimiter ({} [] + ()), delete a space on either side of the cursor. ++ If point is at BOL and surrounded by braces on adjacent lines, collapse + newlines: + { + | + } => {|} ++ Otherwise, resort to `doom--backward-delete-whitespace-to-column'. ++ Resorts to `delete-char' if n > 1" + (interactive "p\nP") + (or (integerp n) + (signal 'wrong-type-argument (list 'integerp n))) + (cond ((and (use-region-p) + delete-active-region + (= n 1)) + ;; If a region is active, kill or delete it. + (if (eq delete-active-region 'kill) + (kill-region (region-beginning) (region-end) 'region) + (funcall region-extract-function 'delete-only))) + ;; In Overwrite mode, maybe untabify while deleting + ((null (or (null overwrite-mode) + (<= n 0) + (memq (char-before) '(?\t ?\n)) + (eobp) + (eq (char-after) ?\n))) + (let ((ocol (current-column))) + (delete-char (- n) killflag) + (save-excursion + (insert-char ?\s (- ocol (current-column)) nil)))) + ;; + ((and (= n 1) (bound-and-true-p smartparens-mode)) + (cond ((and (memq (char-before) (list ?\ ?\t)) + (save-excursion + (and (/= (skip-chars-backward " \t" (line-beginning-position)) 0) + (bolp)))) + (doom--backward-delete-whitespace-to-column)) + ((let* ((pair (ignore-errors (sp-get-thing))) + (op (plist-get pair :op)) + (cl (plist-get pair :cl)) + (beg (plist-get pair :beg)) + (end (plist-get pair :end))) + (cond ((and end beg (= end (+ beg (length op) (length cl)))) + (sp-backward-delete-char 1)) + ((doom-surrounded-p pair 'inline 'balanced) + (delete-char -1 killflag) + (delete-char 1) + (when (= (point) (+ (length cl) beg)) + (sp-backward-delete-char 1) + (sp-insert-pair op))) + ((and (bolp) (doom-surrounded-p pair nil 'balanced)) + (delete-region beg end) + (sp-insert-pair op) + t) + ((run-hook-with-args-until-success 'doom-delete-backward-functions)) + ((doom--backward-delete-whitespace-to-column))))))) + ;; Otherwise, do simple deletion. + ((delete-char (- n) killflag)))) + +;;;###autoload +(defun +default/search-cwd (&optional arg) + "Conduct a text search in files under the current folder. +If prefix ARG is set, prompt for a directory to search from." + (interactive "P") + (let ((default-directory + (if arg + (read-directory-name "Search directory: ") + default-directory))) + (call-interactively + (cond ((featurep! :completion ivy) #'+ivy/project-search-from-cwd) + ((featurep! :completion helm) #'+helm/project-search-from-cwd) + (#'rgrep))))) + +;;;###autoload +(defun +default/search-other-cwd () + "Conduct a text search in another directory." + (interactive) + (+default/search-cwd 'other)) + +;;;###autoload +(defun +default/search-project (&optional arg) + "Conduct a text search in the current project root. +If prefix ARG is set, prompt for a known project to search from." + (interactive "P") + (let ((default-directory + (if arg + (if-let (projects (projectile-relevant-known-projects)) + (completing-read "Search project: " projects + nil t nil nil (doom-project-root)) + (user-error "There are no known projects")) + default-directory))) + (call-interactively + (cond ((featurep! :completion ivy) #'+ivy/project-search) + ((featurep! :completion helm) #'+helm/project-search) + (#'projectile-grep))))) + +;;;###autoload +(defun +default/search-other-project () + "Conduct a text search in a known project." + (interactive) + (+default/search-project 'other)) + +;;;###autoload +(defun +default/search-project-for-symbol-at-point (&optional arg symbol) + "Conduct a text search in the current project for symbol at point. +If prefix ARG is set, prompt for a known project to search from." + (interactive + (list current-prefix-arg + (or (and (use-region-p) + (rxt-quote-pcre + (buffer-substring-no-properties (region-beginning) + (region-end)))) + (rxt-quote-pcre (thing-at-point 'symbol t)) + ""))) + (let ((default-directory + (if arg + (if-let (projects (projectile-relevant-known-projects)) + (completing-read "Switch to project: " projects + nil t nil nil (doom-project-root)) + (user-error "There are no known projects")) + default-directory))) + (cond ((featurep! :completion ivy) + (+ivy/project-search nil symbol)) + ((featurep! :completion helm) + (+helm/project-search nil symbol)) + ((rgrep (regexp-quote symbol)))))) + +;;;###autoload +(defun +default/search-notes-for-symbol-at-point (&optional symbol) + "Conduct a text search in the current project for symbol at point. If prefix +ARG is set, prompt for a known project to search from." + (interactive + (list (rxt-quote-pcre (or (thing-at-point 'symbol t) "")))) + (require 'org) + (let ((default-directory org-directory)) + (+default/search-project-for-symbol-at-point + nil symbol))) + +;;;###autoload +(defun +default/org-notes-search () + "Perform a text search on `org-directory'." + (interactive) + (require 'org) + (let ((default-directory org-directory)) + (+default/search-project-for-symbol-at-point nil ""))) + +;;;###autoload +(defun +default/org-notes-headlines () + "Jump to an Org headline in `org-agenda-files'." + (interactive) + (doom-completing-read-org-headings + "Jump to org headline: " org-agenda-files 3 t)) + +;;;###autoload +(defun +default/lsp-format-region-or-buffer () + "Format the buffer (or selection) with LSP." + (interactive) + (unless (bound-and-true-p lsp-mode) + (user-error "Not in an LSP buffer")) + (call-interactively + (if (use-region-p) + #'lsp-format-region + #'lsp-format-buffer))) + +;;;###autoload +(defun +default/restart-server () + "Restart the Emacs server." + (interactive) + (server-force-delete) + (while (server-running-p) + (sit-for 1)) + (server-start)) + +;;;###autoload +(defun +default/find-file-under-here () + "Perform a recursive file search from the current directory." + (interactive) + (if (featurep! :completion ivy) + (call-interactively #'counsel-file-jump) + (λ! (doom-project-find-file default-directory)))) + +;;;###autoload +(defun +default/insert-file-path (arg) + "Insert the file name (absolute path if prefix ARG). +If `buffer-file-name' isn't set, uses `default-directory'." + (interactive "P") + (let ((path (or buffer-file-name default-directory))) + (insert + (if arg + (abbreviate-file-name path) + (file-name-nondirectory path))))) diff --git a/.emacs.d/modules/config/default/config.el b/.emacs.d/modules/config/default/config.el new file mode 100644 index 0000000..f1112c0 --- /dev/null +++ b/.emacs.d/modules/config/default/config.el @@ -0,0 +1,405 @@ +;;; config/default/config.el -*- lexical-binding: t; -*- + +(defvar +default-minibuffer-maps + `(minibuffer-local-map + minibuffer-local-ns-map + minibuffer-local-completion-map + minibuffer-local-must-match-map + minibuffer-local-isearch-map + read-expression-map + ,@(cond ((featurep! :completion ivy) + '(ivy-minibuffer-map + ivy-switch-buffer-map)) + ((featurep! :completion helm) + '(helm-map + helm-ag-map + helm-read-file-map)))) + "A list of all the keymaps used for the minibuffer.") + + +;; +;;; Reasonable defaults + +;;;###package avy +(setq avy-all-windows nil + avy-all-windows-alt t + avy-background t) + + +(after! epa + (setq epa-file-encrypt-to + (or epa-file-encrypt-to + ;; Collect all public key IDs with your username + (unless (string-empty-p user-full-name) + (cl-loop for key in (ignore-errors (epg-list-keys (epg-make-context) user-full-name)) + collect (epg-sub-key-id (car (epg-key-sub-key-list key))))) + user-mail-address) + ;; With GPG 2.1, this forces gpg-agent to use the Emacs minibuffer to + ;; prompt for the key passphrase. + epa-pinentry-mode 'loopback)) + + +(use-package! drag-stuff + :defer t + :init + (map! "" #'drag-stuff-up + "" #'drag-stuff-down + "" #'drag-stuff-left + "" #'drag-stuff-right)) + + +;;;###package tramp +(unless IS-WINDOWS + (setq tramp-default-method "ssh")) ; faster than the default scp + + +;; +;;; Smartparens config + +(when (featurep! +smartparens) + ;; You can disable :unless predicates with (sp-pair "'" nil :unless nil) + ;; And disable :post-handlers with (sp-pair "{" nil :post-handlers nil) + ;; or specific :post-handlers with: + ;; (sp-pair "{" nil :post-handlers '(:rem ("| " "SPC"))) + (after! smartparens + ;; Smartparens is broken in `cc-mode' as of Emacs 27. See + ;; . + (unless EMACS27+ + (pushnew! sp--special-self-insert-commands 'c-electric-paren 'c-electric-brace)) + + ;; 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! + (add-hook! 'after-change-major-mode-hook + (defun doom-disable-smartparens-navigate-skip-match-h () + (setq sp-navigate-skip-match nil + sp-navigate-consider-sgml-tags nil))) + + ;; Autopair quotes more conservatively; if I'm next to a word/before another + ;; quote, I likely don't want to open a new pair. + (let ((unless-list '(sp-point-before-word-p + sp-point-after-word-p + sp-point-before-same-p))) + (sp-pair "'" nil :unless unless-list) + (sp-pair "\"" nil :unless unless-list)) + + ;; Expand {|} => { | } + ;; Expand {|} => { + ;; | + ;; } + (dolist (brace '("(" "{" "[")) + (sp-pair brace nil + :post-handlers '(("||\n[i]" "RET") ("| " "SPC")) + ;; I likely don't want a new pair if adjacent to a word or opening brace + :unless '(sp-point-before-word-p sp-point-before-same-p))) + + ;; In lisps ( should open a new form if before another parenthesis + (sp-local-pair sp-lisp-modes "(" ")" :unless '(:rem sp-point-before-same-p)) + + ;; Major-mode specific fixes + (sp-local-pair '(ruby-mode enh-ruby-mode) "{" "}" + :pre-handlers '(:rem sp-ruby-pre-handler) + :post-handlers '(:rem sp-ruby-post-handler)) + + ;; Don't do square-bracket space-expansion where it doesn't make sense to + (sp-local-pair '(emacs-lisp-mode org-mode markdown-mode gfm-mode) + "[" nil :post-handlers '(:rem ("| " "SPC"))) + + ;; Reasonable default pairs for HTML-style comments + (sp-local-pair (append sp--html-modes '(markdown-mode gfm-mode)) + "" + :unless '(sp-point-before-word-p sp-point-before-same-p) + :actions '(insert) :post-handlers '(("| " "SPC"))) + + ;; Disable electric keys in C modes because it interferes with smartparens + ;; and custom bindings. We'll do it ourselves (mostly). + (after! cc-mode + (c-toggle-electric-state -1) + (c-toggle-auto-newline -1) + (setq c-electric-flag nil) + (dolist (key '("#" "{" "}" "/" "*" ";" "," ":" "(" ")" "\177")) + (define-key c-mode-base-map key nil)) + + ;; Smartparens and cc-mode both try to autoclose angle-brackets + ;; intelligently. The result isn't very intelligent (causes redundant + ;; characters), so just do it ourselves. + (define-key! c++-mode-map "<" nil ">" nil) + + (defun +default-cc-sp-point-is-template-p (id action context) + "Return t if point is in the right place for C++ angle-brackets." + (and (sp-in-code-p id action context) + (cond ((eq action 'insert) + (sp-point-after-word-p id action context)) + ((eq action 'autoskip) + (/= (char-before) 32))))) + + (defun +default-cc-sp-point-after-include-p (id action context) + "Return t if point is in an #include." + (and (sp-in-code-p id action context) + (save-excursion + (goto-char (line-beginning-position)) + (looking-at-p "[ ]*#include[^<]+")))) + + ;; ...and leave it to smartparens + (sp-local-pair '(c++-mode objc-mode) + "<" ">" + :when '(+default-cc-sp-point-is-template-p + +default-cc-sp-point-after-include-p) + :post-handlers '(("| " "SPC"))) + + (sp-local-pair '(c-mode c++-mode objc-mode java-mode) + "/*!" "*/" + :post-handlers '(("||\n[i]" "RET") ("[d-1]< | " "SPC")))) + + ;; Expand C-style doc comment blocks. Must be done manually because some of + ;; these languages use specialized (and deferred) parsers, whose state we + ;; can't access while smartparens is doing its thing. + (defun +default-expand-asterix-doc-comment-block (&rest _ignored) + (let ((indent (current-indentation))) + (newline-and-indent) + (save-excursion + (newline) + (insert (make-string indent 32) " */") + (delete-char 2)))) + (sp-local-pair + '(js2-mode typescript-mode rjsx-mode rust-mode c-mode c++-mode objc-mode + csharp-mode java-mode php-mode css-mode scss-mode less-css-mode + stylus-mode scala-mode) + "/*" "*/" + :actions '(insert) + :post-handlers '(("| " "SPC") + ("|\n[i]*/[d-2]" "RET") + (+default-expand-asterix-doc-comment-block "*"))) + + (after! smartparens-ml + (sp-with-modes '(tuareg-mode fsharp-mode) + (sp-local-pair "(*" "*)" :actions nil) + (sp-local-pair "(*" "*" + :actions '(insert) + :post-handlers '(("| " "SPC") ("|\n[i]*)[d-2]" "RET"))))) + + (after! smartparens-markdown + (sp-with-modes '(markdown-mode gfm-mode) + (sp-local-pair "```" "```" :post-handlers '(:add ("||\n[i]" "RET"))) + + ;; The original rules for smartparens had an odd quirk: inserting two + ;; asterixex would replace nearby quotes with asterixes. These two rules + ;; set out to fix this. + (sp-local-pair "**" nil :actions :rem) + (sp-local-pair "*" "*" + :actions '(insert skip) + :unless '(:rem sp-point-at-bol-p) + ;; * then SPC will delete the second asterix and assume + ;; you wanted a bullet point. * followed by another * + ;; will produce an extra, assuming you wanted **|**. + :post-handlers '(("[d1]" "SPC") ("|*" "*")))) + + ;; This keybind allows * to skip over **. + (map! :map markdown-mode-map + :ig "*" (λ! (if (looking-at-p "\\*\\* *$") + (forward-char 2) + (call-interactively 'self-insert-command))))) + + ;; Highjacks backspace to: + ;; a) balance spaces inside brackets/parentheses ( | ) -> (|) + ;; b) delete up to nearest column multiple of `tab-width' at a time + ;; c) close empty multiline brace blocks in one step: + ;; { + ;; | + ;; } + ;; becomes {|} + ;; d) refresh smartparens' :post-handlers, so SPC and RET expansions work + ;; even after a backspace. + ;; e) properly delete smartparen pairs when they are encountered, without + ;; the need for strict mode. + ;; f) do none of this when inside a string + (advice-add #'delete-backward-char :override #'+default--delete-backward-char-a)) + + ;; Makes `newline-and-indent' continue comments (and more reliably) + (advice-add #'newline-and-indent :override #'+default--newline-indent-and-continue-comments-a)) + + +;; +;;; Keybinding fixes + +;; This section is dedicated to "fixing" certain keys so that they behave +;; sensibly (and consistently with similar contexts). + +;; Consistently use q to quit windows +(after! tabulated-list + (define-key tabulated-list-mode-map "q" #'quit-window)) + +;; OS specific fixes +(when IS-MAC + ;; Fix MacOS shift+tab + (define-key key-translation-map [S-iso-lefttab] [backtab]) + ;; Fix conventional OS keys in Emacs + (map! "s-`" #'other-frame ; fix frame-switching + ;; fix OS window/frame navigation/manipulation keys + "s-w" #'delete-window + "s-W" #'delete-frame + "s-n" #'+default/new-buffer + "s-N" #'make-frame + "s-q" (if (daemonp) #'delete-frame #'save-buffers-kill-terminal) + "C-s-f" #'toggle-frame-fullscreen + ;; Restore somewhat common navigation + "s-l" #'goto-line + ;; Restore OS undo, save, copy, & paste keys (without cua-mode, because + ;; it imposes some other functionality and overhead we don't need) + "s-f" #'swiper + "s-z" #'undo + "s-Z" #'redo + "s-c" (if (featurep 'evil) #'evil-yank #'copy-region-as-kill) + "s-v" #'yank + "s-s" #'save-buffer + :v "s-x" #'kill-region + ;; Buffer-local font scaling + "s-+" #'doom/reset-font-size + "s-=" #'doom/increase-font-size + "s--" #'doom/decrease-font-size + ;; Conventional text-editing keys & motions + "s-a" #'mark-whole-buffer + "s-/" (λ! (save-excursion (comment-line 1))) + :n "s-/" #'evilnc-comment-or-uncomment-lines + :v "s-/" #'evilnc-comment-operator + :gi [s-backspace] #'doom/backward-kill-to-bol-and-indent + :gi [s-left] #'doom/backward-to-bol-or-indent + :gi [s-right] #'doom/forward-to-last-non-comment-or-eol + :gi [M-backspace] #'backward-kill-word + :gi [M-left] #'backward-word + :gi [M-right] #'forward-word)) + + +;; +;;; Keybind schemes + +;; Custom help keys -- these aren't under `+bindings' because they ought to be +;; universal. +(define-key! help-map + ;; new keybinds + "'" #'describe-char + "E" #'doom/sandbox + "M" #'doom/describe-active-minor-mode + "O" #'+lookup/online + "T" #'doom/toggle-profiler + "V" #'set-variable + "W" #'+default/man-or-woman + "C-k" #'describe-key-briefly + "C-l" #'describe-language-environment + "C-m" #'info-emacs-manual + + ;; Unbind `help-for-help'. Conflicts with which-key's help command for the + ;; h prefix. It's already on ? and F1 anyway. + "C-h" nil + + ;; replacement keybinds + ;; replaces `info-emacs-manual' b/c it's on C-m now + "r" nil + "rr" #'doom/reload + "rt" #'doom/reload-theme + "rp" #'doom/reload-packages + "rf" #'doom/reload-font + "re" #'doom/reload-env + + ;; make `describe-bindings' available under the b prefix which it previously + ;; occupied. Add more binding related commands under that prefix as well + "b" nil + "bb" #'describe-bindings + "bi" #'which-key-show-minor-mode-keymap + "bm" #'which-key-show-major-mode + "bt" #'which-key-show-top-level + "bf" #'which-key-show-full-keymap + "bk" #'which-key-show-keymap + + ;; replaces `apropos-documentation' b/c `apropos' covers this + "d" nil + "da" #'doom/help-autodefs + "db" #'doom/report-bug + "dd" #'doom/toggle-debug-mode + "df" #'doom/help-faq + "dh" #'doom/help + "dk" #'doom/goto-packages-file + "dl" #'doom/help-search-load-path + "dm" #'doom/help-modules + "dn" #'doom/help-news + "dN" #'doom/help-news-search + "di" #'doom/goto-doomblock + "dp" #'doom/help-packages + "dP" #'doom/help-package-homepage + "dc" #'doom/goto-config-file + "dC" #'doom/help-package-config + "ds" #'doom/help-search + "dx" #'doom/sandbox + "dt" #'doom/toggle-profiler + "dv" #'doom/version + + ;; replaces `apropos-command' + "a" #'apropos + "A" #'apropos-documentation + ;; replaces `describe-copying' b/c not useful + "C-c" #'describe-coding-system + ;; replaces `Info-got-emacs-command-node' b/c redundant w/ `Info-goto-node' + "F" #'describe-face + ;; replaces `view-hello-file' b/c annoying + "h" nil + ;; replaces `view-emacs-news' b/c it's on C-n too + "n" #'doom/help-news + ;; replaces `help-with-tutorial', b/c it's less useful than `load-theme' + "t" #'load-theme + ;; replaces `finder-by-keyword' b/c not useful + "p" #'doom/help-packages + ;; replaces `describe-package' b/c redundant w/ `doom/describe-package' + "P" #'find-library) + +(after! which-key + (let ((prefix-re (regexp-opt (list doom-leader-key doom-leader-alt-key)))) + (cl-pushnew `((,(format "\\`\\(?:<\\(?:\\(?:f1\\|help\\)>\\)\\|C-h\\|%s h\\) d\\'" prefix-re)) + nil . "doom") + which-key-replacement-alist) + (cl-pushnew `((,(format "\\`\\(?:<\\(?:\\(?:f1\\|help\\)>\\)\\|C-h\\|%s h\\) r\\'" prefix-re)) + nil . "reload") + which-key-replacement-alist) + (cl-pushnew `((,(format "\\`\\(?:<\\(?:\\(?:f1\\|help\\)>\\)\\|C-h\\|%s h\\) b\\'" prefix-re)) + nil . "bindings") + which-key-replacement-alist))) + + +(when (featurep! +bindings) + ;; Make M-x harder to miss + (define-key! 'override + "M-x" #'execute-extended-command + "A-x" #'execute-extended-command) + + ;; A Doom convention where C-s on popups and interactive searches will invoke + ;; ivy/helm for their superior filtering. + (define-key! :keymaps +default-minibuffer-maps + "C-s" (if (featurep! :completion ivy) + #'counsel-minibuffer-history + #'helm-minibuffer-history)) + + ;; Smarter C-a/C-e for both Emacs and Evil. C-a will jump to indentation. + ;; Pressing it again will send you to the true bol. Same goes for C-e, except + ;; it will ignore comments+trailing whitespace before jumping to eol. + (map! :gi "C-a" #'doom/backward-to-bol-or-indent + :gi "C-e" #'doom/forward-to-last-non-comment-or-eol + ;; Standardizes the behavior of modified RET to match the behavior of + ;; other editors, particularly Atom, textedit, textmate, and vscode, in + ;; which ctrl+RET will add a new "item" below the current one and + ;; cmd+RET (Mac) / meta+RET (elsewhere) will add a new, blank line below + ;; the current one. + :gni [C-return] #'+default/newline-below + :gni [C-S-return] #'+default/newline-above + (:when IS-MAC + :gni [s-return] #'+default/newline-below + :gni [S-s-return] #'+default/newline-above) + (:unless IS-MAC + :gni [M-return] #'+default/newline-below + :gni [M-S-return] #'+default/newline-above))) + + +;; +;;; Bootstrap configs + +(if (featurep 'evil) + (load! "+evil") + (load! "+emacs")) diff --git a/.emacs.d/modules/config/default/packages.el b/.emacs.d/modules/config/default/packages.el new file mode 100644 index 0000000..14a4de0 --- /dev/null +++ b/.emacs.d/modules/config/default/packages.el @@ -0,0 +1,9 @@ +;; -*- no-byte-compile: t; -*- +;;; config/default/packages.el + +(package! avy) +(package! ace-link) +(package! drag-stuff) + +(unless (featurep! :editor evil) + (package! expand-region)) diff --git a/.emacs.d/modules/config/literate/autoload.el b/.emacs.d/modules/config/literate/autoload.el new file mode 100644 index 0000000..c7226db --- /dev/null +++ b/.emacs.d/modules/config/literate/autoload.el @@ -0,0 +1,14 @@ +;;; config/literate/autoload.el -*- lexical-binding: t; -*- + +;;;###autoload +(defalias '+literate/reload #'doom/reload) + +;;;###autoload +(defun +literate-recompile-maybe-h () + "Recompile config.org if we're editing an org file in our DOOMDIR. + +We assume any org file in `doom-private-dir' is connected to your literate +config, and should trigger a recompile if changed." + (when (and (eq major-mode 'org-mode) + (file-in-directory-p buffer-file-name doom-private-dir)) + (+literate-tangle 'force))) diff --git a/.emacs.d/modules/config/literate/init.el b/.emacs.d/modules/config/literate/init.el new file mode 100644 index 0000000..21898e4 --- /dev/null +++ b/.emacs.d/modules/config/literate/init.el @@ -0,0 +1,51 @@ +;;; config/literate/init.el -*- lexical-binding: t; -*- + +(defvar +literate-config-file + (concat doom-private-dir "config.org") + "The file path of your literate config file.") + +(defvar +literate-config-cache-file + (concat doom-cache-dir "literate-last-compile") + "The file path that `+literate-config-file' will be tangled to, then +byte-compiled from.") + + +;; +(defun +literate-tangle (&optional force-p) + "Tangles `+literate-config-file' if it has changed." + (let ((default-directory doom-private-dir)) + (when (or (file-newer-than-file-p +literate-config-file + +literate-config-cache-file) + force-p) + (message "Compiling your literate config...") + (let* ((org (expand-file-name +literate-config-file)) + (dest (concat (file-name-sans-extension +literate-config-file) ".el")) + (output (get-buffer-create "*org-tangle*"))) + (unwind-protect + ;; We tangle in a separate, blank process because loading it here + ;; would load all of :lang org (very expensive!). + (or (and (zerop (call-process + "emacs" nil output nil + "-q" "--batch" + "-l" "ob-tangle" + "--eval" (format "(org-babel-tangle-file %S %S)" + org dest))) + (with-current-buffer output + (message "%s" (buffer-string)) + t) + ;; Write the cache file to serve as our mtime cache + (with-temp-file +literate-config-cache-file + (message "Done!"))) + (warn "There was a problem tangling your literate config!")) + (kill-buffer output)))))) + + +;; Let 'er rip! +(+literate-tangle (or doom-reloading-p noninteractive)) +;; No need to load the resulting file. Doom will do this for us after all +;; modules have finished loading. + + +;; Recompile our literate config if we modify it +(after! org + (add-hook 'after-save-hook #'+literate-recompile-maybe-h)) diff --git a/.emacs.d/modules/editor/evil/+commands.el b/.emacs.d/modules/editor/evil/+commands.el new file mode 100644 index 0000000..18124a4 --- /dev/null +++ b/.emacs.d/modules/editor/evil/+commands.el @@ -0,0 +1,102 @@ +;;; editor/evil/+commands.el -*- lexical-binding: t; -*- + +;; +;;; Custom commands +;; Editing +(evil-ex-define-cmd "@" #'+evil:macro-on-all-lines) ; TODO Test me +(evil-ex-define-cmd "R[ead]" #'+evil:read) +(evil-ex-define-cmd "al[ign]" #'+evil:align) +(evil-ex-define-cmd "ral[ign]" #'+evil:align-right) +(evil-ex-define-cmd "enhtml" #'+web:encode-html-entities) +(evil-ex-define-cmd "dehtml" #'+web:decode-html-entities) +(evil-ex-define-cmd "mc" #'+multiple-cursors:evil-mc) +(evil-ex-define-cmd "iedit" #'evil-multiedit-ex-match) +(evil-ex-define-cmd "na[rrow]" #'+evil:narrow-buffer) +(evil-ex-define-cmd "retab" #'+evil:retab) +(evil-ex-define-cmd "rev[erse]" #'+evil:reverse-lines) +(evil-ex-define-cmd "l[ine]diff" #'evil-quick-diff) + +;;; External resources +;; TODO (evil-ex-define-cmd "db" #'doom:db) +;; TODO (evil-ex-define-cmd "dbu[se]" #'doom:db-select) +;; TODO (evil-ex-define-cmd "go[ogle]" #'doom:google-search) +(evil-ex-define-cmd "lo[okup]" #'+lookup:online) +(evil-ex-define-cmd "dash" #'+lookup:dash) +(evil-ex-define-cmd "http" #'httpd-start) ; start http server +(evil-ex-define-cmd "repl" #'+eval:repl) ; invoke or send to repl +(evil-ex-define-cmd "h[elp]" #'+evil:help) + +;; TODO (evil-ex-define-cmd "rx" 'doom:regex) ; open re-builder +(evil-ex-define-cmd "sh[ell]" #'+eshell:run) +(evil-ex-define-cmd "t[mux]" #'+tmux:run) ; send to tmux +(evil-ex-define-cmd "tcd" #'+tmux:cd-here) ; cd to default-directory in tmux +(evil-ex-define-cmd "pad" #'+evil:open-scratch-buffer) + +;;; GIT +(evil-ex-define-cmd "gist" #'+gist:send) ; send current buffer/region to gist +(evil-ex-define-cmd "gistl" #'+gist:list) ; list gists by user +(evil-ex-define-cmd "gbrowse" #'+vc:git-browse) ; show file/region in github/gitlab +(evil-ex-define-cmd "gissues" #'forge-browse-issues) ; show github issues +(evil-ex-define-cmd "git" #'magit-status) ; open magit status window +(evil-ex-define-cmd "gstage" #'magit-stage) +(evil-ex-define-cmd "gunstage" #'magit-unstage) +(evil-ex-define-cmd "gblame" #'magit-blame) +(evil-ex-define-cmd "grevert" #'git-gutter:revert-hunk) + +;;; Dealing with buffers +(evil-ex-define-cmd "k[ill]" #'doom/kill-current-buffer) +(evil-ex-define-cmd "k[ill]all" #'+evil:kill-all-buffers) +(evil-ex-define-cmd "k[ill]m" #'+evil:kill-matching-buffers) +(evil-ex-define-cmd "k[ill]o" #'doom/kill-other-buffers) +(evil-ex-define-cmd "k[ill]b" #'doom/kill-buried-buffers) +(evil-ex-define-cmd "l[ast]" #'doom/popup-restore) +(evil-ex-define-cmd "messages" #'view-echo-area-messages) +(evil-ex-define-cmd "pop[up]" #'doom/popup-this-buffer) + +;;; Project navigation +(evil-ex-define-cmd "a" #'projectile-find-other-file) +(evil-ex-define-cmd "cd" #'+evil:cd) +(evil-ex-define-cmd "pwd" #'+evil:pwd) + +(evil-define-command +evil:swiper (&optional search) + "Invoke `swiper' with SEARCH, otherwise with the symbol at point." + (interactive "") + (swiper-isearch search)) +(evil-ex-define-cmd "sw[iper]" #'+evil:swiper) + +(cond ((featurep! :completion ivy) + (evil-ex-define-cmd "pg[rep]" #'+ivy:project-search) + (evil-ex-define-cmd "pg[grep]d" #'+ivy:project-search-from-cwd)) + + ((featurep! :completion helm) + (evil-ex-define-cmd "pg[rep]" #'+helm:project-search) + (evil-ex-define-cmd "pg[grep]d" #'+helm:project-search-from-cwd))) + +;;; Project tools +(evil-ex-define-cmd "compile" #'+evil:compile) +(evil-ex-define-cmd "mak[e]" #'+evil:make) +(evil-ex-define-cmd "debug" #'+debugger/start) +(evil-ex-define-cmd "er[rors]" #'flycheck-list-errors) + +;;; File operations +(evil-ex-define-cmd "cp" #'+evil:copy-this-file) +(evil-ex-define-cmd "mv" #'+evil:move-this-file) +(evil-ex-define-cmd "rm" #'+evil:delete-this-file) + +;;; Sessions/tabs +(evil-ex-define-cmd "sclear" #'+workspace/kill-session) +(evil-ex-define-cmd "sl[oad]" #'doom/quickload-session) +(evil-ex-define-cmd "ss[ave]" #'doom/quicksave-session) +(evil-ex-define-cmd "tabc[lose]" #'+workspace:delete) +(evil-ex-define-cmd "tabclear" #'doom/kill-all-buffers) +(evil-ex-define-cmd "tabl[ast]" #'+workspace/switch-to-last) +(evil-ex-define-cmd "tabload" #'+workspace:load) +(evil-ex-define-cmd "tabn[ew]" #'+workspace:new) +(evil-ex-define-cmd "tabn[ext]" #'+workspace:switch-next) +(evil-ex-define-cmd "tabp[rev]" #'+workspace:switch-previous) +(evil-ex-define-cmd "tabr[ename]" #'+workspace:rename) +(evil-ex-define-cmd "tabs" #'+workspace/display) +(evil-ex-define-cmd "tabsave" #'+workspace:save) + +;;; Org-mode +(evil-ex-define-cmd "cap" #'org-capture) diff --git a/.emacs.d/modules/editor/evil/+everywhere.el b/.emacs.d/modules/editor/evil/+everywhere.el new file mode 100644 index 0000000..c5e1312 --- /dev/null +++ b/.emacs.d/modules/editor/evil/+everywhere.el @@ -0,0 +1,238 @@ +;;; editor/evil/+everywhere.el -*- lexical-binding: t; -*- + +;; We load evil-collection ourselves for these reasons: +;; +;; 1. To truly lazy load it. Some of its modules, like +;; evil-collection-{elisp-mode,buff-menu} are loaded immediately, because +;; Emacs loads their packages immediately, which pulls in all of +;; evil-collection (and other packages with it, sometimes). +;; 2. This ensures a predictable load order, versus lazy loading using :defer or +;; :after-call. This means users can use (after! org ...) and be sure that +;; their changes will override evil-collection's. +;; 3. Ideally, we'd do away with evil-collection entirely. It changes too often, +;; introduces breaking bugs too frequently, and I don't agree with all their +;; design choices. Regardless, it does mork than it causes trouble, so it may +;; be here to stay. +;; 4. Adds `+evil-collection-disabled-list', to make it easier for users to +;; disable modules, and to reduce the effort required to maintain our copy of +;; `evil-collection-list' (now I can just copy it from time to time). + +(defvar +evil-collection-disabled-list + '(anaconda-mode + buff-menu + comint + company + custom + eldoc + elisp-mode + ert + free-keys + help + helm + image + kotlin-mode + occur + package-menu + ruby-mode + simple + slime) + "A list of `evil-collection' modules to ignore. See the definition of this +variable for an explanation of the defaults (in comments). See +`evil-collection-mode-list' for a list of available options.") + +(defvar evil-collection-setup-minibuffer nil) + +;; We do this ourselves, and better. +(defvar evil-collection-want-unimpaired-p nil) + +;; This has to be defined here since evil-collection doesn't autoload its own. +;; It must be updated whenever evil-collection updates theirs. Here's an easy +;; way to update it: +;; +;; (with-current-buffer +;; (url-retrieve-synchronously "https://raw.githubusercontent.com/emacs-evil/evil-collection/master/evil-collection.el" t t) +;; (goto-char (point-min)) +;; (when (re-search-forward "^(defvar evil-collection--supported-modes\n[^(]+") +;; (let ((list (sexp-at-point))) +;; ;; Fixes +;; (when (assq 'pdf list) +;; (setf (alist-get 'pdf list) '(pdf-tools))) +;; (kill-new (prin1-to-string list))))) + +(defvar evil-collection-mode-list + `(2048-game + ag + alchemist + anaconda-mode + apropos + arc-mode + bookmark + (buff-menu "buff-menu") + calc + calendar + cider + cmake-mode + comint + company + compile + (custom cus-edit) + cus-theme + daemons + deadgrep + debbugs + debug + diff-mode + dired + disk-usage + doc-view + docker + ebib + edbi + edebug + ediff + eglot + elfeed + elisp-mode + elisp-refs + elisp-slime-nav + emms + epa + ert + eshell + eval-sexp-fu + evil-mc + eww + flycheck + flymake + free-keys + geiser + ggtags + git-timemachine + go-mode + grep + guix + hackernews + helm + help + helpful + hg-histedit + hungry-delete + ibuffer + image + image-dired + image+ + imenu-list + indium + info + ivy + js2-mode + leetcode + log-edit + log-view + lsp-ui-imenu + lua-mode + kotlin-mode + macrostep + man + magit + magit-todos + ,@(if evil-collection-setup-minibuffer '(minibuffer)) + monky + mu4e + mu4e-conversation + neotree + notmuch + nov + (occur replace) + omnisharp + outline + p4 + (package-menu package) + pass + (pdf pdf-tools) + popup + proced + process-menu + prodigy + profiler + python + quickrun + racer + realgud + reftex + restclient + rjsx-mode + robe + rtags + ruby-mode + simple + slime + sly + tablist + (term term ansi-term multi-term) + tetris + tide + transmission + typescript-mode + vc-annotate + vc-dir + vc-git + vdiff + view + vlf + vterm + w3m + wdired + wgrep + which-key + woman + xref + youtube-dl + (ztree ztree-diff))) + +(defun +evil-collection-init (module &optional disabled-list) + "Initialize evil-collection-MODULE. + +Unlike `evil-collection-init', this respects `+evil-collection-disabled-list', +and complains if a module is loaded too early (during startup)." + (unless (memq (or (car-safe module) module) disabled-list) + (doom-log "Initialized evil-collection-%s %s" + (or (car-safe module) module) + (if doom-init-time "" "(too early!)")) + (with-demoted-errors "evil-collection error: %s" + (evil-collection-init (list module))))) + + +;; +;;; Bootstrap + +;; These modes belong to packages that Emacs always loads at startup, causing +;; evil-collection to load immediately. We avoid this by loading them after +;; evil-collection has first loaded... +(with-eval-after-load 'evil-collection + (mapc #'+evil-collection-init '(comint custom help))) + +;; ...or on first invokation of their associated major/minor modes. +(add-transient-hook! 'Buffer-menu-mode + (+evil-collection-init '(buff-menu "buff-menu"))) +(add-transient-hook! 'image-mode + (+evil-collection-init 'image)) +(add-transient-hook! 'emacs-lisp-mode + (+evil-collection-init 'elisp-mode)) +(add-transient-hook! 'occur-mode + (+evil-collection-init 'replace)) + +(evil-define-key* 'normal process-menu-mode-map + "q" #'kill-current-buffer + "d" #'process-menu-delete-process) + +;; Don't overwrite the leader keys +(setq evil-collection-key-blacklist + (list doom-leader-key doom-localleader-key + doom-leader-alt-key doom-localleader-alt-key)) + +;; Load the rest +(dolist (mode evil-collection-mode-list) + (dolist (req (or (cdr-safe mode) (list mode))) + (with-eval-after-load req + (+evil-collection-init mode +evil-collection-disabled-list)))) diff --git a/.emacs.d/modules/editor/evil/README.org b/.emacs.d/modules/editor/evil/README.org new file mode 100644 index 0000000..38563af --- /dev/null +++ b/.emacs.d/modules/editor/evil/README.org @@ -0,0 +1,176 @@ +#+TITLE: feature/evil +#+DATE: February 2, 2017 +#+SINCE: v2.0 +#+STARTUP: inlineimages + +* Table of Contents :TOC_3:noexport: +- [[#description][Description]] + - [[#module-flags][Module Flags]] + - [[#plugins][Plugins]] + - [[#hacks][Hacks]] +- [[#prerequisites][Prerequisites]] +- [[#features][Features]] + - [[#ported-vim-plugins][Ported vim plugins]] + - [[#custom-text-objects][Custom Text Objects]] + - [[#custom-ex-commands][Custom Ex Commands]] +- [[#configuration][Configuration]] + - [[#removing-evil-mode][Removing evil-mode]] + - [[#restoring-old-substitution-behavior-on-ss][Restoring old substitution behavior on s/S]] + +* Description +This holy module brings the vim experience to Emacs. + +** Module Flags ++ =+everywhere= Enables evilified keybinds everywhere possible. Uses the + [[https://github.com/emacs-evil/evil-collection][evil-collection]] plugin as a foundation. + +** Plugins ++ [[https://github.com/emacs-evil/evil][evil]] ++ [[https://github.com/wcsmith/evil-args][evil-args]] ++ [[https://github.com/PythonNut/evil-easymotion][evil-easymotion]] ++ [[https://github.com/cute-jumper/evil-embrace.el][evil-embrace]] ++ [[https://github.com/syl20bnr/evil-escape][evil-escape]] ++ [[https://github.com/Dewdrops/evil-exchange][evil-exchange]] ++ [[https://github.com/TheBB/evil-indent-plus][evil-indent-plus]] ++ [[https://github.com/redguardtoo/evil-nerd-commenter][evil-nerd-commentary]] ++ [[https://github.com/redguardtoo/evil-matchit][evil-matchit]] ++ [[https://github.com/cofi/evil-numbers][evil-numbers]] ++ [[https://github.com/noctuid/evil-textobj-anyblock][evil-textobj-anyblock]] ++ [[https://github.com/hlissner/evil-snipe][evil-snipe]] ++ [[https://github.com/emacs-evil/evil-surround][evil-surround]] ++ [[https://github.com/alexmurray/evil-vimish-fold][evil-vimish-fold]] ++ [[https://github.com/bling/evil-visualstar][evil-visualstar]] ++ [[https://github.com/ninrod/exato][exato]] ++ [[https://github.com/emacs-evil/evil-collection][evil-collection]]* ++ [[https://www.github.com/rgrinberg/evil-quick-diff][evil-quick-diff]] + +** Hacks ++ The o/O keys will respect and continue commented lines (can be disabled by + setting ~+evil-want-o/O-to-continue-comments~ to ~nil~). ++ In visual mode, =*= and =#= will search for the current selection instead of + the word-at-point. ++ The ~:g[lobal]~ ex command has been modified to highlight matches. ++ More of vim's filename modifiers are supported in ex commands (like ~:p~, + ~:p:h~ or ~:t~) than vanilla evil-mode offers. ++ A custom filename modifier is available in Doom: ~:P~, which expands to the + project root (throws an error if not in a project). + +* Prerequisites +This module has no external prerequisites. + +* Features +** Ported vim plugins +The following vim plugins have been ported to evil: + +| Vim Plugin | Emacs Plugin | Keybind(s) | +|-----------------------+--------------------------------+--------------------------------------------| +| vim-commentary | evil-nerd-commenter | omap =gc= | +| vim-easymotion | evil-easymotion | omap =gs= | +| vim-seek or vim-sneak | evil-snipe | mmap =s= / =S=, omap =z= / =Z= & =x= / =X= | +| vim-surround | evil-embrace and evil-surround | vmap =S=, omap =ys= | + +This module has also ported vim-unimpaired keybinds to Emacs. + +In other modules: ++ The tools/neotree & tools/treemacs modules provide a =NERDTree= equivalent. ++ The editor/multiple-cursors module contains functionality equal to the + following vim plugins: + + evil-multiedit => vim-multiedit + + evil-mc => vim-multiple-cursors + +** Custom Text Objects +This module provides a couple extra text objects, along with the built-in ones. +For posterity, here are the built-in ones: + ++ =w W= words ++ =s= sentences ++ =p= paragraphs ++ =b= parenthesized blocks ++ =b ( ) { } [ ] < >= braces, parentheses and brackets ++ =' " `= quotes ++ =t= tags ++ =o= symbols + +And these are text objects added by this module: + ++ =a= C-style function arguments (provided by ~evil-args~) ++ =B= any block delimited by braces, parentheses or brackets (provided by + ~evil-textobj-anyblock~) ++ =i j k= by indentation (=k= includes one line above; =j= includes one line + above and below) (provided by ~evil-indent-plus~) ++ =x= XML attributes (provided by ~exato~) + +** Custom Ex Commands +| Ex Command | Description | +|-----------------------+--------------------------------------------------------------------------------------| +| ~:@~ | Apply macro on selected lines | +| ~:ag[!] REGEXP~ | Perform a project search with ag | +| ~:agcwd[!] REGEXP~ | Perform a project search with ag from the current directory | +| ~:al[ign][!] REGEXP~ | Align text to the first match of REGEXP. If BANG, align all matches on each line | +| ~:cp[!] NEWPATH~ | Copy the current file to NEWPATH | +| ~:dash QUERY~ | Look up QUERY (or the symbol at point) in dash docsets | +| ~:dehtml [INPUT]~ | HTML decode selected text / inserts result if INPUT is given | +| ~:enhtml [INPUT]~ | HTML encode selected text / inserts result if INPUT is given | +| ~:grep[!]~ | Perform a project search with git-grep | +| ~:grepcwd[!]~ | Perform a project search with git-grep from the current directory | +| ~:iedit REGEXP~ | Invoke iedit on all matches for REGEXP | +| ~:k[ill]all[!]~ | Kill all buffers (if BANG, affect buffer across workspaces) | +| ~:k[ill]b~ | Kill all buried buffers | +| ~:k[ill]m[!] REGEXP~ | Kill buffers whose name matches REGEXP (if BANG, affect buffers across workspaces) | +| ~:k[ill]o~ | Kill all other buffers besides the selected one | +| ~:k[ill]~ | Kill the current buffer | +| ~:lo[okup] QUERY~ | Look up QUERY on an online search engine | +| ~:mc REGEXP~ | Invoke multiple cursors on all matches for REGEXP | +| ~:mv[!] NEWPATH~ | Move the current file to NEWPATH | +| ~:na[rrow]~ | Narrow the buffer to the selection | +| ~:pad~ | Open a scratch pad for running code quickly | +| ~:ral[ign][!] REGEXP~ | Right-Align text that matches REGEXP. If BANG, align all matches on each line | +| ~:repl~ | Open a REPL and/or copy the current selection to it | +| ~:retab~ | Convert indentation to the default within the selection | +| ~:rev[erse]~ | Reverse the selected lines | +| ~:rg[!]~ | Perform a project search with ripgrep | +| ~:rgcwd[!]~ | Perform a project search with ripgrep from the current directory | +| ~:rm[!] [PATH]~ | Delete the current buffer's file and buffer | +| ~:tcd[!]~ | Send =cd X= to tmux. X = the project root if BANG, X = ~default-directory~ otherwise | + +* Configuration +** Removing evil-mode +You must do two things to remove Evil: + +1. Remove =:editor evil= from =~/.doom.d/init.el=, +2. Run ~doom refresh~ to clean up lingering dependencies and refresh your + autoloads files. +3. [OPTIONAL] You may want to assign new values to ~doom-leader-alt-key~ and + ~doom-localleader-alt-key~. These are bound to =C-c= and =C-c l= by default. + +#+begin_quote +Ignore ~doom-leader-key~ and ~doom-localleader-key~, they don't apply to +non-evil sessions. +#+end_quote + +Evil-specific configuration and keybindings (defined with ~map!~) will be +ignored without =:editor evil= present (and omitted when byte-compiling). + +Keep in mind that, at the time of this writing, Doom was designed by a vimmer, +for vimmers. Little consideration has been put into designing a keybind scheme +for vanilla Emacs users (though it's being worked on!). + +That means that much of Doom's functionality will be orphaned in an evil-less +setup. You'll have to set your own keybinds. + +I suggest studying [[file:../../config/default/+emacs-bindings.el][config/default/+emacs-bindings.el]] to see what keybinds are +available for non-evil users. Otherwise, you may find inspiration [[file:../../../docs/example_configs.org][on the example +Doom configurations page]]. + +** Restoring old substitution behavior on s/S +Doom replaces the =s= and =S= keys with the =evil-snipe= package (a port of +vim-seek/vim-sneak for 2-character versions of f/F/t/T). + +To disable evil-snipe on s/S, you can either: + +1. Disable ~evil-snipe-mode~ by adding ~(after! evil-snipe (evil-snipe-mode + -1))~ to =$DOOMDIR/config.el=, +2. Or disable =evil-snipe= completely with ~(package! evil-snipe :disable t)~ + added to =$DOOMDIR/packages.el=, but this will also disable incremental + highlighting for the f/F/t/T motions keys. +3. Or use =cl= and =cc=, respectively; they do the same thing. diff --git a/.emacs.d/modules/editor/evil/autoload/advice.el b/.emacs.d/modules/editor/evil/autoload/advice.el new file mode 100644 index 0000000..d1aed1b --- /dev/null +++ b/.emacs.d/modules/editor/evil/autoload/advice.el @@ -0,0 +1,198 @@ +;;; editor/evil/autoload/advice.el -*- lexical-binding: t; -*- + +;;;###autoload +(defun +evil-escape-a (&rest _) + "Call `doom/escape' if `evil-force-normal-state' is called interactively." + (when (called-interactively-p 'any) + (call-interactively #'doom/escape))) + +;;;###autoload +(defun +evil-resolve-vim-path-a (file-name) + "Take a path and resolve any vim-like filename modifiers in it. This adds +support for most vim file modifiers, as well as: + + %:P Resolves to `doom-project-root'. + +See http://vimdoc.sourceforge.net/htmldoc/cmdline.html#filename-modifiers for +more information on 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 ((project-root (doom-project-root (file-name-directory (expand-file-name path))))) + (unless project-root + (user-error "Not in a project")) + (abbreviate-file-name 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))) + +(defun +evil--insert-newline (&optional above _noextranewline) + (let ((pos (save-excursion (beginning-of-line-text) (point))) + comment-auto-fill-only-comments) + (require 'smartparens) + (evil-narrow-to-field + (if above + (if (save-excursion (nth 4 (sp--syntax-ppss pos))) + (evil-save-goal-column + (setq evil-auto-indent nil) + (goto-char pos) + (let ((ws (abs (skip-chars-backward " \t")))) + ;; FIXME oh god why + (save-excursion + (if comment-line-break-function + (funcall comment-line-break-function nil) + (comment-indent-new-line)) + (when (and (derived-mode-p 'c-mode 'c++-mode 'objc-mode 'java-mode 'js2-mode) + (eq (char-after) ?/)) + (insert "*")) + (insert + (make-string (max 0 (+ ws (skip-chars-backward " \t"))) + 32))) + (insert (make-string (max 1 ws) 32)))) + (evil-move-beginning-of-line) + (insert (if use-hard-newlines hard-newline "\n")) + (forward-line -1) + (back-to-indentation)) + (evil-move-end-of-line) + (cond ((sp-point-in-comment pos) + (setq evil-auto-indent nil) + (if comment-line-break-function + (funcall comment-line-break-function) + (comment-indent-new-line))) + ;; TODO Find a better way to do this + ((and (eq major-mode 'haskell-mode) + (fboundp 'haskell-indentation-newline-and-indent)) + (setq evil-auto-indent nil) + (haskell-indentation-newline-and-indent)) + (t + (insert (if use-hard-newlines hard-newline "\n")) + (back-to-indentation))))))) + +;;;###autoload +(defun +evil--insert-newline-below-and-respect-comments-a (orig-fn count) + (if (or (not +evil-want-o/O-to-continue-comments) + (not (eq this-command 'evil-open-below)) + (evil-insert-state-p)) + (funcall orig-fn count) + (cl-letf (((symbol-function 'evil-insert-newline-below) + (lambda () (+evil--insert-newline)))) + (let ((evil-auto-indent evil-auto-indent)) + (funcall orig-fn count))))) + +;;;###autoload +(defun +evil--insert-newline-above-and-respect-comments-a (orig-fn count) + (if (or (not +evil-want-o/O-to-continue-comments) + (not (eq this-command 'evil-open-above)) + (evil-insert-state-p)) + (funcall orig-fn count) + (cl-letf (((symbol-function 'evil-insert-newline-above) + (lambda () (+evil--insert-newline 'above)))) + (let ((evil-auto-indent evil-auto-indent)) + (funcall orig-fn count))))) + +;;;###autoload (autoload '+evil-window-split-a "editor/evil/autoload/advice" nil t) +(evil-define-command +evil-window-split-a (&optional count file) + "Same as `evil-window-split', but correctly updates the window history." + :repeat nil + (interactive "P") + ;; HACK This ping-ponging between the destination and source windows is to + ;; update the window focus history, so that, if you close either split + ;; afterwards you won't be sent to some random window. + (let ((doom-inhibit-switch-window-hooks t) + (origwin (selected-window))) + (select-window (split-window origwin count 'below)) + (unless evil-split-window-below + (select-window origwin)) + (run-hooks 'doom-switch-window-hook)) + (recenter) + (when (and (not count) evil-auto-balance-windows) + (balance-windows (window-parent))) + (if file (evil-edit file))) + +;;;###autoload (autoload '+evil-window-vsplit-a "editor/evil/autoload/advice" nil t) +(evil-define-command +evil-window-vsplit-a (&optional count file) + "Same as `evil-window-split', but correctly updates the window history." + :repeat nil + (interactive "P") + ;; HACK This ping-ponging between the destination and source windows is to + ;; update the window focus history, so that, if you close either split + ;; afterwards you won't be sent to some random window. + (let ((doom-inhibit-switch-window-hooks t) + (origwin (selected-window))) + (select-window (split-window origwin count 'right)) + (unless evil-vsplit-window-right + (select-window origwin)) + (run-hooks 'doom-switch-window-hook)) + (run-hooks) + (recenter) + (when (and (not count) evil-auto-balance-windows) + (balance-windows (window-parent))) + (if file (evil-edit file))) + +;;;###autoload +(defun +evil--fix-dabbrev-in-minibuffer-h () + "Make `try-expand-dabbrev' from `hippie-expand' work in minibuffer. See +`he-dabbrev-beg', so we need to redefine syntax for '/'." + (set-syntax-table (let* ((table (make-syntax-table))) + (modify-syntax-entry ?/ "." table) + table))) diff --git a/.emacs.d/modules/editor/evil/autoload/embrace.el b/.emacs.d/modules/editor/evil/autoload/embrace.el new file mode 100644 index 0000000..37a825e --- /dev/null +++ b/.emacs.d/modules/editor/evil/autoload/embrace.el @@ -0,0 +1,39 @@ +;;; editor/evil/autoload/embrace.el -*- lexical-binding: t; -*- + +;;;###autoload +(defun +evil--embrace-get-pair (char) + (if-let* ((pair (cdr-safe (assoc (string-to-char char) evil-surround-pairs-alist)))) + pair + (if-let* ((pair (assoc-default char embrace--pairs-list))) + (if-let* ((real-pair (and (functionp (embrace-pair-struct-read-function pair)) + (funcall (embrace-pair-struct-read-function pair))))) + real-pair + (cons (embrace-pair-struct-left pair) (embrace-pair-struct-right pair))) + (cons char char)))) + +;;;###autoload +(defun +evil--embrace-escaped () + "Backslash-escaped surround character support for embrace." + (let ((char (read-char "\\"))) + (if (eq char 27) + (cons "" "") + (let ((pair (+evil--embrace-get-pair (string char))) + (text (if (sp-point-in-string) "\\\\%s" "\\%s"))) + (cons (format text (car pair)) + (format text (cdr pair))))))) + +;;;###autoload +(defun +evil--embrace-latex () + "LaTeX command support for embrace." + (cons (format "\\%s{" (read-string "\\")) "}")) + +;;;###autoload +(defun +evil--embrace-elisp-fn () + "Elisp function support for embrace." + (cons (format "(%s " (or (read-string "(") "")) ")")) + +;;;###autoload +(defun +evil--embrace-angle-brackets () + "Type/generic angle brackets." + (cons (format "%s<" (or (read-string "") "")) + ">")) diff --git a/.emacs.d/modules/editor/evil/autoload/evil.el b/.emacs.d/modules/editor/evil/autoload/evil.el new file mode 100644 index 0000000..ccbdebf --- /dev/null +++ b/.emacs.d/modules/editor/evil/autoload/evil.el @@ -0,0 +1,180 @@ +;; editor/evil/autoload/evil.el -*- lexical-binding: t; -*- + +;;;###autodef +(defun set-evil-initial-state! (modes state) + "Set the initialize STATE of MODES using `evil-set-initial-state'." + (declare (indent defun)) + (after! evil + (if (listp modes) + (dolist (mode (doom-enlist modes)) + (evil-set-initial-state mode state)) + (evil-set-initial-state modes state)))) + + +;; +;;; Interactive commands + +;;;###autoload +(defun +evil/visual-indent () + "vnoremap < >gv" + (interactive) + (evil-shift-left (region-beginning) (region-end)) + (evil-normal-state) + (evil-visual-restore)) + +;;;###autoload +(defun +evil/paste-preserve-register () + "Call `evil-paste-after' without overwriting the clipboard (by writing to the +0 register instead). This allows you to paste the same text again afterwards." + (interactive) + (let ((evil-this-register ?0)) + (call-interactively #'evil-paste-after))) + +(defun +evil--window-swap (direction) + "Move current window to the next window in DIRECTION. +If there are no windows there and there is only one window, split in that +direction and place this window there. If there are no windows and this isn't +the only window, use evil-window-move-* (e.g. `evil-window-move-far-left')." + (when (window-dedicated-p) + (user-error "Cannot swap a dedicated window")) + (let* ((this-window (selected-window)) + (this-buffer (current-buffer)) + (that-window (windmove-find-other-window direction nil this-window)) + (that-buffer (window-buffer that-window))) + (when (or (minibufferp that-buffer) + (window-dedicated-p this-window)) + (setq that-buffer nil that-window nil)) + (if (not (or that-window (one-window-p t))) + (funcall (pcase direction + ('left #'evil-window-move-far-left) + ('right #'evil-window-move-far-right) + ('up #'evil-window-move-very-top) + ('down #'evil-window-move-very-bottom))) + (unless that-window + (setq that-window + (split-window this-window nil + (pcase direction + ('up 'above) + ('down 'below) + (_ direction)))) + (with-selected-window that-window + (switch-to-buffer (doom-fallback-buffer))) + (setq that-buffer (window-buffer that-window))) + (with-selected-window this-window + (switch-to-buffer that-buffer)) + (with-selected-window that-window + (switch-to-buffer this-buffer)) + (select-window that-window)))) + +;;;###autoload +(defun +evil/window-move-left () "See `+evil--window-swap'" (interactive) (+evil--window-swap 'left)) +;;;###autoload +(defun +evil/window-move-right () "See `+evil--window-swap'" (interactive) (+evil--window-swap 'right)) +;;;###autoload +(defun +evil/window-move-up () "See `+evil--window-swap'" (interactive) (+evil--window-swap 'up)) +;;;###autoload +(defun +evil/window-move-down () "See `+evil--window-swap'" (interactive) (+evil--window-swap 'down)) + +;;;###autoload +(defun +evil/easymotion () + "Invoke and lazy-load `evil-easymotion' without compromising which-key +integration." + (interactive) + (let ((prefix (this-command-keys))) + (evil-define-key* 'motion 'global prefix nil) + (evilem-default-keybindings (key-description prefix)) + (setq prefix-arg current-prefix-arg + unread-command-events + (mapcar (lambda (e) (cons t e)) + (vconcat (when evil-this-operator + (where-is-internal evil-this-operator + evil-normal-state-map + t)) + prefix))))) + +;;;###autoload (autoload '+evil:apply-macro "editor/evil/autoload/evil" nil t) +(evil-define-operator +evil:apply-macro (beg end) + "Apply macro to each line." + :move-point nil + (interactive "") + (let ((register (or evil-this-register (read-char))) + macro) + (cond ((or (and (eq register ?@) (eq evil-last-register ?:)) + (eq register ?:)) + (setq macro (lambda () (evil-ex-repeat nil)) + evil-last-register ?:)) + ((eq register ?@) + (unless evil-last-register + (user-error "No previously executed keyboard macro.")) + (setq macro (evil-get-register evil-last-register t))) + ((setq macro (evil-get-register register t) + evil-last-register register))) + (unless macro + (user-error "No macro recorded in %c register" register)) + (evil-change-state 'normal) + (evil-with-single-undo + (let ((lines (count-lines beg end))) + (message "Applied macro in %c register %d times" register lines) + (apply-macro-to-region-lines beg end macro) + (message "Applied macro in %c register %d times...DONE" register lines))))) + +;;;###autoload (autoload '+evil:retab "editor/evil/autoload/evil" nil t) +(evil-define-operator +evil:retab (&optional beg end) + "Wrapper around `doom/retab'." + :motion nil :move-point nil :type line + (interactive "") + (doom/retab beg end)) + +;;;###autoload (autoload '+evil:narrow-buffer "editor/evil/autoload/evil" nil t) +(evil-define-operator +evil:narrow-buffer (beg end &optional bang) + "Narrow the buffer to region between BEG and END. + +Widens narrowed buffers first. If BANG, use indirect buffer clones instead." + :move-point nil + (interactive "") + (if (not bang) + (if (buffer-narrowed-p) + (widen) + (narrow-to-region beg end)) + (when (buffer-narrowed-p) + (doom/widen-indirectly-narrowed-buffer t)) + (doom/narrow-buffer-indirectly beg end))) + +;;;###autoload (autoload '+evil:yank-unindented "editor/evil/autoload/evil" nil t) +(evil-define-operator +evil:yank-unindented (beg end _type _register _yank-handler) + "Saves the (reindented) characters in motion into the kill-ring." + :move-point nil + :repeat nil + (interactive "") + (let ((indent (save-excursion (goto-char beg) (current-indentation))) + (text (buffer-substring beg end))) + (with-temp-buffer + (insert text) + (indent-rigidly (point-min) (point-max) (- indent)) + (evil-yank (point-min) (point-max))))) + + +;; +;;; wgrep + +;;;###autoload (autoload '+evil-delete "editor/evil/autoload/evil" nil t) +(evil-define-operator +evil-delete (beg end type register yank-handler) + "A wrapper around `evil-delete' for `wgrep' buffers that will invoke +`wgrep-mark-deletion' on lines you try to delete." + (interactive "") + (condition-case _ex + (evil-delete beg end type register yank-handler) + ('text-read-only + (evil-apply-on-block + (lambda (beg _) + (goto-char beg) + (call-interactively #'wgrep-mark-deletion)) + beg (1- end) nil)))) diff --git a/.emacs.d/modules/editor/evil/autoload/ex.el b/.emacs.d/modules/editor/evil/autoload/ex.el new file mode 100644 index 0000000..776b139 --- /dev/null +++ b/.emacs.d/modules/editor/evil/autoload/ex.el @@ -0,0 +1,190 @@ +;;; editor/evil/autoload/ex.el -*- lexical-binding: t; -*- + +(defvar +evil--flag nil) + +(defun +evil--ex-match-init (name &optional face update-hook) + (with-current-buffer evil-ex-current-buffer + (cond + ((eq +evil--flag 'start) + (evil-ex-make-hl name + :face (or face 'evil-ex-lazy-highlight) + :update-hook (or update-hook #'evil-ex-pattern-update-ex-info)) + (setq +evil--flag 'update)) + + ((eq +evil--flag 'stop) + (evil-ex-delete-hl name))))) + +(defun +evil--ex-buffer-match (arg &optional hl-name flags beg end) + (when (and (eq +evil--flag 'update) + evil-ex-substitute-highlight-all + (not (zerop (length arg)))) + (condition-case lossage + (let* ((pattern (evil-ex-make-substitute-pattern + arg + (or flags (list)))) + (range (or (evil-copy-range evil-ex-range) + (evil-range (or beg (line-beginning-position)) + (or end (line-end-position)) + 'line + :expanded t)))) + (evil-expand-range range) + (evil-ex-hl-set-region hl-name + (max (evil-range-beginning range) (window-start)) + (min (evil-range-end range) (window-end))) + (evil-ex-hl-change hl-name pattern)) + (end-of-file + (evil-ex-pattern-update-ex-info nil "incomplete replacement")) + (user-error + (evil-ex-pattern-update-ex-info nil (format "?%s" lossage)))))) + +;;;###autoload +(defun +evil-ex-regexp-match (flag &optional arg invert) + (let ((hl-name 'evil-ex-buffer-match) + (+evil--flag flag)) + (with-selected-window (minibuffer-selected-window) + (+evil--ex-match-init hl-name) + (cl-destructuring-bind (&optional arg flags) + (evil-delimited-arguments arg 2) + (let ((evil-ex-substitute-global + (if invert + (not evil-ex-substitute-global) + evil-ex-substitute-global))) + (+evil--ex-buffer-match + arg hl-name (string-to-list flags))))))) + + +;; +;;; Ex Commands + +;;;###autoload (autoload '+evil:align "editor/evil/autoload/ex" nil t) +(evil-define-command +evil:align (beg end pattern &optional flags) + "Ex interface to `align-regexp'. + +PATTERN is a vim-style regexp. FLAGS is an optional string of characters. +Supports the following flags: + +g Repeat alignment on all matches in each line" + (interactive "") + (align-regexp + beg end + (concat "\\(\\s-*\\)" (evil-transform-vim-style-regexp pattern)) + 1 1 (memq ?g flags))) + +;;;###autoload (autoload '+evil:align-right "editor/evil/autoload/ex" nil t) +(evil-define-command +evil:align-right (beg end pattern &optional flags) + "Ex interface to `align-regexp' that right-aligns matches. + +PATTERN is a vim-style regexp. FLAGS is an optional string of characters. +Supports the following flags: + +g Repeat alignment on all matches in each line" + (interactive "") + (align-regexp + beg end + (concat "\\(" (evil-transform-vim-style-regexp pattern) "\\)") + -1 1 (memq ?g flags))) + +;; ;;;###autoload (autoload '+evil:sort "editor/evil/autoload/ex" nil nil) +;; (evil-define-command +evil:sort (beg end &optional pattern flags reverse) +;; (interactive "")) + +;;;###autoload (autoload '+evil:open-scratch-buffer "editor/evil/autoload/ex" nil t) +(evil-define-operator +evil:open-scratch-buffer (bang) + (interactive "") + (doom/open-scratch-buffer bang)) + +;;;###autoload (autoload '+evil:pwd "editor/evil/autoload/ex" nil t) +(evil-define-command +evil:pwd (bang) + "Display the current working directory. If BANG, copy it to your clipboard." + (interactive "") + (if (not bang) + (pwd) + (kill-new default-directory) + (message "Copied to clipboard"))) + +;;;###autoload (autoload '+evil:make "editor/evil/autoload/ex" nil t) +(evil-define-command +evil:make (arguments &optional bang) + "Run make with ARGUMENTS. +If BANG is non-nil, open compilation output in a comint buffer. + +If BANG, then run ARGUMENTS as a full command. This command understands vim file +modifiers (like %:p:h). See `+evil-resolve-vim-path-a' for details." + (interactive "") + (+evil:compile (format "make %s" + (evil-ex-replace-special-filenames + arguments)) + bang)) + +;;;###autoload (autoload '+evil:compile "editor/evil/autoload/ex" nil t) +(evil-define-command +evil:compile (arguments &optional bang) + "Run `compile-command' with ARGUMENTS. +If BANG is non-nil, open compilation output in a comint buffer. + +This command understands vim file modifiers (like %:p:h). See +`+evil-resolve-vim-path-a' for details." + (interactive "") + (compile (evil-ex-replace-special-filenames + (format "%s %s" + (eval compile-command) + arguments)) + bang)) + +;;;###autoload (autoload '+evil:reverse-lines "editor/evil/autoload/ex" nil t) +(evil-define-command +evil:reverse-lines (beg end) + "Reverse lines between BEG and END." + (interactive "") + (reverse-region beg end)) + +;;;###autoload (autoload '+evil:cd "editor/evil/autoload/ex" nil t) +(evil-define-command +evil:cd (&optional path) + "Change `default-directory' with `cd'." + (interactive "") + (let ((path (or path "~"))) + (cd path) + (message "Changed directory to '%s'" (abbreviate-file-name (expand-file-name path))))) + +;;;###autoload (autoload '+evil:kill-all-buffers "editor/evil/autoload/ex" nil t) +(evil-define-command +evil:kill-all-buffers (&optional bang) + "Kill all buffers. If BANG, kill current session too." + (interactive "") + (if (and bang (fboundp '+workspace/kill-session)) + (+workspace/kill-session) + (call-interactively #'doom/kill-all-buffers))) + +;;;###autoload (autoload '+evil:kill-matching-buffers "editor/evil/autoload/ex" nil t) +(evil-define-command +evil:kill-matching-buffers (&optional bang pattern) + "Kill all buffers matching PATTERN regexp. If BANG, only match project +buffers." + (interactive "") + (doom/kill-matching-buffers + pattern (if bang (doom-project-buffer-list)))) + +;;;###autoload (autoload '+evil:help "editor/evil/autoload/ex" nil t) +(evil-define-command +evil:help (&optional bang query) + "Look up documentation for QUERY. + +If QUERY is in the format of an ex command, it will map it to the underlying +function and open its documentation with `helpful-function'. Otherwise, it will +search for it with `apropos'. + +If QUERY is empty, this runs the equivalent of 'M-x apropos'. If BANG is +non-nil, a search is preformed against Doom's manual (with `doom/help-search')." + (interactive "") + (if bang + (doom/help-search query) + (save-match-data + (cond ((or (null query) (string-empty-p (string-trim query))) + (call-interactively + (or (command-remapping #'apropos) + #'apropos))) + ((string-match "^ *:\\([^ ]+\\)$" query) + (helpful-function + (evil-ex-completed-binding (match-string 1 query)))) + ((message "Searching for %S, this may take a while..." query) + (apropos query t)))))) + +;;;###autoload (autoload '+evil:read "editor/evil/autoload/ex" nil t) +(evil-define-command +evil:read (count file) + "Alternative version of `evil-read' that replaces filename modifiers in FILE." + (interactive "P") + (evil-read count (evil-ex-replace-special-filenames file))) diff --git a/.emacs.d/modules/editor/evil/autoload/files.el b/.emacs.d/modules/editor/evil/autoload/files.el new file mode 100644 index 0000000..ec871ed --- /dev/null +++ b/.emacs.d/modules/editor/evil/autoload/files.el @@ -0,0 +1,33 @@ +;;; editor/evil/autoload/files.el -*- lexical-binding: t; -*- + +;;;###autoload (autoload '+evil:delete-this-file "editor/evil/autoload/files" nil t) +(evil-define-command +evil:delete-this-file (&optional filename 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)." + :repeat nil + (interactive "") + (doom/delete-this-file (or filename (file-truename buffer-file-name)) + force-p)) + +;;;###autoload (autoload '+evil:move-this-file "editor/evil/autoload/files" nil t) +(evil-define-command +evil:move-this-file (new-path &optional force-p) + "Move current buffer's file to NEW-PATH. Replaces %, # and other vim-esque +filename modifiers (see `+evil*ex-replace-special-filenames'). If FORCE-P, +overwrite the destination file if it exists, without confirmation." + :repeat nil + (interactive "") + (when (or (not new-path) (string-empty-p new-path)) + (user-error "No new path was specified")) + (doom/move-this-file new-path force-p)) + +;;;###autoload (autoload '+evil:copy-this-file "editor/evil/autoload/files" nil nil) +(evil-define-command +evil:copy-this-file (new-path &optional force-p) + "Copy current buffer's file to NEW-PATH. Replaces %, # and other vim-esque +filename modifiers (see `+evil*ex-replace-special-filenames'). If FORCE-P, +overwrite the destination file if it exists, without confirmation." + :repeat nil + (interactive "") + (when (or (not new-path) (string-empty-p new-path)) + (user-error "No new path was specified")) + (doom/copy-this-file new-path force-p)) + diff --git a/.emacs.d/modules/editor/evil/autoload/textobjects.el b/.emacs.d/modules/editor/evil/autoload/textobjects.el new file mode 100644 index 0000000..bdadf22 --- /dev/null +++ b/.emacs.d/modules/editor/evil/autoload/textobjects.el @@ -0,0 +1,13 @@ +;;; editor/evil/autoload/textobjects.el -*- lexical-binding: t; -*- + +;;;###autoload (autoload '+evil:whole-buffer-txtobj "editor/evil/autoload/textobjects" nil nil) +(evil-define-text-object +evil:whole-buffer-txtobj (count &optional _beg _end type) + "Text object to select the whole buffer." + (evil-range (point-min) (point-max) type)) + +;;;###autoload (autoload '+evil:defun-txtobj "editor/evil/autoload/textobjects" nil nil) +(evil-define-text-object +evil:defun-txtobj (count &optional _beg _end type) + "Text object to select the whole buffer." + (cl-destructuring-bind (beg . end) + (bounds-of-thing-at-point 'defun) + (evil-range beg end type))) diff --git a/.emacs.d/modules/editor/evil/autoload/unimpaired.el b/.emacs.d/modules/editor/evil/autoload/unimpaired.el new file mode 100644 index 0000000..4104bc6 --- /dev/null +++ b/.emacs.d/modules/editor/evil/autoload/unimpaired.el @@ -0,0 +1,200 @@ +;;; editor/evil/autoload/unimpaired.el -*- lexical-binding: t; -*- + +;; These are ported from vim-unimpaired https://github.com/tpope/vim-unimpaired +;; and bound in the :config default module (in +evil-bindings.el). + +;; +;;; Next/Previous commands + +;;;###autoload +(defun +evil/next-beginning-of-method (count) + "Jump to the beginning of the COUNT-th method/function after point." + (interactive "p") + (beginning-of-defun (- count))) + +;;;###autoload +(defun +evil/previous-beginning-of-method (count) + "Jump to the beginning of the COUNT-th method/function before point." + (interactive "p") + (beginning-of-defun count)) + +;;;###autoload +(defalias #'+evil/next-end-of-method #'end-of-defun + "Jump to the end of the COUNT-th method/function after point.") + +;;;###autoload +(defun +evil/previous-end-of-method (count) + "Jump to the end of the COUNT-th method/function before point." + (interactive "p") + (end-of-defun (- count))) + +;;;###autoload +(defun +evil/next-preproc-directive (count) + "Jump to the COUNT-th preprocessor directive after point. + +By default, this only recognizes C preproc directives. To change this see +`+evil-preprocessor-regexp'." + (interactive "p") + ;; TODO More generalized search, to support directives in other languages? + (if (re-search-forward +evil-preprocessor-regexp nil t count) + (goto-char (match-beginning 0)) + (user-error "No preprocessor directives %s point" + (if (> count 0) "after" "before")))) + +;;;###autoload +(defun +evil/previous-preproc-directive (count) + "Jump to the COUNT-th preprocessor directive before point. + +See `+evil/next-preproc-directive' for details." + (interactive "p") + (+evil/next-preproc-statement (- count))) + +;;;###autoload +(defun +evil/next-comment (count) + "Jump to the beginning of the COUNT-th commented region after point." + (interactive "p") + (let ((orig-pt (point))) + (require 'newcomment) + (dotimes (_ (abs count)) + (cond ((> count 0) + (while (and (not (eobp)) (sp-point-in-comment)) + (forward-line 1)) + (unless (comment-search-forward (point-max) 'noerror) + (goto-char orig-pt) + (user-error "No comment after point"))) + (t + (while (and (not (bobp)) (sp-point-in-comment)) + (forward-line -1)) + (unless (comment-search-backward nil 'noerror) + (goto-char orig-pt) + (user-error "No comment before point"))))))) + +;;;###autoload +(defun +evil/previous-comment (count) + "Jump to the beginning of the COUNT-th commented region before point." + (interactive "p") + (+evil/next-comment (- count))) + +;;; ] SPC / [ SPC +;;;###autoload +(defun +evil/insert-newline-below (count) + "Insert COUNT blank line(s) below current line. Does not change modes." + (interactive "p") + (dotimes (_ count) + (save-excursion (evil-insert-newline-below)))) + +;;;###autoload +(defun +evil/insert-newline-above (count) + "Insert COUNT blank line(s) above current line. Does not change modes." + (interactive "p") + (dotimes (_ count) + (save-excursion (evil-insert-newline-above)))) + +;;; ]t / [t +;;;###autoload +(defun +evil/next-frame (count) + "Focus next frame." + (interactive "p") + (dotimes (_ (abs count)) + (let ((frame (if (> count 0) (next-frame) (previous-frame)))) + (if (eq frame (selected-frame)) + (user-error "No other frame") + (select-frame-set-input-focus frame))))) + +;;;###autoload +(defun +evil/previous-frame (count) + "Focus previous frame." + (interactive "p") + (+evil/next-frame (- count))) + +;;; ]f / [f +(defun +evil--next-file (n) + (unless buffer-file-name + (user-error "Must be called from a file-visiting buffer")) + (let* ((directory (file-name-directory buffer-file-name)) + (filename (file-name-nondirectory buffer-file-name)) + (files (doom-glob (file-name-directory buffer-file-name) "[!.]*")) + (index (cl-position filename files :test #'file-equal-p))) + (when (null index) + (user-error "Couldn't find this file in current directory")) + (let ((index (+ index n))) + (cond ((>= index (length files)) + (user-error "No files after this one")) + ((< index 0) + (user-error "No files before this one")) + ((expand-file-name (nth index files) directory)))))) + +;;;###autoload +(defun +evil/next-file (count) + "Open file following this one, alphabetically, in the same directory." + (interactive "p") + (find-file (+evil--next-file count))) + +;;;###autoload +(defun +evil/previous-file (count) + "Open file preceding this one, alphabetically, in the same directory." + (interactive "p") + (find-file (+evil--next-file (- count)))) + + +;; +;;; Encoding/Decoding + +;; NOTE For ]x / [x see :lang web +;; - `+web:encode-html-entities' +;; - `+web:decode-html-entities' + +(defun +evil--encode (beg end fn) + (save-excursion + (goto-char beg) + (let* ((end (if (eq evil-this-type 'line) (1- end) end)) + (text (buffer-substring-no-properties beg end))) + (delete-region beg end) + (insert (funcall fn text))))) + +;;; ]u / [u +;;;###autoload (autoload '+evil:url-encode "editor/evil/autoload/unimpaired" nil t) +(evil-define-operator +evil:url-encode (_count &optional beg end) + "TODO" + (interactive "") + (+evil--encode beg end #'url-encode-url)) + +;;;###autoload (autoload '+evil:url-decode "editor/evil/autoload/unimpaired" nil t) +(evil-define-operator +evil:url-decode (_count &optional beg end) + "TODO" + (interactive "") + (+evil--encode beg end #'url-unhex-string)) + +;;; ]y / [y +;;;###autoload (autoload '+evil:c-string-encode "editor/evil/autoload/unimpaired" nil t) +(evil-define-operator +evil:c-string-encode (_count &optional beg end) + "TODO" + (interactive "") + (+evil--encode + beg end + (lambda (text) + (replace-regexp-in-string "[\"\\]" (lambda (ch) (concat "\\" ch)) text)))) + +;;;###autoload (autoload '+evil:c-string-decode "editor/evil/autoload/unimpaired" nil t) +(evil-define-operator +evil:c-string-decode (_count &optional beg end) + "TODO" + (interactive "") + (+evil--encode + beg end + (lambda (text) + (replace-regexp-in-string "\\\\[\"\\]" (lambda (str) (substring str 1)) text)))) + + +;; +;;; Standalone + +;;; gp +;;;###autoload +(defun +evil/reselect-paste () + "Return to visual mode and reselect the last pasted region." + (interactive) + (cl-destructuring-bind (_ _ _ beg end &optional _) + evil-last-paste + (evil-visual-make-selection + (save-excursion (goto-char beg) (point-marker)) + end))) diff --git a/.emacs.d/modules/editor/evil/config.el b/.emacs.d/modules/editor/evil/config.el new file mode 100644 index 0000000..63a5e8f --- /dev/null +++ b/.emacs.d/modules/editor/evil/config.el @@ -0,0 +1,587 @@ +;;; editor/evil/config.el -*- lexical-binding: t; -*- + +;; I'm a vimmer at heart. Its modal philosophy suits me better, and this module +;; strives to make Emacs a much better vim than vim was. + +(defvar +evil-repeat-keys (cons ";" ",") + "The keys to use for universal repeating motions. + +This is a cons cell whose CAR is the key for repeating a motion forward, and +whose CDR is for repeating backward. They should both be kbd-able strings.") + +(defvar +evil-want-o/O-to-continue-comments t + "If non-nil, the o/O keys will continue comment lines if the point is on a +line with a linewise comment.") + +(defvar +evil-preprocessor-regexp "^\\s-*#[a-zA-Z0-9_]" + "The regexp used by `+evil/next-preproc-directive' and +`+evil/previous-preproc-directive' on ]# and [#, to jump between preprocessor +directives. By default, this only recognizes C directives.") + +;; Set these defaults before `evil'; use `defvar' so they can be changed prior +;; to loading. +(defvar evil-want-C-i-jump (or (daemonp) (display-graphic-p))) +(defvar evil-want-C-u-scroll t) +(defvar evil-want-C-w-scroll t) +(defvar evil-want-Y-yank-to-eol t) +(defvar evil-want-abbrev-expand-on-insert-exit nil) + +(use-package! evil + :hook (doom-init-modules . evil-mode) + :demand t + :preface + (setq evil-want-visual-char-semi-exclusive t + evil-magic t + evil-echo-state t + evil-indent-convert-tabs t + evil-ex-search-vim-style-regexp t + evil-ex-substitute-global t + evil-ex-visual-char-range t ; column range for ex commands + evil-insert-skip-empty-lines t + evil-mode-line-format 'nil + evil-respect-visual-line-mode t + ;; more vim-like behavior + evil-symbol-word-search t + ;; cursor appearance + evil-default-cursor '+evil-default-cursor-fn + evil-normal-state-cursor 'box + evil-emacs-state-cursor '(box +evil-emacs-cursor-fn) + evil-insert-state-cursor 'bar + evil-visual-state-cursor 'hollow + ;; must be set before evil/evil-collection is loaded + evil-want-keybinding (not (featurep! +everywhere)) + ;; Only do highlighting in selected window so that Emacs has less work + ;; to do highlighting them all. + evil-ex-interactive-search-highlight 'selected-window) + + :config + (evil-select-search-module 'evil-search-module 'evil-search) + + (put 'evil-define-key* 'lisp-indent-function 'defun) + + ;; 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) + + ;; Start help-with-tutorial in emacs state + (advice-add #'help-with-tutorial :after (lambda (&rest _) (evil-emacs-state +1))) + + ;; Allows you to click buttons without initiating a selection + (define-key evil-motion-state-map [down-mouse-1] nil) + + ;; Done in a hook to ensure the popup rules load as late as possible + (add-hook! 'doom-init-modules-hook + (defun +evil--init-popup-rules-h () + (set-popup-rules! + '(("^\\*evil-registers" :size 0.3) + ("^\\*Command Line" :size 8))))) + + ;; Change the cursor color in emacs state. We do it this roundabout way + ;; instead of changing `evil-default-cursor' (or `evil-emacs-state-cursor') so + ;; it won't interfere with users who have changed these variables. + (defvar +evil--default-cursor-color "#ffffff") + (defvar +evil--emacs-cursor-color "#ff9999") + + (add-hook! 'doom-load-theme-hook + (defun +evil-update-cursor-color-h () + (setq +evil--default-cursor-color (face-background 'cursor) + +evil--emacs-cursor-color (face-foreground 'warning)))) + + (defun +evil-default-cursor-fn () + (evil-set-cursor-color +evil--default-cursor-color)) + (defun +evil-emacs-cursor-fn () + (evil-set-cursor-color +evil--emacs-cursor-color)) + + (setq-hook! 'after-change-major-mode-hook evil-shift-width tab-width) + + + ;; --- keybind fixes ---------------------- + (after! wgrep + ;; A wrapper that invokes `wgrep-mark-deletion' across lines you use + ;; `evil-delete' in wgrep buffers. + (define-key wgrep-mode-map [remap evil-delete] #'+evil-delete)) + + (add-hook! 'doom-escape-hook + (defun +evil-disable-ex-highlights-h () + "Disable ex search buffer highlights." + (when (evil-ex-hl-active-p 'evil-ex-search) + (evil-ex-nohighlight) + t))) + + + ;; --- evil hacks ------------------------- + (unless noninteractive + (setq save-silently t) + (add-hook! 'after-save-hook + (defun +evil-display-vimlike-save-message-h () + "Shorter, vim-esque save messages." + (message "\"%s\" %dL, %dC written" + (if buffer-file-name + (file-relative-name (file-truename buffer-file-name) (doom-project-root)) + (buffer-name)) + (count-lines (point-min) (point-max)) + (buffer-size))))) + + ;; 'gq' moves the cursor to the beginning of selection. Disable this, since + ;; it's more disruptive than helpful. + (defadvice! +evil--dont-move-cursor-a (orig-fn &rest args) + :around #'evil-indent + (save-excursion (apply orig-fn args))) + + ;; In evil, registers 2-9 are buffer-local. In vim, they're global, so... + (defadvice! +evil--make-numbered-markers-global-a (_arg) + :after-until #'evil-global-marker-p + (and (>= char ?2) (<= char ?9))) + + ;; Make ESC (from normal mode) the universal escaper. See `doom-escape-hook'. + (advice-add #'evil-force-normal-state :after #'+evil-escape-a) + + ;; monkey patch `evil-ex-replace-special-filenames' to improve support for + ;; file modifiers like %:p:h. This adds support for most of vim's modifiers, + ;; and one custom one: %:P (expand to the project root). + (advice-add #'evil-ex-replace-special-filenames :override #'+evil-resolve-vim-path-a) + + ;; make `try-expand-dabbrev' (from `hippie-expand') work in minibuffer + (add-hook 'minibuffer-inactive-mode-hook #'+evil--fix-dabbrev-in-minibuffer-h) + + ;; Focus and recenter new splits + (advice-add #'evil-window-split :override #'+evil-window-split-a) + (advice-add #'evil-window-vsplit :override #'+evil-window-vsplit-a) + + ;; Make o/O continue comments (see `+evil-want-o/O-to-continue-comments') + (advice-add #'evil-open-above :around #'+evil--insert-newline-above-and-respect-comments-a) + (advice-add #'evil-open-below :around #'+evil--insert-newline-below-and-respect-comments-a) + + ;; Recenter screen after most searches + (dolist (fn '(evil-visualstar/begin-search-forward + evil-visualstar/begin-search-backward + evil-ex-search-word-backward + evil-ex-search-word-backward + evil-ex-search-forward + evil-ex-search-backward)) + (advice-add fn :after #'doom-recenter-a)) + + ;; --- custom interactive codes ----------- + ;; These arg types will highlight matches in the current buffer + (evil-ex-define-argument-type regexp-match + :runner (lambda (flag &optional arg) (+evil-ex-regexp-match flag arg 'inverted))) + (evil-ex-define-argument-type regexp-global-match + :runner +evil-ex-regexp-match) + + (defun +evil--regexp-match-args (arg) + (when (evil-ex-p) + (cl-destructuring-bind (&optional arg flags) + (evil-delimited-arguments arg 2) + (list arg (string-to-list flags))))) + + ;; Other commands can make use of this + (evil-define-interactive-code "" + :ex-arg regexp-match + (+evil--regexp-match-args evil-ex-argument)) + + (evil-define-interactive-code "" + :ex-arg regexp-global-match + (+evil--regexp-match-args evil-ex-argument)) + + ;; Forward declare these so that ex completion works, even if the autoloaded + ;; functions aren't loaded yet. + (evil-add-command-properties '+evil:align :ex-arg 'regexp-match) + (evil-add-command-properties '+evil:align-right :ex-arg 'regexp-match) + (evil-add-command-properties '+multiple-cursors:evil-mc :ex-arg 'regexp-global-match) + + ;; Lazy load evil ex commands + (delq! 'evil-ex features) + (add-transient-hook! 'evil-ex (provide 'evil-ex)) + (after! evil-ex (load! "+commands"))) + + +;; +;;; Packages + +(use-package! evil-easymotion + :commands evilem-create evilem-default-keybindings + :config + ;; Use evil-search backend, instead of isearch + (evilem-make-motion evilem-motion-search-next #'evil-ex-search-next + :bind ((evil-ex-search-highlight-all nil))) + (evilem-make-motion evilem-motion-search-previous #'evil-ex-search-previous + :bind ((evil-ex-search-highlight-all nil))) + + (evilem-make-motion evilem-motion-search-word-forward #'evil-ex-search-word-forward + :bind ((evil-ex-search-highlight-all nil))) + (evilem-make-motion evilem-motion-search-word-backward #'evil-ex-search-word-backward + :bind ((evil-ex-search-highlight-all nil)))) + + +(use-package! evil-embrace + :commands embrace-add-pair embrace-add-pair-regexp + :hook (LaTeX-mode . embrace-LaTeX-mode-hook) + :hook (org-mode . embrace-org-mode-hook) + :hook ((ruby-mode enh-ruby-mode) . embrace-ruby-mode-hook) + :hook (emacs-lisp-mode . embrace-emacs-lisp-mode-hook) + :hook ((lisp-mode emacs-lisp-mode clojure-mode racket-mode) + . +evil-embrace-lisp-mode-hook-h) + :hook ((org-mode LaTeX-mode) . +evil-embrace-latex-mode-hook-h) + :hook ((c++-mode rust-mode rustic-mode csharp-mode java-mode swift-mode typescript-mode) + . +evil-embrace-angle-bracket-modes-hook-h) + :init + (after! evil-surround + (evil-embrace-enable-evil-surround-integration)) + :config + (setq evil-embrace-show-help-p nil) + + (defun +evil-embrace-latex-mode-hook-h () + (embrace-add-pair-regexp ?l "\\[a-z]+{" "}" #'+evil--embrace-latex)) + + (defun +evil-embrace-lisp-mode-hook-h () + (push (cons ?f (make-embrace-pair-struct + :key ?f + :read-function #'+evil--embrace-elisp-fn + :left-regexp "([^ ]+ " + :right-regexp ")")) + embrace--pairs-list)) + + (defun +evil-embrace-angle-bracket-modes-hook-h () + (set (make-local-variable 'evil-embrace-evil-surround-keys) + (delq ?< evil-embrace-evil-surround-keys)) + (push (cons ?< (make-embrace-pair-struct + :key ?< + :read-function #'+evil--embrace-angle-brackets + :left-regexp "\\[a-z]+<" + :right-regexp ">")) + embrace--pairs-list)) + + ;; Add escaped-sequence support to embrace + (setf (alist-get ?\\ (default-value 'embrace--pairs-list)) + (make-embrace-pair-struct + :key ?\\ + :read-function #'+evil--embrace-escaped + :left-regexp "\\[[{(]" + :right-regexp "\\[]})]"))) + + +(use-package! evil-escape + :commands evil-escape + :after-call pre-command-hook + :init + (setq evil-escape-excluded-states '(normal visual multiedit emacs motion) + evil-escape-excluded-major-modes '(neotree-mode treemacs-mode vterm-mode) + evil-escape-key-sequence "jk" + evil-escape-delay 0.15) + (evil-define-key* '(insert replace visual operator) 'global "\C-g" #'evil-escape) + :config + ;; no `evil-escape' in minibuffer + (add-hook 'evil-escape-inhibit-functions #'minibufferp) + ;; so that evil-escape-mode-hook runs, and can be toggled by evil-mc + (evil-escape-mode +1)) + + +(use-package! evil-exchange + :commands evil-exchange + :config + (add-hook! 'doom-escape-hook + (defun +evil--escape-exchange-h () + (when evil-exchange--overlays + (evil-exchange-cancel) + t)))) + + +(use-package! evil-quick-diff + :commands (evil-quick-diff evil-quick-diff-cancel)) + + +(use-package! evil-nerd-commenter + :commands (evilnc-comment-operator + evilnc-inner-comment + evilnc-outer-commenter)) + + +(use-package! evil-snipe + :commands (evil-snipe-mode + evil-snipe-override-mode + evil-snipe-local-mode + evil-snipe-override-local-mode) + :after-call pre-command-hook + :init + (setq evil-snipe-smart-case t + evil-snipe-scope 'line + evil-snipe-repeat-scope 'visible + evil-snipe-char-fold t) + :config + (pushnew! evil-snipe-disabled-modes 'Info-mode 'calc-mode) + (evil-snipe-mode +1) + (evil-snipe-override-mode +1)) + + +(use-package! evil-surround + :commands (global-evil-surround-mode + evil-surround-edit + evil-Surround-edit + evil-surround-region) + :config (global-evil-surround-mode 1)) + + +(use-package! evil-traces + :after evil-ex + :config + (pushnew! evil-traces-argument-type-alist + '(+evil:align . evil-traces-global) + '(+evil:align-right . evil-traces-global)) + (evil-traces-mode)) + + +;; Allows you to use the selection for * and # +(use-package! evil-visualstar + :commands (evil-visualstar/begin-search + evil-visualstar/begin-search-forward + evil-visualstar/begin-search-backward) + :init + (evil-define-key* 'visual 'global + "*" #'evil-visualstar/begin-search-forward + "#" #'evil-visualstar/begin-search-backward)) + + +;; +;;; Text object plugins + +(use-package! exato + :commands evil-outer-xml-attr evil-inner-xml-attr) + + +;; +;;; Keybinds + +(defmacro set-repeater! (command next-func prev-func) + "Makes ; and , the universal repeat-keys in evil-mode. +To change these keys see `+evil-repeat-keys'." + (let ((fn-sym (intern (format "+evil/repeat-%s" (doom-unquote command))))) + `(progn + (defun ,fn-sym (&rest _) + (when +evil-repeat-keys + (evil-define-key* 'motion 'local + (kbd (car +evil-repeat-keys)) #',next-func + (kbd (cdr +evil-repeat-keys)) #',prev-func))) + (advice-add #',command :after-while #',fn-sym)))) + +;; n/N +(set-repeater! evil-ex-search-next evil-ex-search-next evil-ex-search-previous) +(set-repeater! evil-ex-search-previous evil-ex-search-next evil-ex-search-previous) +(set-repeater! evil-ex-search-forward evil-ex-search-next evil-ex-search-previous) +(set-repeater! evil-ex-search-backward evil-ex-search-next evil-ex-search-previous) + +;; f/F/t/T/s/S +(after! evil-snipe + (setq evil-snipe-repeat-keys nil + evil-snipe-override-evil-repeat-keys nil) ; causes problems with remapped ; + (set-repeater! evil-snipe-f evil-snipe-repeat evil-snipe-repeat-reverse) + (set-repeater! evil-snipe-F evil-snipe-repeat evil-snipe-repeat-reverse) + (set-repeater! evil-snipe-t evil-snipe-repeat evil-snipe-repeat-reverse) + (set-repeater! evil-snipe-T evil-snipe-repeat evil-snipe-repeat-reverse) + (set-repeater! evil-snipe-s evil-snipe-repeat evil-snipe-repeat-reverse) + (set-repeater! evil-snipe-S evil-snipe-repeat evil-snipe-repeat-reverse) + (set-repeater! evil-snipe-x evil-snipe-repeat evil-snipe-repeat-reverse) + (set-repeater! evil-snipe-X evil-snipe-repeat evil-snipe-repeat-reverse)) + +;; */# +(set-repeater! evil-visualstar/begin-search-forward + evil-ex-search-next evil-ex-search-previous) +(set-repeater! evil-visualstar/begin-search-backward + evil-ex-search-previous evil-ex-search-next) + + +;; `evil-collection' +(when (featurep! +everywhere) + (unless doom-reloading-p + (load! "+everywhere")) + + (setq evil-collection-company-use-tng (featurep! :completion company +tng)) + + ;; Don't let evil-collection interfere with certain keys + (appendq! evil-collection-key-blacklist + (append (when (featurep! :tools lookup) + '("gd" "gf" "K")) + (when (featurep! :tools eval) + '("gr" "gR")) + '("[" "]" "gz" ""))) + + (defadvice! +evil-collection-disable-blacklist-a (orig-fn) + :around #'evil-collection-vterm-toggle-send-escape ; allow binding to ESC + (let (evil-collection-key-blacklist) + (apply orig-fn)))) + +;; Keybinds that have no Emacs+evil analogues (i.e. don't exist): +;; zq - mark word at point as good word +;; zw - mark word at point as bad +;; zu{q,w} - undo last marking +;; Keybinds that evil define: +;; z= - correct flyspell word at point +;; ]s - jump to previous spelling error +;; [s - jump to next spelling error + +(map! :v "@" #'+evil:apply-macro + + ;; ported from vim-unimpaired + :n "] SPC" #'+evil/insert-newline-below + :n "[ SPC" #'+evil/insert-newline-above + :n "]b" #'next-buffer + :n "[b" #'previous-buffer + :n "]f" #'+evil/next-file + :n "[f" #'+evil/previous-file + :m "]u" #'+evil:url-encode + :m "[u" #'+evil:url-decode + :m "]y" #'+evil:c-string-encode + :m "[y" #'+evil:c-string-decode + (:when (featurep! :lang web) + :m "]x" #'+web:encode-html-entities + :m "[x" #'+web:decode-html-entities) + (:when (featurep! :ui vc-gutter) + :m "]d" #'git-gutter:next-hunk + :m "[d" #'git-gutter:previous-hunk) + (:when (featurep! :ui hl-todo) + :m "]t" #'hl-todo-next + :m "[t" #'hl-todo-previous) + (:when (featurep! :ui workspaces) + :n "gt" #'+workspace:switch-next + :n "gT" #'+workspace:switch-previous + :n "]w" #'+workspace/switch-right + :n "[w" #'+workspace/switch-left) + + ;; custom vim-unmpaired-esque keys + :m "]#" #'+evil/next-preproc-directive + :m "[#" #'+evil/previous-preproc-directive + :m "]a" #'evil-forward-arg + :m "[a" #'evil-backward-arg + :m "]c" #'+evil/next-comment + :m "[c" #'+evil/previous-comment + :m "]e" #'next-error + :m "[e" #'previous-error + :n "]F" #'+evil/next-frame + :n "[F" #'+evil/previous-frame + :m "]h" #'outline-next-visible-heading + :m "[h" #'outline-previous-visible-heading + :m "]m" #'+evil/next-beginning-of-method + :m "[m" #'+evil/previous-beginning-of-method + :m "]M" #'+evil/next-end-of-method + :m "[M" #'+evil/previous-end-of-method + :n "[o" #'+evil/insert-newline-above + :n "]o" #'+evil/insert-newline-below + :n "gp" #'+evil/reselect-paste + :v "gp" #'+evil/paste-preserve-register + :nv "g@" #'+evil:apply-macro + :nv "gc" #'evilnc-comment-operator + :nv "gx" #'evil-exchange + :nv "gy" #'+evil:yank-unindented + :n "g=" #'evil-numbers/inc-at-pt + :n "g-" #'evil-numbers/dec-at-pt + :v "g=" #'evil-numbers/inc-at-pt-incremental + :v "g-" #'evil-numbers/dec-at-pt-incremental + :v "g+" #'evil-numbers/inc-at-pt + (:when (featurep! :tools lookup) + :nv "K" #'+lookup/documentation + :nv "gd" #'+lookup/definition + :nv "gD" #'+lookup/references + :nv "gf" #'+lookup/file) + (:when (featurep! :tools eval) + :nv "gr" #'+eval:region + :n "gR" #'+eval/buffer + :v "gR" #'+eval:replace-region + ;; Restore these keybinds, since the blacklisted/overwritten gr/gR will + ;; undo them: + (:after dired + :map dired-mode-map + :n "gr" #'revert-buffer) + (:after notmuch + :map notmuch-common-keymap + :n "gr" #'notmuch-refresh-this-buffer + :n "gR" #'notmuch-poll-and-refresh-this-buffer) + (:after elfeed + :map elfeed-search-update--force + :n "gr" #'elfeed-search-update--force + :n "gR" #'elfeed-search-fetch)) + + :nv "z=" #'flyspell-correct-word-generic + ;; custom evil keybinds + :nv "zn" #'+evil:narrow-buffer + :n "zN" #'doom/widen-indirectly-narrowed-buffer + :n "zx" #'kill-current-buffer + :n "ZX" #'bury-buffer + ;; don't leave visual mode after shifting + :v "<" #'+evil/visual-dedent ; vnoremap < " #'+evil/visual-indent ; vnoremap > >gv + + ;; window management (prefix "C-w") + (:map evil-window-map + ;; Navigation + "C-h" #'evil-window-left + "C-j" #'evil-window-down + "C-k" #'evil-window-up + "C-l" #'evil-window-right + "C-w" #'other-window + ;; Swapping windows + "H" #'+evil/window-move-left + "J" #'+evil/window-move-down + "K" #'+evil/window-move-up + "L" #'+evil/window-move-right + "C-S-w" #'ace-swap-window + ;; Window undo/redo + (:prefix "m" + "m" #'doom/window-maximize-buffer + "v" #'doom/window-maximize-vertically + "s" #'doom/window-maximize-horizontally) + "u" #'winner-undo + "C-u" #'winner-undo + "C-r" #'winner-redo + "o" #'doom/window-enlargen + ;; Delete window + "d" #'evil-window-delete + "C-C" #'ace-delete-window) + + ;; text objects + :textobj "a" #'evil-inner-arg #'evil-outer-arg + :textobj "B" #'evil-textobj-anyblock-inner-block #'evil-textobj-anyblock-a-block + :textobj "c" #'evilnc-inner-comment #'evilnc-outer-commenter + :textobj "f" #'+evil:defun-txtobj #'+evil:defun-txtobj + :textobj "g" #'+evil:whole-buffer-txtobj #'+evil:whole-buffer-txtobj + :textobj "i" #'evil-indent-plus-i-indent #'evil-indent-plus-a-indent + :textobj "j" #'evil-indent-plus-i-indent-up-down #'evil-indent-plus-a-indent-up-down + :textobj "k" #'evil-indent-plus-i-indent-up #'evil-indent-plus-a-indent-up + :textobj "x" #'evil-inner-xml-attr #'evil-outer-xml-attr + + ;; evil-easymotion + (:after evil-easymotion + :map evilem-map + "a" (evilem-create #'evil-forward-arg) + "A" (evilem-create #'evil-backward-arg) + "s" #'evil-avy-goto-char-2 + "SPC" (λ!! #'evil-avy-goto-char-timer t) + "/" #'evil-avy-goto-char-timer) + + ;; evil-snipe + (:after evil-snipe + :map evil-snipe-parent-transient-map + "C-;" (λ! (require 'evil-easymotion) + (call-interactively + (evilem-create #'evil-snipe-repeat + :bind ((evil-snipe-scope 'whole-buffer) + (evil-snipe-enable-highlight) + (evil-snipe-enable-incremental-highlight)))))) + + ;; evil-surround + :v "S" #'evil-surround-region + :o "s" #'evil-surround-edit + :o "S" #'evil-Surround-edit + + ;; Omni-completion + (:when (featurep! :completion company) + (:prefix "C-x" + :i "C-l" #'+company/whole-lines + :i "C-k" #'+company/dict-or-keywords + :i "C-f" #'company-files + :i "C-]" #'company-etags + :i "s" #'company-ispell + :i "C-s" #'company-yasnippet + :i "C-o" #'company-capf + :i "C-n" #'+company/dabbrev + :i "C-p" #'+company/dabbrev-code-previous))) diff --git a/.emacs.d/modules/editor/evil/packages.el b/.emacs.d/modules/editor/evil/packages.el new file mode 100644 index 0000000..a05949c --- /dev/null +++ b/.emacs.d/modules/editor/evil/packages.el @@ -0,0 +1,29 @@ +;; -*- no-byte-compile: t; -*- +;;; editor/evil/packages.el + +(package! evil) +(package! evil-args) +(package! evil-easymotion) +(package! evil-embrace) +(package! evil-escape) +(package! evil-exchange) +(package! evil-indent-plus) +(package! evil-nerd-commenter) +(package! evil-numbers :recipe (:host github :repo "janpath/evil-numbers")) +(package! evil-snipe) +(package! evil-surround) +(package! evil-textobj-anyblock) +(package! evil-traces) +(package! evil-visualstar) +(package! exato) +(package! evil-quick-diff :recipe (:host github :repo "rgrinberg/evil-quick-diff")) + +;; +(when (featurep! +everywhere) + ;; `evil-collection-neotree' uses the `neotree-make-executor' macro, but this + ;; requires neotree be available during byte-compilation (while installing). + (when (featurep! :ui neotree) + (package! neotree) + (autoload 'neotree-make-executor "neotree" nil nil 'macro)) + + (package! evil-collection)) diff --git a/.emacs.d/modules/editor/evil/test/test-evil.el b/.emacs.d/modules/editor/evil/test/test-evil.el new file mode 100644 index 0000000..99694f1 --- /dev/null +++ b/.emacs.d/modules/editor/evil/test/test-evil.el @@ -0,0 +1,69 @@ +;; -*- no-byte-compile: t; -*- +;;; editor/evil/test/test-evil.el + +(describe "feature/evil" + :var (resv project-root) + + (require! :editor evil) + (require 'evil) + (load! "../autoload/evil") + + (before-each + (fset 'resv #'+evil-resolve-vim-path-a) + (spy-on 'doom-project-root :and-call-fake (lambda () project-root))) + + ;; `evil-ex-replace-special-filenames' / `+evil-resolve-vim-path-a' + (describe "file modifiers" + (it "supports basic vim file modifiers" + (let ((buffer-file-name "~/.emacs.d/test/modules/feature/test-evil.el") + (default-directory "~/.emacs.d/test/modules/") + (project-root "~/.emacs.d/")) + (expect (resv "%") :to-equal "feature/test-evil.el") + (expect (resv "%:r") :to-equal "feature/test-evil") + (expect (resv "%:r.elc") :to-equal "feature/test-evil.elc") + (expect (resv "%:e") :to-equal "el") + (expect (resv "%:p") :to-equal (expand-file-name buffer-file-name)) + (expect (resv "%:h") :to-equal "feature") + (expect (resv "%:t") :to-equal "test-evil.el") + (expect (resv "%:.") :to-equal "feature/test-evil.el") + (expect (resv "%:~") :to-equal "~/.emacs.d/test/modules/feature/test-evil.el") + (expect (file-truename (resv "%:p")) + :to-equal (file-truename buffer-file-name)))) + + (it "supports nested vim file modifiers" + (let ((buffer-file-name "~/vim/src/version.c") + (default-directory "~/vim/") + (project-root "~/vim/")) + (expect (resv "%:p") :to-equal (expand-file-name "~/vim/src/version.c")) + (expect (resv "%:p:.") :to-equal "src/version.c") + (expect (resv "%:p:~") :to-equal "~/vim/src/version.c") + (expect (resv "%:h") :to-equal "src") + (expect (resv "%:p:h") :to-equal (expand-file-name "~/vim/src")) + (expect (resv "%:p:h:h") :to-equal (expand-file-name "~/vim")) + (expect (resv "%:t") :to-equal "version.c") + (expect (resv "%:p:t") :to-equal "version.c") + (expect (resv "%:r") :to-equal "src/version") + (expect (resv "%:p:r") :to-equal (expand-file-name "~/vim/src/version")) + (expect (resv "%:t:r") :to-equal "version"))) + + (it "cleans up empty file modifiers" + (let (buffer-file-name default-directory) + (expect (resv "%") :to-equal "") + (expect (resv "%:r") :to-equal "") + (expect (resv "%:e") :to-equal "") + (expect (resv "%:h") :to-equal "") + (expect (resv "%:t") :to-equal "") + (expect (resv "%:.") :to-equal "") + (expect (resv "%:~") :to-equal "") + (expect (resv "%:P") :to-equal ""))) + + (it "supports substitution modifiers" + (let ((buffer-file-name "~/.emacs.d/test/modules/feature/test-evil.el") + (default-directory "~/.emacs.d/test/modules/")) + (expect (resv "%:s?e?x?") :to-equal "fxature/test-evil.el") + (expect (resv "%:gs?e?x?") :to-equal "fxaturx/txst-xvil.xl"))) + + (it "cleans up empty substitution modifiers" + (let (buffer-file-name default-directory) + (expect (resv "%:s?e?x?") :to-equal "") + (expect (resv "%:gs?e?x?") :to-equal ""))))) diff --git a/.emacs.d/modules/editor/file-templates/README.org b/.emacs.d/modules/editor/file-templates/README.org new file mode 100644 index 0000000..519c0ee --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/README.org @@ -0,0 +1,79 @@ +#+TITLE: editor/file-templates +#+DATE: February 11, 2017 +#+SINCE: v2.0 +#+STARTUP: inlineimages + +* Table of Contents :TOC_2:noexport: +- [[Description][Description]] + - [[Module Flags][Module Flags]] + - [[Plugins][Plugins]] +- [[Prerequisites][Prerequisites]] +- [[Usage][Usage]] + - [[Inserting OSS licenses][Inserting OSS licenses]] +- [[Configuration][Configuration]] + - [[Registering a new file template][Registering a new file template]] + - [[Changing existing file templates][Changing existing file templates]] + - [[Adding new OSS licenses][Adding new OSS licenses]] +- [[Troubleshooting][Troubleshooting]] +- [[Appendix][Appendix]] + - [[API][API]] + - [[Commands][Commands]] + - [[Variables][Variables]] + +* Description +This module adds file templates for blank files, powered by yasnippet. + +** Module Flags +This module provides no flags. + +** Plugins +This module installs no plugins. + +* Prerequisites +This module has no prerequisites. + +* Usage +File templates are automatically expanded when opening empty files. + +They are also regular yasnippet snippets, which can be expanded by typing their +trigger and pressing =TAB=. By convention, the triggers for file templates are +prefixed with two underscores ~__~. + +** Inserting OSS licenses +A special command is available for inserting software licenses: ~M-x ++file-templates/insert-license~. + +#+begin_quote +Licenses with a ~-bp~ suffix are boilerplate templates; shorter versions meant +for comment headers in code. +#+end_quote + +* Configuration +** TODO Registering a new file template +** TODO Changing existing file templates +** Adding new OSS licenses +The ~+file-templates/insert-license~ command searches for snippets under +~text-mode~ that are named ~__license-ABC~, where ABC is the short name of the +license. e.g. ~__license-mit~. + +So long as these files exist, ~+file-templates/insert-license~ will recognize +them. + +* Troubleshooting +If a file template isn't expanding where you expect it to, run ~M-x ++file-templates/debug~. This will report to you what file template rule would +apply for the correct file. + +* Appendix +** API ++ ~set-file-template! PRED &rest PLIST~ ++ ~set-file-templates! &rest TEMPLATES~ + +** Commands ++ ~+file-templates/insert-license~ ++ ~+file-templates/debug~ + +** Variables ++ ~+file-templates-dir~ ++ ~+file-templates-default-trigger~ ++ ~+file-templates-alist~ diff --git a/.emacs.d/modules/editor/file-templates/autoload.el b/.emacs.d/modules/editor/file-templates/autoload.el new file mode 100644 index 0000000..e776227 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/autoload.el @@ -0,0 +1,118 @@ +;;; editor/file-templates/autoload.el -*- lexical-binding: t; -*- + +(defun +file-templates--set (pred plist) + (if (null (car-safe plist)) + (setq +file-templates-alist + (delq (assoc pred +file-templates-alist) + +file-templates-alist)) + (push `(,pred ,@plist) +file-templates-alist))) + +;;;###autodef +(defun set-file-template! (pred &rest plist) + "Register a file template. + +PRED can either be a regexp string or a major mode symbol. PLIST may contain +these properties: + + :when FUNCTION + Provides a secondary predicate. This function takes no arguments and is + executed from within the target buffer. If it returns nil, this rule will be + skipped over. + :trigger STRING|FUNCTION + If a string, this is the yasnippet trigger keyword used to trigger the + target snippet. + If a function, this function will be run in the context of the buffer to + insert a file template into. It is given no arguments and must insert text + into the current buffer manually. + If omitted, `+file-templates-default-trigger' is used. + :mode SYMBOL + What mode to get the yasnippet snippet from. If omitted, either PRED (if + it's a major-mode symbol) or the mode of the buffer is used. + :project BOOL + If non-nil, ignore this template if this buffer isn't in a project. + :ignore BOOL + If non-nil, don't expand any template for this file and don't test any other + file template rule against this buffer. + +\(fn PRED &key WHEN TRIGGER MODE PROJECT IGNORE)" + (declare (indent defun)) + (defer-until! (boundp '+file-templates-alist) + (+file-templates--set pred plist))) + +;;;###autodef +(defun set-file-templates! (&rest templates) + "Like `set-file-templates!', but can register multiple file templates at once. + +\(fn &rest (PRED &key WHEN TRIGGER MODE PROJECT IGNORE))" + (defer-until! (boundp '+file-templates-alist) + (dolist (template templates) + (+file-templates--set (car template) (cdr template))))) + + +;; +;;; Library + +;;;###autoload +(cl-defun +file-templates--expand (pred &key project mode trigger ignore _when) + "Auto insert a yasnippet snippet into current file and enter insert mode (if +evil is loaded and enabled)." + (when (and pred (not ignore)) + (when (if project (doom-project-p) t) + (unless mode + (setq mode (if (symbolp pred) pred major-mode))) + (unless mode + (user-error "Couldn't determine mode for %s file template" pred)) + (unless trigger + (setq trigger +file-templates-default-trigger)) + (if (functionp trigger) + (funcall trigger) + (require 'yasnippet) + (unless yas-minor-mode + (yas-minor-mode-on)) + (when (and yas-minor-mode + (when-let + (template (cl-find trigger (yas--all-templates (yas--get-snippet-tables mode)) + :key #'yas--template-key :test #'equal)) + (yas-expand-snippet (yas--template-content template))) + (and (featurep 'evil) evil-local-mode) + (and yas--active-field-overlay + (overlay-buffer yas--active-field-overlay) + (overlay-get yas--active-field-overlay 'yas--field))) + (evil-initialize-state 'insert)))))) + +;;;###autoload +(defun +file-templates-get-short-path () + "Fetches a short file path for the header in Doom module templates." + (let ((path (file-truename (or buffer-file-name default-directory)))) + (save-match-data + (cond ((string-match "/modules/\\(.+\\)$" path) + (match-string 1 path)) + ((file-in-directory-p path doom-emacs-dir) + (file-relative-name path doom-emacs-dir)) + ((abbreviate-file-name path)))))) + + +;; +;;; Commands + +;;;###autoload +(defun +file-templates/insert-license () + "Insert a license file template into the current file." + (interactive) + (require 'yasnippet) + (let ((templates + (let (yas-choose-tables-first ; avoid prompts + yas-choose-keys-first) + (cl-loop for tpl in (yas--all-templates (yas--get-snippet-tables 'text-mode)) + for uuid = (yas--template-uuid tpl) + if (string-prefix-p "__license-" uuid) + collect (cons (string-remove-prefix "__license-" uuid) tpl))))) + (when-let (uuid (yas-choose-value (mapcar #'car templates))) + (yas-expand-snippet (cdr (assoc uuid templates)))))) + +;;;###autoload +(defun +file-templates/debug () + "Tests the current buffer and outputs the file template rule most appropriate +for it. This is used for testing." + (interactive) + (message "Found %s" (cl-find-if #'+file-template-p +file-templates-alist))) diff --git a/.emacs.d/modules/editor/file-templates/config.el b/.emacs.d/modules/editor/file-templates/config.el new file mode 100644 index 0000000..46248b5 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/config.el @@ -0,0 +1,159 @@ +;;; editor/file-templates/config.el -*- lexical-binding: t; -*- + +(defvar +file-templates-dir + (expand-file-name "templates/" (file-name-directory load-file-name)) + "The path to a directory of yasnippet folders to use for file templates.") + +(defvar +file-templates-default-trigger "__" + "The default yasnippet trigger key (a string) for file template rules that +don't have a :trigger property in `+file-templates-alist'.") + +(defvar +file-templates-alist + `(;; General + (gitignore-mode) + (dockerfile-mode) + ("/docker-compose\\.yml$" :mode yaml-mode) + ("/Makefile$" :mode makefile-gmake-mode) + ;; elisp + ("/.dir-locals.el$") + ("/packages\\.el$" :when +file-templates-in-emacs-dirs-p + :trigger "__doom-packages" + :mode emacs-lisp-mode) + ("/doctor\\.el$" :when +file-templates-in-emacs-dirs-p + :trigger "__doom-doctor" + :mode emacs-lisp-mode) + ("/test/.+\\.el$" :when +file-templates-in-emacs-dirs-p + :trigger "__doom-test" + :mode emacs-lisp-mode) + ("\\.el$" :when +file-templates-in-emacs-dirs-p + :trigger "__doom-module" + :mode emacs-lisp-mode) + ("-test\\.el$" :mode emacs-ert-mode) + (emacs-lisp-mode :trigger "__initfile") + (snippet-mode) + ;; C/C++ + ("/main\\.c\\(?:c\\|pp\\)$" :trigger "__main.cpp" :mode c++-mode) + ("/win32_\\.c\\(?:c\\|pp\\)$" :trigger "__winmain.cpp" :mode c++-mode) + ("\\.c\\(?:c\\|pp\\)$" :trigger "__cpp" :mode c++-mode) + ("\\.h\\(?:h\\|pp\\|xx\\)$" :trigger "__hpp" :mode c++-mode) + ("\\.h$" :trigger "__h" :mode c-mode) + (c-mode :trigger "__c") + ;; direnv + ("/\\.envrc$" :trigger "__envrc" :mode direnv-envrc-mode) + ;; go + ("/main\\.go$" :trigger "__main.go" :mode go-mode :project t) + (go-mode :trigger "__.go") + ;; web-mode + ("/normalize\\.scss$" :trigger "__normalize.scss" :mode scss-mode) + ("/master\\.scss$" :trigger "__master.scss" :mode scss-mode) + ("\\.html$" :trigger "__.html" :mode web-mode) + (scss-mode) + ;; java + ("/main\\.java$" :trigger "__main" :mode java-mode) + ("/build\\.gradle$" :trigger "__build.gradle" :mode android-mode) + ("/src/.+\\.java$" :mode java-mode) + ;; javascript + ("/package\\.json$" :trigger "__package.json" :mode json-mode) + ("/bower\\.json$" :trigger "__bower.json" :mode json-mode) + ("/gulpfile\\.js$" :trigger "__gulpfile.js" :mode js-mode) + ("/webpack\\.config\\.js$" :trigger "__webpack.config.js" :mode js-mode) + ("\\.js\\(?:on\\|hintrc\\)$" :mode json-mode) + ;; Lua + ("/main\\.lua$" :trigger "__main.lua" :mode love-mode) + ("/conf\\.lua$" :trigger "__conf.lua" :mode love-mode) + ;; Markdown + (markdown-mode) + ;; Markdown + (nxml-mode) + ;; Nix + ("/shell\\.nix$" :trigger "__shell.nix") + (nix-mode) + ;; Org + ("/README\\.org$" + :when +file-templates-in-emacs-dirs-p + :trigger "__doom-readme" + :mode org-mode) + ("\\.org$" :trigger "__" :mode org-mode) + ;; PHP + ("\\.class\\.php$" :trigger "__.class.php" :mode php-mode) + (php-mode) + ;; Python + ;; TODO ("tests?/test_.+\\.py$" :trigger "__" :mode nose-mode) + ;; TODO ("/setup\\.py$" :trigger "__setup.py" :mode python-mode) + (python-mode) + ;; Ruby + ("/lib/.+\\.rb$" :trigger "__module" :mode ruby-mode :project t) + ("/spec_helper\\.rb$" :trigger "__helper" :mode rspec-mode :project t) + ("_spec\\.rb$" :mode rspec-mode :project t) + ("/\\.rspec$" :trigger "__.rspec" :mode rspec-mode :project t) + ("\\.gemspec$" :trigger "__.gemspec" :mode ruby-mode :project t) + ("/Gemfile$" :trigger "__Gemfile" :mode ruby-mode :project t) + ("/Rakefile$" :trigger "__Rakefile" :mode ruby-mode :project t) + (ruby-mode) + ;; Rust + ("/Cargo.toml$" :trigger "__Cargo.toml" :mode rust-mode) + ("/main\\.rs$" :trigger "__main.rs" :mode rust-mode) + ;; Slim + ("/\\(?:index\\|main\\)\\.slim$" :mode slim-mode) + ;; Shell scripts + ("\\.zunit$" :trigger "__zunit" :mode sh-mode) + (fish-mode) + (sh-mode) + ;; Solidity + (solidity-mode :trigger "__sol")) + "An alist of file template rules. The CAR of each rule is either a major mode +symbol or regexp string. The CDR is a plist. See `set-file-template!' for more +information.") + + +;; +;; Library + +(defun +file-templates-in-emacs-dirs-p (file) + "Returns t if FILE is in Doom or your private directory." + (or (file-in-directory-p file doom-private-dir) + (file-in-directory-p file doom-emacs-dir))) + +(defun +file-template-p (rule) + "Return t if RULE applies to the current buffer." + (let ((pred (car rule)) + (plist (cdr rule))) + (and (cond ((symbolp pred) (eq major-mode pred)) + ((and (stringp pred) buffer-file-name) + (string-match-p pred buffer-file-name)) + ((not (plist-member plist :when)) t) + ((funcall (plist-get plist :when) buffer-file-name))) + rule))) + +(defun +file-templates-check-h () + "Check if the current buffer is a candidate for file template expansion. It +must be non-read-only, empty, and there must be a rule in +`+file-templates-alist' that applies to it." + (and buffer-file-name + (not buffer-read-only) + (bobp) (eobp) + (not (member (substring (buffer-name) 0 1) '("*" " "))) + (not (file-exists-p buffer-file-name)) + ;; Prevent file-templates from breaking org-capture when target file + ;; doesn't exist and has a file template. + (or (not (fboundp 'org-capture-get)) + (not (org-capture-get :new-buffer))) + (when-let (rule (cl-find-if #'+file-template-p +file-templates-alist)) + (apply #'+file-templates--expand rule)))) + + +;; +;;; Bootstrap + +(after! yasnippet + (if (featurep! :editor snippets) + (add-to-list 'yas-snippet-dirs '+file-templates-dir 'append #'eq) + (setq yas-prompt-functions (delq #'yas-dropdown-prompt yas-prompt-functions) + yas-snippet-dirs '(+file-templates-dir)) + ;; Exit snippets on ESC from normal mode + (add-hook 'doom-escape-hook #'yas-abort-snippet) + ;; Ensure file templates in `+file-templates-dir' are visible + (yas-reload-all))) + +;; +(add-hook 'find-file-hook #'+file-templates-check-h) diff --git a/.emacs.d/modules/editor/file-templates/packages.el b/.emacs.d/modules/editor/file-templates/packages.el new file mode 100644 index 0000000..7a059ca --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/packages.el @@ -0,0 +1,5 @@ +;; -*- no-byte-compile: t; -*- +;;; editor/file-templates/packages.el + +(package! yasnippet) + diff --git a/.emacs.d/modules/editor/file-templates/templates/.editorconfig b/.emacs.d/modules/editor/file-templates/templates/.editorconfig new file mode 100644 index 0000000..c6d4238 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/.editorconfig @@ -0,0 +1,7 @@ +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false diff --git a/.emacs.d/modules/editor/file-templates/templates/android-mode/__build.gradle b/.emacs.d/modules/editor/file-templates/templates/android-mode/__build.gradle new file mode 100644 index 0000000..68ab148 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/android-mode/__build.gradle @@ -0,0 +1,49 @@ +# -*- mode: snippet -*- +# name: build.gradle template +# condition: (eq major-mode 'groovy-mode) +# -- +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.2.3' + } +} + +apply plugin: 'android' + +android { + compileSdkVersion 22 + buildToolsVersion "22.0.1" + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['java'] + resources.srcDirs = ['java'] + aidl.srcDirs = ['java'] + renderscript.srcDirs = ['java'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + + // Move the tests to tests/java, tests/res, etc... + androidTest.setRoot('../tests') + + // Move the build types to build-types/ + // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... + // This moves them out of them default location under src//... which would + // conflict with src/ being used by the main source set. + // Adding new build types or product flavors should be accompanied + // by a similar customization. + debug.setRoot('build-types/debug') + release.setRoot('build-types/release') + } + + lintOptions { + abortOnError false + } +} + +$0 diff --git a/.emacs.d/modules/editor/file-templates/templates/c++-mode/__cpp b/.emacs.d/modules/editor/file-templates/templates/c++-mode/__cpp new file mode 100644 index 0000000..10f8293 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/c++-mode/__cpp @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# group: file templates +# contributor: Henrik Lissner +# -- +#include "`(file-name-nondirectory (file-name-sans-extension (buffer-file-name)))`.h" + +$0 diff --git a/.emacs.d/modules/editor/file-templates/templates/c++-mode/__hpp b/.emacs.d/modules/editor/file-templates/templates/c++-mode/__hpp new file mode 100644 index 0000000..c38ae15 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/c++-mode/__hpp @@ -0,0 +1,10 @@ +# -*- mode: snippet -*- +# group: file templates +# contributor: Henrik Lissner +# -- +#ifndef ${1:__`(upcase (file-name-base buffer-file-name))`_H_$(upcase yas-text)} +#define $1 + +$0 + +#endif // $1 diff --git a/.emacs.d/modules/editor/file-templates/templates/c++-mode/__main.cpp b/.emacs.d/modules/editor/file-templates/templates/c++-mode/__main.cpp new file mode 100644 index 0000000..b4a1557 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/c++-mode/__main.cpp @@ -0,0 +1,13 @@ +# -*- mode: snippet -*- +# group: file templates +# contributor: Henrik Lissner +# -- +#include + +using namespace std; + +int main(int argc, char *argv[]) { + $0 + + return 0; +} diff --git a/.emacs.d/modules/editor/file-templates/templates/c++-mode/__winmain.cpp b/.emacs.d/modules/editor/file-templates/templates/c++-mode/__winmain.cpp new file mode 100644 index 0000000..67947be --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/c++-mode/__winmain.cpp @@ -0,0 +1,12 @@ +# -*- mode: snippet -*- +# group: file templates +# contributor: Henrik Lissner +# -- +#include + +int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + $0 + + return 0; +} diff --git a/.emacs.d/modules/editor/file-templates/templates/c-mode/__c b/.emacs.d/modules/editor/file-templates/templates/c-mode/__c new file mode 100644 index 0000000..10f8293 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/c-mode/__c @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# group: file templates +# contributor: Henrik Lissner +# -- +#include "`(file-name-nondirectory (file-name-sans-extension (buffer-file-name)))`.h" + +$0 diff --git a/.emacs.d/modules/editor/file-templates/templates/c-mode/__h b/.emacs.d/modules/editor/file-templates/templates/c-mode/__h new file mode 100644 index 0000000..c38ae15 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/c-mode/__h @@ -0,0 +1,10 @@ +# -*- mode: snippet -*- +# group: file templates +# contributor: Henrik Lissner +# -- +#ifndef ${1:__`(upcase (file-name-base buffer-file-name))`_H_$(upcase yas-text)} +#define $1 + +$0 + +#endif // $1 diff --git a/.emacs.d/modules/editor/file-templates/templates/direnv-envrc-mode/__envrc b/.emacs.d/modules/editor/file-templates/templates/direnv-envrc-mode/__envrc new file mode 100644 index 0000000..605f9c9 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/direnv-envrc-mode/__envrc @@ -0,0 +1,15 @@ +# -*- mode: snippet -*- +# group: file templates +# contributor: Henrik Lissner +# condition: (executable-find "nix-env") +# -- +if type lorri &>/dev/null; then + echo "direnv: using lorri" + eval "$(lorri direnv)" +else + # fall back to using direnv's builtin nix support + # to prevent bootstrapping problems. + use nix +fi + +`%`$0 \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/dockerfile-mode/__ b/.emacs.d/modules/editor/file-templates/templates/dockerfile-mode/__ new file mode 100644 index 0000000..9d1f81e --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/dockerfile-mode/__ @@ -0,0 +1,4 @@ +FROM ${1:phusion/baseimage:latest} +MAINTAINER ${2:`user-full-name` <`user-mail-address`>} + +$0 \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/emacs-ert-mode/__ b/.emacs.d/modules/editor/file-templates/templates/emacs-ert-mode/__ new file mode 100644 index 0000000..425b713 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/emacs-ert-mode/__ @@ -0,0 +1,3 @@ +;;; `(file-name-nondirectory buffer-file-name)` + +$0 \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-doctor b/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-doctor new file mode 100644 index 0000000..af30ef1 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-doctor @@ -0,0 +1,3 @@ +;;; `(+file-templates-get-short-path)` -*- lexical-binding: t; -*- + +$0 diff --git a/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-module b/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-module new file mode 100644 index 0000000..af30ef1 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-module @@ -0,0 +1,3 @@ +;;; `(+file-templates-get-short-path)` -*- lexical-binding: t; -*- + +$0 diff --git a/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-packages b/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-packages new file mode 100644 index 0000000..bf02a04 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-packages @@ -0,0 +1,4 @@ +;; -*- no-byte-compile: t; -*- +;;; `(+file-templates-get-short-path)` + +$0 diff --git a/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-test b/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-test new file mode 100644 index 0000000..0548197 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__doom-test @@ -0,0 +1,4 @@ +;; -*- no-byte-compile: t; -*- +;;; `(+file-templates-get-short-path)` + +$0 \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__initfile b/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__initfile new file mode 100644 index 0000000..23a1301 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/emacs-lisp-mode/__initfile @@ -0,0 +1,6 @@ +;;; `(file-name-nondirectory buffer-file-name)`${1: --- ${2:description}} -*- lexical-binding: t; -*- + +$0 + +(provide '`(file-name-base buffer-file-name)`) +;;; `(file-name-nondirectory buffer-file-name)` ends here diff --git a/.emacs.d/modules/editor/file-templates/templates/fish-mode/__ b/.emacs.d/modules/editor/file-templates/templates/fish-mode/__ new file mode 100644 index 0000000..a4ef95d --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/fish-mode/__ @@ -0,0 +1,3 @@ +#!/usr/bin/env fish + +$0 diff --git a/.emacs.d/modules/editor/file-templates/templates/gitignore-mode/__ b/.emacs.d/modules/editor/file-templates/templates/gitignore-mode/__ new file mode 100644 index 0000000..ef1380d --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/gitignore-mode/__ @@ -0,0 +1,31 @@ +.DS_Store +.idea +*.log +tmp/ + +`(let ((type-ignore (yas-choose-value '("(none)" "python" "ruby" "java" "js")))) + (string-join + (cond ((string= type-ignore "python") + '("*.py[cod]" + "*.egg" + "build" + "htmlcov")) + ((string= type-ignore "ruby") + '(".ruby-version" + ".bundle" + "vendor" + "Gemfile.lock" + "coverage")) + ((string= type-ignore "java") + '("*.class" + "build")) + ((string= type-ignore "js") + '("*.tern-port" + "node_modules/" + "npm-debug.log*" + "yarn-debug.log*" + "yarn-error.log*" + "*.tsbuildinfo" + ".npm" + ".eslintcache"))) + "\n"))` diff --git a/.emacs.d/modules/editor/file-templates/templates/go-mode/__.go b/.emacs.d/modules/editor/file-templates/templates/go-mode/__.go new file mode 100644 index 0000000..c37b758 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/go-mode/__.go @@ -0,0 +1,7 @@ +package ${1:main} + +import ( + "fmt" +) + +$0 \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/go-mode/__main.go b/.emacs.d/modules/editor/file-templates/templates/go-mode/__main.go new file mode 100644 index 0000000..9aacb34 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/go-mode/__main.go @@ -0,0 +1,9 @@ +package ${1:main} + +import ( + "fmt" +) + +func main() { + $0 +} \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/java-mode/.yas-setup.el b/.emacs.d/modules/editor/file-templates/templates/java-mode/.yas-setup.el new file mode 100644 index 0000000..b00feed --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/java-mode/.yas-setup.el @@ -0,0 +1,9 @@ +(defun yas-java-project-package () + (or (and (eq major-mode 'java-mode) + (+java-current-package)) + "")) + +(defun yas-java-class-name () + (or (and (eq major-mode 'java-mode) + (+java-current-class)) + "")) diff --git a/.emacs.d/modules/editor/file-templates/templates/java-mode/__ b/.emacs.d/modules/editor/file-templates/templates/java-mode/__ new file mode 100644 index 0000000..2f45b0c --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/java-mode/__ @@ -0,0 +1,9 @@ +# -*- mode: snippet -*- +# name: Java file template +# -- +package `(yas-java-project-package)`; + +public class `(yas-java-class-name)` $1 +{ + $0 +} diff --git a/.emacs.d/modules/editor/file-templates/templates/java-mode/__main b/.emacs.d/modules/editor/file-templates/templates/java-mode/__main new file mode 100644 index 0000000..20dde12 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/java-mode/__main @@ -0,0 +1,11 @@ +# -*- mode: snippet -*- +# name: Java MAIN file template +# -- +package `(yas-java-project-package)`; + +public class `(yas-java-class-name)` $1 +{ + public static void main(String[] args) { + $0 + } +} diff --git a/.emacs.d/modules/editor/file-templates/templates/js-mode/__gulpfile.js b/.emacs.d/modules/editor/file-templates/templates/js-mode/__gulpfile.js new file mode 100644 index 0000000..8707798 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/js-mode/__gulpfile.js @@ -0,0 +1,4 @@ +const gulp = require('gulp'); + +// Default task +gulp.task('default', [$1]); \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/js-mode/__webpack.config.js b/.emacs.d/modules/editor/file-templates/templates/js-mode/__webpack.config.js new file mode 100644 index 0000000..d7998e1 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/js-mode/__webpack.config.js @@ -0,0 +1,21 @@ +module.exports = { + entry: [ + ${1:'./app/main.js'} + ], + output: { + path: __dirname + '/dist', + filename: "app.bundle.js" + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: { + loader: "babel-loader" + } + } + ] + }, + // plugins: [] +}; diff --git a/.emacs.d/modules/editor/file-templates/templates/json-mode/__ b/.emacs.d/modules/editor/file-templates/templates/json-mode/__ new file mode 100644 index 0000000..e959722 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/json-mode/__ @@ -0,0 +1,3 @@ +[ + $0 +] diff --git a/.emacs.d/modules/editor/file-templates/templates/json-mode/__bower.json b/.emacs.d/modules/editor/file-templates/templates/json-mode/__bower.json new file mode 100644 index 0000000..6513f26 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/json-mode/__bower.json @@ -0,0 +1,14 @@ +{ + "name": "${1:package-name}", + "description": "${2:description}", + "license": "MIT", + "authors": [ + "`user-full-name` <`user-mail-address`>" + ], + "main": "public/app.js", + "moduleType": [], + "homepage": "", + "dependencies": { + `%`$0 + } +} \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/json-mode/__package.json b/.emacs.d/modules/editor/file-templates/templates/json-mode/__package.json new file mode 100644 index 0000000..cff3553 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/json-mode/__package.json @@ -0,0 +1,13 @@ +{ + "name": "${1:package-name}", + "description": "${2:description}", + "version": "1.0.0", + "author": "`user-full-name` <`user-mail-address`>", + "license": "MIT", + "main": "${3:app/${4:main.js}}", + "scripts": { + $0 + }, + "dependencies": {}, + "devDependencies": {} +} \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/love-mode/__conf.lua b/.emacs.d/modules/editor/file-templates/templates/love-mode/__conf.lua new file mode 100644 index 0000000..ee66815 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/love-mode/__conf.lua @@ -0,0 +1,40 @@ +# -*- mode: snippet -*- +# name: love.conf +# condition: (eq major-mode 'lua-mode) +# -- +function love.conf(t) + t.identity = nil -- The name of the save directory (string) + t.version = "0.9.1" -- The LÖVE version this game was made for (string) + t.console = false -- Attach a console (boolean, Windows only) + + t.window.title = "${1:Untitled}" -- The window title (string) + t.window.icon = nil -- Filepath to an image to use as the window's icon (string) + t.window.width = 800 -- The window width (number) + t.window.height = 600 -- The window height (number) + t.window.borderless = false -- Remove all border visuals from the window (boolean) + t.window.resizable = false -- Let the window be user-resizable (boolean) + t.window.minwidth = 1 -- Minimum window width if the window is resizable (number) + t.window.minheight = 1 -- Minimum window height if the window is resizable (number) + t.window.fullscreen = false -- Enable fullscreen (boolean) + t.window.fullscreentype = "normal" -- Standard fullscreen or desktop fullscreen mode (string) + t.window.vsync = true -- Enable vertical sync (boolean) + t.window.fsaa = 0 -- The number of samples to use with multi-sampled antialiasing (number) + t.window.display = 1 -- Index of the monitor to show the window in (number) + t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean). Added in 0.9.1 + t.window.srgb = false -- Enable sRGB gamma correction when drawing to the screen (boolean). Added in 0.9.1 + + t.modules.audio = true -- Enable the audio module (boolean) + t.modules.event = true -- Enable the event module (boolean) + t.modules.graphics = true -- Enable the graphics module (boolean) + t.modules.image = true -- Enable the image module (boolean) + t.modules.joystick = true -- Enable the joystick module (boolean) + t.modules.keyboard = true -- Enable the keyboard module (boolean) + t.modules.math = true -- Enable the math module (boolean) + t.modules.mouse = true -- Enable the mouse module (boolean) + t.modules.physics = true -- Enable the physics module (boolean) + t.modules.sound = true -- Enable the sound module (boolean) + t.modules.system = true -- Enable the system module (boolean) + t.modules.timer = true -- Enable the timer module (boolean) + t.modules.window = true -- Enable the window module (boolean) + t.modules.thread = true -- Enable the thread module (boolean) +end diff --git a/.emacs.d/modules/editor/file-templates/templates/love-mode/__main.lua b/.emacs.d/modules/editor/file-templates/templates/love-mode/__main.lua new file mode 100644 index 0000000..e69de29 diff --git a/.emacs.d/modules/editor/file-templates/templates/makefile-gmake-mode/__ b/.emacs.d/modules/editor/file-templates/templates/makefile-gmake-mode/__ new file mode 100644 index 0000000..466fab2 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/makefile-gmake-mode/__ @@ -0,0 +1,9 @@ +## +# ${1:Project Title} +# +# @file +# @version 0.1 + +$0 + +# end diff --git a/.emacs.d/modules/editor/file-templates/templates/makefile-gmake-mode/__cpp b/.emacs.d/modules/editor/file-templates/templates/makefile-gmake-mode/__cpp new file mode 100644 index 0000000..d589c26 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/makefile-gmake-mode/__cpp @@ -0,0 +1,46 @@ +# -*- mode: snippet -*- +# contributor: Henrik Lissner +# -- +CC = clang++ +TAGS = ctags -e -x >TAGS +FLAGS = -std=c++11 -stdlib=libc++ -Iinclude -I/usr/local/include -L/usr/local/lib +CFLAGS = -pedantic -Wall -Wextra -ggdb3 +LIBS = # ... + +DEBUG_FLAGS = -O0 -D _DEBUG +RELEASE_FLAGS = -O2 -D NDEBUG -combile -fwhole-program + +RELEASE_DIR = ./build/release +DEBUG_DIR = ./build/debug + +TARGET = ${1:appname$(replace-regexp-in-string " " "" yas-text nil t)} +HEADERS = \$(shell echo include/*.h) +SOURCES = \$(shell echo src/*.cpp) \$(shell echo src/**/*.cpp) +OBJECTS = \$(SOURCES:.cpp=.o) + +### + +all: \$(TARGET) + +debug: FLAGS += \$(DEBUG_FLAGS) +debug: \$(TARGET) + +release: FLAGS += \$(RELEASE_FLAGS) +release: \$(TARGET) + +profile: CFLAGS += -pg +profile: \$(TARGET) + +\$(TARGET): \$(OBJECTS) + \$(CC) \$(FLAGS) \$(CFLAGS) -o \$(TARGET) \$(OBJECTS) + +%.o: %.cpp + \$(CC) \$(FLAGS) \$(CFLAGS) -c -o \$@ \$< + +tags: \$(SOURCES) + \$(CTAGS) \$(SOURCES) + +clean: + -rm -f \$(OBJECTS) \$(TARGET) + +.PHONY: all profile release debug clean \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/markdown-mode/__ b/.emacs.d/modules/editor/file-templates/templates/markdown-mode/__ new file mode 100644 index 0000000..74c930c --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/markdown-mode/__ @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# group: file templates +# -- +# ${1:Document Title} + +$0 \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/markdown-mode/__jekyll-post b/.emacs.d/modules/editor/file-templates/templates/markdown-mode/__jekyll-post new file mode 100644 index 0000000..42fd944 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/markdown-mode/__jekyll-post @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# group: file templates +# -- +--- +layout: ${1:default} +--- +$0 diff --git a/.emacs.d/modules/editor/file-templates/templates/nix-mode/__ b/.emacs.d/modules/editor/file-templates/templates/nix-mode/__ new file mode 100644 index 0000000..427a3dc --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/nix-mode/__ @@ -0,0 +1,9 @@ +# -*- mode: snippet -*- +# group: file templates +# contributor: Henrik Lissner +# -- +{ config, lib, pkgs, ... }: + +{ + $0 +} \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/nix-mode/__shell.nix b/.emacs.d/modules/editor/file-templates/templates/nix-mode/__shell.nix new file mode 100644 index 0000000..f11f38d --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/nix-mode/__shell.nix @@ -0,0 +1,9 @@ +{ pkgs ? import {} }: + +with pkgs; + +mkShell { + buildInputs = [ + ${0:`%`} + ]; +} \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/nose-mode/__ b/.emacs.d/modules/editor/file-templates/templates/nose-mode/__ new file mode 100644 index 0000000..e69de29 diff --git a/.emacs.d/modules/editor/file-templates/templates/nxml-mode/__ b/.emacs.d/modules/editor/file-templates/templates/nxml-mode/__ new file mode 100644 index 0000000..478d949 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/nxml-mode/__ @@ -0,0 +1,2 @@ + +`%`$0 \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/org-mode/__ b/.emacs.d/modules/editor/file-templates/templates/org-mode/__ new file mode 100644 index 0000000..23d7b52 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/org-mode/__ @@ -0,0 +1,6 @@ +# -*- mode: snippet -*- +# name: Org template +# -- +#+TITLE: ${1:`(file-name-base buffer-file-name)`} + +$0 \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/org-mode/__contact.org b/.emacs.d/modules/editor/file-templates/templates/org-mode/__contact.org new file mode 100644 index 0000000..b346247 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/org-mode/__contact.org @@ -0,0 +1,17 @@ +#+TITLE:${1:Contact Name} +#+DATE: `(format-time-string "%Y-%m-%d")` +#+EMAIL: ${2:email@address.com} +#+ADDRESS: $3 +#+CITY: $4 +#+COUNTRY: $5 + +${0:...} + +* Associates ++ Associate's name :: associates@email.com +* Projects [/] +** ACTIVE ... [/] +* Invoices [/] +** PENDING YYMM-0000 *1,000 USD* +* Notes +* Correspondence \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/org-mode/__doom-readme b/.emacs.d/modules/editor/file-templates/templates/org-mode/__doom-readme new file mode 100644 index 0000000..8e8af8f --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/org-mode/__doom-readme @@ -0,0 +1,42 @@ +# -*- mode: snippet -*- +# name: Doom module readme +# -- +#+TITLE: ${1:`(if (string-match "modules/\\([^/]+\\)/\\([^/]+\\)/.+" buffer-file-name) + (format "%s/%s" + (match-string 1 buffer-file-name) + (match-string 2 buffer-file-name)) + "")`} +#+DATE: `(format (format-time-string "%B %%s, %Y") (string-to-number (format-time-string "%d")))` +#+SINCE: ${2:{replace with next tagged release version}} +#+STARTUP: inlineimages + +* Table of Contents :TOC_3:noexport: + +* Description +${3:A summary of what this module does.} + ++ If possible, include a brief list of feature highlights here ++ Like code completion, syntax checking or available snippets ++ Include links to packages & external things where possible + +** Module Flags +This module provides no flags. + +** Plugins +{A list of linked plugins} + +** Hacks +{A list of internal modifications to included packages} + +* Prerequisites +This module has no prereqisites. + +* Features +An in-depth list of features, how to use them, and their dependencies. + +* Configuration +How to configure this module, including common problems and how to address them. + +* Troubleshooting +Common issues and their solution, or places to look for help. +$0 \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/org-mode/__invoice.org b/.emacs.d/modules/editor/file-templates/templates/org-mode/__invoice.org new file mode 100644 index 0000000..f0241c6 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/org-mode/__invoice.org @@ -0,0 +1,21 @@ +#+TITLE:${1:1234-5678} +#+DATE: `(format-time-string "%Y-%m-%d")` +#+ISSUED: ${2:`(format-time-string "%Y-%m-%d")`} +#+PAID: ${3:`(format-time-string "%Y-%m-%d")`} +#+CURRENCY: ${4:CAD|USD|DKK|GBP} +#+CONTACT: ${5:Contact ID} +#+PROJECT: ${6:Project ID} + +| *Description* | *qty* | *rate* | *total* | +|----------------------------------------------+-----+--------+-------| +| $0 [1] | | | | +|----------------------------------------------+-----+--------+-------| +| | | | | +#+TBLFM: $4=$2+$3::@>$4=vsum(@2..@-1) + +[1] ... + +* Comments +... +* Notes +** Attachments [0/0] \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/org-mode/__project.org b/.emacs.d/modules/editor/file-templates/templates/org-mode/__project.org new file mode 100644 index 0000000..4546410 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/org-mode/__project.org @@ -0,0 +1,9 @@ +#+TITLE:${1:Project Name} +#+DATE: `(format-time-string "%Y-%m-%d")` +#+CONTACT: ${2:Contact name} + +$0 + +* Tasks [/] +* Timeframe +* Notes \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/php-mode/__ b/.emacs.d/modules/editor/file-templates/templates/php-mode/__ new file mode 100644 index 0000000..14ae8ba --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/php-mode/__ @@ -0,0 +1,3 @@ +" ] +$0 diff --git a/.emacs.d/modules/editor/file-templates/templates/rust-mode/__main.rs b/.emacs.d/modules/editor/file-templates/templates/rust-mode/__main.rs new file mode 100644 index 0000000..141e9ce --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/rust-mode/__main.rs @@ -0,0 +1,5 @@ +use std::io; + +fn main() { + ${0:println!("Hello, world!");} +} diff --git a/.emacs.d/modules/editor/file-templates/templates/scss-mode/__ b/.emacs.d/modules/editor/file-templates/templates/scss-mode/__ new file mode 100644 index 0000000..e69de29 diff --git a/.emacs.d/modules/editor/file-templates/templates/scss-mode/__master.scss b/.emacs.d/modules/editor/file-templates/templates/scss-mode/__master.scss new file mode 100644 index 0000000..e69de29 diff --git a/.emacs.d/modules/editor/file-templates/templates/scss-mode/__normalize.scss b/.emacs.d/modules/editor/file-templates/templates/scss-mode/__normalize.scss new file mode 100644 index 0000000..e69de29 diff --git a/.emacs.d/modules/editor/file-templates/templates/sh-mode/__ b/.emacs.d/modules/editor/file-templates/templates/sh-mode/__ new file mode 100644 index 0000000..3bb86a6 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/sh-mode/__ @@ -0,0 +1,3 @@ +#!/usr/bin/env `(if (equal (file-name-extension buffer-file-name) "zsh") "zsh" "bash")` + +$0 diff --git a/.emacs.d/modules/editor/file-templates/templates/sh-mode/__zunit b/.emacs.d/modules/editor/file-templates/templates/sh-mode/__zunit new file mode 100644 index 0000000..6abced4 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/sh-mode/__zunit @@ -0,0 +1,5 @@ +#!/usr/bin/env zunit + +@test '...' { + $0 +} diff --git a/.emacs.d/modules/editor/file-templates/templates/slim-mode/__ b/.emacs.d/modules/editor/file-templates/templates/slim-mode/__ new file mode 100644 index 0000000..51e72e9 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/slim-mode/__ @@ -0,0 +1,17 @@ +doctype html +html(lang="en") + head + meta charset="utf-8" + meta http-equiv="x-ua-compatible" content="ie=edge" + + title ${1:Page Title} + meta name="description" content="" + meta name="viewport" content="width=device-width, initial-scale=1" + + / link rel="apple-touch-icon" href="apple-touch-icon.png" + + link rel="stylesheet" type="text/css" href="css/master.css" + + script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js" + body + $0 diff --git a/.emacs.d/modules/editor/file-templates/templates/snippet-mode/__ b/.emacs.d/modules/editor/file-templates/templates/snippet-mode/__ new file mode 100644 index 0000000..9b4496f --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/snippet-mode/__ @@ -0,0 +1,8 @@ +# -*- mode: snippet -*- +# -- +# -*- mode: snippet -*- +# name: $1 +# key: ${2:trigger-key}${3: +# condition: t} +# -- +$0 diff --git a/.emacs.d/modules/editor/file-templates/templates/solidity-mode/__sol b/.emacs.d/modules/editor/file-templates/templates/solidity-mode/__sol new file mode 100644 index 0000000..46facfd --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/solidity-mode/__sol @@ -0,0 +1,10 @@ +# -*- mode: snippet -*- +# group: file templates +# contributor: Edmund Miller +# name: solidity template +# -- +pragma solidity ^0.4.22; + +contract $0 { + +} \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/text-mode/__license b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license new file mode 100644 index 0000000..b22a289 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license @@ -0,0 +1,5 @@ +# -*- mode: snippet -*- +# name: Insert a license +# type: command +# -- +(+file-templates/insert-license) \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-apache b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-apache new file mode 100644 index 0000000..a50a612 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-apache @@ -0,0 +1,207 @@ +# -*- mode: snippet -*- +# name: Apache License 2.0 +# uuid: __license-apache +# group: Licenses +# contributor: https://choosealicense.com/licenses/apache-2.0/ +# -- + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright `(format-time-string "%Y")` ${1:`user-full-name`} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-apache-bp b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-apache-bp new file mode 100644 index 0000000..95d9d8b --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-apache-bp @@ -0,0 +1,19 @@ +# -*- mode: snippet -*- +# name: Apache License 2.0 (boilerplate) +# uuid: __license-apache-bp +# group: Licenses +# contributor: https://choosealicense.com/licenses/apache-2.0/ +# -- +Copyright ${1:`(format-time-string "%Y")` `user-full-name`} + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-bsd2 b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-bsd2 new file mode 100644 index 0000000..bbff2e3 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-bsd2 @@ -0,0 +1,28 @@ +# -*- mode: snippet -*- +# name: 2-clause BSD License +# uuid: __license-bsd2 +# group: Licenses +# -- +Copyright (c) ${1:`(format-time-string "%Y")`} ${2:`user-full-name`} +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-bsd3 b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-bsd3 new file mode 100644 index 0000000..dc3d0e8 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-bsd3 @@ -0,0 +1,31 @@ +# -*- mode: snippet -*- +# name: 3-clause BSD License +# uuid: __license-bsd3 +# -- +Copyright (c) ${1:`(format-time-string "%Y")`} ${2:`user-full-name`} +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-gpl3 b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-gpl3 new file mode 100644 index 0000000..197d810 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-gpl3 @@ -0,0 +1,680 @@ +# -*- mode: snippet -*- +# name: GNU GPL 3.0 License +# uuid: __license-gpl3 +# group: Licenses +# contributor: https://choosealicense.com/licenses/gpl-3.0/ +# -- + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-gpl3-bp b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-gpl3-bp new file mode 100644 index 0000000..4e8ff3c --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-gpl3-bp @@ -0,0 +1,21 @@ +# -*- mode: snippet -*- +# name: GNU GPL 3.0 License (boilerplate) +# uuid: __license-gpl3-bp +# group: Licenses +# contributor: https://choosealicense.com/licenses/lgpl-3.0 +# -- +${1:} +Copyright (C) ${2:`(format-time-string "%Y")`} ${3:`user-full-name`} + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-lgpl3 b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-lgpl3 new file mode 100644 index 0000000..8aefb50 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-lgpl3 @@ -0,0 +1,171 @@ +# -*- mode: snippet -*- +# name: GNU LGPL v3 License +# uuid: __license-lgpl3 +# group: Licenses +# contribuer: https://choosealicense.com/licenses/lgpl-3.0/ +# -- + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-mit b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-mit new file mode 100644 index 0000000..dd041f8 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-mit @@ -0,0 +1,28 @@ +# -*- mode: snippet -*- +# name: MIT License +# uuid: __license-mit +# group: Licenses +# contributor: https://choosealicense.com/licenses/mit/ +# -- +The MIT License (MIT) + +Copyright (c) ${1:`(format-time-string "%Y")`} ${2:`user-full-name`} + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-mozilla b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-mozilla new file mode 100644 index 0000000..bba13b7 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-mozilla @@ -0,0 +1,380 @@ +# -*- mode: snippet -*- +# name: Mozilla Public License 2.0 +# uuid: __license-mozilla +# group: Licenses +# contributor: https://choosealicense.com/licenses/mpl-2.0 +# -- +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + diff --git a/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-mozilla-bp b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-mozilla-bp new file mode 100644 index 0000000..7a7fc42 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-mozilla-bp @@ -0,0 +1,9 @@ +# -*- mode: snippet -*- +# name: Mozilla Public License 2.0 (boilerplate) +# uuid: __license-mozilla-bp +# group: Licenses +# contributor: https://choosealicense.com/licenses/mpl-2.0 +# -- +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. \ No newline at end of file diff --git a/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-unlicense b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-unlicense new file mode 100644 index 0000000..6eb9927 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/text-mode/__license-unlicense @@ -0,0 +1,30 @@ +# -*- mode: snippet -*- +# name: The Unlicense +# uuid: __license-unlicense +# group: Licenses +# contributor: https://choosealicense.com/licenses/unlicense +# -- +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/.emacs.d/modules/editor/file-templates/templates/web-mode/__.html b/.emacs.d/modules/editor/file-templates/templates/web-mode/__.html new file mode 100644 index 0000000..632e8c8 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/web-mode/__.html @@ -0,0 +1,25 @@ + + + + + + ${1:Untitled} + + + + + + + + + + + $0 + + diff --git a/.emacs.d/modules/editor/file-templates/templates/web-mode/__jekyll-layout.html b/.emacs.d/modules/editor/file-templates/templates/web-mode/__jekyll-layout.html new file mode 100644 index 0000000..e69de29 diff --git a/.emacs.d/modules/editor/file-templates/templates/yaml-mode/__docker-compose.yml b/.emacs.d/modules/editor/file-templates/templates/yaml-mode/__docker-compose.yml new file mode 100644 index 0000000..29d2053 --- /dev/null +++ b/.emacs.d/modules/editor/file-templates/templates/yaml-mode/__docker-compose.yml @@ -0,0 +1,15 @@ +web: + build: . + ports: + - "8080:80" + volumes: + - ./public:/var/www + links: + - mysql + - memcache + +mysql: + image: mysql + +redis: + image: redis diff --git a/.emacs.d/modules/editor/fold/README.org b/.emacs.d/modules/editor/fold/README.org new file mode 100644 index 0000000..55c93ff --- /dev/null +++ b/.emacs.d/modules/editor/fold/README.org @@ -0,0 +1,32 @@ +#+TITLE: editor/fold +#+DATE: February 17, 2019 +#+SINCE: v2.1 +#+STARTUP: inlineimages + +* Table of Contents :TOC_3:noexport: +- [[Description][Description]] + - [[Module Flags][Module Flags]] + - [[Plugins][Plugins]] +- [[Prerequisites][Prerequisites]] +- [[Features][Features]] +- [[Configuration][Configuration]] +- [[Troubleshooting][Troubleshooting]] + +* Description +This module marries hideshow, vimish-fold and outline-minor-mode to bring you +marker, indent and syntax-based code folding for as many languages as possible. + +** Module Flags +This module provides no flags. + +** Plugins ++ evil-vimish-fold* + +* Prerequisites +This module has no prerequisites. + +* TODO Features + +* TODO Configuration + +* TODO Troubleshooting diff --git a/.emacs.d/modules/editor/fold/autoload/fold.el b/.emacs.d/modules/editor/fold/autoload/fold.el new file mode 100644 index 0000000..7105885 --- /dev/null +++ b/.emacs.d/modules/editor/fold/autoload/fold.el @@ -0,0 +1,155 @@ +;;; editor/fold/autoload/fold.el -*- lexical-binding: t; -*- + +;; `hideshow' is a decent code folding implementation, but it won't let you +;; create custom folds. `vimish-fold' offers custom folds, but essentially +;; ignores any other type of folding (indent or custom markers, which hideshow +;; and `outline-mode' give you). This is my effort to combine them. + +;; +;;; Helpers + +(defun +fold--ensure-hideshow-mode () + (unless (bound-and-true-p hs-minor-mode) + (hs-minor-mode +1))) + +(defun +fold--vimish-fold-p () + (and (featurep 'vimish-fold) + (cl-some #'vimish-fold--vimish-overlay-p + (overlays-at (point))))) + +(defun +fold--outline-fold-p () + (and (or (bound-and-true-p outline-minor-mode) + (derived-mode-p 'outline-mode)) + (outline-on-heading-p))) + +(defun +fold--hideshow-fold-p () + (+fold--ensure-hideshow-mode) + (save-excursion + (ignore-errors + (or (hs-looking-at-block-start-p) + (hs-find-block-beginning))))) + +(defun +fold--invisible-points (count) + (let (points) + (save-excursion + (catch 'abort + (if (< count 0) (beginning-of-line)) + (while (re-search-forward hs-block-start-regexp nil t + (if (> count 0) 1 -1)) + (unless (invisible-p (point)) + (end-of-line) + (when (hs-already-hidden-p) + (push (point) points) + (when (>= (length points) count) + (throw 'abort nil)))) + (forward-line (if (> count 0) 1 -1))))) + points)) + +(defmacro +fold-from-eol (&rest body) + "Perform action after moving to the end of the line." + `(save-excursion + (end-of-line) + ,@body)) + + +;; +;;; Commands + +;;;###autoload +(defun +fold/toggle () + "Toggle the fold at point. + +Targets `vimmish-fold', `hideshow' and `outline' folds." + (interactive) + (save-excursion + (cond ((+fold--vimish-fold-p) (vimish-fold-toggle)) + ((+fold--outline-fold-p) + (cl-letf (((symbol-function #'outline-hide-subtree) + (symbol-function #'outline-hide-entry))) + (outline-toggle-children))) + ((+fold--hideshow-fold-p) (+fold-from-eol (hs-toggle-hiding)))))) + +;;;###autoload +(defun +fold/open () + "Open the folded region at point. + +Targets `vimmish-fold', `hideshow' and `outline' folds." + (interactive) + (save-excursion + (cond ((+fold--vimish-fold-p) (vimish-fold-unfold)) + ((+fold--outline-fold-p) + (outline-show-children) + (outline-show-entry)) + ((+fold--hideshow-fold-p) (+fold-from-eol (hs-show-block)))))) + +;;;###autoload +(defun +fold/close () + "Close the folded region at point. + +Targets `vimmish-fold', `hideshow' and `outline' folds." + (interactive) + (save-excursion + (cond ((+fold--vimish-fold-p) (vimish-fold-refold)) + ((+fold--hideshow-fold-p) (+fold-from-eol (hs-hide-block))) + ((+fold--outline-fold-p) (outline-hide-subtree))))) + +;;;###autoload +(defun +fold/open-all (&optional level) + "Open folds at LEVEL (or all folds if LEVEL is nil)." + (interactive + (list (if current-prefix-arg (prefix-numeric-value current-prefix-arg)))) + (when (featurep 'vimish-fold) + (vimish-fold-unfold-all)) + (save-excursion + (+fold--ensure-hideshow-mode) + (if (integerp level) + (progn + (outline-hide-sublevels (max 1 (1- level))) + (hs-life-goes-on + (hs-hide-level-recursive (1- level) (point-min) (point-max)))) + (hs-show-all) + (when (fboundp 'outline-show-all) + (outline-show-all))))) + +;;;###autoload +(defun +fold/close-all (&optional level) + "Close folds at LEVEL (or all folds if LEVEL is nil)." + (interactive + (list (if current-prefix-arg (prefix-numeric-value current-prefix-arg)))) + (save-excursion + (when (featurep 'vimish-fold) + (vimish-fold-refold-all)) + (+fold--ensure-hideshow-mode) + (hs-life-goes-on + (if (integerp level) + (hs-hide-level-recursive (1- level) (point-min) (point-max)) + (hs-hide-all))))) + +;;;###autoload +(defun +fold/next (count) + "Jump to the next vimish fold, outline heading or folded region." + (interactive "p") + (cl-loop with orig-pt = (point) + for fn + in (list (lambda () + (when (bound-and-true-p hs-block-start-regexp) + (car (+fold--invisible-points count)))) + (lambda () + (when (featurep 'vimish-fold) + (if (> count 0) + (evil-vimish-fold/next-fold count) + (evil-vimish-fold/previous-fold (- count)))) + (if (/= (point) orig-pt) (point)))) + if (save-excursion (funcall fn)) + collect it into points + finally do + (if-let* ((pt (car (sort points (if (> count 0) #'< #'>))))) + (goto-char pt) + (message "No more folds %s point" (if (> count 0) "after" "before")) + (goto-char orig-pt)))) + +;;;###autoload +(defun +fold/previous (count) + "Jump to the previous vimish fold, outline heading or folded region." + (interactive "p") + (+fold/next (- count))) diff --git a/.emacs.d/modules/editor/fold/autoload/hideshow.el b/.emacs.d/modules/editor/fold/autoload/hideshow.el new file mode 100644 index 0000000..a124506 --- /dev/null +++ b/.emacs.d/modules/editor/fold/autoload/hideshow.el @@ -0,0 +1,83 @@ +;;; editor/fold/autoload/hideshow.el -*- lexical-binding: t; -*- + +(defface +fold-hideshow-folded-face + `((t (:inherit font-lock-comment-face :weight light))) + "Face to hightlight `hideshow' overlays." + :group 'doom-themes) + +;;;###autoload +(defun +fold-hideshow-haml-forward-sexp-fn (arg) + (haml-forward-sexp arg) + (move-beginning-of-line 1)) + +;;;###autoload +(defun +fold-hideshow-forward-block-by-indent-fn (_arg) + (let ((start (current-indentation))) + (forward-line) + (unless (= start (current-indentation)) + (let ((range (+fold-hideshow-indent-range))) + (goto-char (cadr range)) + (end-of-line))))) + +;;;###autoload +(defun +fold-hideshow-set-up-overlay-fn (ov) + (when (eq 'code (overlay-get ov 'hs)) + (when (featurep 'vimish-fold) + (overlay-put + ov 'before-string + (propertize "…" 'display + (list vimish-fold-indication-mode + 'empty-line + 'vimish-fold-fringe)))) + (overlay-put + ov 'display (propertize " [...] " 'face '+fold-hideshow-folded-face)))) + + +;; +;;; Indentation detection + +(defun +fold--hideshow-empty-line-p (_) + (string= "" (string-trim (thing-at-point 'line 'no-props)))) + +(defun +fold--hideshow-geq-or-empty-p (base-indent) + (or (+fold--hideshow-empty-line-p base-indent) + (>= (current-indentation) base-indent))) + +(defun +fold--hideshow-g-or-empty-p (base-indent) + (or (+fold--hideshow-empty-line-p base-indent) + (> (current-indentation) base-indent))) + +(defun +fold--hideshow-seek (start direction before skip predicate base-indent) + "Seeks forward (if direction is 1) or backward (if direction is -1) from start, until predicate +fails. If before is nil, it will return the first line where predicate fails, otherwise it returns +the last line where predicate holds." + (save-excursion + (goto-char start) + (goto-char (point-at-bol)) + (let ((bnd (if (> 0 direction) + (point-min) + (point-max))) + (pt (point))) + (when skip (forward-line direction)) + (cl-loop while (and (/= (point) bnd) (funcall predicate base-indent)) + do (progn + (when before (setq pt (point-at-bol))) + (forward-line direction) + (unless before (setq pt (point-at-bol))))) + pt))) + +(defun +fold-hideshow-indent-range (&optional point) + "Return the point at the begin and end of the text block with the same (or +greater) indentation. If `point' is supplied and non-nil it will return the +begin and end of the block surrounding point." + (save-excursion + (when point + (goto-char point)) + (let ((base-indent (current-indentation)) + (begin (point)) + (end (point))) + (setq begin (+fold--hideshow-seek begin -1 t nil #'+fold--hideshow-geq-or-empty-p base-indent) + begin (+fold--hideshow-seek begin 1 nil nil #'+fold--hideshow-g-or-empty-p base-indent) + end (+fold--hideshow-seek end 1 t nil #'+fold--hideshow-geq-or-empty-p base-indent) + end (+fold--hideshow-seek end -1 nil nil #'+fold--hideshow-empty-line-p base-indent)) + (list begin end base-indent)))) diff --git a/.emacs.d/modules/editor/fold/config.el b/.emacs.d/modules/editor/fold/config.el new file mode 100644 index 0000000..05ab100 --- /dev/null +++ b/.emacs.d/modules/editor/fold/config.el @@ -0,0 +1,79 @@ +;;; editor/fold/config.el -*- lexical-binding: t; -*- + +(when (featurep! :editor evil) + ;; Add vimish-fold, outline-mode & hideshow support to folding commands + (define-key! 'global + [remap evil-toggle-fold] #'+fold/toggle + [remap evil-close-fold] #'+fold/close + [remap evil-open-fold] #'+fold/open + [remap evil-open-fold-rec] #'+fold/open + [remap evil-close-folds] #'+fold/close-all + [remap evil-open-folds] #'+fold/open-all) + (evil-define-key* 'motion 'global + "zj" #'+fold/next + "zk" #'+fold/previous)) + + +;; +;; Packages + +(use-package! hideshow ; built-in + :commands (hs-toggle-hiding + hs-hide-block + hs-hide-level + hs-show-all + hs-hide-all) + :config + (setq hs-hide-comments-when-hiding-all nil + ;; Nicer code-folding overlays (with fringe indicators) + hs-set-up-overlay #'+fold-hideshow-set-up-overlay-fn) + + (defadvice! +fold--hideshow-ensure-mode-a (&rest _) + "Ensure `hs-minor-mode' is enabled." + :before '(hs-toggle-hiding hs-hide-block hs-hide-level hs-show-all hs-hide-all) + (unless (bound-and-true-p hs-minor-mode) + (hs-minor-mode +1))) + + ;; extra folding support for more languages + (unless (assq 't hs-special-modes-alist) + (setq hs-special-modes-alist + (append + '((vimrc-mode "{{{" "}}}" "\"") + (yaml-mode "\\s-*\\_<\\(?:[^:]+\\)\\_>" + "" + "#" + +fold-hideshow-forward-block-by-indent-fn nil) + (haml-mode "[#.%]" "\n" "/" +fold-hideshow-haml-forward-sexp-fn nil) + (ruby-mode "class\\|d\\(?:ef\\|o\\)\\|module\\|[[{]" + "end\\|[]}]" + "#\\|=begin" + ruby-forward-sexp) + (enh-ruby-mode "class\\|d\\(?:ef\\|o\\)\\|module\\|[[{]" + "end\\|[]}]" + "#\\|=begin" + enh-ruby-forward-sexp nil) + (matlab-mode "if\\|switch\\|case\\|otherwise\\|while\\|for\\|try\\|catch" + "end" + nil (lambda (_arg) (matlab-forward-sexp))) + (nxml-mode "\\|]*[^/]>" + "" . #Xe113) + ("->" . #Xe114) + ("->>" . #Xe115) + ("-<" . #Xe116) + ("-<<" . #Xe117) + ("-~" . #Xe118) + ("#{" . #Xe119) + ("#[" . #Xe11a) + ("##" . #Xe11b) + ("###" . #Xe11c) + ("####" . #Xe11d) + ("#(" . #Xe11e) + ("#?" . #Xe11f) + ("#_" . #Xe120) + ("#_(" . #Xe121) + (".-" . #Xe122) + (".=" . #Xe123) + (".." . #Xe124) + ("..<" . #Xe125) + ("..." . #Xe126) + ("?=" . #Xe127) + ("??" . #Xe128) + (";;" . #Xe129) + ("/*" . #Xe12a) + ("/**" . #Xe12b) + ("/=" . #Xe12c) + ("/==" . #Xe12d) + ("/>" . #Xe12e) + ("//" . #Xe12f) + ("///" . #Xe130) + ("&&" . #Xe131) + ("||" . #Xe132) + ("||=" . #Xe133) + ("|=" . #Xe134) + ("|>" . #Xe135) + ("^=" . #Xe136) + ("$>" . #Xe137) + ("++" . #Xe138) + ("+++" . #Xe139) + ("+>" . #Xe13a) + ("=:=" . #Xe13b) + ("==" . #Xe13c) + ("===" . #Xe13d) + ("==>" . #Xe13e) + ("=>" . #Xe13f) + ("=>>" . #Xe140) + ("=<" . #Xe141) + ("=<<" . #Xe142) + ("=/=" . #Xe143) + (">-" . #Xe144) + (">=" . #Xe145) + (">=>" . #Xe146) + (">>" . #Xe147) + (">>-" . #Xe148) + (">>=" . #Xe149) + (">>>" . #Xe14a) + ("<*" . #Xe14b) + ("<*>" . #Xe14c) + ("<|" . #Xe14d) + ("<|>" . #Xe14e) + ("<$" . #Xe14f) + ("<$>" . #Xe150) + ("" . #Xe101) + ("<--->" . #Xe102) + ("<---->" . #Xe103) + ("<----->" . #Xe104) + ;; Double-ended equals arrows + ("<=>" . #Xe105) + ("<==>" . #Xe106) + ("<===>" . #Xe107) + ("<====>" . #Xe108) + ("<=====>" . #Xe109) + ;; Double-ended asterisk operators + ("<**>" . #Xe10a) + ("<***>" . #Xe10b) + ("<****>" . #Xe10c) + ("<*****>" . #Xe10d) + ;; HTML comments + ("" . #Xe152) + ("-->-" . #Xe153) + ("-->--" . #Xe154) + ("-->>" . #Xe155) + ("-->>-" . #Xe156) + ("-->>--" . #Xe157) + ("-->>>" . #Xe158) + ("-->>>-" . #Xe159) + ("-->>>--" . #Xe15a) + (">-" . #Xe15b) + (">--" . #Xe15c) + (">>-" . #Xe15d) + (">>--" . #Xe15e) + (">>>-" . #Xe15f) + (">>>--" . #Xe160) + ("=>" . #Xe161) + ("=>=" . #Xe162) + ("=>==" . #Xe163) + ("=>>" . #Xe164) + ("=>>=" . #Xe165) + ("=>>==" . #Xe166) + ("=>>>" . #Xe167) + ("=>>>=" . #Xe168) + ("=>>>==" . #Xe169) + ("==>" . #Xe16a) + ("==>=" . #Xe16b) + ("==>==" . #Xe16c) + ("==>>" . #Xe16d) + ("==>>=" . #Xe16e) + ("==>>==" . #Xe16f) + ("==>>>" . #Xe170) + ("==>>>=" . #Xe171) + ("==>>>==" . #Xe172) + (">=" . #Xe173) + (">==" . #Xe174) + (">>=" . #Xe175) + (">>==" . #Xe176) + (">>>=" . #Xe177) + (">>>==" . #Xe178) + ("<-" . #Xe179) + ("-<-" . #Xe17a) + ("--<-" . #Xe17b) + ("<<-" . #Xe17c) + ("-<<-" . #Xe17d) + ("--<<-" . #Xe17e) + ("<<<-" . #Xe17f) + ("-<<<-" . #Xe180) + ("--<<<-" . #Xe181) + ("<--" . #Xe182) + ("-<--" . #Xe183) + ("--<--" . #Xe184) + ("<<--" . #Xe185) + ("-<<--" . #Xe186) + ("--<<--" . #Xe187) + ("<<<--" . #Xe188) + ("-<<<--" . #Xe189) + ("--<<<--" . #Xe18a) + ("-<" . #Xe18b) + ("--<" . #Xe18c) + ("-<<" . #Xe18d) + ("--<<" . #Xe18e) + ("-<<<" . #Xe18f) + ("--<<<" . #Xe190) + ("<=" . #Xe191) + ("=<=" . #Xe192) + ("==<=" . #Xe193) + ("<<=" . #Xe194) + ("=<<=" . #Xe195) + ("==<<=" . #Xe196) + ("<<<=" . #Xe197) + ("=<<<=" . #Xe198) + ("==<<<=" . #Xe199) + ("<==" . #Xe19a) + ("=<==" . #Xe19b) + ("==<==" . #Xe19c) + ("<<==" . #Xe19d) + ("=<<==" . #Xe19e) + ("==<<==" . #Xe19f) + ("<<<==" . #Xe1a0) + ("=<<<==" . #Xe1a1) + ("==<<<==" . #Xe1a2) + ("=<" . #Xe1a3) + ("==<" . #Xe1a4) + ("=<<" . #Xe1a5) + ("==<<" . #Xe1a6) + ("=<<<" . #Xe1a7) + ("==<<<" . #Xe1a8) + ;; Monadic operators + (">=>" . #Xe1a9) + (">->" . #Xe1aa) + (">-->" . #Xe1ab) + (">==>" . #Xe1ac) + ("<=<" . #Xe1ad) + ("<-<" . #Xe1ae) + ("<--<" . #Xe1af) + ("<==<" . #Xe1b0) + ;; Composition operators + (">>" . #Xe1b1) + (">>>" . #Xe1b2) + ("<<" . #Xe1b3) + ("<<<" . #Xe1b4) + ;; Lens operators + (":+" . #Xe1b5) + (":-" . #Xe1b6) + (":=" . #Xe1b7) + ("+:" . #Xe1b8) + ("-:" . #Xe1b9) + ("=:" . #Xe1ba) + ("=^" . #Xe1bb) + ("=+" . #Xe1bc) + ("=-" . #Xe1bd) + ("=*" . #Xe1be) + ("=/" . #Xe1bf) + ("=%" . #Xe1c0) + ("^=" . #Xe1c1) + ("+=" . #Xe1c2) + ("-=" . #Xe1c3) + ("*=" . #Xe1c4) + ("/=" . #Xe1c5) + ("%=" . #Xe1c6) + ;; Logical + ("/\\" . #Xe1c7) + ("\\/" . #Xe1c8) + ;; Semigroup/monoid operators + ("<>" . #Xe1c9) + ("<+" . #Xe1ca) + ("<+>" . #Xe1cb) + ("+>" . #Xe1cc)) + "Defines the character mappings for ligatures for Iosevka.") + +(defun +pretty-code-setup-iosevka-ligatures-h () + (set-fontset-font t '(#Xe100 . #Xe1cc) +pretty-code-iosevka-font-name) + (setq-default prettify-symbols-alist + (append prettify-symbols-alist + +pretty-code-iosevka-font-ligatures))) + +(add-hook 'doom-init-ui-hook #'+pretty-code-setup-iosevka-ligatures-h) diff --git a/.emacs.d/modules/ui/pretty-code/+pragmata-pro.el b/.emacs.d/modules/ui/pretty-code/+pragmata-pro.el new file mode 100644 index 0000000..45e7b20 --- /dev/null +++ b/.emacs.d/modules/ui/pretty-code/+pragmata-pro.el @@ -0,0 +1,259 @@ +;;; ui/pretty-code/+pragmata-pro.el -*- lexical-binding: t; -*- + +(defvar +pretty-code-pragmata-pro-font-name "PragmataPro" + "Name of the Pragmata Pro ligature font.") + +(defvar +pretty-code-pragmata-pro-font-ligatures + '(("[ERROR]" . #XE2C0) + ("[DEBUG]" . #XE2C1) + ("[INFO]" . #XE2C2) + ("[WARN]" . #XE2C3) + ("[WARNING]" . #XE2C4) + ("[ERR]" . #XE2C5) + ("[FATAL]" . #XE2C6) + ("[TRACE]" . #XE2C7) + ("[FIXME]" . #XE2C8) + ("[TODO]" . #XE2C9) + ("[BUG]" . #XE2CA) + ("[NOTE]" . #XE2CB) + ("[HACK]" . #XE2CC) + ("[MARK]" . #XE2CD) + ("# ERROR" . #XE2F0) + ("# DEBUG" . #XE2F1) + ("# INFO" . #XE2F2) + ("# WARN" . #XE2F3) + ("# WARNING" . #XE2F4) + ("# ERR" . #XE2F5) + ("# FATAL" . #XE2F6) + ("# TRACE" . #XE2F7) + ("# FIXME" . #XE2F8) + ("# TODO" . #XE2F9) + ("# BUG" . #XE2FA) + ("# NOTE" . #XE2FB) + ("# HACK" . #XE2FC) + ("# MARK" . #XE2FD) + ("// ERROR" . #XE2E0) + ("// DEBUG" . #XE2E1) + ("// INFO" . #XE2E2) + ("// WARN" . #XE2E3) + ("// WARNING". #XE2E4) + ("// ERR" . #XE2E5) + ("// FATAL" . #XE2E6) + ("// TRACE" . #XE2E7) + ("// FIXME" . #XE2E8) + ("// TODO" . #XE2E9) + ("// BUG" . #XE2EA) + ("// NOTE" . #XE2EB) + ("// HACK" . #XE2EC) + ("// MARK" . #XE2ED) + ("!!" . #XE900) + ("!=" . #XE901) + ("!==" . #XE902) + ("!!!" . #XE903) + ("!≡" . #XE904) + ("!≡≡" . #XE905) + ("!>" . #XE906) + ("!=<" . #XE907) + ("#(" . #XE920) + ("#_" . #XE921) + ("#{" . #XE922) + ("#?" . #XE923) + ("#>" . #XE924) + ("##" . #XE925) + ("#_(" . #XE926) + ("%=" . #XE930) + ("%>" . #XE931) + ("%>%" . #XE932) + ("%<%" . #XE933) + ("&%" . #XE940) + ("&&" . #XE941) + ("&*" . #XE942) + ("&+" . #XE943) + ("&-" . #XE944) + ("&/" . #XE945) + ("&=" . #XE946) + ("&&&" . #XE947) + ("&>" . #XE948) + ("$>" . #XE955) + ("***" . #XE960) + ("*=" . #XE961) + ("*/" . #XE962) + ("*>" . #XE963) + ("++" . #XE970) + ("+++" . #XE971) + ("+=" . #XE972) + ("+>" . #XE973) + ("++=" . #XE974) + ("--" . #XE980) + ("-<" . #XE981) + ("-<<" . #XE982) + ("-=" . #XE983) + ("->" . #XE984) + ("->>" . #XE985) + ("---" . #XE986) + ("-->" . #XE987) + ("-+-" . #XE988) + ("-\\/" . #XE989) + ("-|>" . #XE98A) + ("-<|" . #XE98B) + (".." . #XE990) + ("..." . #XE991) + ("..<" . #XE992) + (".>" . #XE993) + (".~" . #XE994) + (".=" . #XE995) + ("/*" . #XE9A0) + ("//" . #XE9A1) + ("/>" . #XE9A2) + ("/=" . #XE9A3) + ("/==" . #XE9A4) + ("///" . #XE9A5) + ("/**" . #XE9A6) + (":::" . #XE9AF) + ("::" . #XE9B0) + (":=" . #XE9B1) + (":≡" . #XE9B2) + (":>" . #XE9B3) + (":=>" . #XE9B4) + (":(" . #XE9B5) + (":-(" . #XE9B6) + (":)" . #XE9B7) + (":-)" . #XE9B8) + (":/" . #XE9B9) + (":\\" . #XE9BA) + (":3" . #XE9BB) + (":D" . #XE9BC) + (":P" . #XE9BD) + (":>:" . #XE9BE) + (":<:" . #XE9BF) + ("<$>" . #XE9C0) + ("<*" . #XE9C1) + ("<*>" . #XE9C2) + ("<+>" . #XE9C3) + ("<-" . #XE9C4) + ("<<" . #XE9C5) + ("<<<" . #XE9C6) + ("<<=" . #XE9C7) + ("<=" . #XE9C8) + ("<=>" . #XE9C9) + ("<>" . #XE9CA) + ("<|>" . #XE9CB) + ("<<-" . #XE9CC) + ("<|" . #XE9CD) + ("<=<" . #XE9CE) + ("<~" . #XE9CF) + ("<~~" . #XE9D0) + ("<<~" . #XE9D1) + ("<$" . #XE9D2) + ("<+" . #XE9D3) + ("" . #XE9D4) + ("<@>" . #XE9D5) + ("<#>" . #XE9D6) + ("<%>" . #XE9D7) + ("<^>" . #XE9D8) + ("<&>" . #XE9D9) + ("" . #XE9DA) + ("<.>" . #XE9DB) + ("" . #XE9DC) + ("<\\>" . #XE9DD) + ("<\">" . #XE9DE) + ("<:>" . #XE9DF) + ("<~>" . #XE9E0) + ("<**>" . #XE9E1) + ("<<^" . #XE9E2) + ("" . #XE9EF) + ("" . #XE9F8) + ("<<==" . #XE9F9) + ("<==" . #XE9FA) + ("=<<" . #XEA00) + ("==" . #XEA01) + ("===" . #XEA02) + ("==>" . #XEA03) + ("=>" . #XEA04) + ("=~" . #XEA05) + ("=>>" . #XEA06) + ("=/=" . #XEA07) + ("=~=" . #XEA08) + ("==>>" . #XEA09) + ("≡≡" . #XEA10) + ("≡≡≡" . #XEA11) + ("≡:≡" . #XEA12) + (">-" . #XEA20) + (">=" . #XEA21) + (">>" . #XEA22) + (">>-" . #XEA23) + (">>=" . #XEA24) + (">>>" . #XEA25) + (">=>" . #XEA26) + (">>^" . #XEA27) + (">>|" . #XEA28) + (">!=" . #XEA29) + (">->" . #XEA2A) + ("??" . #XEA40) + ("?~" . #XEA41) + ("?=" . #XEA42) + ("?>" . #XEA43) + ("???" . #XEA44) + ("?." . #XEA45) + ("^=" . #XEA48) + ("^." . #XEA49) + ("^?" . #XEA4A) + ("^.." . #XEA4B) + ("^<<" . #XEA4C) + ("^>>" . #XEA4D) + ("^>" . #XEA4E) + ("\\\\" . #XEA50) + ("\\>" . #XEA51) + ("\\/-" . #XEA52) + ("@>" . #XEA57) + ("|=" . #XEA60) + ("||" . #XEA61) + ("|>" . #XEA62) + ("|||" . #XEA63) + ("|+|" . #XEA64) + ("|->" . #XEA65) + ("|-->" . #XEA66) + ("|=>" . #XEA67) + ("|==>" . #XEA68) + ("|>-" . #XEA69) + ("|<<" . #XEA6A) + ("||>" . #XEA6B) + ("|>>" . #XEA6C) + ("|-" . #XEA6D) + ("||-" . #XEA6E) + ("~=" . #XEA70) + ("~>" . #XEA71) + ("~~>" . #XEA72) + ("~>>" . #XEA73) + ("[[" . #XEA80) + ("]]" . #XEA81) + ("\">" . #XEA90) + ("_|_" . #XEA97)) + "Defines the character mappings for ligatures for Pragmata Pro.") + +(defun +pretty-code-setup-pragmata-pro-ligatures-h () + (setq-default prettify-symbols-alist + (append prettify-symbols-alist + (mapcar #'+pretty-code--correct-symbol-bounds + +pretty-code-pragmata-pro-font-ligatures)))) + +(add-hook 'doom-init-ui-hook #'+pretty-code-setup-pragmata-pro-ligatures-h) diff --git a/.emacs.d/modules/ui/pretty-code/autoload.el b/.emacs.d/modules/ui/pretty-code/autoload.el new file mode 100644 index 0000000..1d2a54c --- /dev/null +++ b/.emacs.d/modules/ui/pretty-code/autoload.el @@ -0,0 +1,55 @@ +;;; ui/pretty-code/settings.el -*- lexical-binding: t; -*- + +;;;###autoload +(defvar +pretty-code-symbols-alist '((t)) + "An alist containing a mapping of major modes to its value for +`prettify-symbols-alist'.") + +;;;###autodef +(defun set-pretty-symbols! (modes &rest plist) + "Associates string patterns with icons in certain major-modes. + + MODES is a major mode symbol or a list of them. + PLIST is a property list whose keys must match keys in `+pretty-code-symbols', +and whose values are strings representing the text to be replaced with that +symbol. If the car of PLIST is nil, then unset any pretty symbols previously +defined for MODES. + +The following properties are special: + + :alist ALIST + Appends ALIST to `prettify-symbols-alist' literally, without mapping text to + `+pretty-code-symbols'. + :merge BOOL + If non-nil, merge with previously defined `prettify-symbols-alist', + otherwise overwrite it. + +For example, the rule for emacs-lisp-mode is very simple: + + (set-pretty-symbols! 'emacs-lisp-mode + :lambda \"lambda\") + +This will replace any instances of \"lambda\" in emacs-lisp-mode with the symbol +assicated with :lambda in `+pretty-code-symbols'. + +Pretty symbols can be unset for emacs-lisp-mode with: + + (set-pretty-symbols! 'emacs-lisp-mode nil)" + (declare (indent defun)) + (if (null (car-safe plist)) + (dolist (mode (doom-enlist modes)) + (delq (assq mode +pretty-code-symbols-alist) + +pretty-code-symbols-alist)) + (let (results merge key) + (while plist + (pcase (setq key (pop plist)) + (:merge (setq merge (pop plist))) + (:alist (setq results (append (pop plist) results))) + (_ + (when-let (char (plist-get +pretty-code-symbols key)) + (push (cons (pop plist) char) results))))) + (dolist (mode (doom-enlist modes)) + (unless merge + (delq (assq mode +pretty-code-symbols-alist) + +pretty-code-symbols-alist)) + (push (cons mode results) +pretty-code-symbols-alist))))) diff --git a/.emacs.d/modules/ui/pretty-code/config.el b/.emacs.d/modules/ui/pretty-code/config.el new file mode 100644 index 0000000..49dae0c --- /dev/null +++ b/.emacs.d/modules/ui/pretty-code/config.el @@ -0,0 +1,93 @@ +;;; ui/pretty-code/config.el -*- lexical-binding: t; -*- + +(defvar +pretty-code-symbols + '(;; org + :name "»" + :src_block "»" + :src_block_end "«" + ;; Functional + :lambda "λ" + :def "ƒ" + :composition "∘" + :map "↦" + ;; Types + :null "∅" + :true "𝕋" + :false "𝔽" + :int "ℤ" + :float "ℝ" + :str "𝕊" + :bool "𝔹" + ;; Flow + :not "¬" + :in "∈" + :not-in "∉" + :and "∧" + :or "∨" + :for "∀" + :some "∃" + :return "⟼" + :yield "⟻" + ;; Other + :tuple "⨂" + :pipe "" ;; FIXME: find a non-private char + :dot "•") + "Options plist for `set-pretty-symbols!'. + +This should not contain any symbols from the Unicode Private Area! There is no +universal way of getting the correct symbol as that area varies from font to +font.") + +(defun +pretty-code--correct-symbol-bounds (ligature-alist) + "Prepend non-breaking spaces to a ligature. + +This way `compose-region' (called by `prettify-symbols-mode') will use the +correct width of the symbols instead of the width measured by `char-width'." + (let ((len (length (car ligature-alist))) + (acc (list (cdr ligature-alist)))) + (while (> len 1) + (setq acc (cons #X00a0 (cons '(Br . Bl) acc)) + len (1- len))) + (cons (car ligature-alist) acc))) + +(defvar +pretty-code-enabled-modes t + "List of major modes in which `prettify-symbols-mode' should be enabled. +If t, enable it everywhere. If the first element is 'not, enable it in any mode +besides what is listed.") + +;; When you get to the right edge, it goes back to how it normally prints +(setq prettify-symbols-unprettify-at-point 'right-edge) + +(defun +pretty-code-init-pretty-symbols-h () + "Enable `prettify-symbols-mode'. + +If in fundamental-mode, or a mode derived from special, comint, eshell or term +modes, this function does nothing. + +Otherwise it builds `prettify-code-symbols-alist' according to +`+pretty-code-symbols-alist' for the current major-mode." + (unless (or (eq major-mode 'fundamental-mode) + (eq (get major-mode 'mode-class) 'special) + (derived-mode-p 'comint-mode 'eshell-mode 'term-mode)) + (when (or (eq +pretty-code-enabled-modes t) + (if (eq (car +pretty-code-enabled-modes) 'not) + (not (memq major-mode (cdr +pretty-code-enabled-modes))) + (memq major-mode +pretty-code-enabled-modes))) + (setq prettify-symbols-alist + (append (cdr (assq major-mode +pretty-code-symbols-alist)) + (default-value 'prettify-symbols-alist))) + (when prettify-symbols-mode + (prettify-symbols-mode -1)) + (prettify-symbols-mode +1)))) + +(add-hook 'after-change-major-mode-hook #'+pretty-code-init-pretty-symbols-h) + +;; Font-specific ligature support +(cond ((featurep! +fira) + (load! "+fira")) + ((featurep! +iosevka) + (load! "+iosevka")) + ((featurep! +hasklig) + (load! "+hasklig")) + ((featurep! +pragmata-pro) + (load! "+pragmata-pro"))) diff --git a/.emacs.d/modules/ui/tabs/README.org b/.emacs.d/modules/ui/tabs/README.org new file mode 100644 index 0000000..7d4cb95 --- /dev/null +++ b/.emacs.d/modules/ui/tabs/README.org @@ -0,0 +1,18 @@ +#+TITLE: ui/tabs +#+DATE: July 12, 2019 +#+SINCE: v2.1 +#+STARTUP: inlineimages + +* Table of Contents :TOC_3:noexport: +- [[#description][Description]] + - [[#module-flags][Module Flags]] + - [[#plugins][Plugins]] + +* Description +This module adds an Atom-esque tab bar to the Emacs UI. + +** Module Flags +This module provides no flags. + +** Plugins ++ [[https://github.com/ema2159/centaur-tabs][centaur-tabs]] diff --git a/.emacs.d/modules/ui/tabs/autoload.el b/.emacs.d/modules/ui/tabs/autoload.el new file mode 100644 index 0000000..be3e62c --- /dev/null +++ b/.emacs.d/modules/ui/tabs/autoload.el @@ -0,0 +1,77 @@ +;;; ui/tabs/autoload.el -*- lexical-binding: t; -*- + +;;;###autoload +(defun +tabs-buffer-predicate (buffer) + "TODO" + (or (memq buffer (window-parameter nil 'tab-buffers)) + (eq buffer (doom-fallback-buffer)))) + + +;; +;;; Commands + +;;;###autoload +(defun +tabs/close-tab-or-window () + "TODO" + (interactive) + (call-interactively + (cond ((cdr (window-parameter nil 'tab-buffers)) + #'kill-current-buffer) + ((fboundp '+workspace/close-window-or-workspace) + #'+workspace/close-window-or-workspace) + (#'delete-window)))) + + +;; +;;; Advice + +;;;###autoload +(defun +tabs-kill-current-buffer-a (&rest _) + (+tabs-remove-buffer-h)) + +;;;###autoload +(defun +tabs-bury-buffer-a (orig-fn &rest args) + (if centaur-tabs-mode + (let ((b (current-buffer))) + (apply orig-fn args) + (unless (eq b (current-buffer)) + (with-current-buffer b + (+tabs-remove-buffer-h)))) + (apply orig-fn args))) + +;;;###autoload +(defun +tabs-kill-tab-maybe-a (tab) + (let ((buffer (centaur-tabs-tab-value tab))) + (with-current-buffer buffer + ;; `kill-current-buffer' is advised not to kill buffers visible in another + ;; window, so it behaves better than `kill-buffer'. + (kill-current-buffer)) + (centaur-tabs-display-update))) + + +;; +;;; Hooks + +;;;###autoload +(defun +tabs-add-buffer-h () + (when (and centaur-tabs-mode + (doom-real-buffer-p (current-buffer))) + (let* ((this-buf (current-buffer)) + (buffers (window-parameter nil 'tab-buffers))) + (cl-pushnew this-buf buffers) + (add-hook 'kill-buffer-hook #'+tabs-remove-buffer-h nil t) + (set-window-parameter nil 'tab-buffers buffers)))) + +;;;###autoload +(defun +tabs-remove-buffer-h () + (when centaur-tabs-mode + (set-window-parameter + nil + 'tab-buffers (delete (current-buffer) + (window-parameter nil 'tab-buffers))))) + +;;;###autoload +(defun +tabs-new-window-h () + (when centaur-tabs-mode + (unless (window-parameter nil 'tab-buffers) + (+tabs-add-buffer-h)))) diff --git a/.emacs.d/modules/ui/tabs/config.el b/.emacs.d/modules/ui/tabs/config.el new file mode 100644 index 0000000..ff2b0f7 --- /dev/null +++ b/.emacs.d/modules/ui/tabs/config.el @@ -0,0 +1,71 @@ +;;; ui/tabs/config.el -*- lexical-binding: t; -*- + +(use-package! centaur-tabs + :after-call after-find-file dired-initial-position-hook + :init + (setq centaur-tabs-height 28 + centaur-tabs-set-bar 'left + centaur-tabs-set-modified-marker t) + + :config + (add-hook! 'centaur-tabs-mode-hook + (defun +tabs-init-frames-h () + (dolist (frame (frame-list)) + (if (not centaur-tabs-mode) + (set-frame-parameter frame 'buffer-predicate (frame-parameter frame 'old-buffer-predicate)) + (set-frame-parameter frame 'old-buffer-predicate (frame-parameter frame 'buffer-predicate)) + (set-frame-parameter frame 'buffer-predicate #'+tabs-buffer-predicate))))) + + (add-to-list 'window-persistent-parameters '(tab-buffers . t)) + + (defun +tabs-window-buffer-list-fn () + (centaur-tabs-filter-out + 'centaur-tabs-hide-tab-cached + (delq nil + (cl-mapcar #'(lambda (b) + (cond + ;; Always include the current buffer. + ((eq (current-buffer) b) b) + ((buffer-file-name b) b) + ((char-equal ?\ (aref (buffer-name b) 0)) nil) + ((buffer-live-p b) b))) + (window-parameter nil 'tab-buffers))))) + + (defun +tabs-buffer-groups-fn () + (list + (cond ((or (string-equal "*" (substring (buffer-name) 0 1)) + (memq major-mode '(magit-process-mode + magit-status-mode + magit-diff-mode + magit-log-mode + magit-file-mode + magit-blob-mode + magit-blame-mode + ))) + "Emacs") + ((derived-mode-p 'eshell-mode) + "EShell") + ((derived-mode-p 'dired-mode) + "Dired") + ((centaur-tabs-get-group-name (current-buffer)))))) + + (setq centaur-tabs-buffer-list-function #'+tabs-window-buffer-list-fn + centaur-tabs-buffer-groups-function #'+tabs-buffer-groups-fn) + + (advice-add #'centaur-tabs-buffer-close-tab :override #'+tabs-kill-tab-maybe-a) + (advice-add #'bury-buffer :around #'+tabs-bury-buffer-a) + (advice-add #'kill-current-buffer :before #'+tabs-kill-current-buffer-a) + (add-hook 'doom-switch-buffer-hook #'+tabs-add-buffer-h) + (add-hook 'doom-switch-window-hook #'+tabs-new-window-h) + + (add-hook '+doom-dashboard-mode-hook #'centaur-tabs-local-mode) + + (map! (:map centaur-tabs-mode-map + [remap delete-window] #'+tabs/close-tab-or-window + [remap +workspace/close-window-or-workspace] #'+tabs/close-tab-or-window) + (:after persp-mode + :map persp-mode-map + [remap delete-window] #'+tabs/close-tab-or-window + [remap +workspace/close-window-or-workspace] #'+tabs/close-tab-or-window)) + + (centaur-tabs-mode +1)) diff --git a/.emacs.d/modules/ui/tabs/packages.el b/.emacs.d/modules/ui/tabs/packages.el new file mode 100644 index 0000000..1bec6d7 --- /dev/null +++ b/.emacs.d/modules/ui/tabs/packages.el @@ -0,0 +1,4 @@ +;; -*- no-byte-compile: t; -*- +;;; ui/tabs/packages.el + +(package! centaur-tabs) diff --git a/.emacs.d/modules/ui/treemacs/autoload.el b/.emacs.d/modules/ui/treemacs/autoload.el new file mode 100644 index 0000000..6f8b75a --- /dev/null +++ b/.emacs.d/modules/ui/treemacs/autoload.el @@ -0,0 +1,41 @@ +;;; ui/treemacs/autoload.el -*- lexical-binding: t; -*- + +(defun +treemacs--init () + (require 'treemacs) + (let ((origin-buffer (current-buffer))) + (cl-letf (((symbol-function 'treemacs-workspace->is-empty?) + (symbol-function 'ignore))) + (treemacs--init)) + (dolist (project (treemacs-workspace->projects (treemacs-current-workspace))) + (treemacs-do-remove-project-from-workspace project)) + (with-current-buffer origin-buffer + (let ((project-root (or (doom-project-root) default-directory))) + (treemacs-do-add-project-to-workspace + (treemacs--canonical-path project-root) + (doom-project-name project-root))) + (setq treemacs--ready-to-follow t) + (when (or treemacs-follow-after-init treemacs-follow-mode) + (treemacs--follow))))) + +;;;###autoload +(defun +treemacs/toggle () + "Initialize or toggle treemacs. + +Ensures that only the current project is present and all other projects have +been removed. + +Use `treemacs' command for old functionality." + (interactive) + (require 'treemacs) + (pcase (treemacs-current-visibility) + (`visible (delete-window (treemacs-get-local-window))) + (_ (+treemacs--init)))) + +;;;###autoload +(defun +treemacs/find-file (arg) + "Open treemacs (if necessary) and find current file." + (interactive "P") + (let ((origin-buffer (current-buffer))) + (+treemacs--init) + (with-current-buffer origin-buffer + (treemacs-find-file arg)))) diff --git a/.emacs.d/modules/ui/treemacs/config.el b/.emacs.d/modules/ui/treemacs/config.el new file mode 100644 index 0000000..fe15354 --- /dev/null +++ b/.emacs.d/modules/ui/treemacs/config.el @@ -0,0 +1,45 @@ +;;; ui/treemacs/config.el -*- lexical-binding: t; -*- + +(setq treemacs-follow-after-init t + treemacs-is-never-other-window t + treemacs-sorting 'alphabetic-case-insensitive-asc + treemacs-persist-file (concat doom-cache-dir "treemacs-persist") + treemacs-last-error-persist-file (concat doom-cache-dir "treemacs-last-error-persist")) + + +(after! treemacs + (set-popup-rule! "^ \\*Treemacs" + :side treemacs-position + :size treemacs-width + :quit nil + :ttl 0) + + ;; Don't follow the cursor + (treemacs-follow-mode -1) + + ;; Allow ace-window to target treemacs windows + (after! ace-window + (delq! 'treemacs-mode aw-ignored-buffers))) + + +(use-package! treemacs-evil + :when (featurep! :editor evil +everywhere) + :after treemacs + :config + (define-key! evil-treemacs-state-map + [return] #'treemacs-RET-action + [tab] #'treemacs-TAB-action + "TAB" #'treemacs-TAB-action + ;; REVIEW Fix #1875 to be consistent with C-w {v,s}, but this should really + ;; be considered upstream. + "o v" #'treemacs-visit-node-horizontal-split + "o s" #'treemacs-visit-node-vertical-split)) + + +(use-package! treemacs-projectile + :after treemacs) + + +(use-package! treemacs-magit + :when (featurep! :tools magit) + :after treemacs magit) diff --git a/.emacs.d/modules/ui/treemacs/packages.el b/.emacs.d/modules/ui/treemacs/packages.el new file mode 100644 index 0000000..929b95f --- /dev/null +++ b/.emacs.d/modules/ui/treemacs/packages.el @@ -0,0 +1,9 @@ +;; -*- no-byte-compile: t; -*- +;;; ui/treemacs/packages.el + +(package! treemacs) +(when (featurep! :editor evil +everywhere) + (package! treemacs-evil)) +(package! treemacs-projectile) +(when (featurep! :tools magit) + (package! treemacs-magit)) diff --git a/.emacs.d/modules/ui/unicode/README.org b/.emacs.d/modules/ui/unicode/README.org new file mode 100644 index 0000000..8b9e7ec --- /dev/null +++ b/.emacs.d/modules/ui/unicode/README.org @@ -0,0 +1,10 @@ +#+TITLE: :ui unicode + +This unicode extends Doom's ability to display non-English unicode. + +This is for non-English Emacs users, for whom Doom's built-in unicode support in insufficient. + +When this module is enabled... + ++ The first time you run Emacs a unicode cache will be generated -- this will take a while! ++ Doom will ignore the ~doom-unicode-font~ variable and the ~:unicode-font~ setting. diff --git a/.emacs.d/modules/ui/unicode/autoload.el b/.emacs.d/modules/ui/unicode/autoload.el new file mode 100644 index 0000000..25f1944 --- /dev/null +++ b/.emacs.d/modules/ui/unicode/autoload.el @@ -0,0 +1,21 @@ +;;; ui/unicode/autoload.el -*- lexical-binding: t; -*- + +;;;###autoload +(add-hook! 'doom-init-ui-hook + (defun +unicode-init-fonts-h () + "Set up `unicode-fonts' to eventually run; accommodating the daemon, if +necessary." + (setq-default bidi-display-reordering t + doom-unicode-font nil) + (if initial-window-system + (+unicode-setup-fonts-h (selected-frame)) + (add-hook 'after-make-frame-functions #'+unicode-setup-fonts-h)))) + +;;;###autoload +(defun +unicode-setup-fonts-h (&optional frame) + "Initialize `unicode-fonts', if in a GUI session." + (when (and frame (display-graphic-p frame)) + (with-selected-frame frame + (require 'unicode-fonts) + ;; NOTE will impact startup time on first run + (unicode-fonts-setup)))) diff --git a/.emacs.d/modules/ui/unicode/packages.el b/.emacs.d/modules/ui/unicode/packages.el new file mode 100644 index 0000000..9879939 --- /dev/null +++ b/.emacs.d/modules/ui/unicode/packages.el @@ -0,0 +1,4 @@ +;; -*- no-byte-compile: t; -*- +;;; ui/unicode/packages.el + +(package! unicode-fonts) diff --git a/.emacs.d/modules/ui/vc-gutter/autoload.el b/.emacs.d/modules/ui/vc-gutter/autoload.el new file mode 100644 index 0000000..741080d --- /dev/null +++ b/.emacs.d/modules/ui/vc-gutter/autoload.el @@ -0,0 +1,27 @@ +;;; ui/vc-gutter/autoload.el -*- lexical-binding: t; -*- +;;;###if (featurep! :ui hydra) + +;;;###autoload (autoload '+vc/gutter-hydra/body "ui/vc-gutter/autoload" nil t) +(defhydra +vc/gutter-hydra + (:body-pre (git-gutter-mode 1) :hint nil) + " + [git gutter] + Movement Hunk Actions Misc. +%-4s(car (git-gutter:statistic))/ -%-4s(cdr (git-gutter:statistic)) + ╭──────────────────────────────────┴────────────────╯ + ^_g_^ [_s_] stage [_R_] set start Rev + ^_k_^ [_r_] revert + ^↑ ^ [_m_] mark + ^↓ ^ [_p_] popup ╭───────────────────── + ^_j_^ │[_q_] quit + ^_G_^ │[_Q_] Quit and disable" + ("j" (progn (git-gutter:next-hunk 1) (recenter))) + ("k" (progn (git-gutter:previous-hunk 1) (recenter))) + ("g" (progn (goto-char (point-min)) (git-gutter:next-hunk 1))) + ("G" (progn (goto-char (point-min)) (git-gutter:previous-hunk 1))) + ("s" git-gutter:stage-hunk) + ("r" git-gutter:revert-hunk) + ("m" git-gutter:mark-hunk) + ("p" git-gutter:popup-hunk) + ("R" git-gutter:set-start-revision) + ("q" nil :color blue) + ("Q" (git-gutter-mode -1) :color blue)) diff --git a/.emacs.d/modules/ui/vc-gutter/config.el b/.emacs.d/modules/ui/vc-gutter/config.el new file mode 100644 index 0000000..0f6ede1 --- /dev/null +++ b/.emacs.d/modules/ui/vc-gutter/config.el @@ -0,0 +1,111 @@ +;;; ui/vc-gutter/config.el -*- lexical-binding: t; -*- + +(defvar +vc-gutter-in-margin nil + "If non-nil, use the margin for diffs instead of the fringe.") + +(defvar +vc-gutter-in-remote-files nil + "If non-nil, enable the vc gutter in remote files (e.g. open through TRAMP).") + +(defvar +vc-gutter-diff-unsaved-buffer nil + "If non-nil, `diff-hl-flydiff-mode' will be activated. This allows on-the-fly +diffing, even for unsaved buffers.") + +(defvar +vc-gutter-default-style t + "If non-nil, enable the default look of the vc gutter. This means subtle thin +bitmaps on the left, an arrow bitmap for flycheck, and flycheck indicators moved +to the right fringe.") + + +;; +;; Packages + +(use-package! git-gutter + :commands git-gutter:revert-hunk git-gutter:stage-hunk + :init + (add-hook! 'find-file-hook + (defun +vc-gutter-init-maybe-h () + "Enable `git-gutter-mode' in the current buffer. + +If the buffer doesn't represent an existing file, `git-gutter-mode's activation +is deferred until the file is saved. Respects `git-gutter:disabled-modes'." + (when (or +vc-gutter-in-remote-files + (not (file-remote-p (or buffer-file-name default-directory)))) + (if (not buffer-file-name) + (add-hook 'after-save-hook #'+vc-gutter-init-maybe-h nil 'local) + (when (and (vc-backend buffer-file-name) + (progn + (require 'git-gutter) + (not (memq major-mode git-gutter:disabled-modes)))) + (if (and (display-graphic-p) + (require 'git-gutter-fringe nil t)) + (progn + (setq-local git-gutter:init-function #'git-gutter-fr:init) + (setq-local git-gutter:view-diff-function #'git-gutter-fr:view-diff-infos) + (setq-local git-gutter:clear-function #'git-gutter-fr:clear) + (setq-local git-gutter:window-width -1)) + (setq-local git-gutter:init-function 'nil) + (setq-local git-gutter:view-diff-function #'git-gutter:view-diff-infos) + (setq-local git-gutter:clear-function #'git-gutter:clear-diff-infos) + (setq-local git-gutter:window-width 1)) + (git-gutter-mode +1) + (remove-hook 'after-save-hook #'+vc-gutter-init-maybe-h 'local)))))) + + ;; Disable in Org mode, as per + ;; and + ;; . Apparently, the + ;; mode-enabling function for global minor modes gets called for new buffers + ;; while they are still in `fundamental-mode', before a major mode has been + ;; assigned. I don't know why this is the case, but adding `fundamental-mode' + ;; here fixes the issue. + (setq git-gutter:disabled-modes '(fundamental-mode image-mode pdf-view-mode)) + + ;; standardize default fringe width + (if (fboundp 'fringe-mode) (fringe-mode '4)) + :config + (set-popup-rule! "^\\*git-gutter" :select nil :size '+popup-shrink-to-fit) + + ;; Update git-gutter on focus (in case I was using git externally) + (add-hook 'focus-in-hook #'git-gutter:update-all-windows) + + (add-hook! '(doom-escape-hook doom-switch-window-hook) :append + (defun +vc-gutter-update-h (&rest _) + "Refresh git-gutter on ESC. Return nil to prevent shadowing other +`doom-escape-hook' hooks." + (when (and git-gutter-mode + (not (memq this-command '(git-gutter:stage-hunk + git-gutter:revert-hunk)))) + (ignore (git-gutter))))) + ;; update git-gutter when using magit commands + (advice-add #'magit-stage-file :after #'+vc-gutter-update-h) + (advice-add #'magit-unstage-file :after #'+vc-gutter-update-h) + + (defadvice! +vc-gutter--fix-linearity-of-hunks-a (diffinfos is-reverse) + "Fixes `git-gutter:next-hunk' and `git-gutter:previous-hunk' sometimes + jumping to random hunks." + :override #'git-gutter:search-near-diff-index + (cl-position-if (let ((lineno (line-number-at-pos))) + (lambda (line) + (funcall (if is-reverse #'> #'<) lineno line))) + diffinfos + :key #'git-gutter-hunk-start-line + :from-end is-reverse))) + + +;; subtle diff indicators in the fringe +(when +vc-gutter-default-style + (after! git-gutter-fringe + ;; places the git gutter outside the margins. + (setq-default fringes-outside-margins t) + ;; thin fringe bitmaps + (define-fringe-bitmap 'git-gutter-fr:added [224] + nil nil '(center repeated)) + (define-fringe-bitmap 'git-gutter-fr:modified [224] + nil nil '(center repeated)) + (define-fringe-bitmap 'git-gutter-fr:deleted [128 192 224 240] + nil nil 'bottom) + ;; let diff have left fringe, flycheck can have right fringe + (after! flycheck + (setq flycheck-indication-mode 'right-fringe) + ;; A non-descript, left-pointing arrow + (define-fringe-bitmap 'flycheck-fringe-bitmap-double-arrow + [16 48 112 240 112 48 16] nil nil 'center)))) diff --git a/.emacs.d/modules/ui/vc-gutter/packages.el b/.emacs.d/modules/ui/vc-gutter/packages.el new file mode 100644 index 0000000..a12b41b --- /dev/null +++ b/.emacs.d/modules/ui/vc-gutter/packages.el @@ -0,0 +1,4 @@ +;; -*- no-byte-compile: t; -*- +;;; ui/vc-gutter/packages.el + +(package! git-gutter-fringe) diff --git a/.emacs.d/modules/ui/vi-tilde-fringe/autoload.el b/.emacs.d/modules/ui/vi-tilde-fringe/autoload.el new file mode 100644 index 0000000..0d695a6 --- /dev/null +++ b/.emacs.d/modules/ui/vi-tilde-fringe/autoload.el @@ -0,0 +1,5 @@ +;;; ui/vi-tilde-fringe/autoload.el -*- lexical-binding: t; -*- + +;;;###autoload +(add-hook! '(prog-mode-hook text-mode-hook conf-mode-hook) + #'vi-tilde-fringe-mode) diff --git a/.emacs.d/modules/ui/vi-tilde-fringe/packages.el b/.emacs.d/modules/ui/vi-tilde-fringe/packages.el new file mode 100644 index 0000000..10b52d8 --- /dev/null +++ b/.emacs.d/modules/ui/vi-tilde-fringe/packages.el @@ -0,0 +1,4 @@ +;; -*- no-byte-compile: t; -*- +;;; ui/vi-tilde-fringe/packages.el + +(package! vi-tilde-fringe) diff --git a/.emacs.d/modules/ui/window-select/README.org b/.emacs.d/modules/ui/window-select/README.org new file mode 100644 index 0000000..1f080e2 --- /dev/null +++ b/.emacs.d/modules/ui/window-select/README.org @@ -0,0 +1,57 @@ +#+TITLE: ui/window-select +#+DATE: October 8, 2017 +#+SINCE: v2.0.7 +#+STARTUP: inlineimages + +* Table of Contents :TOC: +- [[#description][Description]] + - [[#module-flags][Module Flags]] + - [[#packages][Packages]] +- [[#prerequisites][Prerequisites]] +- [[#features][Features]] +- [[#configuration][Configuration]] + - [[#ace-window][ace-window]] + - [[#switch-window][switch-window]] + +* Description +This module provides several methods for selecting windows without the use of +the mouse or spatial navigation (e.g. =C-w {h,j,k,l}=). + +The command ~other-window~ is remapped to either ~switch-window~ or +~ace-window~, depending on which backend you've enabled. It is bound to ~C-x o~ +(and ~C-w C-w~ for evil users). + +It also provides numbered windows and selection with the ~winum~ package, if +desired. Evil users can jump to window N in =C-w = (where N is a number +between 0 and 9). Non evil users have =C-x w = instead. + +** Module Flags ++ =+switch-window= Use the switch-window package as the backend, instead of + ace-window (avy). ++ =+numbers= Enable numbered windows and window selection (using winum). + +** Packages ++ [[https://github.com/dimitri/switch-window][switch-window]] (if =+switch-window=) ++ [[https://github.com/abo-abo/ace-window][ace-window]] (if =+switch-window= isn't enabled) ++ [[https://github.com/deb0ch/emacs-winum][winum]] (if =+numbers=) + +* Prerequisites +This module has no additional dependencies. + +* TODO Features +* Configuration +This module provides two backends, both providing the same functionality, but +with different visual cues. They are =ace-window= and =switch-window=. + +** ace-window +The first character of the buffers changes to a highlighted, user-selectable +character. + + + Pros: the content of the buffers are always visible. + + Cons: The displayed characters are small and difficult to see. + +** switch-window +Replaces the entire buffer with large letters. + + + Pros: The displayed characters are /really/ easy to see. + + Cons: You can't see the contents of the buffers. diff --git a/.emacs.d/modules/ui/window-select/config.el b/.emacs.d/modules/ui/window-select/config.el new file mode 100644 index 0000000..fd34980 --- /dev/null +++ b/.emacs.d/modules/ui/window-select/config.el @@ -0,0 +1,39 @@ +;;; ui/window-select/config.el -*- lexical-binding: t; -*- + +(use-package! switch-window + :when (featurep! +switch-window) + :defer t + :init + (global-set-key [remap other-window] #'switch-window) + :config + (setq switch-window-shortcut-style 'qwerty + switch-window-qwerty-shortcuts '("a" "s" "d" "f" "g" "h" "j" "k" "l"))) + + +(use-package! ace-window + :unless (featurep! +switch-window) + :defer t + :init + (global-set-key [remap other-window] #'ace-window) + :config + (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l) + aw-scope 'frame + aw-background t)) + + +(use-package! winum + :when (featurep! +numbers) + :after-call doom-switch-window-hook + :config + (winum-mode +1) + (map! :map evil-window-map + "0" #'winum-select-window-0-or-10 + "1" #'winum-select-window-1 + "2" #'winum-select-window-2 + "3" #'winum-select-window-3 + "4" #'winum-select-window-4 + "5" #'winum-select-window-5 + "6" #'winum-select-window-6 + "7" #'winum-select-window-7 + "8" #'winum-select-window-8 + "9" #'winum-select-window-9)) diff --git a/.emacs.d/modules/ui/window-select/packages.el b/.emacs.d/modules/ui/window-select/packages.el new file mode 100644 index 0000000..aa96593 --- /dev/null +++ b/.emacs.d/modules/ui/window-select/packages.el @@ -0,0 +1,9 @@ +;; -*- no-byte-compile: t; -*- +;;; ui/window-select/packages.el + +(if (featurep! +switch-window) + (package! switch-window) + (package! ace-window)) + +(when (featurep! +numbers) + (package! winum)) diff --git a/.emacs.d/modules/ui/workspaces/README.org b/.emacs.d/modules/ui/workspaces/README.org new file mode 100644 index 0000000..55335b0 --- /dev/null +++ b/.emacs.d/modules/ui/workspaces/README.org @@ -0,0 +1,104 @@ +#+TITLE: ui/workspaces +#+DATE: February 4, 2017 +#+SINCE: v1.3 +#+STARTUP: inlineimages + +* Table of Contents :TOC: +- [[#description][Description]] + - [[#module-flags][Module Flags]] + - [[#packages][Packages]] +- [[#prerequisites][Prerequisites]] +- [[#features][Features]] + - [[#isolated-buffer-list][Isolated buffer-list]] + - [[#automatic-workspaces][Automatic workspaces]] + - [[#session-persistence][Session persistence]] + - [[#workspace-persistence][Workspace persistence]] +- [[#appendix][Appendix]] + - [[#commands--keybindings][Commands & Keybindings]] + - [[#api][API]] + +* Description +This module adds support for workspaces, powered by persp_mode, as well as a API +for manipulating them. + +#+begin_quote +There are many ways to use workspaces. I spawn a workspace per task. Say I'm +working in the main workspace, when I realize there is a bug in another part of +my project. I open a new workspace and deal with it in there. In the meantime, I +need to check my email, so mu4e gets its own workspace. + +Once I've completed the task, I close the workspace and return to main. +#+end_quote + +** Module Flags +This module provides no flags. + +** Packages ++ [[https://github.com/Bad-ptr/persp-mode.el][persp-mode]] + +* Prerequisites +This module has no additional dependencies. + +* Features +** Isolated buffer-list +When persp-mode is active, ~doom-buffer-list~ becomes workspace-restricted. You +can overcome this by using ~buffer-list~. + +** Automatic workspaces +A workspace is automatically created (and switched to) when you: + ++ Create a new frame (with =make-frame=; bound to =M-N= by default). ++ Switch to a project using ~projectile-switch-project~. + +** Session persistence +By default, your session is autosaved when you quit Emacs (or disable +~persp-mode~). You can load a previous session with ~M-x ++workspace/load-session~ or ~:sl[oad]~ (ex command). + +You can supply either a name to load a specific session to replace your current +one. + +** Workspace persistence +If you'd like to save a specific workspace, use ~M-x +workspace/save~, which can +be loaded into the current session (as another workspace) with ~M-x ++workspace/load~. + +* Appendix +** Commands & Keybindings +Here is a list of available commands, their default keybindings (defined in +[[../../config/default/+evil-bindings.el][private/default/+bindings.el]]), and corresponding ex commands (if any -- defined +in [[../../editor/evil/+commands.el][private/default/+evil-commands.el]]). + +| command | key / ex command | description | +|-----------------------------------+----------------------------+------------------------------------------------------------| +| ~+workspace/new~ | =SPC TAB n= | Create a new, blank workspace | +| ~+workspace/display~ | =SPC TAB TAB= | Display open workspaces in the mode-line | +| ~+workspace/load~ | =SPC TAB l= | Load a saved workspace into the current session | +| ~+workspace/restore-last-session~ | =SPC TAB R= | Restore last session | +| ~+workspace/rename~ | =SPC TAB r= | Rename the current workspace | +| ~+workspace/save~ | =SPC TAB s= | Save the current workspace to a file | +| ~+workspace/switch-to~ | =SPC TAB .= | Switch to an open workspace | +| ~+workspace/other~ | =SPC TAB `= | Switch to last workspace | +| ~+workspace/switch-left~ | =SPC TAB [= / =[ w= / =gT= | Switch to previous workspace | +| ~+workspace/switch-right~ | =SPC TAB ]= / =] w= / =gt= | Switch to next workspace | +| ~+workspace/delete~ | =SPC TAB d= | Delete the current workspace | +| ~+workspace/kill-session~ | =SPC TAB x= / =:sclear= | Clears the current session (kills all windows and buffers) | + +** API ++ ~+workspace-list~ -> list ++ ~+workspace-list-names~ -> list ++ ~+workspace-buffer-list &optional PERSP~ -> bool ++ ~+workspace-p OBJ~ -> bool ++ ~+workspace-exists-p NAME~ -> bool ++ ~+workspace-get NAME &optional NOERROR~ -> Struct ++ ~+workspace-current &optional FRAME WINDOW~ -> Struct ++ ~+workspace-current-name~ -> string ++ ~+workspace-load NAME~ ++ ~+workspace-load-session NAME~ ++ ~+workspace-save NAME~ ++ ~+workspace-save-session NAME~ ++ ~+workspace-new NAME~ ++ ~+workspace-rename NAME NEW-NAME~ ++ ~+workspace-delete NAME &optional INHIBIT-KILL-P~ ++ ~+workspace-switch NAME &optional AUTO-CREATE-P~ ++ ~+workspace-protected-p NAME~ -> bool diff --git a/.emacs.d/modules/ui/workspaces/autoload/evil.el b/.emacs.d/modules/ui/workspaces/autoload/evil.el new file mode 100644 index 0000000..ceaa051 --- /dev/null +++ b/.emacs.d/modules/ui/workspaces/autoload/evil.el @@ -0,0 +1,39 @@ +;;; ui/workspaces/autoload/evil.el -*- lexical-binding: t; -*- +;;;###if (featurep! :editor evil) + +;;;###autoload (autoload '+workspace:save "ui/workspaces/autoload/evil" nil t) +(evil-define-command +workspace:save (&optional name) + "Ex wrapper around `+workspace/save-session'." + (interactive "") (+workspace/save name)) + +;;;###autoload (autoload '+workspace:load "ui/workspaces/autoload/evil" nil t) +(evil-define-command +workspace:load (&optional name) + "Ex wrapper around `+workspace/load-session'." + (interactive "") (+workspace/load name)) + +;;;###autoload (autoload '+workspace:new "ui/workspaces/autoload/evil" nil t) +(evil-define-command +workspace:new (bang name) + "Ex wrapper around `+workspace/new'. If BANG, clone the current workspace." + (interactive "") (+workspace/new name bang)) + +;;;###autoload (autoload '+workspace:rename "ui/workspaces/autoload/evil" nil t) +(evil-define-command +workspace:rename (new-name) + "Ex wrapper around `+workspace/rename'." + (interactive "") (+workspace/rename new-name)) + +;;;###autoload (autoload '+workspace:delete "ui/workspaces/autoload/evil" nil t) +(evil-define-command +workspace:delete () + "Ex wrapper around `+workspace/delete'." + (interactive) (+workspace/delete (+workspace-current-name))) + +;;;###autoload (autoload '+workspace:switch-next "ui/workspaces/autoload/evil" nil t) +(evil-define-command +workspace:switch-next (&optional count) + "Switch to next workspace. If COUNT, switch to COUNT-th workspace." + (interactive "") + (if count (+workspace/switch-to count) (+workspace/cycle +1))) + +;;;###autoload (autoload '+workspace:switch-previous "ui/workspaces/autoload/evil" nil t) +(evil-define-command +workspace:switch-previous (&optional count) + "Switch to previous workspace. If COUNT, switch to COUNT-th workspace." + (interactive "") + (if count (+workspace/switch-to count) (+workspace/cycle -1))) diff --git a/.emacs.d/modules/ui/workspaces/autoload/workspaces.el b/.emacs.d/modules/ui/workspaces/autoload/workspaces.el new file mode 100644 index 0000000..349c6dd --- /dev/null +++ b/.emacs.d/modules/ui/workspaces/autoload/workspaces.el @@ -0,0 +1,531 @@ +;;; feature/workspaces/autoload/workspaces.el -*- lexical-binding: t; -*- + +(defvar +workspace--last nil) +(defvar +workspace--index 0) + +;;;###autoload +(defface +workspace-tab-selected-face '((t (:inherit highlight))) + "The face for selected tabs displayed by `+workspace/display'" + :group 'persp-mode) + +;;;###autoload +(defface +workspace-tab-face '((t (:inherit default))) + "The face for selected tabs displayed by `+workspace/display'" + :group 'persp-mode) + + +;; +;;; Library + +(defun +workspace--protected-p (name) + (equal name persp-nil-name)) + +(defun +workspace--generate-id () + (or (cl-loop for name in (+workspace-list-names) + when (string-match-p "^#[0-9]+$" name) + maximize (string-to-number (substring name 1)) into max + finally return (if max (1+ max))) + 1)) + + +;;; Predicates +;;;###autoload +(defalias #'+workspace-p #'perspective-p + "Return t if OBJ is a perspective hash table.") + +;;;###autoload +(defun +workspace-exists-p (name) + "Returns t if NAME is the name of an existing workspace." + (member name (+workspace-list-names))) + +;;;###autoload +(defalias #'+workspace-contains-buffer-p #'persp-contain-buffer-p + "Return non-nil if BUFFER is in WORKSPACE (defaults to current workspace).") + + +;;; Getters +;;;###autoload +(defalias #'+workspace-current #'get-current-persp + "Return the currently active workspace.") + +;;;###autoload +(defun +workspace-get (name &optional noerror) + "Return a workspace named NAME. Unless NOERROR is non-nil, this throws an +error if NAME doesn't exist." + (cl-check-type name string) + (when-let (persp (persp-get-by-name name)) + (cond ((+workspace-p persp) persp) + ((not noerror) + (error "No workspace called '%s' was found" name))))) + +;;;###autoload +(defun +workspace-current-name () + "Get the name of the current workspace." + (safe-persp-name (+workspace-current))) + +;;;###autoload +(defun +workspace-list () + "Return a list of workspace structs (satisifes `+workspace-p')." + ;; We don't use `hash-table-values' because it doesn't ensure order in older + ;; versions of Emacs + (cdr (cl-loop for persp being the hash-values of *persp-hash* + collect persp))) + +;;;###autoload +(defun +workspace-list-names () + "Return the list of names of open workspaces." + (mapcar #'safe-persp-name (+workspace-list))) + +;;;###autoload +(defun +workspace-buffer-list (&optional persp) + "Return a list of buffers in PERSP. + +The buffer list is ordered by recency (same as `buffer-list'). + +PERSP can be a string (name of a workspace) or a workspace (satisfies +`+workspace-p'). If nil or omitted, it defaults to the current workspace." + (let ((persp (or persp (+workspace-current)))) + (unless (+workspace-p persp) + (user-error "Not in a valid workspace (%s)" persp)) + (persp-buffers persp))) + +;;;###autoload +(defun +workspace-orphaned-buffer-list () + "Return a list of buffers that aren't associated with any perspective." + (cl-remove-if #'persp--buffer-in-persps (buffer-list))) + + +;;; Actions +;;;###autoload +(defun +workspace-load (name) + "Loads a single workspace (named NAME) into the current session. Can only +retrieve perspectives that were explicitly saved with `+workspace-save'. + +Returns t if successful, nil otherwise." + (when (+workspace-exists-p name) + (user-error "A workspace named '%s' already exists." name)) + (persp-load-from-file-by-names + (expand-file-name +workspaces-data-file persp-save-dir) + *persp-hash* (list name)) + (+workspace-exists-p name)) + +;;;###autoload +(defun +workspace-save (name) + "Saves a single workspace (NAME) from the current session. Can be loaded again +with `+workspace-load'. NAME can be the string name of a workspace or its +perspective hash table. + +Returns t on success, nil otherwise." + (unless (+workspace-exists-p name) + (error "'%s' is an invalid workspace" name)) + (let ((fname (expand-file-name +workspaces-data-file persp-save-dir))) + (persp-save-to-file-by-names fname *persp-hash* (list name)) + (and (member name (persp-list-persp-names-in-file fname)) + t))) + +;;;###autoload +(defun +workspace-new (name) + "Create a new workspace named NAME. If one already exists, return nil. +Otherwise return t on success, nil otherwise." + (when (+workspace--protected-p name) + (error "Can't create a new '%s' workspace" name)) + (when (+workspace-exists-p name) + (error "A workspace named '%s' already exists" name)) + (let ((persp (persp-add-new name)) + (+popup--inhibit-transient t)) + (save-window-excursion + (let ((ignore-window-parameters t) + (+popup--inhibit-transient t)) + (persp-delete-other-windows)) + (switch-to-buffer (doom-fallback-buffer)) + (setf (persp-window-conf persp) + (funcall persp-window-state-get-function (selected-frame)))) + persp)) + +;;;###autoload +(defun +workspace-rename (name new-name) + "Rename the current workspace named NAME to NEW-NAME. Returns old name on +success, nil otherwise." + (when (+workspace--protected-p name) + (error "Can't rename '%s' workspace" name)) + (persp-rename new-name (+workspace-get name))) + +;;;###autoload +(defun +workspace-delete (workspace &optional inhibit-kill-p) + "Delete the workspace denoted by WORKSPACE, which can be the name of a perspective +or its hash table. If INHIBIT-KILL-P is non-nil, don't kill this workspace's +buffers." + (unless (stringp workspace) + (setq workspace (persp-name workspace))) + (when (+workspace--protected-p workspace) + (error "Can't delete '%s' workspace" workspace)) + (+workspace-get workspace) ; error checking + (persp-kill workspace inhibit-kill-p) + (not (+workspace-exists-p workspace))) + +;;;###autoload +(defun +workspace-switch (name &optional auto-create-p) + "Switch to another workspace named NAME (a string). + +If AUTO-CREATE-P is non-nil, create the workspace if it doesn't exist, otherwise +throws an error." + (unless (+workspace-exists-p name) + (if auto-create-p + (+workspace-new name) + (error "%s is not an available workspace" name))) + (let ((old-name (+workspace-current-name))) + (setq +workspace--last + (or (and (not (string= old-name persp-nil-name)) + old-name) + +workspaces-main)) + (persp-frame-switch name) + (equal (+workspace-current-name) name))) + + +;; +;;; Commands + +;;;###autoload +(defalias '+workspace/restore-last-session #'doom/quickload-session) + +;;;###autoload +(defun +workspace/load (name) + "Load a workspace and switch to it. If called with C-u, try to reload the +current workspace (by name) from session files." + (interactive + (list + (if current-prefix-arg + (+workspace-current-name) + (completing-read + "Workspace to load: " + (persp-list-persp-names-in-file + (expand-file-name +workspaces-data-file persp-save-dir)))))) + (if (not (+workspace-load name)) + (+workspace-error (format "Couldn't load workspace %s" name)) + (+workspace/switch-to name) + (+workspace/display))) + +;;;###autoload +(defun +workspace/save (name) + "Save the current workspace. If called with C-u, autosave the current +workspace." + (interactive + (list + (if current-prefix-arg + (+workspace-current-name) + (completing-read "Workspace to save: " (+workspace-list-names))))) + (if (+workspace-save name) + (+workspace-message (format "'%s' workspace saved" name) 'success) + (+workspace-error (format "Couldn't save workspace %s" name)))) + +;;;###autoload +(defun +workspace/rename (new-name) + "Rename the current workspace." + (interactive (list (read-from-minibuffer "New workspace name: "))) + (condition-case-unless-debug ex + (let* ((current-name (+workspace-current-name)) + (old-name (+workspace-rename current-name new-name))) + (unless old-name + (error "Failed to rename %s" current-name)) + (+workspace-message (format "Renamed '%s'->'%s'" old-name new-name) 'success)) + ('error (+workspace-error ex t)))) + +;;;###autoload +(defun +workspace/delete (name) + "Delete this workspace. If called with C-u, prompts you for the name of the +workspace to delete." + (interactive + (let ((current-name (+workspace-current-name))) + (list + (if current-prefix-arg + (completing-read (format "Delete workspace (default: %s): " current-name) + (+workspace-list-names) + nil nil nil nil current-name) + current-name)))) + (condition-case-unless-debug ex + ;; REVIEW refactor me + (let ((workspaces (+workspace-list-names))) + (if (not (member name workspaces)) + (+workspace-message (format "'%s' workspace doesn't exist" name) 'warn) + (cond ((delq (selected-frame) (persp-frames-with-persp (get-frame-persp))) + (user-error "Can't close workspace, it's visible in another frame")) + ((not (equal (+workspace-current-name) name)) + (+workspace-delete name)) + ((cdr workspaces) + (+workspace-delete name) + (+workspace-switch + (if (+workspace-exists-p +workspace--last) + +workspace--last + (car (+workspace-list-names)))) + (unless (doom-buffer-frame-predicate (window-buffer)) + (switch-to-buffer (doom-fallback-buffer)))) + (t + (+workspace-switch +workspaces-main t) + (unless (string= (car workspaces) +workspaces-main) + (+workspace-delete name)) + (doom/kill-all-buffers (doom-buffer-list)))) + (+workspace-message (format "Deleted '%s' workspace" name) 'success))) + ('error (+workspace-error ex t)))) + +;;;###autoload +(defun +workspace/kill-session () + "Delete the current session, all workspaces, windows and their buffers." + (interactive) + (unless (cl-every #'+workspace-delete (+workspace-list-names)) + (+workspace-error "Could not clear session")) + (+workspace-switch +workspaces-main t) + (doom/kill-all-buffers (buffer-list))) + +;;;###autoload +(defun +workspace/kill-session-and-quit () + "Kill emacs without saving anything." + (interactive) + (let ((persp-auto-save-opt 0)) + (kill-emacs))) + +;;;###autoload +(defun +workspace/new (&optional name clone-p) + "Create a new workspace named NAME. If CLONE-P is non-nil, clone the current +workspace, otherwise the new workspace is blank." + (interactive "iP") + (unless name + (setq name (format "#%s" (+workspace--generate-id)))) + (condition-case e + (cond ((+workspace-exists-p name) + (error "%s already exists" name)) + (clone-p (persp-copy name t)) + (t + (+workspace-switch name t) + (+workspace/display))) + ((debug error) (+workspace-error (cadr e) t)))) + +;;;###autoload +(defun +workspace/switch-to (index) + "Switch to a workspace at a given INDEX. A negative number will start from the +end of the workspace list." + (interactive + (list (or current-prefix-arg + (if (featurep! :completion ivy) + (ivy-read "Switch to workspace: " + (+workspace-list-names) + :caller #'+workspace/switch-to + :preselect (+workspace-current-name)) + (completing-read "Switch to workspace: " (+workspace-list-names)))))) + (when (and (stringp index) + (string-match-p "^[0-9]+$" index)) + (setq index (string-to-number index))) + (condition-case-unless-debug ex + (let ((names (+workspace-list-names)) + (old-name (+workspace-current-name))) + (cond ((numberp index) + (let ((dest (nth index names))) + (unless dest + (error "No workspace at #%s" (1+ index))) + (+workspace-switch dest))) + ((stringp index) + (+workspace-switch index t)) + (t + (error "Not a valid index: %s" index))) + (unless (called-interactively-p 'interactive) + (if (equal (+workspace-current-name) old-name) + (+workspace-message (format "Already in %s" old-name) 'warn) + (+workspace/display)))) + ('error (+workspace-error (cadr ex) t)))) + +;;;###autoload +(dotimes (i 9) + (defalias (intern (format "+workspace/switch-to-%d" i)) + (lambda () (interactive) (+workspace/switch-to i)))) + +;;;###autoload +(defun +workspace/switch-to-final () + "Switch to the final workspace in open workspaces." + (interactive) + (+workspace/switch-to (car (last (+workspace-list-names))))) + +;;;###autoload +(defun +workspace/other () + "Switch to the last activated workspace." + (interactive) + (+workspace/switch-to +workspace--last)) + +;;;###autoload +(defun +workspace/cycle (n) + "Cycle n workspaces to the right (default) or left." + (interactive (list 1)) + (let ((current-name (+workspace-current-name))) + (if (equal current-name persp-nil-name) + (+workspace-switch +workspaces-main t) + (condition-case-unless-debug ex + (let* ((persps (+workspace-list-names)) + (perspc (length persps)) + (index (cl-position current-name persps))) + (when (= perspc 1) + (user-error "No other workspaces")) + (+workspace/switch-to (% (+ index n perspc) perspc)) + (unless (called-interactively-p 'interactive) + (+workspace/display))) + ('user-error (+workspace-error (cadr ex) t)) + ('error (+workspace-error ex t)))))) + +;;;###autoload +(defun +workspace/switch-left () (interactive) (+workspace/cycle -1)) + +;;;###autoload +(defun +workspace/switch-right () (interactive) (+workspace/cycle +1)) + +;;;###autoload +(defun +workspace/close-window-or-workspace () + "Close the selected window. If it's the last window in the workspace, either +close the workspace (as well as its associated frame, if one exists) and move to +the next." + (interactive) + (let ((delete-window-fn (if (featurep 'evil) #'evil-window-delete #'delete-window))) + (if (window-dedicated-p) + (funcall delete-window-fn) + (let ((current-persp-name (+workspace-current-name))) + (cond ((or (+workspace--protected-p current-persp-name) + (cdr (doom-visible-windows))) + (funcall delete-window-fn)) + + ((cdr (+workspace-list-names)) + (let ((frame-persp (frame-parameter nil 'workspace))) + (if (string= frame-persp (+workspace-current-name)) + (delete-frame) + (+workspace/delete current-persp-name)))) + + ((+workspace-error "Can't delete last workspace" t))))))) + + +;; +;;; Tabs display in minibuffer + +(defun +workspace--tabline (&optional names) + (let ((names (or names (+workspace-list-names))) + (current-name (+workspace-current-name))) + (mapconcat + #'identity + (cl-loop for name in names + for i to (length names) + collect + (propertize (format " [%d] %s " (1+ i) name) + 'face (if (equal current-name name) + '+workspace-tab-selected-face + '+workspace-tab-face))) + " "))) + +(defun +workspace--message-body (message &optional type) + (concat (+workspace--tabline) + (propertize " | " 'face 'font-lock-comment-face) + (propertize (format "%s" message) + 'face (pcase type + ('error 'error) + ('warn 'warning) + ('success 'success) + ('info 'font-lock-comment-face))))) + +;;;###autoload +(defun +workspace-message (message &optional type) + "Show an 'elegant' message in the echo area next to a listing of workspaces." + (message "%s" (+workspace--message-body message type))) + +;;;###autoload +(defun +workspace-error (message &optional noerror) + "Show an 'elegant' error in the echo area next to a listing of workspaces." + (funcall (if noerror #'message #'error) + "%s" (+workspace--message-body message 'error))) + +;;;###autoload +(defun +workspace/display () + "Display a list of workspaces (like tabs) in the echo area." + (interactive) + (let (message-log-max) + (message "%s" (+workspace--tabline)))) + + +;; +;;; Hooks + +;;;###autoload +(defun +workspaces-delete-associated-workspace-h (&optional frame) + "Delete workspace associated with current frame. +A workspace gets associated with a frame when a new frame is interactively +created." + (when persp-mode + (unless frame + (setq frame (selected-frame))) + (let ((frame-persp (frame-parameter frame 'workspace))) + (when (string= frame-persp (+workspace-current-name)) + (+workspace/delete frame-persp))))) + +;;;###autoload +(defun +workspaces-associate-frame-fn (frame &optional _new-frame-p) + "Create a blank, new perspective and associate it with FRAME." + (when persp-mode + (if (not (persp-frame-list-without-daemon)) + (+workspace-switch +workspaces-main t) + (with-selected-frame frame + (+workspace-switch (format "#%s" (+workspace--generate-id)) t) + (unless (doom-real-buffer-p (current-buffer)) + (switch-to-buffer (doom-fallback-buffer))) + (set-frame-parameter frame 'workspace (+workspace-current-name)) + ;; ensure every buffer has a buffer-predicate + (persp-set-frame-buffer-predicate frame)) + (run-at-time 0.1 nil #'+workspace/display)))) + +(defvar +workspaces--project-dir nil) +;;;###autoload +(defun +workspaces-set-project-action-fn () + "A `projectile-switch-project-action' that sets the project directory for +`+workspaces-switch-to-project-h'." + (setq +workspaces--project-dir default-directory)) + +;;;###autoload +(defun +workspaces-switch-to-project-h (&optional dir) + "Creates a workspace dedicated to a new project. If one already exists, switch +to it. If in the main workspace and it's empty, recycle that workspace, without +renaming it. + +Afterwords, runs `+workspaces-switch-project-function'. By default, this prompts +the user to open a file in the new project. + +This be hooked to `projectile-after-switch-project-hook'." + (when dir + (setq +workspaces--project-dir dir)) + (when (and persp-mode +workspaces--project-dir) + (unwind-protect + (if (and (not (null +workspaces-on-switch-project-behavior)) + (or (eq +workspaces-on-switch-project-behavior t) + (+workspace-buffer-list))) + (let* ((persp + (let ((project-name (doom-project-name +workspaces--project-dir))) + (or (+workspace-get project-name t) + (+workspace-new project-name)))) + (new-name (persp-name persp))) + (+workspace-switch new-name) + (with-current-buffer (doom-fallback-buffer) + (setq default-directory +workspaces--project-dir)) + (unless current-prefix-arg + (funcall +workspaces-switch-project-function +workspaces--project-dir)) + (+workspace-message + (format "Switched to '%s' in new workspace" new-name) + 'success)) + (with-current-buffer (doom-fallback-buffer) + (setq default-directory +workspaces--project-dir) + (message "Switched to '%s'" (doom-project-name +workspaces--project-dir))) + (with-demoted-errors "Workspace error: %s" + (+workspace-rename (+workspace-current-name) (doom-project-name +workspaces--project-dir))) + (unless current-prefix-arg + (funcall +workspaces-switch-project-function +workspaces--project-dir))) + (setq +workspaces--project-dir nil)))) + + +;; +;;; Advice + +;;;###autoload +(defun +workspaces-autosave-real-buffers-a (orig-fn &rest args) + "Don't autosave if no real buffers are open." + (when (doom-real-buffer-list) + (apply orig-fn args)) + t) diff --git a/.emacs.d/modules/ui/workspaces/config.el b/.emacs.d/modules/ui/workspaces/config.el new file mode 100644 index 0000000..cc50576 --- /dev/null +++ b/.emacs.d/modules/ui/workspaces/config.el @@ -0,0 +1,222 @@ +;;; ui/workspaces/config.el -*- lexical-binding: t; -*- + +;; `persp-mode' gives me workspaces, a workspace-restricted `buffer-list', and +;; file-based session persistence. I used workgroups2 before this, but abandoned +;; it because it was unstable and slow; `persp-mode' is neither (and still +;; maintained). +;; +;; NOTE persp-mode requires `workgroups' for file persistence in Emacs 24.4. + +(defvar +workspaces-main "main" + "The name of the primary and initial workspace, which cannot be deleted.") + +(defvar +workspaces-switch-project-function #'doom-project-find-file + "The function to run after `projectile-switch-project' or +`counsel-projectile-switch-project'. This function must take one argument: the +new project directory.") + +(defvar +workspaces-on-switch-project-behavior 'non-empty + "Controls the behavior of workspaces when switching to a new project. + +Can be one of the following: + +t Always create a new workspace for the project +'non-empty Only create a new workspace if the current one already has buffers + associated with it. +nil Never create a new workspace on project switch.") + +;; FIXME actually use this for wconf bookmark system +(defvar +workspaces-data-file "_workspaces" + "The basename of the file to store single workspace perspectives. Will be +stored in `persp-save-dir'.") + +(defvar +workspace--old-uniquify-style nil) + + +;; +;; Packages + +(use-package! persp-mode + :commands persp-switch-to-buffer + :init + (add-hook! 'doom-init-modules-hook + (defun +workspaces-init-h () + (unless noninteractive + ;; Remove default buffer predicate so persp-mode can put in its own + (delq! 'buffer-predicate default-frame-alist 'assq) + (require 'persp-mode) + (if (daemonp) + (add-hook 'after-make-frame-functions #'persp-mode-start-and-remove-from-make-frame-hook) + (persp-mode +1))))) + :config + (setq persp-autokill-buffer-on-remove 'kill-weak + persp-nil-hidden t + persp-auto-save-fname "autosave" + persp-save-dir (concat doom-etc-dir "workspaces/") + persp-set-last-persp-for-new-frames t + persp-switch-to-added-buffer nil + persp-remove-buffers-from-nil-persp-behaviour nil + persp-auto-resume-time -1 ; Don't auto-load on startup + persp-auto-save-opt (if noninteractive 0 1)) ; auto-save on kill + + (advice-add #'persp-asave-on-exit :around #'+workspaces-autosave-real-buffers-a) + + (add-hook! '(persp-mode-hook persp-after-load-state-functions) + (defun +workspaces-ensure-main-workspace-h (&rest _) + "Ensure the main workspace exists and the nil workspace is never active." + (when persp-mode + (let (persp-before-switch-functions) + ;; The default perspective persp-mode creates (`persp-nil-name') is + ;; special and doesn't represent a real persp object, so buffers can't + ;; really be assigned to it, among other quirks. We create a *real* main + ;; workspace to fill this role. + (unless (persp-get-by-name +workspaces-main) + (persp-add-new +workspaces-main)) + ;; Switch to it if we're in the nil perspective + (dolist (frame (frame-list)) + (when (string= (safe-persp-name (get-current-persp frame)) persp-nil-name) + (persp-frame-switch +workspaces-main frame) + ;; Fix #319: the warnings buffer gets swallowed by creating + ;; `+workspaces-main', so we display it manually, if it exists. + (when-let (warnings (get-buffer "*Warnings*")) + (save-excursion + (display-buffer-in-side-window + warnings '((window-height . shrink-window-if-larger-than-buffer))))))))))) + + (add-hook! 'persp-mode-hook + (defun +workspaces-init-persp-mode-h () + (cond (persp-mode + ;; `uniquify' breaks persp-mode. It renames old buffers, which causes + ;; errors when switching between perspective (their buffers are + ;; serialized by name and persp-mode expects them to have the same + ;; name when restored). + (when uniquify-buffer-name-style + (setq +workspace--old-uniquify-style uniquify-buffer-name-style)) + (setq uniquify-buffer-name-style nil) + ;; Ensure `persp-kill-buffer-query-function' is last + (remove-hook 'kill-buffer-query-functions #'persp-kill-buffer-query-function) + (add-hook 'kill-buffer-query-functions #'persp-kill-buffer-query-function t) + ;; Restrict buffer list to workspace + (advice-add #'doom-buffer-list :override #'+workspace-buffer-list)) + (t + (when +workspace--old-uniquify-style + (setq uniquify-buffer-name-style +workspace--old-uniquify-style)) + (advice-remove #'doom-buffer-list #'+workspace-buffer-list))))) + + ;; We don't rely on the built-in mechanism for auto-registering a buffer to + ;; the current workspace; some buffers slip through the cracks. Instead, we + ;; add buffers when they are switched to. + (setq persp-add-buffer-on-find-file nil + persp-add-buffer-on-after-change-major-mode nil) + + (add-hook! '(doom-switch-buffer-hook server-visit-hook) + (defun +workspaces-add-current-buffer-h () + "Add current buffer to focused perspective." + (and persp-mode + (not (persp-buffer-filtered-out-p + (current-buffer) + persp-add-buffer-on-after-change-major-mode-filter-functions)) + (persp-add-buffer (current-buffer) (get-current-persp) nil nil)))) + + (add-hook 'persp-add-buffer-on-after-change-major-mode-filter-functions + #'doom-unreal-buffer-p) + + (defadvice! +workspaces--evil-alternate-buffer-a (&optional window) + "Make `evil-alternate-buffer' ignore buffers outside the current workspace." + :override #'evil-alternate-buffer + (let* ((prev-buffers + (if persp-mode + (cl-remove-if-not #'persp-contain-buffer-p (window-prev-buffers) + :key #'car) + (window-prev-buffers))) + (head (car prev-buffers))) + (if (eq (car head) (window-buffer window)) + (cadr prev-buffers) + head))) + + ;; Delete the current workspace if closing the last open window + (define-key! persp-mode-map + [remap delete-window] #'+workspace/close-window-or-workspace + [remap evil-window-delete] #'+workspace/close-window-or-workspace) + + ;; per-frame workspaces + (setq persp-init-frame-behaviour t + persp-init-new-frame-behaviour-override nil + persp-interactive-init-frame-behaviour-override #'+workspaces-associate-frame-fn + persp-emacsclient-init-frame-behaviour-override #'+workspaces-associate-frame-fn) + (add-hook 'delete-frame-functions #'+workspaces-delete-associated-workspace-h) + + ;; per-project workspaces, but reuse current workspace if empty + (setq projectile-switch-project-action #'+workspaces-set-project-action-fn + counsel-projectile-switch-project-action + '(1 ("o" +workspaces-switch-to-project-h "open project in new workspace") + ("O" counsel-projectile-switch-project-action "jump to a project buffer or file") + ("f" counsel-projectile-switch-project-action-find-file "jump to a project file") + ("d" counsel-projectile-switch-project-action-find-dir "jump to a project directory") + ("b" counsel-projectile-switch-project-action-switch-to-buffer "jump to a project buffer") + ("m" counsel-projectile-switch-project-action-find-file-manually "find file manually from project root") + ("w" counsel-projectile-switch-project-action-save-all-buffers "save all project buffers") + ("k" counsel-projectile-switch-project-action-kill-buffers "kill all project buffers") + ("r" counsel-projectile-switch-project-action-remove-known-project "remove project from known projects") + ("c" counsel-projectile-switch-project-action-compile "run project compilation command") + ("C" counsel-projectile-switch-project-action-configure "run project configure command") + ("e" counsel-projectile-switch-project-action-edit-dir-locals "edit project dir-locals") + ("v" counsel-projectile-switch-project-action-vc "open project in vc-dir / magit / monky") + ("s" (lambda (project) + (let ((projectile-switch-project-action + (lambda () (call-interactively #'+ivy/project-search)))) + (counsel-projectile-switch-project-by-name project))) "search project") + ("xs" counsel-projectile-switch-project-action-run-shell "invoke shell from project root") + ("xe" counsel-projectile-switch-project-action-run-eshell "invoke eshell from project root") + ("xt" counsel-projectile-switch-project-action-run-term "invoke term from project root") + ("X" counsel-projectile-switch-project-action-org-capture "org-capture into project"))) + + (add-hook 'projectile-after-switch-project-hook #'+workspaces-switch-to-project-h) + + ;; Fix #1973: visual selection surviving workspace changes + (add-hook 'persp-before-deactivate-functions #'deactivate-mark) + + ;; Fix #1017: stop session persistence from restoring a broken posframe + (after! posframe + (add-hook! 'persp-after-load-state-functions + (defun +workspaces-delete-all-posframes-h (&rest _) + (posframe-delete-all)))) + + ;; Fix #1525: Ignore dead buffers in PERSP's buffer list + (defun +workspaces-dead-buffer-p (buf) + (not (buffer-live-p buf))) + (add-hook 'persp-filter-save-buffers-functions #'+workspaces-dead-buffer-p) + + ;; + ;; eshell + (persp-def-buffer-save/load + :mode 'eshell-mode :tag-symbol 'def-eshell-buffer + :save-vars '(major-mode default-directory)) + ;; compile + (persp-def-buffer-save/load + :mode 'compilation-mode :tag-symbol 'def-compilation-buffer + :save-vars + '(major-mode default-directory compilation-directory compilation-environment compilation-arguments)) + ;; Restore indirect buffers + (defvar +workspaces--indirect-buffers-to-restore nil) + (persp-def-buffer-save/load + :tag-symbol 'def-indirect-buffer + :predicate #'buffer-base-buffer + :save-function (lambda (buf tag vars) + (list tag (buffer-name buf) vars + (buffer-name (buffer-base-buffer buf)))) + :load-function (lambda (savelist &rest _rest) + (cl-destructuring-bind (buf-name _vars base-buf-name &rest _) + (cdr savelist) + (push (cons buf-name base-buf-name) + +workspaces--indirect-buffers-to-restore) + nil))) + (add-hook! 'persp-after-load-state-functions + (defun +workspaces-reload-indirect-buffers-h (&rest _) + (dolist (ibc +workspaces--indirect-buffers-to-restore) + (cl-destructuring-bind (buffer-name . base-buffer-name) ibc + (when (buffer-live-p (get-buffer base-buffer-name)) + (when (get-buffer buffer-name) + (setq buffer-name (generate-new-buffer-name buffer-name))) + (make-indirect-buffer bb buffer-name t)))) + (setq +workspaces--indirect-buffers-to-restore nil)))) diff --git a/.emacs.d/modules/ui/workspaces/packages.el b/.emacs.d/modules/ui/workspaces/packages.el new file mode 100644 index 0000000..9b9cb3f --- /dev/null +++ b/.emacs.d/modules/ui/workspaces/packages.el @@ -0,0 +1,5 @@ +;; -*- no-byte-compile: t; -*- +;;; ui/workspaces/packages.el + +(package! persp-mode) + diff --git a/.emacs.d/modules/ui/workspaces/test/test-workspaces.el b/.emacs.d/modules/ui/workspaces/test/test-workspaces.el new file mode 100644 index 0000000..cc0e76c --- /dev/null +++ b/.emacs.d/modules/ui/workspaces/test/test-workspaces.el @@ -0,0 +1,124 @@ +;; -*- no-byte-compile: t; -*- +;;; ui/workspaces/test/test-workspaces.el + +(describe "ui/workspaces" + :var (persp-auto-resume-time + persp-auto-save-opt + persp-switch-to-added-buffer + persp-autokill-persp-when-removed-last-buffer + persp-autokill-buffer-on-remove + in1 in2 out1 out2 + persp1 persp1-name persp2 persp2-name + wconf) + + (require! :ui workspaces) + (require 'persp-mode) + + (before-all + (delete-other-windows)) + + (before-each + (switch-to-buffer "*scratch*") + (setq wconf (current-window-configuration) + persp-auto-resume-time -1 + persp-auto-save-opt 0 + persp-switch-to-added-buffer nil + persp-autokill-persp-when-removed-last-buffer nil + persp-autokill-buffer-on-remove nil + in1 (get-buffer-create "in1") + in2 (get-buffer-create "in2") + out1 (get-buffer-create "out1") + out2 (get-buffer-create "out2")) + (doom-set-buffer-real in1 t) + (doom-set-buffer-real out1 t) + (let (noninteractive) + (persp-mode +1) + (let (persp-before-switch-functions persp-activated-functions) + (setq persp1-name +workspaces-main + persp1 (persp-add-new persp1-name) + persp2-name "test" + persp2 (persp-add-new persp2-name)) + (persp-switch persp1-name) + (persp-add-buffer (list in1 in2) persp1)))) + + (after-each + (let (kill-buffer-query-functions kill-buffer-hook) + (let (noninteractive ignore-window-parameters) + (dolist (persp (persp-names)) + (ignore-errors (persp-kill persp))) + (persp-mode -1)) + (set-window-configuration wconf) + (mapc #'kill-buffer (list in1 in2 out1 out2)))) + + ;; + (describe "switch" + (it "throws an error when switching to a non-existent workspace" + (expect (+workspace-switch "non-existent") :to-throw)) + (it "switches to a valid workspace" + (+workspace-switch persp2-name) + (expect (+workspace-current-name) :to-equal persp2-name))) + + (describe "current" + (it "returns the current workspace persp" + (expect (+workspace-p (+workspace-current))) + (expect (+workspace-current) :to-equal (get-current-persp))) + (it "returns the current workspace's name" + (expect (+workspace-current-name) :to-equal persp1-name) + (persp-switch (persp-name persp2)) + (expect (+workspace-current-name) :to-equal persp2-name))) + + (describe "exists-p" + (it "returns t for valid workspaces" + (expect (+workspace-exists-p persp1-name))) + (it "returns t for non-current (but valid) workspaces" + (expect (+workspace-exists-p persp2-name))) + (it "returns nil for non-existent workspaces" + (expect (+workspace-exists-p "non-existent") :to-be nil))) + + (describe "buffer membership" + (it "returns t for buffers in current workspace" + (expect (+workspace-contains-buffer-p in1))) + (it "returns nil for buffers outside of current workspace" + (expect (+workspace-contains-buffer-p out1) :to-be nil)) + (xit "returns a list of orphaned buffers" + (expect (+workspace-orphaned-buffer-list) :to-contain out2))) + + (describe "list" + (it "returns a list of names" + (expect (+workspace-list-names) + :to-have-same-items-as (list persp1-name persp2-name))) + (it "returns a list of perspective structs" + (expect (+workspace-list) + :to-have-same-items-as (list persp1 persp2)))) + + (describe "CRUD" + (it "creates new workspaces" + (+workspace-new "X") + (expect (+workspace-list-names) :to-contain "X")) + (it "renames an existing workspace" + (+workspace-rename persp2-name "X") + (expect (persp-name persp2) :to-equal "X") + (expect (+workspace-list-names) + :to-have-same-items-as (list persp1-name "X"))) + (it "deletes a live workspace" + (+workspace-delete persp2-name) + (expect (+workspace-list-names) :not :to-contain persp2-name))) + + (describe "command" + (describe "close-window-or-workspace" + (before-each + (+workspace-switch persp2-name) + (split-window) + (expect (length (doom-visible-windows)) :to-be 2)) + (it "kills window if more than one window" + (quiet! (+workspace/close-window-or-workspace)) + (expect (length (doom-visible-windows)) :to-be 1)) + (it "kills workspace on last window" + (quiet! (+workspace/close-window-or-workspace) + (+workspace/close-window-or-workspace)) + (expect (+workspace-current-name) :to-equal persp1-name))) + + (describe "rename" + (it "renames the current workspace" + (quiet! (+workspace/rename "X")) + (expect (+workspace-current-name) :to-equal "X")))))