Merge branch 'develop'

* develop: (173 commits)
  v2.0.6 bump
  Add zunit file template & file extension
  Remove +workspace/cleanup (doom/cleanup-buffers is better)
  Remove org/org dependency on doom-themes
  General refactor & docstring updates
  Revert "Preload modules before compiling #219"
  Fix 'variable reference to nil’ compiler warning
  Fix markdown specific keybindings being added to global map
  Fix bad doom/other-popup alias breaking which-key #223
  Preload modules before compiling #219
  Popup library: minor refactor & appease byte-compiler
  Fix compile error caused by defunct keybinding #219
  Remove org/org-notebook
  Add org unit tests to init.test.el
  Add unit test file template
  Fix modeline duplication in buffer-file-name :help-echo
  org/org: new command +org/remove-link, bound to C-c C-S-l
  org/org: add tests for +org/insert-item
  Add test helper macros to test.el lib
  +org/insert-item: fix one-too-many prepended newlines in 1st-level headers
  ...
This commit is contained in:
Henrik Lissner
2017-10-06 02:39:17 +02:00
126 changed files with 2900 additions and 2247 deletions

View File

@ -10,6 +10,7 @@ before_install:
env:
- EVM_EMACS=emacs-25.1-travis
- EVM_EMACS=emacs-25.2-travis
- EVM_EMACS=emacs-25.3-travis
script:
- emacs --version
- make test

View File

@ -1,7 +1,7 @@
#+TITLE: Changelog
- [[#todo][Todo]]
- [[#unreleased-master][Unreleased (master)]]
- [[#unreleased-develop][Unreleased (develop)]]
- [[#206-oct-05-2017][2.0.6 (Oct 05, 2017)]]
- [[#205-sep-03-2017][2.0.5 (Sep 03, 2017)]]
- [[#204-jul-14-2017][2.0.4 (Jul 14, 2017)]]
- [[#203-jun-11-2017][2.0.3 (Jun 11, 2017)]]
@ -9,72 +9,116 @@
- [[#201-apr-8-2017][2.0.1 (Apr 8, 2017)]]
- [[#200-jan-17-2017][2.0.0 (Jan 17, 2017)]]
* Todo
+ *Potential plugins:*
+ =app/present= [[https://github.com/larstvei/Focus][focus]], for presenting code
+ [[https://github.com/emacs-lsp/lsp-mode][lsp-mode]], client for MS Language Server Protocol, keep an eye on this
+ =lang/javascript= [[https://github.com/NicolasPetton/Indium][indium]] (IDE), keep an eye on this
+ =lang/javascript= [[https://github.com/codesuki/add-node-modules-path][add-node-modules-path]] (adds node_modules to ~exec-path~)
+ =lang/javascript= [[https://github.com/lbolla/emacs-flycheck-flow][flycheck-flow]] (Flow support for JS)
+ =lang/org= [[https://github.com/Malabarba/latex-extra][orgit]] (org links to magit buffers)
+ =lang/org= [[https://github.com/jkitchin/org-ref][org-ref]] (bibtex/citation helper)
+ =lang/org= [[https://github.com/tashrifsanil/org-easy-img-insert][org-easy-img-insert]]
+ =lang/latex= [[https://github.com/Malabarba/latex-extra][latex-extra]] (utility commands)
+ =lang/latex= [[**https://github.com/jsinglet/latex-preview-pane][latex-preview-pane]]
+ =lang/julia= [[ https://github.com/dennisog/julia-shell-mode][julia-shell]] (unsure if better than inferior-julia in julia-mode)
+ =lang/python= [[https://github.com/Wilfred/pyimport][pyimport]] or [[https://github.com/anachronic/importmagic.el][importmagic]]
+ [[https://github.com/mhayashi1120/Emacs-imagex][emacs-imagex]], for manipulating images at point (zooming?)
+ =tools/term= [[https://github.com/riscy/shx-for-emacs][shx]], an extension for the shell in Emacs
+ =app/crm= [[https://github.com/skeeto/emacsql][emacsql]], a sqlite backend; possibly useful for CRM storage.
+ =core= [[https://github.com/Wilfred/helpful][helpful]], a better help buffer; replacement for ~describe-function~?
+ *Planned modules:*
+ =app/crm= -- Customer Relations Management, in Emacs, using org-mode.
+ =app/write= -- Make Emacs into a focused plaintext word processor (using markdown, org and rst) for writing papers and stories.
+ =app/regex= -- PCRE IDE, with live buffer matching, search/replace support, and an export-to-code feature for various languages.
+ +Perl backend+
+ Search and replace support
+ Highlight replaced segments
+ Export-to-code feature for:
+ python (use ~re~ or ~regex~)
+ php (~preg_(match(_all)?|replace)~)
+ ruby (~%r[.+]~)
+ javascript (node) (~/.+/.test(...)~)
+ C (~regex.h~ + ~regcomp~)
+ C++ (~regex reg(regexp, ...)~)
+ Syntax highlighter for ~+regex-mode~ (plus make it a major mode)
+ Optimize: communicate with perl process (with ~make-process~ instead of ~call-process~)
+ =org/org-publish= -- publishing org files to HTML (thanks to [[https://github.com/matthewgraybosch][matthewgraybosch]])
+ =org/org-attach= -- my own, simpler attachment system with drag-drop image attachment support and centralized storage.
+ =app/torrents= -- Emacs as a torrent client (powered by transmission.el)
+ =core-ui= Replace or fix ~winner-mode~ unreliability (will close windows trying to revive killed buffers). Perhaps make ~doom/kill-this-buffer~ only disassociate buffer from persp-mode or bury buffer if persp-mode is inactive.
+ =org=
+ Better shackle + org-agenda integration
+ Fix janky visual line motions (~evil-next-visual-line~, etc)
+ Fix janky cursor positioning when jumping between org-table cells from insert mode.
+ Certain characters/keys--when typed in a table--cause the cell to shrink (likely cause: custom self-insert-char behavior -- like smartparens pairs & custom SPC/BKSPC binds)
+ =feature/jump= Automatic etags generation (for dwim go-to-definition and, perhaps, code-completion for some languages; lua maybe?).
+ =lang/php= Automatic and async tags generation using [[https://github.com/xcwen/phpctags][phpctags]].
+ =lang/lua= True, dynamic code-completion? Looks like [[https://github.com/immerrr/lua-mode/pull/119][this PR in lua-mode]] may have the answer. Does it make ~company-lua~ redundant?
+ =tools/upload= Add ~+upload/open-remote-file~ command to open current file on the remote (with TRAMP).
+ Add =bin/org-alert= script -- a cron script that scans TODOs in org files and dispatches system alerts.
+ =feature/workspaces= Add a bookmarks feature, but for wconfs, that can revive file buffers. Also needs an interface.
+ =ui/doom-modeline=
+ Fix hardcoded spacing in between segments.
+ Fix ~0/0~ leftover panel in modeline (caused by lingering anzu state).
+ Update =bin/org-capture= to read from stdin in the absence of arguments.
+ =core-popups= Add support for moving popup windows to the ~+evil/window-move-*~ commands #171
* Unreleased (develop)
* Unreleased (master)
+ =doom=
+ Added new module: ~lang/ledger~, for editing ledger files.
+ Fixed ~make update~ to work even if Doom is installed somewhere other than ~\~/.emacs.d~ (see [[https://github.com/hlissner/doom-emacs/issues/190][#190]]).
* 2.0.6 (Oct 05, 2017)
+ *Module changes:*
+ Add =lang/ledger=
+ Add =ui/vi-tilde-fringe= -- used to be in =core-ui=; indicates beyond-EOB,
using tildes in the fringe (inspired by vim).
+ Add =feature/services= -- used to be =tools/prodigy=. Adds a way of managing
external processes and services.
+ Add =tools/make= -- for running project Makefile commands from Emacs.
+ Add =tools/imenu= -- adds a sidebar for imenu (~imenu-list~), and a way of
jumping to imenu entries across all open buffers (~imenu-anywhere~).
+ Move =feature/hydra= into =core-keybinds=.
+ Rename =feature/debug= to =feature/debugger= (and disabled it by default; it
is currently unstable and needs some work).
+ Remove =org/org-notebook=. It was unused and too small to warrant its own
module. Useful tidbits were merged into =org/org=.
+ =general=
+ =Makefile=
+ Fix ~make update~ to work even if Doom is installed somewhere other than
~\~/.emacs.d~ (see [[https://github.com/hlissner/doom-emacs/issues/190][#190]]).
+ Removed colons from makefile task target names (like =compile:core=);
replaced them with dashses, e.g. =compile-core=. Colons broke compatibility
with certain versions of make.
+ =autoload=
+ New library: =menu.el= -- allows context-sensitive and customizable
fuzzy-searchable menus; this was written to replace long lists of
major-mode-local key bindings, like refactoring and code building
commands. This replaces =feature/eval='s build task system.
+ =editor.el= Fix old scratch buffer commands and renamed them:
~doom/open-scratch-buffer~ and ~doom/open-project-scratch-buffer~. The
former opens a temporary, transient scratch buffer, the latter opens a
permanent one tied to the current project, kept in
~doom-scratch-files-dir~.
+ =window.el= Changed ~doom-resize-window~ to accept two more arguments,
=WINDOW= and =FORCE-P=: ~doom-resize-window WINDOW NEW-SIZE &optional
HORIZONTAL FORCE-P~. If =FORCE-P= is non-nil, this function will resize a
window regardless of ~window-size-fixed~.
+ =core-keybinds= Add new =def-hydra!= alias macro for ~defhydra~ (for
consistency, and in case we want to wrap it later).
+ =core-projects= Redesign ~def-project-mode!~ for efficiency, and:
+ The =:init FORM= property is now =:on-load FORM=.
+ Three new properties: =:on-enter FORM=, =:on-exit FORM= and =:add-hooks
LIST=.
+ =core-popups=
+ Added two new popup properties:
+ ~:static~ If non-nil, treat this popup like a permanent window, making
it impervious to automatic closing and being tracked in popup history.
This is excellent for semi-permanent popups, like sidebars (think
Neotree or imenu-list).
+ ~:autofit~ If non-nil, this popup will resize to fit its buffer
contents. This only works with popups where the buffer content is
immediately available, and not for, say, buffers tied to async
processes.
+ ~doom-popup-buffer~ and ~doom-popup-file~ no longer take a variadic
argument. Their signature is now ~doom-popup-buffer buffer plist &optional
extend-p~ and ~doom-popup-file file plist &optional extend-p~, where
=EXTEND-P= will cause =PLIST= to extend from the base rule for that
buffer.
+ Rename ~doom-popup-prop~ to ~doom-popup-property~.
+ Add support for moving popup windows. See the ~doom/popup-move-*~
commands. There are used by ~+evil/window-move-*~, which provides
universal support for moving windows.
+ Add command: ~doom/popup-raise~, for promoting a popup into a regular
window.
+ Add helper macro: ~save-popup! BODY~ -- hides the popups before running
BODY.
+ Fix ~doom/popup-toggle~ and ~save-popups!~ killing popups with an
=:autokill= property.
+ =feature=
+ =hydra= Display a separator along the bottom of hydra windows for extra contrast.
+ =hydra= Display a separator on the bottom of hydra windows for contrast.
+ =eval= Build-task management has been removed from =feature/eval= in favor
of ~def-menu!~.
+ =ui=
+ =doom-dashboard= Elements are now centered using window-local margins, which fixes discrepancies when multiple dashboards are visible in different sized windows and/or frames (see [[https://github.com/hlissner/doom-emacs/issues/192][#192]]).
+ =doom-dashboard=
+ Fix /horizontal/ centering discrepancies caused by multiple visible
dashboards in windows/frames with different sizes (see [[https://github.com/hlissner/doom-emacs/issues/192][#192]]). Still
doesn't address vertical centering.
+ Fix dashboard's default-directory not changing to the last open project
when switched to.
+ =doom-modeline= Add a new style to ~+doom-modeline-buffer-file-name-style~:
~relative-from-project~, which displays on the buffer's path relative to
(and including) the project.
+ =hl-todo= Add face-based detection for commented regions, so hl-todo can
work in modes with no/poor syntax-table support.
+ =tools=
+ =neotree=
+ Fix neotree shrinking by 1 when vertical splits were closed.
+ Fix Neotree popup rule not taking ~neo-window-width~ and
~neo-window-position~ into account.
+ =term= Renamed commands for consistency (to ~+term/open~, ~+term/open-popup~
and ~+term/open-popup-in-project~).
+ =eshell= Renamed commands for consistency (to ~+eshell/open~,
~+eshell/open-popup~ and ~+eshell/open-workspace~).
+ =lang=
+ =ruby= Add rake support. See the ~rake~ command.
+ =web= Only install company-web if =:completion company= is enabled.
+ =javascript=
+ Add eslint_d and eslint_d-fix detection and support.
+ =./node_modules/.bin= is now added to ~exec-path~ in NPM project buffers.
+ =haskell= There is no longer a 'default' implementation for Haskell. The
=+intero= and/or =+dante= module flags must be specified in init.el.
+ =java= Meghanada is no longer the 'default' implementation for Java. The
=+meghanada= and/or =+eclim= module flags must be specified in init.el.
+ =org=
+ If a table is under point when ~+org/toggle-fold~ is invoked, the table is realigned.
+ =org-capture= Fix a vestigial reference to a long-since-renamed function: ~doom/project-root~.
+ If a table is under point when ~+org/toggle-fold~ is invoked, the table is
realigned.
+ Fix the incorrect version of org being loaded (site, instead of ELPA) by
pushing it up further in the ~load-path~.
+ Fix ~+org/insert-item~ not jumping over sublists to append a new list item.
* 2.0.5 (Sep 03, 2017)
+ =doom=
@ -150,7 +194,7 @@
+ Unit-tests have been moved to their respective modules (and =core/test/=).
+ Fix ~def-setting!~ to act more like ~defmacro~; don't aggressively evaluate its arguments on expansion.
+ New function: ~doom-set-buffer-real BUFFER FLAG~ -- makes Doom consider BUFFER real, no matter what.
+ Add ~INSTALLED-ONLY-P~ argument to ~doom-get-packages~ to filter packages that aren't installed.
+ Add INSTALLED-ONLY-P argument to ~doom-get-packages~ to filter packages that aren't installed.
+ =core-ui=
+ Add quit confirmation when trying to close a frame that contains real buffers.
+ Fix quit confirmations for clients connected to ~emacs --daemon~ with ~emacsclient~.
@ -170,7 +214,7 @@
+ =core-packages=
+ Generalize ~doom-package-*-p~ functions into ~(doom-package-prop NAME PROPERTY)~.
+ Fix quelpa temporary files (in ~quelpa-build-dir~) not being removed when a quelpa package was uninstalled.
+ New hook: ~doom-reload-hook~ (sort of). This has been around for a while, but now it is defined and documented. It runs when ~doom/reload~ is called (which gets called remotely if you run package management while an Emacs session is active).
+ New hook: ~doom-reload-hook~ (sort of). This has been around for a while, but now it is defined and documented. It runs when ~doom/reload-load-path~ is called (which gets called remotely if you run package management while an Emacs session is active).
+ ~load!~ can now accept a string as its first argument (the path).
+ =feature=
+ =feature/evil=
@ -354,7 +398,7 @@
+ =feature=
+ =feature/eval=
+ Fix ~:repl~ & ~+eval/repl-send-region~.
+ Fix ~+eval/region~ failing only on first invocation because ~+eval-runners-alist~ wasn't populated until quickrun is loaded.
+ Fix ~+eval/region~ failing only on first invocation because ~+eval-runners~ wasn't populated until quickrun is loaded.
+ Add TAB auto-completion in comint-mode and REPL buffers
+ =feature/evil=
+ Fix ~:mv~ & ~:rm~.

View File

@ -7,6 +7,15 @@ MODULES=$(patsubst modules/%, %, $(shell find modules/ -maxdepth 2 -type d))
all: autoloads autoremove install
## Aliases
a: autoloads
i: install
u: update
r: autoremove
c: compile
cc: compile-core
ce: compile-elpa
## Package management
install: init.el .local/autoloads.el
@$(EMACS) -f doom/packages-install
@ -23,21 +32,21 @@ autoloads: init.el
## Byte compilation
# compile
# compile:core
# compile:module
# compile:module/submodule
# compile-core
# compile-module
# compile-module/submodule
compile: init.el clean
@$(EMACS) -f doom/compile
compile\:core: init.el clean
compile-core: init.el clean
@$(EMACS) -f doom/compile -- init.el core
compile\:elpa: init.el
compile-elpa: init.el
@$(EMACS) -f doom/recompile-packages
$(patsubst %, compile\:%, $(MODULES)): init.el .local/autoloads.el
@rm -fv $(shell find $(patsubst compile:%, modules/%, $@) -type f -name '*.elc')
@$(EMACS) -f doom/compile -- $(patsubst compile:%, modules/%, $@)
$(patsubst %, compile-%, $(MODULES)): init.el .local/autoloads.el
@rm -fv $(shell find $(patsubst compile-%, modules/%, $@) -type f -name '*.elc')
@$(EMACS) -f doom/compile -- $(patsubst compile-%, modules/%, $@)
recompile: init.el
@$(EMACS) -f doom/recompile
@ -54,14 +63,14 @@ reset:
## Unit tests
# test
# test:core
# test:module
# test:module/submodule
# test-core
# test-module
# test-module/submodule
test: init.el .local/autoloads.el
@$(EMACS) -f doom-run-tests
test\:core $(patsubst %, test\:%, $(MODULES)): init.el .local/autoloads.el
@$(EMACS) -f doom-run-tests -- $(subst test:, , $@)
test-core $(patsubst %, test-%, $(MODULES)): init.el .local/autoloads.el
@$(EMACS) -f doom-run-tests -- $(subst test-, , $@)
# run tests interactively
testi: init.el .local/autoloads.el

View File

@ -9,20 +9,35 @@
set -e
key="${1:-n}"
cleanup() {
emacsclient --eval '(kill-emacs)'
}
# If emacs isn't running, we start a temporary daemon, solely for this window.
daemon=
if ! pgrep emacs >/dev/null; then
emacs --daemon
trap cleanup EXIT INT TERM
daemon=1
fi
# TODO Allow piping from stdin
# org-capture key mapped to argument flags
keys=$(emacsclient -e "(+org-capture-available-keys)" | cut -d '"' -f2)
while getopts $keys opt; do
key="\"$opt\""
break
done
shift $((OPTIND-1))
emacsclient -c \
-F '((name . "org-capture") (width . 70) (height . 25))' \
--eval "(+org-capture/dwim \"$2\" \"$key\")"
[ -t 0 ] && str="$*" || str=$(cat)
if [[ $daemon ]]; then
emacsclient -a "" \
-c -F '((name . "org-capture") (width . 70) (height . 25))' \
-e "(+org-capture/open-frame \"$str\" ${key:-nil})"
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

View File

@ -53,7 +53,7 @@ Inspired from http://demonastery.org/2013/04/emacs-evil-narrow-region/"
If no project is active, return all buffers."
(let ((buffers (doom-buffer-list)))
(if-let (project-root (doom-project-root t))
(if-let (project-root (if (doom-project-p) (doom-project-root)))
(cl-loop for buf in buffers
if (projectile-project-buffer-p buf project-root)
collect buf)
@ -116,9 +116,9 @@ buffers. If there's nothing left, switch to `doom-fallback-buffer'. See
(project-dir (doom-project-root)))
(cond ((or (not buffers)
(zerop (% n (1+ (length buffers)))))
(set-window-buffer nil (doom-fallback-buffer)))
(switch-to-buffer (doom-fallback-buffer) nil t))
((= (length buffers) 1)
(set-window-buffer nil (car buffers)))
(switch-to-buffer (car buffers) nil t))
(t
;; Why this instead of switching straight to the Nth buffer in
;; BUFFERS? Because `switch-to-next-buffer' and
@ -136,9 +136,19 @@ buffers. If there's nothing left, switch to `doom-fallback-buffer'. See
;;;###autoload
(defun doom-real-buffer-p (&optional buffer-or-name)
"Returns t if BUFFER-OR-NAME is a 'real' buffer. Real means it a) isn't a
popup window/buffer and b) isn't a special buffer."
(let ((buf (window-normalize-buffer buffer-or-name)))
"Returns t if BUFFER-OR-NAME is a 'real' buffer. The complete criteria for a
real buffer is:
1. The buffer-local value of `doom-real-buffer-p' (variable) is non-nil OR
2. Any function in `doom-real-buffer-functions' must return non-nil when
passed this buffer OR
3. The current buffer:
a) has a `buffer-file-name' defined AND
b) is not in a popup window (see `doom-popup-p') AND
c) is not a special buffer (its name isn't something like *Help*)
If BUFFER-OR-NAME is omitted or nil, the current buffer is tested."
(when-let (buf (ignore-errors (window-normalize-buffer buffer-or-name)))
(or (buffer-local-value 'doom-real-buffer-p buf)
(run-hook-with-args-until-success 'doom-real-buffer-functions buf)
(not (or (doom-popup-p buf)
@ -148,14 +158,14 @@ popup window/buffer and b) isn't a special buffer."
;;;###autoload
(defun doom/next-buffer ()
"Switch to the next real buffer, skipping special buffers. See
"Switch to the next real buffer, skipping non-real buffers. See
`doom-real-buffer-p' for what 'real' means."
(interactive)
(doom--cycle-real-buffers +1))
;;;###autoload
(defun doom/previous-buffer ()
"Switch to the previous real buffer, skipping special buffers. See
"Switch to the previous real buffer, skipping non-real buffers. See
`doom-real-buffer-p' for what 'real' means."
(interactive)
(doom--cycle-real-buffers -1))
@ -163,7 +173,10 @@ popup window/buffer and b) isn't a special buffer."
;;;###autoload
(defun doom-kill-buffer (&optional buffer dont-save)
"Kill BUFFER (falls back to current buffer if omitted) then switch to a real
buffer, but only bury the buffer if it is present in another window.
buffer. If the buffer is present in another window, only bury it.
Will prompt to save unsaved buffers when attempting to kill them, unless
DONT-SAVE is non-nil.
See `doom-real-buffer-p' for what 'real' means."
(setq buffer (or buffer (current-buffer)))
@ -250,10 +263,12 @@ regex PATTERN. Returns the number of killed buffers."
;;;###autoload
(defun doom/kill-all-buffers (&optional project-p)
"Kill all buffers.
"Kill all buffers and closes their windows.
If PROJECT-P, kill all buffers that belong to the current project."
If PROJECT-P (universal argument), kill only buffers that belong to the current
project."
(interactive "P")
(doom/popup-kill-all)
(let ((buffers (if project-p (doom-project-buffer-list) (doom-buffer-list))))
(mapc #'doom-kill-buffer-and-windows buffers)
(unless (doom-real-buffer-p)
@ -262,10 +277,10 @@ If PROJECT-P, kill all buffers that belong to the current project."
;;;###autoload
(defun doom/kill-other-buffers (&optional project-p)
"Kill all other buffers.
"Kill all other buffers (besides the current one).
If PROJECT-P (universal argument), kill only the other buffers that belong to
the current project."
If PROJECT-P (universal argument), kill only buffers that belong to the current
project."
(interactive "P")
(let ((buffers (if project-p (doom-project-buffer-list) (doom-buffer-list)))
(current-buffer (current-buffer)))
@ -291,7 +306,7 @@ project."
;;;###autoload
(defun doom/cleanup-buffers (&optional all-p)
"Clean up buried and process buffers in the current workspace."
"Clean up buried and inactive process buffers in the current workspace."
(interactive "P")
(let ((buffers (doom-buried-buffers (if all-p (buffer-list))))
(n 0))

View File

@ -7,18 +7,13 @@
Interactively prints the list to the echo area. Noninteractively, returns a list
whose car is the list of faces and cadr is the list of overlay faces."
(interactive)
(unless pos
(setq pos (point)))
(let ((faces (let ((face (get-text-property pos 'face)))
(if (keywordp (car-safe face))
(list face)
(cl-loop for f in (if (listp face) face (list face))
collect f))))
(overlays (cl-loop for ov in (overlays-at pos (1+ pos))
nconc (cl-loop with face = (overlay-get ov 'face)
for f in (if (listp face) face (list face))
collect f))))
(let* ((pos (or pos (point)))
(faces (let ((face (get-text-property pos 'face)))
(if (keywordp (car-safe face))
(list face)
(cl-loop for f in (doom-enlist face) collect f))))
(overlays (cl-loop for ov in (overlays-at pos (1+ pos))
nconc (doom-enlist (overlay-get ov 'face)))))
(cond ((called-interactively-p 'any)
(message "%s %s\n%s %s"
(propertize "Faces:" 'face 'font-lock-comment-face)
@ -63,6 +58,8 @@ selection of all minor-modes, active or not."
"Test to see if your root certificates are securely configured in emacs."
(declare (interactive-only t))
(interactive)
(unless (string-match-p "\\_<GNUTLS\\_>" 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/"

View File

@ -15,21 +15,19 @@
(interactive)
(doom/sudo-find-file (file-truename buffer-file-name)))
(defun doom--goto-first-non-blank ()
(beginning-of-visual-line)
(skip-chars-forward " \t\r"))
;;;###autoload
(defun doom/backward-to-bol-or-indent ()
"Move back to the current line's indentation. If already there, move to the
beginning of the line instead. If at bol, do nothing."
(interactive)
(let ((boi (save-excursion (back-to-indentation) (point)))
(point (point)))
(if (= boi point)
(beginning-of-visual-line)
(unless (= (line-beginning-position) point)
(doom--goto-first-non-blank)))))
(if (bound-and-true-p visual-line-mode)
(beginning-of-visual-line)
(let ((ci (current-indentation))
(cc (current-column)))
(cond ((or (> cc ci) (= cc 0))
(back-to-indentation))
((<= cc ci)
(beginning-of-visual-line))))))
;;;###autoload
(defun doom/forward-to-last-non-comment-or-eol ()
@ -80,22 +78,28 @@ If already there, do nothing."
(interactive)
(if indent-tabs-mode
(call-interactively #'backward-delete-char)
(save-excursion
(unless (looking-back "^[\s\t]*" (line-beginning-position))
(doom--goto-first-non-blank))
(let* ((movement (% (current-column) tab-width))
(spaces (if (= 0 movement) tab-width (- tab-width movement))))
(delete-char (- spaces))))))
(unless (bolp)
(save-excursion
(when (> (current-column) (current-indentation))
(back-to-indentation))
(let ((movement (% (current-column) tab-width)))
(delete-char
(- (if (= 0 movement)
tab-width
(- tab-width movement)))))))))
;;;###autoload
(defun doom/backward-kill-to-bol-and-indent ()
"Kill line to the first non-blank character. If invoked again
afterwards, kill line to column 1."
(interactive)
(let ((empty-line (save-excursion (beginning-of-line) (looking-at-p "[ \t]*$"))))
(funcall (if (featurep 'evil) #'evil-delete #'delete-region)
(let ((empty-line-p (save-excursion (beginning-of-line)
(looking-at-p "[ \t]*$"))))
(funcall (if (featurep 'evil)
#'evil-delete
#'delete-region)
(point-at-bol) (point))
(unless empty-line
(unless empty-line-p
(indent-according-to-mode))))
;;;###autoload
@ -131,7 +135,9 @@ possible, or just one char if that's not possible."
(save-match-data
(if (string-match "\\w*\\(\\s-+\\)$"
(buffer-substring-no-properties (max (point-min) (- p movement)) p))
(sp-delete-char (- 0 (- (match-end 1) (match-beginning 1))))
(sp-delete-char
(- 0 (- (match-end 1)
(match-beginning 1))))
(call-interactively delete-backward-char)))))
;; Otherwise do a regular delete
@ -157,18 +163,19 @@ spaces on either side of the point if so. Resorts to
`doom/backward-delete-whitespace-to-column' otherwise."
(interactive)
(save-match-data
(cond ((doom--surrounded-p)
(let ((whitespace-match (match-string 1)))
(cond ((not whitespace-match)
(call-interactively #'delete-backward-char))
((string-match "\n" whitespace-match)
(funcall (if (featurep 'evil) #'evil-delete #'delete-region)
(point-at-bol) (point))
(call-interactively #'delete-backward-char)
(save-excursion (call-interactively #'delete-char)))
(t (just-one-space 0)))))
(t
(doom/backward-delete-whitespace-to-column)))))
(if (doom--surrounded-p)
(let ((whitespace-match (match-string 1)))
(cond ((not whitespace-match)
(call-interactively #'delete-backward-char))
((string-match "\n" whitespace-match)
(funcall (if (featurep 'evil)
#'evil-delete
#'delete-region)
(point-at-bol) (point))
(call-interactively #'delete-backward-char)
(save-excursion (call-interactively #'delete-char)))
(t (just-one-space 0))))
(doom/backward-delete-whitespace-to-column))))
;;;###autoload
(defun doom/newline-and-indent ()
@ -179,21 +186,22 @@ with weak native support."
(cond ((sp-point-in-string)
(newline))
((sp-point-in-comment)
(cond ((memq major-mode '(js2-mode rjsx-mode))
(call-interactively #'js2-line-break))
((memq major-mode '(java-mode php-mode))
(c-indent-new-comment-line))
((memq major-mode '(c-mode c++-mode objc-mode css-mode scss-mode js2-mode))
(newline-and-indent)
(insert "* ")
(indent-according-to-mode))
(t
;; Fix an off-by-one cursor-positioning issue
;; with `indent-new-comment-line'
(let ((col (save-excursion (comment-beginning) (current-column))))
(indent-new-comment-line)
(unless (= col (current-column))
(insert " "))))))
(pcase major-mode
((or 'js2-mode 'rjsx-mode)
(call-interactively #'js2-line-break))
((or 'java-mode 'php-mode)
(c-indent-new-comment-line))
((or 'c-mode 'c++-mode 'objc-mode 'css-mode 'scss-mode 'js2-mode)
(newline-and-indent)
(insert "* ")
(indent-according-to-mode))
(_
;; Fix an off-by-one cursor-positioning issue
;; with `indent-new-comment-line'
(let ((col (save-excursion (comment-beginning) (current-column))))
(indent-new-comment-line)
(unless (= col (current-column))
(insert " "))))))
(t
(newline nil t)
(indent-according-to-mode))))
@ -210,47 +218,8 @@ consistent throughout a selected region, depending on `indent-tab-mode'."
(tabify beg end)
(untabify beg end)))
;;;###autoload
(defun doom/toggle-sticky (&optional beg end)
"Make a selection sticky by placing it in the header line. Possibly helpful
for function signatures or notes. Run again to clear the header line."
(interactive "r")
(setq header-line-format
(when mark-active
(concat (propertize (format linum-format (line-number-at-pos beg))
'face 'font-lock-comment-face)
(let ((content (buffer-substring beg end)))
(setq content (replace-regexp-in-string "\n" " " content t t))
(setq content (replace-regexp-in-string "\\s-+" " " content))
content)))))
;;;###autoload
(defun doom/scratch-buffer (&optional arg)
"Opens the scratch buffer in a popup window.
If ARG (universal argument) is non-nil, open it in the current window instead of
a popup.
If a region is active, copy it into the scratch buffer."
(interactive "P")
(let ((text (and (region-active-p)
(buffer-substring-no-properties
(region-beginning) (region-end))))
(mode major-mode)
(derived-p (derived-mode-p 'prog-mode 'text-mode))
(old-project (doom-project-root))
(new-buf (get-buffer-create "*doom:scratch*")))
(if arg
(switch-to-buffer new-buf)
(doom-popup-buffer new-buf))
(with-current-buffer new-buf
(setq default-directory old-project)
(when (and (not (eq major-mode mode))
derived-p
(functionp mode))
(funcall mode))
(if text (insert text)))))
;;;###autoload
(defun doom|enable-delete-trailing-whitespace ()
"Attaches `delete-trailing-whitespace' to a buffer-local `before-save-hook'."
(add-hook 'before-save-hook #'delete-trailing-whitespace nil t))

View File

@ -6,7 +6,7 @@
(interactive
;; TODO try to read setting from whole line
(list (completing-read "Describe setting%s: "
(mapcar #'car doom-settings)
(sort (mapcar #'car doom-settings) #'string-lessp)
nil t nil nil)))
(let ((fn (cdr (assq (intern setting) doom-settings))))
(unless fn

104
core/autoload/menu.el Normal file
View File

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

View File

@ -354,7 +354,7 @@ package.el as appropriate."
"")))))
(message! (bold (green "Finished!")))
(doom/reload))))
(doom/reload-load-path))))
;;;###autoload
(defun doom/packages-update ()
@ -395,7 +395,7 @@ package.el as appropriate."
(if result "DONE" "FAILED"))))))
(message! (bold (green "Finished!")))
(doom/reload)))))
(doom/reload-load-path)))))
;;;###autoload
(defun doom/packages-autoremove ()
@ -428,7 +428,7 @@ package.el as appropriate."
pkg)))))
(message! (bold (green "Finished!")))
(doom/reload)))))
(doom/reload-load-path)))))
;;;###autoload
(defalias 'doom/install-package #'package-install)

View File

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

53
core/autoload/scratch.el Normal file
View File

@ -0,0 +1,53 @@
;;; core/autoload/scratch.el -*- lexical-binding: t; -*-
(defvar doom-scratch-files-dir (concat doom-etc-dir "scratch/")
"Where to store project scratch files, created by
`doom/open-project-scratch-buffer'.")
(defvar doom-scratch-buffer-hook ()
"The hooks to run after a scratch buffer is made.")
(defun doom--create-scratch-buffer (&optional project-p)
(let ((text (and (region-active-p)
(buffer-substring-no-properties
(region-beginning) (region-end))))
(mode major-mode)
(derived-p (derived-mode-p 'prog-mode 'text-mode))
(old-project (doom-project-root)))
(unless (file-directory-p doom-scratch-files-dir)
(mkdir doom-scratch-files-dir t))
(with-current-buffer
(if project-p
(find-file-noselect
(expand-file-name (replace-regexp-in-string
"\\." "_" (projectile-project-name)
t t)
doom-scratch-files-dir)
nil t)
(get-buffer-create "*doom:scratch*"))
(when project-p
(rename-buffer (format "*doom:scratch (%s)*" (projectile-project-name))))
(setq default-directory old-project)
(when (and (not (eq major-mode mode))
derived-p
(functionp mode))
(funcall mode))
(if text (insert text))
(run-hooks 'doom-scratch-buffer-hook)
(current-buffer))))
;;;###autoload
(defun doom/open-scratch-buffer ()
"Opens a temporary scratch buffer in a popup window. It is discarded once it
is closed. If a region is active, copy it to the scratch buffer."
(interactive)
(doom-popup-buffer (doom--create-scratch-buffer)))
;;;###autoload
(defun doom/open-project-scratch-buffer ()
"Opens a (persistent) scratch buffer associated with the current project in a
popup window. Scratch buffers are stored in `doom-scratch-files-dir'. If a
region is active, copy it to the scratch buffer."
(interactive)
(doom-popup-buffer (doom--create-scratch-buffer t)))

View File

@ -1,19 +1,5 @@
;;; core/autoload/test.el -*- lexical-binding: t; -*-
;;;###autoload
(defmacro def-test! (name &rest body)
"Define a namespaced ERT test."
(declare (indent defun) (doc-string 2))
(unless (plist-get body :disabled)
`(ert-deftest
,(cl-loop with path = (file-relative-name (file-name-sans-extension load-file-name)
doom-emacs-dir)
for (rep . with) in '(("/test/" . "/") ("/" . ":"))
do (setq path (replace-regexp-in-string rep with path t t))
finally return (intern (format "%s::%s" path name))) ()
()
,@body)))
;;;###autoload
(defun doom-run-tests (&optional modules)
"Run all loaded tests, specified by MODULES (a list of module cons cells) or
@ -83,3 +69,78 @@ If neither is available, run all tests in all enabled modules."
(lwarn 'doom-test :error
"%s -> %s"
(car ex) (error-message-string ex)))))
;; --- Test helpers -----------------------
(defmacro def-test! (name &rest body)
"Define a namespaced ERT test."
(declare (indent defun) (doc-string 2))
(unless (plist-get body :disabled)
`(ert-deftest
,(cl-loop with path = (file-relative-name (file-name-sans-extension load-file-name)
doom-emacs-dir)
for (rep . with) in '(("/test/" . "/") ("/" . ":"))
do (setq path (replace-regexp-in-string rep with path t t))
finally return (intern (format "%s::%s" path name))) ()
()
,@body)))
(defmacro should-buffer! (initial expected &rest body)
"Test that a buffer with INITIAL text, run BODY, then test it against EXPECTED.
INITIAL will recognize cursor markers in the form {[0-9]}. A {0} marker marks
where the cursor should be after setup. Otherwise, the cursor will be placed at
`point-min'.
EXPECTED will recognize one (optional) cursor marker: {|}, this is the
'expected' location of the cursor after BODY is finished, and will be tested
against."
(declare (indent 2))
`(with-temp-buffer
(cl-loop for line in ',initial
do (insert line "\n"))
(goto-char (point-min))
(let (marker-list)
(save-excursion
(while (re-search-forward "{\\([0-9]\\)}" nil t)
(push (cons (match-string 1)
(set-marker (make-marker) (match-beginning 0)))
marker-list)
(replace-match "" t t))
(if (not marker-list)
(goto-char (point-min))
(sort marker-list
(lambda (m1 m2) (< (marker-position m1)
(marker-position m2))))
(when (equal (caar marker-list) "0")
(goto-char! 0)))
,@body
(let ((result-text (buffer-substring-no-properties (point-min) (point-max)))
(point (point))
same-point
expected-text)
(with-temp-buffer
(cl-loop for line in ',expected
do (insert line "\n"))
(save-excursion
(goto-char 1)
(when (re-search-forward "{|}" nil t)
(setq same-point (= point (match-beginning 0)))
(replace-match "" t t)))
(setq expected-text (buffer-substring-no-properties (point-min) (point-max)))
(should (equal expected-text result-text))
(should same-point)))))))
(defmacro goto-char! (index)
"Meant to be used with `should-buffer!'. Will move the cursor to one of the
cursor markers. e.g. Go to marker {2} with (goto-char! 2)."
`(goto-char (point! ,index)))
(defmacro point! (index)
"Meant to be used with `should-buffer!'. Returns the position of a cursor
marker. e.g. {2} can be retrieved with (point! 2)."
`(cdr (assoc ,(cond ((numberp index) (number-to-string index))
((symbolp index) (symbol-name index))
((stringp index) index))
marker-list)))

View File

@ -26,10 +26,13 @@
(error "No line number plugin detected"))))
;;;###autoload
(defun doom-resize-window (new-size &optional horizontal)
"Resize a window to NEW-SIZE. If HORIZONTAL, do it width-wise."
(enlarge-window (- new-size (if horizontal (window-width) (window-height)))
horizontal))
(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/window-zoom ()
@ -52,8 +55,8 @@ window changes before then, the undo expires."
(assoc ?_ register-alist))
(ignore (jump-to-register ?_))
(window-configuration-to-register ?_)
(doom-resize-window (truncate (/ (frame-width) 1.2)) t)
(doom-resize-window (truncate (/ (frame-height) 1.2)))
(doom-resize-window nil (truncate (/ (frame-width) 1.2)) t)
(doom-resize-window nil (truncate (/ (frame-height) 1.2)))
t)))
;;;###autoload

View File

@ -155,7 +155,6 @@ with functions that require it (like modeline segments)."
(setq recentf-save-file (concat doom-cache-dir "recentf")
recentf-max-menu-items 0
recentf-max-saved-items 300
recentf-filename-handlers '(abbreviate-file-name)
recentf-exclude
(list "^/tmp/" "^/ssh:" "\\.?ido\\.last$" "\\.revive$" "/TAGS$"
"^/var/folders/.+$"
@ -173,7 +172,6 @@ with functions that require it (like modeline segments)."
;; specify their own formatting rules.
(def-package! editorconfig
:demand t
:mode ("\\.?editorconfig$" . editorconfig-conf-mode)
:init
(def-setting! :editorconfig (action value)
":add or :remove an entry in `editorconfig-indentation-alist'."
@ -201,6 +199,9 @@ with functions that require it (like modeline segments)."
(whitespace-mode +1))))
(add-hook 'editorconfig-custom-hooks #'doom|editorconfig-whitespace-mode-maybe))
(def-package! editorconfig-conf-mode
:mode "\\.?editorconfig$")
;; Auto-close delimiters and blocks as you type
(def-package! smartparens
:demand t
@ -269,11 +270,6 @@ with functions that require it (like modeline segments)."
:commands (describe-buffer describe-command describe-file
describe-keymap describe-option describe-option-of-type))
(def-package! imenu-anywhere
:commands (ido-imenu-anywhere ivy-imenu-anywhere helm-imenu-anywhere))
(def-package! imenu-list :commands imenu-list-minor-mode)
(def-package! pcre2el :commands rxt-quote-pcre)
(def-package! smart-forward

View File

@ -36,6 +36,60 @@
(add-hook 'doom-init-hook #'which-key-mode))
(def-package! hydra
:demand t
:init
;; In case I later need to wrap defhydra in any special functionality.
(defalias 'def-hydra! 'defhydra)
(defalias 'def-hydra-radio! 'defhydradio)
:config
(setq lv-use-seperator t)
(def-hydra! doom@text-zoom (:hint t :color red)
"
Text zoom: _j_:zoom in, _k_:zoom out, _0_:reset
"
("j" text-scale-increase "in")
("k" text-scale-decrease "out")
("0" (text-scale-set 0) "reset"))
(def-hydra! doom@window-nav (:hint nil)
"
Split: _v_ert _s_:horz
Delete: _c_lose _o_nly
Switch Window: _h_:left _j_:down _k_:up _l_:right
Buffers: _p_revious _n_ext _b_:select _f_ind-file
Resize: _H_:splitter left _J_:splitter down _K_:splitter up _L_:splitter right
Move: _a_:up _z_:down _i_menu
"
("z" scroll-up-line)
("a" scroll-down-line)
("i" idomenu)
("h" windmove-left)
("j" windmove-down)
("k" windmove-up)
("l" windmove-right)
("p" doom/previous-buffer)
("n" doom/next-buffer)
("b" switch-to-buffer)
("f" find-file)
("s" split-window-below)
("v" split-window-right)
("c" delete-window)
("o" delete-other-windows)
("H" hydra-move-splitter-left)
("J" hydra-move-splitter-down)
("K" hydra-move-splitter-up)
("L" hydra-move-splitter-right)
("q" nil)))
;;
(defun doom--keybind-register (key desc &optional modes)
"Register a description for KEY with `which-key' in MODES.

View File

@ -29,7 +29,7 @@
;; Helpers
;;
(defun doom--resolve-paths (paths &optional root)
(defun doom--resolve-path-forms (paths &optional root)
(cond ((stringp paths)
`(file-exists-p
(expand-file-name
@ -39,10 +39,10 @@
(or root `(doom-project-root))))))
((listp paths)
(cl-loop for i in paths
collect (doom--resolve-paths i root)))
collect (doom--resolve-path-forms i root)))
(t paths)))
(defun doom--resolve-hooks (hooks)
(defun doom--resolve-hook-forms (hooks)
(cl-loop with quoted-p = (eq (car-safe hooks) 'quote)
for hook in (doom-enlist (doom-unquote hooks))
if (eq (car-safe hook) 'quote)
@ -61,6 +61,81 @@
"Return EXP wrapped in a list, or as-is if already a list."
(if (listp exp) exp (list exp)))
(defun doom-resolve-vim-path (file-name)
"Take a path and resolve any vim-like filename modifiers in it. This adds
support for these special modifiers:
%:P Resolves to `doom-project-root'.
See http://vimdoc.sourceforge.net/htmldoc/cmdline.html#filename-modifiers."
(let* (case-fold-search
(regexp (concat "\\(?:^\\|[^\\\\]\\)"
"\\([#%]\\)"
"\\(\\(?::\\(?:[PphtreS~.]\\|g?s[^:\t\n ]+\\)\\)*\\)"))
(matches
(cl-loop with i = 0
while (and (< i (length file-name))
(string-match regexp file-name i))
do (setq i (1+ (match-beginning 0)))
and collect
(cl-loop for j to (/ (length (match-data)) 2)
collect (match-string j file-name)))))
(dolist (match matches)
(let ((flags (split-string (car (cdr (cdr match))) ":" t))
(path (and buffer-file-name
(pcase (car (cdr match))
("%" (file-relative-name buffer-file-name))
("#" (save-excursion (other-window 1) (file-relative-name buffer-file-name))))))
flag global)
(if (not path)
(setq path "")
(while flags
(setq flag (pop flags))
(when (string-suffix-p "\\" flag)
(setq flag (concat flag (pop flags))))
(when (string-prefix-p "gs" flag)
(setq global t
flag (substring flag 1)))
(setq path
(or (pcase (substring flag 0 1)
("p" (expand-file-name path))
("~" (concat "~/" (file-relative-name path "~")))
("." (file-relative-name path default-directory))
("t" (file-name-nondirectory (directory-file-name path)))
("r" (file-name-sans-extension path))
("e" (file-name-extension path))
("S" (shell-quote-argument path))
("h"
(let ((parent (file-name-directory (expand-file-name path))))
(unless (equal (file-truename path)
(file-truename parent))
(if (file-name-absolute-p path)
(directory-file-name parent)
(file-relative-name parent)))))
("s"
(if (featurep 'evil)
(when-let (args (evil-delimited-arguments (substring flag 1) 2))
(let ((pattern (evil-transform-vim-style-regexp (car args)))
(replace (cadr args)))
(replace-regexp-in-string
(if global pattern (concat "\\(" pattern "\\).*\\'"))
(evil-transform-vim-style-regexp replace) path t t
(unless global 1))))
path))
("P"
(let ((default-directory (file-name-directory (expand-file-name path))))
(abbreviate-file-name (doom-project-root))))
(_ path))
"")))
;; strip trailing slash, if applicable
(when (and (not (string= path "")) (equal (substring path -1) "/"))
(setq path (substring path 0 -1))))
(setq file-name
(replace-regexp-in-string (format "\\(?:^\\|[^\\\\]\\)\\(%s\\)"
(regexp-quote (string-trim-left (car match))))
path file-name t t 1))))
(replace-regexp-in-string regexp "\\1" file-name t)))
;;
;; Library
@ -153,7 +228,7 @@ Body forms can access the hook's arguments through the let-bound variable
(:append (setq append-p t))
(:local (setq local-p t))
(:remove (setq hook-fn 'remove-hook))))
(let ((hooks (doom--resolve-hooks (pop args)))
(let ((hooks (doom--resolve-hook-forms (pop args)))
(funcs
(let ((val (car args)))
(if (memq (car-safe val) '(quote function))
@ -199,11 +274,11 @@ Body forms can access the hook's arguments through the let-bound variable
(not ,mode)
(and buffer-file-name (not (file-remote-p buffer-file-name)))
,(if match `(if buffer-file-name (string-match-p ,match buffer-file-name)) t)
,(if files (doom--resolve-paths files) t)
,(if files (doom--resolve-path-forms files) t)
,(or pred-form t))
(,mode 1)))
,@(if (and modes (listp modes))
(cl-loop for hook in (doom--resolve-hooks modes)
(cl-loop for hook in (doom--resolve-hook-forms modes)
collect `(add-hook ',hook ',hook-name))
`((add-hook 'after-change-major-mode-hook ',hook-name))))))
(match
@ -213,11 +288,10 @@ Body forms can access the hook's arguments through the let-bound variable
;; I'm a fan of concise, hassle-free front-facing configuration. Rather than
;; littering my config with `after!' blocks, these two macros offer a faster and
;; more robust alternative. The motivation: to facilitate concise cross-module
;; configuration.
;;
;; It also benefits from byte-compilation.
;; littering my config with `after!' blocks, and checking if features and
;; modules are loaded before every line of config, I wrote `set!' as a more
;; robust alternative. If a setting doesn't exist at run-time, the `set!' call
;; is ignored. It also benefits from byte-compilation.
(defvar doom-settings nil)
(defmacro def-setting! (keyword arglist &optional docstring &rest forms)

View File

@ -76,7 +76,7 @@ missing) and shouldn't be deleted.")
"A list of packages that should be ignored by `def-package!'.")
(defvar doom-reload-hook nil
"A list of hooks to run when `doom/reload' is called.")
"A list of hooks to run when `doom/reload-load-path' is called.")
(defvar doom--site-load-path load-path
"The load path of built in Emacs libraries.")
@ -90,7 +90,6 @@ missing) and shouldn't be deleted.")
"A backup of `load-path' before it was altered by `doom-initialize'. Used as a
base by `doom!' and for calculating how many packages exist.")
(defvar doom--module nil)
(defvar doom--refresh-p nil)
(setq load-prefer-newer (or noninteractive doom-debug-mode)
@ -136,33 +135,27 @@ base by `doom!' and for calculating how many packages exist.")
(defun doom-initialize (&optional force-p)
"Initialize installed packages (using package.el) and ensure the core packages
are installed. If you byte-compile core/core.el, this function will be avoided
to speed up startup."
are installed.
If you byte-compile core/core.el, this function will be avoided to speed up
startup."
;; Called early during initialization; only use native functions!
(when (or (not doom-package-init-p) force-p)
(unless noninteractive
(message "Doom initialized"))
(setq load-path doom--base-load-path
package-activated-list nil)
;; Ensure core folders exist
(dolist (dir (list doom-local-dir doom-etc-dir doom-cache-dir package-user-dir))
(unless (file-directory-p dir)
(make-directory dir t)))
(package-initialize t)
;; Sure, we could let `package-initialize' fill `load-path', but package
;; activation costs precious milliseconds and does other stuff I don't
;; really care about (like load autoload files). My premature optimization
;; quota isn't filled yet.
;; We could let `package-initialize' fill `load-path', but it costs precious
;; milliseconds and does other stuff I don't need (like load autoload
;; files). My premature optimization quota isn't filled yet.
;;
;; Also, in some edge cases involving package initialization during a
;; non-interactive session, `package-initialize' fails to fill `load-path'.
;; If we want something done right, do it ourselves!
(setq doom--package-load-path (directory-files package-user-dir t "^[^.]" t)
load-path (append load-path doom--package-load-path))
;; Ensure core packages are installed
(dolist (pkg doom-core-packages)
(unless (package-installed-p pkg)
@ -174,11 +167,11 @@ to speed up startup."
(if (package-installed-p pkg)
(message "Installed %s" pkg)
(error "Couldn't install %s" pkg))))
(load "quelpa" nil t)
(load "use-package" nil t)
(setq doom-package-init-p t)))
(setq doom-package-init-p t)
(unless noninteractive
(message "Doom initialized"))))
(defun doom-initialize-autoloads ()
"Ensures that `doom-autoload-file' exists and is loaded. Otherwise run
@ -195,6 +188,7 @@ If FORCE-P is non-nil, do it even if they are.
This aggressively reloads core autoload files."
(doom-initialize force-p)
(let ((noninteractive t)
(load-prefer-newer t)
(load-fn
(lambda (file &optional noerror)
(condition-case-unless-debug ex
@ -219,9 +213,7 @@ This aggressively reloads core autoload files."
(funcall load-fn (expand-file-name "packages.el" doom-core-dir))
(cl-loop for (module . submodule) in (doom--module-pairs)
for path = (doom-module-path module submodule "packages.el")
do
(let ((doom--module (cons module submodule)))
(funcall load-fn path t))))))
do (funcall load-fn path t)))))
(defun doom-initialize-modules (modules)
"Adds MODULES to `doom-modules'. MODULES must be in mplist format.
@ -233,14 +225,10 @@ This aggressively reloads core autoload files."
:rehash-threshold 1.0)))
(let (mode)
(dolist (m modules)
(cond ((keywordp m)
(setq mode m))
((not mode)
(error "No namespace specified on `doom!' for %s" m))
((listp m)
(doom-module-enable mode (car m) (cdr m)))
(t
(doom-module-enable mode m))))))
(cond ((keywordp m) (setq mode m))
((not mode) (error "No namespace specified on `doom!' for %s" m))
((listp m) (doom-module-enable mode (car m) (cdr m)))
(t (doom-module-enable mode m))))))
(defun doom-module-path (module submodule &optional file)
"Get the full path to a module: e.g. :lang emacs-lisp maps to
@ -252,6 +240,12 @@ This aggressively reloads core autoload files."
(expand-file-name (concat module "/" submodule "/" file)
doom-modules-dir))
(defun doom-module-from-path (path)
"Get module cons cell (MODULE . SUBMODULE) for PATH, if possible."
(when (string-match (concat doom-modules-dir "\\([^/]+\\)/\\([^/]+\\)/") path)
(cons (intern (concat ":" (match-string 1 path)))
(intern (match-string 2 path)))))
(defun doom-module-flags (module submodule)
"Returns a list of flags provided for MODULE SUBMODULE."
(and (hash-table-p doom-modules)
@ -288,14 +282,13 @@ added, if the file exists."
if (file-exists-p path)
collect path))
(defun doom--display-benchmark ()
(message "Loaded %s packages in %.03fs"
(message "Doom loaded %s packages across %d modules in %.03fs"
;; Certainly imprecise, especially where custom additions to
;; load-path are concerned, but I don't mind a [small] margin of
;; error in the plugin count in exchange for faster startup.
(- (length load-path) (length doom--base-load-path))
(hash-table-size doom-modules)
(setq doom-init-time (float-time (time-subtract after-init-time before-init-time)))))
@ -326,7 +319,8 @@ MODULES is an malformed plist of modules to load."
(unless (server-running-p)
(server-start)))
(add-hook 'doom-init-hook #'doom--display-benchmark t))))
(add-hook 'doom-init-hook #'doom--display-benchmark t)
(message "Doom modules initialized"))))
(defmacro def-package! (name &rest plist)
"A thin wrapper around `use-package'.
@ -406,8 +400,7 @@ The module is only loaded once. If RELOAD-P is non-nil, load it again."
(unless loaded-p
(doom-module-enable module submodule flags))
`(condition-case-unless-debug ex
(let ((doom--module ',(cons module submodule)))
(load! config ,(doom-module-path module submodule) t))
(load! config ,(doom-module-path module submodule) t)
('error
(lwarn 'doom-modules :error
"%s in '%s %s' -> %s"
@ -418,11 +411,13 @@ The module is only loaded once. If RELOAD-P is non-nil, load it again."
"A convenience macro wrapper for `doom-module-loaded-p'. It is evaluated at
compile-time/macro-expansion time."
(unless submodule
(unless doom--module
(error "featurep! was used incorrectly (doom--module wasn't unset)"))
(setq flag module
module (car doom--module)
submodule (cdr doom--module)))
(let* ((path (or load-file-name byte-compile-current-file))
(module-pair (doom-module-from-path path)))
(unless module-pair
(error "featurep! couldn't detect what module I'm in! (in %s)" path))
(setq flag module
module (car module-pair)
submodule (cdr module-pair))))
(if flag
(and (memq flag (doom-module-flags module submodule)) t)
(doom-module-loaded-p module submodule)))
@ -487,7 +482,7 @@ loads MODULE SUBMODULE's packages.el file."
;; Commands
;;
(defun doom/reload ()
(defun doom/reload-load-path ()
"Reload `load-path' and recompile files (if necessary).
Use this when `load-path' is out of sync with your plugins. This should only
@ -495,12 +490,12 @@ happen if you manually modify/update/install packages from outside Emacs, while
an Emacs session is running.
This isn't necessary if you use Doom's package management commands because they
call `doom/reload' remotely (through emacsclient)."
call `doom/reload-load-path' remotely (through emacsclient)."
(interactive)
(cond (noninteractive
(message "Reloading...")
(require 'server)
(unless (ignore-errors (server-eval-at "server" '(doom/reload)))
(unless (ignore-errors (server-eval-at "server" '(doom/reload-load-path)))
(message "Recompiling")
(doom/recompile)))
(t
@ -626,6 +621,7 @@ If ONLY-RECOMPILE-P is non-nil, only recompile out-of-date files."
total-fail)
(t
(message! (green "Compiled %s" short-name))
(quiet! (load file t t))
total-success))))))
(message!
(bold

View File

@ -18,10 +18,6 @@
"A list of popups that were last closed. Used by `doom/popup-restore' and
`doom*popups-save'.")
(defvar doom-popup-remember-history t
"If non-nil, DOOM will remember the last popup(s) that were open in
`doom-popup-history'.")
(defvar doom-popup-other-window nil
"The last window selected before a popup was opened.")
@ -32,33 +28,53 @@
"A list of open popup windows.")
(defvar-local doom-popup-rules nil
"The shackle rule that caused this buffer to be recognized as a popup.")
"The shackle rule that caused this buffer to be recognized as a popup. Don't
edit this directly.")
(put 'doom-popup-rules 'permanent-local t)
(defvar doom-popup-window-parameters
'(:noesc :modeline :autokill :autoclose)
'(:noesc :modeline :autokill :autoclose :autofit :static)
"A list of window parameters that are set (and cleared) when `doom-popup-mode
is enabled/disabled.'")
(defvar doom-popup-remember-history t
"Don't modify this directly. If non-nil, DOOM will remember the last popup(s)
that was/were open in `doom-popup-history'.")
(defvar doom-popup-inhibit-autokill nil
"Don't modify this directly. When it is non-nil, no buffers will be killed
when their associated popup windows are closed, despite their :autokill
property.")
(def-setting! :popup (&rest rules)
"Prepend a new popup rule to `shackle-rules' (see for format details).
Several custom properties have been added that are not part of shackle, but are
recognized by DOOM's popup system. They are:
:noesc If non-nil, pressing ESC *inside* the popup will close it.
Used by `doom/popup-close-maybe'.
:noesc If non-nil, the popup won't be closed if you press ESC from *inside*
its window. Used by `doom/popup-close-maybe'.
:modeline By default, mode-lines are hidden in popups unless this
is non-nil. If it is a symbol, it'll use `doom-modeline'
to fetch a modeline config (in `doom-popup-mode').
:modeline By default, mode-lines are hidden in popups unless this is non-nil.
If it is a symbol, it'll use `doom-modeline' to fetch a modeline
config (in `doom-popup-mode').
:autokill If non-nil, the popup's buffer will be killed when the
popup is closed. Used by `doom*delete-popup-window'.
NOTE `doom/popup-restore' can't restore non-file popups
that have an :autokill property.
:autokill If non-nil, the popup's buffer will be killed when the popup is
closed. Used by `doom*delete-popup-window'. NOTE
`doom/popup-restore' can't restore non-file popups that have an
:autokill property.
:autoclose If non-nil, close popup if ESC is pressed from outside
the popup window."
:autoclose If non-nil, close popup if ESC is pressed from outside the popup
window.
:autofit If non-nil, resize the popup to fit its content. Uses the value of
the :size property as the maximum height/width. This will not work
if the popup has no content when displayed.
:static If non-nil, don't treat this window like a popup. This makes it
impervious to being automatically closed or tracked in popup
history. Excellent for permanent sidebars."
(if (cl-every #'listp (mapcar #'doom-unquote rules))
`(setq shackle-rules (nconc (list ,@rules) shackle-rules))
`(push (list ,@rules) shackle-rules)))
@ -74,17 +90,19 @@ recognized by DOOM's popup system. They are:
(setq shackle-default-alignment 'below
shackle-default-size 8
shackle-rules
'(("^\\*ftp " :noselect t :autokill t :noesc t)
'(("^\\*eww" :regexp t :size 0.5 :select t :autokill t :noesc t)
("^\\*ftp " :noselect t :autokill t :noesc t)
;; doom
("^\\*doom:" :regexp t :size 0.35 :noesc t :select t :modeline t)
("^\\*doom " :regexp t :noselect t :autokill t :autoclose t)
("^\\*doom:scratch" :regexp t :size 15 :noesc t :select t :modeline t :autokill t :static t)
("^\\*doom:" :regexp t :size 0.35 :noesc t :select t)
("^ ?\\*doom " :regexp t :noselect t :autokill t :autoclose t :autofit t)
;; built-in (emacs)
("*ert*" :same t :modeline t)
("*info*" :size 0.5 :select t :autokill t)
("*Backtrace*" :size 20 :noselect t)
("*Warnings*" :size 8 :noselect t)
("*Warnings*" :size 12 :noselect t :autofit t)
("*Messages*" :size 12 :noselect t)
("*Help*" :size 0.3)
("*Help*" :size 0.4 :autofit t)
("^\\*.*Shell Command.*\\*$" :regexp t :size 20 :noselect t :autokill t)
(apropos-mode :size 0.3 :autokill t :autoclose t)
(Buffer-menu-mode :size 20 :autokill t)
@ -92,171 +110,43 @@ recognized by DOOM's popup system. They are:
(grep-mode :size 25 :noselect t :autokill t)
(profiler-report-mode :size 0.3 :regexp t :autokill t :modeline minimal)
(tabulated-list-mode :noesc t)
(special-mode :noselect t :autokill t :autoclose t)
("^\\*" :regexp t :noselect t :autokill t)
("^ \\*" :regexp t :size 12 :noselect t :autokill t :autoclose t)))
("^ ?\\*" :regexp t :size 0.3 :noselect t :autokill t :autoclose t :autofit t)))
:config
(add-hook 'doom-post-init-hook #'shackle-mode)
(defun doom*shackle-always-align (plist)
"Ensure popups are always aligned and selected by default. Eliminates the need
for :align t on every rule."
(when plist
(unless (or (plist-member plist :align)
(plist-member plist :same)
(plist-member plist :frame))
(plist-put plist :align t))
(unless (or (plist-member plist :select)
(plist-member plist :noselect))
(plist-put plist :select t)))
plist)
(advice-add #'shackle--match :filter-return #'doom*shackle-always-align))
;; no modeline in popups
(add-hook 'doom-popup-mode-hook #'doom|hide-modeline-in-popup)
;; ensure every rule without an :align, :same or :frame property has an
;; implicit :align (see `shackle-default-alignment')
(advice-add #'shackle--match :filter-return #'doom*shackle-always-align)
;; bootstrap popup system
(advice-add #'shackle-display-buffer :around #'doom*popup-init)
(advice-add #'balance-windows :around #'doom*popups-save)
(advice-add #'delete-window :before #'doom*delete-popup-window)
;;
;; Integration
;;
;; Tell `window-state-get' and `current-window-configuration' to recognize
;; these custom parameters. Helpful for `persp-mode' and persisting window
;; configs that have popups in them.
(dolist (param `(popup ,@doom-popup-window-parameters))
(push (cons param 'writable) window-persistent-parameters))
;; Tell `window-state-get' and `current-window-configuration' to recognize these
;; custom parameters. Helpful for `persp-mode' and persisting window configs
;; that have popups in them.
(dolist (param (cons 'popup doom-popup-window-parameters))
(push (cons param 'writable) window-persistent-parameters))
(defvar doom-popup-mode-map
(let ((map (make-sparse-keymap)))
(define-key map [escape] 'doom/popup-close-maybe)
(define-key map (kbd "ESC") 'doom/popup-close-maybe)
(define-key map [remap doom-kill-buffer] 'kill-this-buffer)
(define-key map [remap doom/kill-this-buffer] 'kill-this-buffer)
(define-key map [remap split-window-right] 'ignore)
(define-key map [remap split-window-below] 'ignore)
(define-key map [remap split-window-horizontally] 'ignore)
(define-key map [remap split-window-vertically] 'ignore)
(define-key map [remap mouse-split-window-horizontally] 'ignore)
(define-key map [remap mouse-split-window-vertically] 'ignore)
map)
"Active keymap in popup windows.")
(define-minor-mode doom-popup-mode
"Minor mode for popup windows."
:init-value nil
:keymap doom-popup-mode-map
(let ((window (selected-window)))
;; If `doom-popup-rules' isn't set for some reason, try to set it
(when-let (plist (and (not doom-popup-rules)
(window-parameter window 'popup)))
(setq-local doom-popup-rules (window-parameter window 'popup)))
;; Ensure that buffer-opening functions/commands (like
;; `switch-to-buffer-other-window' won't use this window).
(set-window-parameter window 'no-other-window doom-popup-mode)
;; Makes popup window resist interactively changing its buffer.
(set-window-dedicated-p window doom-popup-mode)
(cond (doom-popup-mode
(when doom-popup-no-fringes
(set-window-fringes window 0 0 fringes-outside-margins))
;; Save metadata into window parameters so it can be saved by window
;; config persisting plugins like workgroups or persp-mode.
(set-window-parameter window 'popup (or doom-popup-rules t))
(when doom-popup-rules
(cl-loop for param in doom-popup-window-parameters
when (plist-get doom-popup-rules param)
do (set-window-parameter window param it))))
(t
(when doom-popup-no-fringes
(set-window-fringes window
doom-fringe-size doom-fringe-size
fringes-outside-margins))
;; Ensure window parameters are cleaned up
(set-window-parameter window 'popup nil)
(dolist (param doom-popup-window-parameters)
(set-window-parameter window param nil))))))
;; Major mode changes (and other things) may call `kill-all-local-variables',
;; turning off things like `doom-popup-mode'. This prevents that.
(put 'doom-popup-mode 'permanent-local t)
(put 'doom-popup-rules 'permanent-local t)
(defun doom|hide-modeline-in-popup ()
"Don't show modeline in popup windows without a :modeline rule. If one exists
and it's a symbol, use `doom-modeline' to grab the format. If non-nil, show the
mode-line as normal. If nil (or omitted, by default), then hide the modeline
entirely."
(if doom-popup-mode
(let ((modeline (plist-get doom-popup-rules :modeline)))
(cond ((or (eq modeline 'nil)
(not modeline))
(doom-hide-modeline-mode +1))
((and (symbolp modeline)
(not (eq modeline 't)))
(setq-local doom--modeline-format (doom-modeline modeline))
(when doom--modeline-format
(doom-hide-modeline-mode +1)))))
(when doom-hide-modeline-mode
(doom-hide-modeline-mode -1))))
(add-hook 'doom-popup-mode-hook #'doom|hide-modeline-in-popup)
;;
(defun doom*popup-init (orig-fn &rest args)
"Initializes a window as a popup window by enabling `doom-popup-mode' in it
and setting `doom-popup-rules' within it. Returns the window."
(unless (doom-popup-p)
(setq doom-popup-other-window (selected-window)))
(let* ((plist (or (nth 2 args)
(cond ((windowp (car args))
(shackle-match (window-buffer (car args))))
((bufferp (car args))
(shackle-match (car args))))))
(buffer (get-buffer (car args)))
(window-min-height (if (plist-get plist :modeline) 4 2))
window)
(when (and (doom-real-buffer-p buffer)
(get-buffer-window-list buffer nil t))
(setq plist (append (list :autokill t) plist))
(setcar args (clone-indirect-buffer (buffer-name (car args)) nil t)))
(unless (setq window (apply orig-fn args))
(error "No popup window was found for %s: %s" (car args) plist))
(cl-pushnew window doom-popup-windows :test #'eq)
(with-selected-window window
(unless (eq plist t)
(setq-local doom-popup-rules plist))
(doom-popup-mode +1))
window))
(defun doom*popups-save (orig-fn &rest args)
"Sets aside all popups before executing the original function, usually to
prevent the popup(s) from messing up the UI (or vice versa)."
(let ((in-popup-p (doom-popup-p))
(popups (doom-popup-windows))
(doom-popup-remember-history t))
(when popups
(mapc #'doom/popup-close popups))
(unwind-protect (apply orig-fn args)
(when popups
(let ((origin (selected-window)))
(doom/popup-restore)
(unless in-popup-p
(select-window origin)))))))
(defun doom*delete-popup-window (&optional window)
"Ensure that popups are deleted properly, and killed if they have :autokill
properties."
(let ((window (or window (selected-window))))
(when (doom-popup-p window)
(setq doom-popup-windows (delq window doom-popup-windows))
(when doom-popup-remember-history
(setq doom-popup-history (list (doom--popup-data window))))
(let ((autokill-p (plist-get doom-popup-rules :autokill)))
(with-selected-window window
(doom-popup-mode -1)
(when autokill-p
(kill-buffer (current-buffer))))))))
(advice-add #'shackle-display-buffer :around #'doom*popup-init)
(advice-add #'balance-windows :around #'doom*popups-save)
(advice-add #'delete-window :before #'doom*delete-popup-window)
(defvar doom-popup-mode-map
(let ((map (make-sparse-keymap)))
(define-key map [escape] #'doom/popup-close-maybe)
(define-key map (kbd "ESC") #'doom/popup-close-maybe)
(define-key map [remap quit-window] #'doom/popup-close-maybe)
(define-key map [remap delete-window] #'doom/popup-close-maybe)
(define-key map [remap doom/kill-this-buffer] #'delete-window)
(define-key map [remap split-window-right] #'ignore)
(define-key map [remap split-window-below] #'ignore)
(define-key map [remap split-window-horizontally] #'ignore)
(define-key map [remap split-window-vertically] #'ignore)
(define-key map [remap mouse-split-window-horizontally] #'ignore)
(define-key map [remap mouse-split-window-vertically] #'ignore)
map)
"Active keymap in popup windows."))
;;
@ -264,20 +154,20 @@ properties."
;;
(progn ; hacks for built-in functions
(defun doom*buffer-menu (&optional arg)
"Open `buffer-menu' in a popup window."
(interactive "P")
(let ((buf (list-buffers-noselect arg)))
(doom-popup-buffer buf)
(with-current-buffer buf
(setq mode-line-format "Commands: d, s, x, u; f, o, 1, 2, m, v; ~, %; q to quit; ? for help."))))
(advice-add #'buffer-menu :override #'doom*buffer-menu)
(defun doom*suppress-pop-to-buffer-same-window (orig-fn &rest args)
(cl-letf (((symbol-function 'pop-to-buffer-same-window)
(symbol-function 'pop-to-buffer)))
(apply orig-fn args)))
(advice-add #'info :around #'doom*suppress-pop-to-buffer-same-window))
(advice-add #'info :around #'doom*suppress-pop-to-buffer-same-window)
(advice-add #'eww :around #'doom*suppress-pop-to-buffer-same-window)
(advice-add #'eww-browse-url :around #'doom*suppress-pop-to-buffer-same-window)
(defun doom*popup-buffer-menu (&optional arg)
"Open `buffer-menu' in a popup window."
(interactive "P")
(with-selected-window (doom-popup-buffer (list-buffers-noselect arg))
(setq mode-line-format "Commands: d, s, x, u; f, o, 1, 2, m, v; ~, %; q to quit; ? for help.")))
(advice-add #'buffer-menu :override #'doom*popup-buffer-menu))
(after! comint
@ -310,12 +200,12 @@ properties."
(after! evil
(let ((map doom-popup-mode-map))
(define-key map [remap evil-window-delete] #'doom/popup-close)
(define-key map [remap evil-save-modified-and-close] #'doom/popup-close)
(define-key map [remap evil-window-move-very-bottom] #'ignore)
(define-key map [remap evil-window-move-very-top] #'ignore)
(define-key map [remap evil-window-move-far-left] #'ignore)
(define-key map [remap evil-window-move-far-right] #'ignore)
(define-key map [remap evil-window-delete] #'doom/popup-close-maybe)
(define-key map [remap evil-save-modified-and-close] #'doom/popup-close-maybe)
(define-key map [remap evil-window-move-very-bottom] #'doom/popup-move-bottom)
(define-key map [remap evil-window-move-very-top] #'doom/popup-move-top)
(define-key map [remap evil-window-move-far-left] #'doom/popup-move-left)
(define-key map [remap evil-window-move-far-right] #'doom/popup-move-right)
(define-key map [remap evil-window-split] #'ignore)
(define-key map [remap evil-window-vsplit] #'ignore))
@ -323,10 +213,9 @@ properties."
"If current window is a popup, close it. If minibuffer is open, close it. If
not in a popup, close all popups with an :autoclose property."
(cond ((doom-popup-p)
(unless (doom-popup-prop :noesc)
(unless (doom-popup-property :noesc)
(delete-window)))
(t
(doom/popup-close-all))))
(t (doom/popup-close-all))))
(add-hook '+evil-esc-hook #'doom|popup-close-maybe t)
;; Make evil-mode cooperate with popups
@ -384,10 +273,10 @@ the command buffer."
(after! helm
;; Helm tries to clean up after itself, but shackle has already done this.
;; This fixes that. To reproduce, add a helm rule in `shackle-rules', open two
;; splits side-by-side, move to the buffer on the right and invoke helm. It
;; will close all but the left-most buffer.
;; Helm tries to clean up after itself, but shackle has already done this,
;; causing problems. This fixes that. To reproduce, add a helm rule in
;; `shackle-rules', open two splits side-by-side, move to the buffer on the
;; right and invoke helm. It will close all but the left-most buffer.
(setq-default helm-reuse-last-window-split-state t
helm-split-window-in-side-p t)
@ -486,7 +375,7 @@ the command buffer."
(after! mu4e
(defun doom*mu4e-popup-window (buf _height)
(doom-popup-buffer buf :size 10 :noselect t)
(doom-popup-buffer buf '(:size 10 :noselect t))
buf)
(advice-add #'mu4e~temp-window :override #'doom*mu4e-popup-window))
@ -502,7 +391,7 @@ the command buffer."
;;
;; By handing neotree over to shackle, which is better integrated into the
;; rest of my config (and persp-mode), this is no longer a problem.
(set! :popup " *NeoTree*" :align 'left :size 25)
(set! :popup " *NeoTree*" :align neo-window-position :size neo-window-width :static t)
(defun +evil-neotree-display-fn (buf _alist)
"Hand neotree off to shackle."
@ -515,7 +404,10 @@ the command buffer."
"Repair neotree state whenever its popup state is restored. This ensures
that `doom*popup-save' won't break it."
(when (equal (buffer-name) neo-buffer-name)
(setq neo-global--window (selected-window))))
(setq neo-global--window (selected-window))
;; Fix neotree shrinking when closing nearby vertical splits
(when neo-window-fixed-size
(doom-resize-window neo-global--window neo-window-width t t))))
(add-hook 'doom-popup-mode-hook #'+evil|neotree-fix-popup))
@ -523,7 +415,7 @@ that `doom*popup-save' won't break it."
(defun doom*persp-mode-restore-popups (&rest _)
"Restore popup windows when loading a perspective from file."
(dolist (window (window-list))
(when-let (plist (window-parameter window 'popup))
(when-let (plist (doom-popup-properties window))
(with-selected-window window
(unless doom-popup-mode
(setq-local doom-popup-rules plist)
@ -582,16 +474,16 @@ you came from."
'("*Org Links*" :size 5 :noselect t)
'("*Org Export Dispatcher*" :noselect t)
'(" *Agenda Commands*" :noselect t)
'("^\\*Org Agenda" :regexp t :size 30)
'("^\\*Org Agenda" :regexp t :size 20)
'("*Org Clock*" :noselect t)
'("^\\*Org Src" :regexp t :size 0.35 :noesc t)
'("*Edit Formulas*" :size 10)
'("^\\*Org-Babel" :regexp t :size 25 :noselect t)
'("^CAPTURE.*\\.org$" :regexp t :size 20))
;; Org has its own window management system with a scorched earth philosophy
;; I'm not fond of. i.e. it kills all windows and monopolizes the frame. No
;; thanks. We can do better with shackle's help.
;; Org has a scorched-earth window management system I'm not fond of. i.e.
;; it kills all windows and monopolizes the frame. No thanks. We can do
;; better with shackle's help.
(defun doom*suppress-delete-other-windows (orig-fn &rest args)
(cl-letf (((symbol-function 'delete-other-windows)
(symbol-function 'ignore)))
@ -600,17 +492,17 @@ you came from."
(advice-add #'org-capture-place-template :around #'doom*suppress-delete-other-windows)
(advice-add #'org-export--dispatch-ui :around #'doom*suppress-delete-other-windows)
;; `org-edit-src-code' simply clones and narrows the buffer to a src block,
;; so we are secretly manipulating the same buffer. Since truely killing it
;; would kill the original org buffer we've got to do things differently.
(defun doom*org-src-switch-to-buffer (buffer _context)
;; Hand off the src-block window to a shackle popup window.
(defun doom*org-src-pop-to-buffer (buffer _context)
"Open the src-edit in a way that shackle can detect."
(if (eq org-src-window-setup 'switch-invisibly)
(set-buffer buffer)
(pop-to-buffer buffer)))
(advice-add #'org-src-switch-to-buffer :override #'doom*org-src-switch-to-buffer)
(advice-add #'org-src-switch-to-buffer :override #'doom*org-src-pop-to-buffer)
;; Ensure todo, agenda, and other minor popups handed off to shackle.
;; Ensure todo, agenda, and other minor popups are delegated to shackle.
(defun doom*org-pop-to-buffer (&rest args)
"Use `pop-to-buffer' instead of `switch-to-buffer' to open buffer.'"
(let ((buf (car args)))
(pop-to-buffer
(cond ((stringp buf) (get-buffer-create buf))
@ -624,15 +516,13 @@ you came from."
;; Hide modeline in org-agenda
(add-hook 'org-agenda-finalize-hook #'doom-hide-modeline-mode)
(add-hook 'org-agenda-finalize-hook #'org-fit-window-to-buffer)
;; Don't monopolize frame!
(advice-add #'org-agenda :around #'doom*suppress-delete-other-windows)
;; ensure quit keybindings work propertly
(map! :map org-agenda-mode-map
:m [escape] 'org-agenda-Quit
:m "ESC" 'org-agenda-Quit)
(let ((map org-agenda-mode-map))
(define-key map "q" 'org-agenda-Quit)
(define-key map "Q" 'org-agenda-Quit)))))
:m "ESC" 'org-agenda-Quit))))
(add-hook 'doom-init-hook #'doom|init-org-popups)
(provide 'core-popups)

View File

@ -10,43 +10,35 @@ state are passed in.")
:config
(setq projectile-cache-file (concat doom-cache-dir "projectile.cache")
projectile-enable-caching (not noninteractive)
projectile-file-exists-remote-cache-expire nil
projectile-indexing-method 'alien
projectile-known-projects-file (concat doom-cache-dir "projectile.projects")
projectile-require-project-root nil
projectile-project-root-files
'(".git" ".hg" ".svn" ".project" "package.json" "setup.py" "Gemfile"
"build.gradle")
projectile-other-file-alist
(append '(("less" "css")
("styl" "css")
("sass" "css")
("scss" "css")
("css" "scss" "sass" "less" "styl")
("jade" "html")
("pug" "html")
("html" "jade" "pug" "jsx" "tsx"))
projectile-other-file-alist)
projectile-globally-ignored-file-suffixes '(".elc" ".pyc" ".o")
projectile-globally-ignored-files '(".DS_Store" "Icon
")
projectile-globally-ignored-directories
(append (list doom-local-dir ".sync")
projectile-globally-ignored-files '(".DS_Store" "Icon
" "TAGS")
projectile-globally-ignored-file-suffixes '(".elc" ".pyc" ".o"))
;; a more generic project root file
(push ".project" projectile-project-root-files-bottom-up)
(nconc projectile-globally-ignored-directories (list doom-local-dir ".sync"))
(nconc projectile-other-file-alist '(("css" . ("scss" "sass" "less" "style"))
("scss" . ("css"))
("sass" . ("css"))
("less" . ("css"))
("styl" . ("css"))))
;; Projectile root-searching functions can cause an infinite loop on TRAMP
;; connections, so disable them.
(defun doom*projectile-locate-dominating-file (orig-fn &rest args)
(defun doom*projectile-locate-dominating-file (orig-fn &rest args)
"Don't traverse the file system if on a remote connection."
(unless (file-remote-p default-directory)
(apply orig-fn args)))
(advice-add #'projectile-locate-dominating-file :around #'doom*projectile-locate-dominating-file)
(defun doom*projectile-cache-current-file (orig-fun &rest args)
"Don't cache ignored files."
(unless (cl-some (lambda (path)
(string-prefix-p buffer-file-name
(expand-file-name path)))
"Don't cache ignored files."
(unless (cl-loop for path in (projectile-ignored-directories)
if (string-prefix-p buffer-file-name (expand-file-name path))
return t)
(apply orig-fun args)))
(advice-add #'projectile-cache-current-file :around #'doom*projectile-cache-current-file))
@ -55,26 +47,34 @@ state are passed in.")
;;
;; Library
;;
(defun doom/reload-project ()
"Reload the project root cache."
(interactive)
(projectile-invalidate-cache nil)
(projectile-reset-cached-project-root)
(dolist (fn projectile-project-root-files-functions)
(remhash (format "%s-%s" fn default-directory) projectile-project-root-cache)))
(defun doom-project-p ()
"Whether or not this buffer is currently in a project or not."
"Whether or not this buffer is currently in a project or not."
(let ((projectile-require-project-root t))
(projectile-project-p)))
(defun doom-project-root (&optional strict-p)
"Get the path to the root of your project."
(let ((projectile-require-project-root strict-p))
(defun doom-project-root ()
"Get the path to the root of your project.
If STRICT-P, return nil if no project was found, otherwise return
`default-directory'."
(let (projectile-require-project-root)
(projectile-project-root)))
(defun doom*project-root (&rest _)
"An advice function used to replace project-root-detection functions in other
libraries."
(defalias 'doom-project-expand #'projectile-expand-root)
(defmacro doom-project-has! (files)
"Checks if the project has the specified FILES, relative to the project root,
unless the path begins with ./ or ../, in which case it's relative to
`default-directory'. Recognizes (and ...) and/or (or ...) forms."
(defmacro doom-project-has! (files)
"Checks if the project has the specified FILES.
Paths are relative to the project root, unless they start with ./ or ../ (in
which case they're relative to `default-directory'). If they start with a slash,
they are absolute."
(doom--resolve-path-forms files (doom-project-root)))
@ -82,21 +82,22 @@ unless the path begins with ./ or ../, in which case it's relative to
;; Projects
;;
(defvar-local doom-project nil
(defvar-local doom-project nil
"A list of project mode to enable. Used for .dir-locals.el.")
(defun doom|autoload-project-mode ()
"Auto-enable projects listed in `doom-project', which is meant to be set from
.dir-locals.el files."
(dolist (mode doom-project)
.dir-locals.el files."
(cl-loop for mode in doom-project
unless (symbol-value mode)
do (funcall mode)))
(add-hook 'after-change-major-mode-hook #'doom|autoload-project-mode)
(defmacro def-project-mode! (name &rest plist)
"Define a project minor-mode named NAME and declare where and how it is
activated. Project modes allow you to configure 'sub-modes' for major-modes that
are specific to a specific folder, certain project structure, framework or
arbitrary context you define. These project modes can have their own settings,
(defmacro def-project-mode! (name &rest plist)
"Define a project minor-mode named NAME (a symbol) and declare where and how
it is activated. Project modes allow you to configure 'sub-modes' for
major-modes that are specific to a specific folder, certain project structure,
framework or arbitrary context you define. These project modes can have their
own settings, keymaps, hooks, snippets, etc.
This creates NAME-hook and NAME-map as well.
@ -118,34 +119,52 @@ should be activated. If they are *all* true, NAME is activated.
: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))
Relevant: `doom-project-hook'."
(declare (indent 1) (doc-string 2))
(let ((doc-string (if (stringp (car plist))
(prog1 (car plist)
(setq plist (cdr plist)))
"A project minor mode."))
(modes (plist-get plist :modes))
(files (plist-get plist :files))
(when (plist-get plist :when))
(match (plist-get plist :match))
(init-form (plist-get plist :init))
(match (plist-get plist :match))
(hooks (plist-get plist :add-hooks))
(load-form (plist-get plist :on-load))
(enter-form (plist-get plist :on-enter))
(exit-form (plist-get plist :on-exit))
(init-var (intern (format "%s-init" name))))
`(progn
(defvar ,keymap-sym (make-sparse-keymap)
`(progn
,(if load-form `(defvar ,init-var nil))
(define-minor-mode ,name
(define-minor-mode ,name
,doc-string
:init-value nil
:init-value nil
:lighter ""
:keymap (make-sparse-keymap)
(if (not ,name)
,exit-form
(run-hook-with-args 'doom-project-hook ',name)
,(when load-form
`(unless ,init-var
,load-form
(setq ,init-var t)))
,enter-form))
,(when hooks
`(setq ,(intern (format "%s-hook" name)) ',hooks))
,(when (or modes match files when)
`(associate! ,name
:modes ,modes
:match ,match
:files ,files
:when ,when))
(add-hook! ,name
(run-hook-with-args 'doom-project-hook ',name))
,(when init-form
`(add-transient-hook! ',(intern (format "%s-hook" name))
:files ,files
:when ,when)))))
(provide 'core-projects)

View File

@ -34,22 +34,32 @@ shorter major mode name in the mode-line. See `doom|set-mode-name'.")
;; Settings
(def-setting! :theme (theme)
"Sets the current THEME (a symbol)."
`(unless doom-theme
(setq doom-theme ,theme)))
(def-setting! :font (family &rest spec)
"Sets the default font (if one wasn't already set). FAMILY is the name of the
font, and SPEC is a `font-spec'."
`(unless doom-font
(setq doom-font (font-spec :family ,family ,@spec))))
(def-setting! :variable-pitch-font (family &rest spec)
"Sets the default font for the variable-pitch face and minor mode (if one
wasn't already set). FAMILY is the name of the font, and SPEC is a `font-spec'."
`(unless doom-variable-pitch-font
(setq doom-variable-pitch-font (font-spec :family ,family ,@spec))))
(def-setting! :big-font (family &rest spec)
"Sets the font to use for `doom-big-font-mode' (if one wasn't already set).
FAMILY is the name of the font, and SPEC is a `font-spec'."
`(unless doom-big-font
(setq doom-big-font (font-spec :family ,family ,@spec))))
(def-setting! :unicode-font (family &rest spec)
"Sets the font to use for unicode characters (if one wasn't already set).
FAMILY is the name of the font, and SPEC is a `font-spec'. This is ignored if
the ':ui unicode' module is enabled."
`(unless doom-unicode-font
(setq doom-unicode-font (font-spec :family ,family ,@spec))))
@ -169,12 +179,9 @@ local value, whether or not it's permanent-local. Therefore, we cycle
"Set the major mode's `mode-name', as dictated by `doom-major-mode-names'."
(when-let (name (cdr (assq major-mode doom-major-mode-names)))
(setq mode-name
(cond ((functionp name)
(funcall name))
((stringp name)
name)
(t
(error "'%s' isn't a valid name for %s" name major-mode))))))
(cond ((functionp name) (funcall name))
((stringp name) name)
(t (error "'%s' isn't a valid name for %s" name major-mode))))))
(add-hook 'after-change-major-mode-hook #'doom|set-mode-name)
@ -227,7 +234,7 @@ local value, whether or not it's permanent-local. Therefore, we cycle
;; prompts the user for confirmation when deleting a non-empty frame
(define-key global-map [remap delete-frame] #'doom/delete-frame)
;; buffer name in frame title
;; simple name in frame title
(setq-default frame-title-format '("DOOM Emacs"))
;; auto-enabled in Emacs 25+; I'll do it myself
(global-eldoc-mode -1)
@ -273,6 +280,7 @@ local value, whether or not it's permanent-local. Therefore, we cycle
:commands (fringe-helper-define fringe-helper-convert)
:init
(unless (fboundp 'define-fringe-bitmap)
;; doesn't exist in terminal Emacs; define it to prevent errors
(defun define-fringe-bitmap (&rest _))))
(def-package! hideshow ; built-in
@ -310,13 +318,6 @@ local value, whether or not it's permanent-local. Therefore, we cycle
:config (setq rainbow-delimiters-max-face-count 3)
:init (add-hook 'lisp-mode-hook #'rainbow-delimiters-mode))
;; indicators for empty lines past EOF
(def-package! vi-tilde-fringe
:commands (global-vi-tilde-fringe-mode vi-tilde-fringe-mode)
:init
(add-hook 'doom-init-ui-hook #'global-vi-tilde-fringe-mode)
(defun doom|disable-vi-tilde-fringe () (vi-tilde-fringe-mode -1)))
;; For a distractions-free-like UI, that dynamically resizes margets and can
;; center a buffer.
(def-package! visual-fill-column

View File

@ -8,14 +8,16 @@
;; doom:... an evil operator, motion or command
;; doom|... hook function
;; doom*... advising functions
;; doom@... a hydra command
;; ...! a macro or function that configures DOOM
;; =... an interactive command that starts an app module
;; %... functions used for in-snippet logic
;; +... Any of the above but part of a module, e.g. `+emacs-lisp|init-hook'
;;
;; Autoloaded functions are in core/autoload/*.el and modules/*/*/autoload.el or
;; modules/*/*/autoload/*.el.
(defvar doom-version "2.0.5"
(defvar doom-version "2.0.6"
"Current version of DOOM emacs.")
(defvar doom-debug-mode (or (getenv "DEBUG") init-file-debug)
@ -61,8 +63,7 @@ problems.")
(defvar doom-packages-dir (concat doom-local-dir "packages/")
"Where package.el and quelpa plugins (and their caches) are stored.")
(defvar doom-autoload-file
(concat doom-local-dir "autoloads.el")
(defvar doom-autoload-file (concat doom-local-dir "autoloads.el")
"Location of the autoloads file generated by `doom/reload-autoloads'.")
(defgroup doom nil

View File

@ -22,7 +22,6 @@
(package! nlinum-hl)
(package! nlinum-relative))
(package! rainbow-delimiters)
(package! vi-tilde-fringe)
(package! visual-fill-column)
;; core-popups.el
@ -36,8 +35,6 @@
(package! editorconfig)
(package! expand-region)
(package! help-fns+)
(package! imenu-anywhere)
(package! imenu-list)
(package! pcre2el)
(package! smart-forward)
(package! smartparens)
@ -49,3 +46,4 @@
;; core-keybinds.el
(package! which-key)
(package! hydra)

View File

@ -3,18 +3,18 @@
;; --- Helpers ----------------------------
;; `doom--resolve-paths'
(def-test! resolve-paths
;; `doom--resolve-path-forms'
(def-test! resolve-path-forms
(should
(equal (doom--resolve-paths '(and "fileA" "fileB"))
(equal (doom--resolve-path-forms '(and "fileA" "fileB"))
'(and (file-exists-p (expand-file-name "fileA" (doom-project-root)))
(file-exists-p (expand-file-name "fileB" (doom-project-root)))))))
;; `doom--resolve-hooks'
(def-test! resolve-hooks
(should (equal (doom--resolve-hooks '(js2-mode haskell-mode))
;; `doom--resolve-hook-forms'
(def-test! resolve-hook-forms
(should (equal (doom--resolve-hook-forms '(js2-mode haskell-mode))
'(js2-mode-hook haskell-mode-hook)))
(should (equal (doom--resolve-hooks '(quote (js2-mode-hook haskell-mode-hook)))
(should (equal (doom--resolve-hook-forms '(quote (js2-mode-hook haskell-mode-hook)))
'(js2-mode-hook haskell-mode-hook))))
;; `doom-unquote'
@ -32,6 +32,48 @@
(should (equal (doom-enlist 'a) '(a)))
(should (equal (doom-enlist '(a)) '(a))))
;; `doom-resolve-vim-path'
(def-test! resolve-vim-path
(cl-flet ((do-it #'doom-resolve-vim-path))
;; file modifiers
(let ((buffer-file-name "~/.emacs.d/test/modules/feature/test-evil.el")
(default-directory "~/.emacs.d/test/modules/"))
(should (equal (do-it "%") "feature/test-evil.el"))
(should (equal (do-it "%:r") "feature/test-evil"))
(should (equal (do-it "%:r.elc") "feature/test-evil.elc"))
(should (equal (do-it "%:e") "el"))
(should (equal (do-it "%:p") (expand-file-name buffer-file-name)))
(should (equal (do-it "%:h") "feature"))
(should (equal (do-it "%:t") "test-evil.el"))
(should (equal (do-it "%:.") "feature/test-evil.el"))
(should (equal (do-it "%:~") "~/.emacs.d/test/modules/feature/test-evil.el"))
(should (equal (file-truename (do-it "%:p"))
(file-truename buffer-file-name))))
;; nested file modifiers
(let ((buffer-file-name "~/vim/src/version.c")
(default-directory "~/vim/"))
(should (equal (do-it "%:p") (expand-file-name "~/vim/src/version.c")))
(should (equal (do-it "%:p:.") "src/version.c"))
(should (equal (do-it "%:p:~") "~/vim/src/version.c"))
(should (equal (do-it "%:h") "src"))
(should (equal (do-it "%:p:h") (expand-file-name "~/vim/src")))
(should (equal (do-it "%:p:h:h") (expand-file-name "~/vim")))
(should (equal (do-it "%:t") "version.c"))
(should (equal (do-it "%:p:t") "version.c"))
(should (equal (do-it "%:r") "src/version"))
(should (equal (do-it "%:p:r") (expand-file-name "~/vim/src/version")))
(should (equal (do-it "%:t:r") "version")))
;; empty file modifiers
(let (buffer-file-name default-directory)
(should (equal (do-it "%") ""))
(should (equal (do-it "%:r") ""))
(should (equal (do-it "%:e") ""))
(should (equal (do-it "%:h") ""))
(should (equal (do-it "%:t") ""))
(should (equal (do-it "%:.") ""))
(should (equal (do-it "%:~") ""))
(should (equal (do-it "%:P") "")))))
;; --- Macros -----------------------------

View File

@ -30,107 +30,107 @@
(require 'core (concat user-emacs-directory "core/core"))
(doom! :feature
evil ; come to the dark side, we have cookies
jump ; helping you get around
snippets ; my elves. They type so I don't have to
file-templates ; auto-snippets for empty files
hydra ; keybindings that stick around
spellcheck ; tasing you for misspelling mispelling
syntax-checker ; tasing you for every semicolon you forget
version-control ; remember, remember that commit in November
workspaces ; tab emulation, persistence & separate workspaces
eval ; repls, runners 'n builders; run code, run
;debug ; FIXME stepping through code, to help you add bugs
;debugger ; FIXME stepping through code, to help you add bugs
eval ; run code, run (also, repls)
evil ; come to the dark side, we have cookies
file-templates ; auto-snippets for empty files
jump ; helping you get around
services ; TODO managing external services & code builders
snippets ; my elves. They type so I don't have to
spellcheck ; tasing you for misspelling mispelling
syntax-checker ; tasing you for every semicolon you forget
version-control ; remember, remember that commit in November
workspaces ; tab emulation, persistence & separate workspaces
:completion
company ; the ultimate code completion backend
ivy ; a search engine for love and life
;helm ; the *other* search engine for love and life
;ido ; the other *other* search engine...
company ; the ultimate code completion backend
ivy ; a search engine for love and life
;helm ; the *other* search engine for love and life
;ido ; the other *other* search engine...
:ui
doom ; what makes DOOM look the way it does
doom-dashboard ; a nifty splash screen for Emacs
doom-modeline ; a snazzy Atom-inspired mode-line
doom-quit ; DOOM quit-message prompts when you quit Emacs
hl-todo ; highlight TODO/FIXME/NOTE tags
nav-flash ; blink the current line after jumping
evil-goggles ; display visual hints when editing in evil
;unicode ; extended unicode support for various languages
;tabbar ; FIXME an (incomplete) tab bar for Emacs
doom ; what makes DOOM look the way it does
doom-dashboard ; a nifty splash screen for Emacs
doom-modeline ; a snazzy Atom-inspired mode-line
doom-quit ; DOOM quit-message prompts when you quit Emacs
hl-todo ; highlight TODO/FIXME/NOTE tags
nav-flash ; blink the current line after jumping
evil-goggles ; display visual hints when editing in evil
;unicode ; extended unicode support for various languages
;tabbar ; FIXME an (incomplete) tab bar for Emacs
vi-tilde-fringe ; fringe tildes to mark beyond EOB
:tools
dired ; making dired pretty [functional]
electric-indent ; smarter, keyword-based electric-indent
eshell ; a consistent, cross-platform shell (WIP)
gist ; interacting with github gists
impatient-mode ; show off code over HTTP
;macos ; MacOS-specific commands
neotree ; a project drawer, like NERDTree for vim
password-store ; password manager for nerds
prodigy ; manage external services from within emacs
rotate-text ; cycle region at point between text candidates
term ; terminals in Emacs
tmux ; an API for interacting with tmux
upload ; map local to remote projects via ssh/ftp
dired ; making dired pretty [functional]
electric-indent ; smarter, keyword-based electric-indent
eshell ; a consistent, cross-platform shell (WIP)
gist ; interacting with github gists
imenu ; an imenu sidebar and searchable code index
impatient-mode ; show off code over HTTP
;macos ; MacOS-specific commands
make ; run make tasks from Emacs
neotree ; a project drawer, like NERDTree for vim
password-store ; password manager for nerds
rotate-text ; cycle region at point between text candidates
term ; terminals in Emacs
tmux ; an API for interacting with tmux
upload ; map local to remote projects via ssh/ftp
:lang
;assembly ; assembly for fun or debugging
;cc ; C/C++/Obj-C madness
;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
;go ; the hipster dialect
;haskell ; a language that's lazier than I am
;hy ; readability of scheme w/ speed of python
;java ; the poster child for carpal tunnel syndrome
;javascript ; all(hope(abandon(ye(who(enter(here))))))
;julia ; a better, faster MATLAB
;latex ; writing papers in Emacs has never been so fun
;ledger ; an accounting system in Emacs
;lua ; one-based indices? one-based indices
;markdown ; writing docs for people to ignore
;ocaml ; an objective camel
;perl ; write code no one else can comprehend
;php ; make php less awful to work with
;plantuml ; diagrams for confusing people more
;purescript ; javascript, but functional
;python ; beautiful is better than ugly
;rest ; Emacs as a REST client
;ruby ; 1.step do {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
;rust ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
;scala ; java, but good
;sh ; she sells (ba|z)sh shells on the C xor
;swift ; who asked for emoji variables?
;typescript ; javascript, but better
;web ; the tubes
assembly ; assembly for fun or debugging
cc ; C/C++/Obj-C madness
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
go ; the hipster dialect
(haskell +intero) ; a language that's lazier than I am
hy ; readability of scheme w/ speed of python
(java +meghanada) ; the poster child for carpal tunnel syndrome
javascript ; all(hope(abandon(ye(who(enter(here))))))
julia ; a better, faster MATLAB
latex ; writing papers in Emacs has never been so fun
ledger ; an accounting system in Emacs
lua ; one-based indices? one-based indices
markdown ; writing docs for people to ignore
ocaml ; an objective camel
perl ; write code no one else can comprehend
php ; make php less awful to work with
plantuml ; diagrams for confusing people more
purescript ; javascript, but functional
python ; beautiful is better than ugly
rest ; Emacs as a REST client
ruby ; 1.step do {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
rust ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
scala ; java, but good
sh ; she sells (ba|z)sh shells on the C xor
swift ; who asked for emoji variables?
typescript ; javascript, but better
web ; the tubes
:org
org ; organize your plain life in plain text
org-babel ; executable code snippets in org-mode
org-attach ; a simpler attachment system
org-capture ; a better org-capture, in or outside of Emacs
org-export ; a custom, centralized export system
org-notebook ; org-mode as a notebook
org-present ; using org-mode for presentations
;org-sync ; TODO sync with mobile
;org-publish ; TODO org + blogs
org ; organize your plain life in plain text
org-babel ; executable code snippets in org-mode
org-attach ; a simpler attachment system
org-capture ; a better org-capture, in or outside of Emacs
org-export ; a custom, centralized export system
org-present ; using org-mode for presentations
;org-sync ; TODO sync with mobile
;org-publish ; TODO org + blogs
;; Applications are complex and opinionated modules that transform Emacs
;; toward a specific purpose. They may have additional dependencies and
;; should be loaded last.
:app
email ; emacs as an email client
irc ; how neckbeards socialize
rss ; emacs as an RSS reader
twitter ; twitter client https://twitter.com/vnought
write ; emacs as a word processor (latex + org + markdown)
email ; emacs as an email client
irc ; how neckbeards socialize
rss ; emacs as an RSS reader
twitter ; twitter client https://twitter.com/vnought
write ; emacs as a word processor (latex + org + markdown)
;; Private modules named after your username are loaded automatically.
;; Leaving this here is harmless though. Also, they are omitted from
;; source control (except for mine; use it as a reference).
;; Leaving this here is harmless though.
:private hlissner)

View File

@ -15,5 +15,8 @@
:lang
web
:org
org
:private
hlissner)

View File

@ -26,9 +26,11 @@
(all-files-p +ivy--file-search-all-files-p)
(query
(or query
(and beg end
(> (abs (- end beg)) 1)
(rxt-quote-pcre (buffer-substring-no-properties beg end)))
(if (evil-visual-state-p)
(and beg end
(> (abs (- end beg)) 1)
(rxt-quote-pcre (buffer-substring-no-properties beg end)))
+ivy--file-last-search)
+ivy--file-last-search))
(prompt
(format "%s%%s %s"

View File

@ -10,9 +10,8 @@
face to render it with.")
(defmacro +ivy-do-action! (action)
"A factory function that returns an interactive lamba that sets the current
ivy action and immediately runs it on the current candidate (ending the ivy
session)."
"Returns an interactive lambda that sets the current ivy action and
immediately runs it on the current candidate (ending the ivy session)."
`(lambda ()
(interactive)
(ivy-set-action ,action)
@ -63,10 +62,10 @@ session)."
[remap describe-face] #'counsel-describe-face)
;; Show more buffer information in switch-buffer commands
(ivy-set-display-transformer 'ivy-switch-buffer #'+ivy-buffer-transformer)
(ivy-set-display-transformer 'ivy-switch-buffer-other-window #'+ivy-buffer-transformer)
(ivy-set-display-transformer '+ivy/switch-workspace-buffer #'+ivy-buffer-transformer)
(ivy-set-display-transformer 'counsel-recentf #'abbreviate-file-name)
(ivy-set-display-transformer #'ivy-switch-buffer #'+ivy-buffer-transformer)
(ivy-set-display-transformer #'ivy-switch-buffer-other-window #'+ivy-buffer-transformer)
(ivy-set-display-transformer #'+ivy/switch-workspace-buffer #'+ivy-buffer-transformer)
(ivy-set-display-transformer #'counsel-recentf #'abbreviate-file-name)
(when (featurep! :feature workspaces)
(nconc ivy-sort-functions-alist
@ -111,3 +110,51 @@ session)."
(setq smex-save-file (concat doom-cache-dir "/smex-items"))
(smex-initialize))
(def-package! ivy-hydra
:commands (+ivy@coo/body ivy-dispatching-done-hydra)
:init
(map! :map ivy-minibuffer-map
"C-o" #'+ivy@coo/body
"M-o" #'ivy-dispatching-done-hydra)
:config
(def-hydra! +ivy@coo (: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
("j" ivy-next-line)
("k" ivy-previous-line)
("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)
("C-g" keyboard-escape-quit :exit t)
("<escape>" keyboard-escape-quit :exit t)
("C-o" nil)
("i" nil)
("TAB" ivy-alt-done :exit nil)
("C-j" ivy-alt-done :exit nil)
;; ("d" ivy-done :exit t)
("RET" ivy-done :exit t)
("C-m" ivy-done :exit t)
("f" ivy-call)
("c" ivy-toggle-calling)
("m" ivy-toggle-fuzzy)
(">" ivy-minibuffer-grow)
("<" ivy-minibuffer-shrink)
("w" ivy-prev-action)
("s" ivy-next-action)
("a" ivy-read-action)
("t" (setq truncate-lines (not truncate-lines)))
("C" ivy-toggle-case-fold)
("o" ivy-occur :exit t)))

View File

@ -6,3 +6,4 @@
(package! counsel-projectile)
(package! smex)
(package! swiper)
(package! ivy-hydra)

View File

@ -1,9 +0,0 @@
;;; feature/debug/autoload/debug.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +debug/quit ()
(interactive)
(ignore-errors (call-interactively 'realgud:cmd-quit))
(doom/popup-close)
(evil-normal-state))

View File

@ -1,31 +0,0 @@
;;; feature/debug/autoload/evil.el -*- lexical-binding: t; -*-
;;;###autoload (autoload '+debug:run "feature/debug/autoload/evil" nil t)
(evil-define-command +debug:run (&optional path)
"Initiate debugger for current major mode"
(interactive "<f>")
(let ((default-directory (doom-project-root)))
(cond ((memq major-mode '(c-mode c++-mode))
(realgud:gdb (if path (concat "gdb " path))))
((memq major-mode '(ruby-mode enh-ruby-mode))
(doom:repl nil (format "run '%s'" (file-name-nondirectory (or path buffer-file-name)))))
((eq major-mode 'sh-mode)
(let ((shell sh-shell))
(when (string= shell "sh")
(setq shell "bash"))
(cond ((string= shell "bash")
(realgud:bashdb (if path (concat "bashdb " path))))
((string= shell "zsh")
(realgud:zshdb (if path (concat "zshdb " path))))
(t (user-error "No shell debugger for %s" shell)))))
;; TODO Add python debugging
((memq major-mode '(js-mode js2-mode js3-mode))
(realgud:trepanjs))
((eq major-mode 'haskell-mode)
(haskell-debug))
(t (user-error "No debugger for %s" major-mode)))))
;;;###autoload (autoload '+debug:toggle-breakpoint "feature/debug/autoload/evil" nil t)
(evil-define-command +debug:toggle-breakpoint (&optional bang)
(interactive "<!>")
(call-interactively (if bang 'realgud:cmd-clear 'realgud:cmd-break)))

View File

@ -0,0 +1,11 @@
;;; feature/debugger/autoload/debug.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +debugger/quit ()
"Quit the active debugger, if any."
(interactive)
(ignore-errors (call-interactively #'realgud:cmd-quit))
(doom/popup-close)
(when (featurep 'evil)
(evil-normal-state)))

View File

@ -0,0 +1,33 @@
;;; feature/debugger/autoload/evil.el -*- lexical-binding: t; -*-
;;;###autoload (autoload '+debugger:start "feature/debugger/autoload/evil" nil t)
(evil-define-command +debugger:start (&optional path)
"Initiate debugger for current major mode"
(interactive "<f>")
;; TODO Add python debugging
(let ((default-directory (doom-project-root)))
(pcase major-mode
((or 'c-mode 'c++-mode)
(realgud:gdb (if path (concat "gdb " path))))
((or 'ruby-mode 'enh-ruby-mode)
;; FIXME
(doom:repl nil (format "run '%s'" (file-name-nondirectory (or path buffer-file-name)))))
('sh-mode
(let ((shell sh-shell))
(when (string= shell "sh")
(setq shell "bash"))
(pcase shell
("bash"
(realgud:bashdb (if path (concat "bashdb " path))))
("zsh"
(realgud:zshdb (if path (concat "zshdb " path))))
(_ (user-error "No shell debugger for %s" shell)))))
((or 'js-mode 'js2-mode 'js3-mode)
(realgud:trepanjs))
('haskell-mode (haskell-debug))
(_ (user-error "No debugger for %s" major-mode)))))
;;;###autoload (autoload '+debugger:toggle-breakpoint "feature/debugger/autoload/evil" nil t)
(evil-define-command +debugger:toggle-breakpoint (&optional bang)
(interactive "<!>")
(call-interactively (if bang #'realgud:cmd-clear #'realgud:cmd-break)))

View File

@ -1,4 +1,4 @@
;;; feature/debug/config.el -*- lexical-binding: t; -*-
;;; feature/debugger/config.el -*- lexical-binding: t; -*-
(def-package! realgud
:commands (realgud:gdb realgud:trepanjs realgud:bashdb realgud:zshdb)
@ -19,7 +19,7 @@
;; Monkey-patch `realgud:run-process' to run in a popup.
;; TODO Find a more elegant solution
;; FIXME Causes realgud:cmd-* to focus popup on every invocation
(defun +debug*realgud-run-process
(defun +debugger*realgud-run-process
(debugger-name script-filename cmd-args minibuffer-history-var &optional no-reset)
(let* ((cmd-buf (apply #'realgud-exec-shell debugger-name script-filename
(car cmd-args) no-reset (cdr cmd-args)))
@ -42,5 +42,5 @@
(if cmd-buf (switch-to-buffer cmd-buf))
(message "Error running command: %s" (mapconcat #'identity cmd-args " "))))
cmd-buf))
(advice-add #'realgud:run-process :override #'+debug*realgud-run-process))
(advice-add #'realgud:run-process :override #'+debugger*realgud-run-process))

View File

@ -1,4 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; feature/debug/packages.el
;;; feature/debugger/packages.el
(package! realgud)

View File

@ -1,13 +1,12 @@
#+TITLE: :feature eval
This modules adds support for REPLs, build tasks and code evaluation.
This modules adds support for evaluating code from inside Emacs. This includes REPLs and direct access to the interpreters and compilers of many languages.
* Table of Contents :TOC:
- [[#install][Install]]
- [[#usage][Usage]]
- [[#configuration][Configuration]]
- [[#repls][REPLs]]
- [[#build-tasks][Build Tasks]]
- [[#code-evaluation][Code Evaluation]]
* Install
@ -20,15 +19,8 @@ Check the README.org in that language's module for details.
Invoked via:
+ ~:repl~ (evil ex-command)
+ =<leader> o r= in normal mode (or visual mode, which sends the selection to the open REPL)
+ ~M-x +eval/repl~
+ ~M-x +eval/repl-send-region~ while a selection (and REPL) is active
+ *Build Tasks*
You will be prompted to select a task. Only the ones that meet the predicate will be listed.
+ ~:build~ (evil ex-command)
+ =M-b= (by default)
+ =<leader> o b= in normal mode
+ ~M-x +eval/build~
+ ~M-x +eval/open-repl~
+ ~M-x +eval/send-region-to-repl~ while a selection (and REPL) is active
+ *Code Evaluation*
Quickrun can be invoked via:
@ -60,35 +52,6 @@ FUNCTION must return the repl buffer. Any window changes are ignored, then hande
(set! :repl 'emacs-lisp-mode #'+emacs-lisp/repl)
#+END_SRC
** Build Tasks
A build task is little more than a major-mode-local interactive command that performs a task, such as compiling the current project or running unit tests. A predicate function can be supplied to ensure a command is only available when it is appropriate.
#+BEGIN_SRC emacs-lisp
(defun +lua/run-love ()
"Run the current project in love 10.0."
(async-shell-command
(format "/usr/bin/love %s"
(shell-quote-argument (doom-project-root)))))
(defun +lua/build ()
"Run a build script in the project root."
(let ((default-directory (doom-project-root)))
(compile "luajit build.lua")))
(defun +lua/generate-docs ()
"Generate project documentation."
(let ((default-directory (doom-project-root)))
(compile "luadoc *.lua")))
(defun +lua-love-p ()
"Returns non-nil if the current project is a love project."
(doom-project-has! (and "main.lua" "config.lua")))
(set! :build 'run 'lua-mode #'+lua/run-love :when (+lua-love-p))
(set! :build 'build-project 'lua-mode #'+lua/build :when (+lua-love-p))
(set! :build 'generate-docs 'lua-mode #'+lua/generate-docs)
#+END_SRC
** Code Evaluation
Run regions or entire buffers with [[https://github.com/syohex/emacs-quickrun][Quickrun]]. Output will be sent to a popup window.

View File

@ -1,45 +0,0 @@
;;; feature/eval/autoload/build.el -*- lexical-binding: t; -*-
(defvar-local +eval-last-builder nil
"The last builder run in the current buffer.")
(defvar +eval-current-builder nil
"The spec for the currently running builder. Available from inside builder
functions.")
(defun +eval--read-builder ()
(when-let (builders
(cl-remove-if-not
(lambda (plist)
(if-let (pred (plist-get plist :when))
(and (or (symbolp pred)
(functionp pred))
(funcall pred))
t))
(cl-delete-duplicates
(reverse (cdr (assq major-mode +eval-builders)))
:key 'car)
:key 'cdr))
(if (= (length builders) 1)
(car builders)
(when-let (builder (completing-read "Build: " (mapcar #'car builders) nil t))
(assq (intern builder) builders)))))
;;;###autoload
(defun +eval/build (builder)
"TODO"
(interactive
(list (or +eval-last-builder
(+eval--read-builder)
(error "No builder for this buffer"))))
(unless builder
(error "Builder not found in registered builders"))
(let ((name (car builder))
(fn (plist-get (cdr builder) :fn)))
(message "Running %s" name)
(if (or (functionp fn)
(and (symbolp fn) (fboundp fn)))
(let ((+eval-current-builder builder))
(funcall fn))
(error "'%s' builder is invalid" name))))

View File

@ -4,22 +4,22 @@
(defun +eval/buffer ()
"Evaluate the whole buffer."
(interactive)
(cond ((assq major-mode +eval-runners-alist)
(cond ((assq major-mode +eval-runners)
(+eval/region (point-min) (point-max)))
(t (quickrun))))
;;;###autoload
(defun +eval/region (beg end)
"Evaluate a region and, if large enough, prints its output to a popup buffer (if an
elisp buffer). Otherwise forward the region to Quickrun."
"Evaluate a region between BEG and END and display the output."
(interactive "r")
(let ((load-file-name buffer-file-name))
(if-let (runner (cdr (assq major-mode +eval-runners-alist)))
(if-let (runner (cdr (assq major-mode +eval-runners)))
(funcall runner beg end)
(quickrun-region beg end))))
;;;###autoload
(defun +eval/region-and-replace (beg end)
"Evaluation a region between BEG and END, and replace it with the result."
(interactive "r")
(cond ((eq major-mode 'emacs-lisp-mode)
(kill-region beg end)

View File

@ -18,5 +18,5 @@
:move-point nil
(interactive "<r><!>")
(if (evil-normal-state-p)
(+eval/repl)
(+eval/repl-send-region beg end bang)))
(+eval/open-repl)
(+eval/send-region-to-repl beg end bang)))

View File

@ -25,7 +25,7 @@
t))))
;;;###autoload
(defun +eval/repl ()
(defun +eval/open-repl ()
"Opens (or reopens) the REPL associated with the current major-mode and place
the cursor at the prompt."
(interactive)
@ -36,7 +36,7 @@ the cursor at the prompt."
t)))
;;;###autoload
(defun +eval/repl-send-region (beg end &optional auto-execute-p)
(defun +eval/send-region-to-repl (beg end &optional auto-execute-p)
"REPL must be open! Sends a selected region to it. If AUTO-EXECUTE-P, then
execute it immediately after."
(interactive "r")

View File

@ -1,39 +1,15 @@
;;; feature/eval/config.el -*- lexical-binding: t; -*-
;;
;; Code building
;;
(defvar +eval-builders nil
"A nested alist, mapping major modes to build function plists. Used by
`+eval/build' and filled with the `:build' setting.")
(def-setting! :build (name modes fn &rest plist)
"Define a build function (FN) for MODES (can be major or minor) called NAME.
PLIST accepts the following properties:
:when FORM A predicate to determine if the builder is appropriate for this
buffer."
`(dolist (mode ',(doom-enlist (doom-unquote modes)) +eval-builders)
(unless (assq mode +eval-builders)
(push (list mode) +eval-builders))
(cl-pushnew (cons ,name (append (list :fn ,fn) (list ,@plist)))
(cdr (assq mode +eval-builders))
:test #'eq :key #'car)))
;;
;; REPLs
;;
(defvar +eval-repls nil
"An alist mapping major modes to plists that describe REPLs. Used by
`+eval/repl' and filled with the `:repl' setting.")
`+eval/open-repl' and filled with the `:repl' setting.")
(define-minor-mode +eval-repl-mode
"A minor mode for REPL buffers."
:init-value nil)
"A minor mode for REPL buffers.")
(def-setting! :repl (mode command)
"Define a REPL for a mode. MODE is a major mode symbol and COMMAND is a
@ -53,7 +29,7 @@ function that creates and returns the REPL buffer."
(setq eval-expression-print-length nil
eval-expression-print-level nil)
(defvar +eval-runners-alist nil
(defvar +eval-runners nil
"Alist mapping major modes to interactive runner functions.")
(def-setting! :eval (mode command)
@ -67,10 +43,10 @@ function that creates and returns the REPL buffer."
3. If MODE is not a string and COMMAND is an alist, see `quickrun-add-command':
(quickrun-add-command MODE COMMAND :mode MODE).
4. If MODE is not a string and COMMANd is a symbol, add it to
`+eval-runners-alist', which is used by `+eval/region'."
`+eval-runners', which is used by `+eval/region'."
(let ((command (doom-unquote command)))
(cond ((symbolp command)
`(push (cons ,mode ',command) +eval-runners-alist))
`(push (cons ,mode ',command) +eval-runners))
((stringp command)
`(after! quickrun
(push (cons ,mode ',command)
@ -91,12 +67,10 @@ function that creates and returns the REPL buffer."
quickrun-compile-only
quickrun-replace-region)
:init
(add-hook 'quickrun--mode-hook #'nlinum-mode)
(unless (boundp 'display-line-numbers)
(add-hook 'quickrun--mode-hook #'nlinum-mode))
:config
(set! :popup
'("*quickrun*" :size 10 :noesc t :autokill t :autoclose t)
'("*eval*" :size 12 :noselect t :autokill t :autoclose t)
'("*Pp Eval Output*" :size 12 :noselect t :autokill t :autoclose t))
(set! :popup "*quickrun*" :size 6 :autokill t :autoclose t)
(defun +eval*quickrun-auto-close (&rest _)
"Allows us to silently re-run quickrun from within the quickrun buffer."
@ -111,6 +85,7 @@ function that creates and returns the REPL buffer."
(defun +eval|quickrun-scroll-to-bof ()
"Ensures window is scrolled to BOF on invocation."
(with-selected-window (get-buffer-window quickrun--buffer-name)
(goto-char (point-min))))
(goto-char (point-min))
(doom-popup-fit-to-buffer)))
(add-hook 'quickrun-after-run-hook #'+eval|quickrun-scroll-to-bof))

View File

@ -26,81 +26,13 @@
(save-excursion (goto-char beg) (point-marker))
end)))
;;;###autoload
(defun +evil*ex-replace-special-filenames (file-name)
"Replace special symbols in FILE-NAME. Modified to include other substitution
flags. See http://vimdoc.sourceforge.net/htmldoc/cmdline.html#filename-modifiers."
(let* (case-fold-search
(regexp (concat "\\(?:^\\|[^\\\\]\\)"
"\\([#%]\\)"
"\\(\\(?::\\(?:[PphtreS~.]\\|g?s[^:\t\n ]+\\)\\)*\\)"))
(matches
(cl-loop with i = 0
while (and (< i (length file-name))
(string-match regexp file-name i))
do (setq i (1+ (match-beginning 0)))
and collect
(cl-loop for j to (/ (length (match-data)) 2)
collect (match-string j file-name)))))
(dolist (match matches)
(let ((flags (split-string (car (cdr (cdr match))) ":" t))
(path (and buffer-file-name
(pcase (car (cdr match))
("%" (file-relative-name buffer-file-name))
("#" (save-excursion (other-window 1) (file-relative-name buffer-file-name))))))
flag global)
(if (not path)
(setq path "")
(while flags
(setq flag (pop flags))
(when (string-suffix-p "\\" flag)
(setq flag (concat flag (pop flags))))
(when (string-prefix-p "gs" flag)
(setq global t
flag (substring flag 1)))
(setq path
(or (pcase (substring flag 0 1)
("p" (expand-file-name path))
("~" (concat "~/" (file-relative-name path "~")))
("." (file-relative-name path default-directory))
("t" (file-name-nondirectory (directory-file-name path)))
("r" (file-name-sans-extension path))
("e" (file-name-extension path))
("S" (shell-quote-argument path))
("h"
(let ((parent (file-name-directory (expand-file-name path))))
(unless (equal (file-truename path)
(file-truename parent))
(if (file-name-absolute-p path)
(directory-file-name parent)
(file-relative-name parent)))))
("s"
(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)))))
("P"
(let ((default-directory (file-name-directory (expand-file-name path))))
(abbreviate-file-name (doom-project-root))))
(_ path))
"")))
;; strip trailing slash, if applicable
(when (and (not (string= path "")) (equal (substring path -1) "/"))
(setq path (substring path 0 -1))))
(setq file-name
(replace-regexp-in-string (format "\\(?:^\\|[^\\\\]\\)\\(%s\\)"
(regexp-quote (string-trim-left (car match))))
path file-name t t 1))))
(setq file-name (replace-regexp-in-string regexp "\\1" file-name t))))
(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 (doom-popup-p)
(doom/popup-raise))
(let* ((this-window (get-buffer-window))
(this-buffer (current-buffer))
(that-window (windmove-find-other-window direction nil this-window))

View File

@ -92,7 +92,7 @@
;; --- evil hacks -------------------------
(defvar +evil-esc-hook '(t)
"A hook run after ESC is pressed in normal mode (invoked by
`evil-force-normal-state'). If a hook returns non-nil, all hooks after it are
`evil-force-normal-state'). If any hook returns non-nil, all hooks after it are
ignored.")
(defun +evil*attach-escape-hook ()
@ -124,8 +124,7 @@ across windows."
;; monkey patch `evil-ex-replace-special-filenames' to add more ex
;; substitution flags to evil-mode
(advice-add #'evil-ex-replace-special-filenames
:override #'+evil*ex-replace-special-filenames)
(advice-add #'evil-ex-replace-special-filenames :override #'doom-resolve-vim-path)
;; These arg types will highlight matches in the current buffer
(evil-ex-define-argument-type buffer-match :runner +evil-ex-buffer-match)
@ -144,6 +143,8 @@ across windows."
(evil-define-interactive-code "<//g>"
:ex-arg global-match (list (when (evil-ex-p) evil-ex-argument)))
;; Forward declare these so that ex completion works, even if the autoloaded
;; functions aren't loaded yet.
(evil-set-command-properties
'+evil:align :move-point t :ex-arg 'buffer-match :ex-bang t :evil-mc t :keep-visual t :suppress-operator t)
(evil-set-command-properties
@ -282,7 +283,7 @@ the new algorithm is confusing, like in python or ruby."
:commands (evil-mc-make-cursor-here evil-mc-make-all-cursors
evil-mc-undo-all-cursors evil-mc-pause-cursors
evil-mc-resume-cursors evil-mc-make-and-goto-first-cursor
evil-mc-make-and-goto-last-cursor evil-mc-make-cursor-here
evil-mc-make-and-goto-last-cursor
evil-mc-make-cursor-move-next-line
evil-mc-make-cursor-move-prev-line evil-mc-make-cursor-at-pos
evil-mc-has-cursors-p evil-mc-make-and-goto-next-cursor
@ -382,3 +383,64 @@ the new algorithm is confusing, like in python or ruby."
(def-package! evil-textobj-anyblock
:commands (evil-textobj-anyblock-inner-block evil-textobj-anyblock-a-block))
;;
;; Multiple cursors compatibility (for the plugins that use it)
;;
;; mc doesn't play well with evil, this attempts to assuage some of its problems
;; so that certain plugins (which I have no control over) can still use it in
;; relative safety.
(after! multiple-cursors-core
(map! :map mc/keymap :ne "<escape>" #'mc/keyboard-quit)
(defvar +evil--mc-compat-evil-prev-state nil)
(defvar +evil--mc-compat-mark-was-active nil)
(defsubst +evil--visual-or-normal-p ()
"True if evil mode is enabled, and we are in normal or visual mode."
(and (bound-and-true-p evil-mode)
(not (memq evil-state '(insert emacs)))))
(defun +evil|mc-compat-switch-to-emacs-state ()
(when (+evil--visual-or-normal-p)
(setq +evil--mc-compat-evil-prev-state evil-state)
(when (region-active-p)
(setq +evil--mc-compat-mark-was-active t))
(let ((mark-before (mark))
(point-before (point)))
(evil-emacs-state 1)
(when (or +evil--mc-compat-mark-was-active (region-active-p))
(goto-char point-before)
(set-mark mark-before)))))
(defun +evil|mc-compat-back-to-previous-state ()
(when +evil--mc-compat-evil-prev-state
(unwind-protect
(case +evil--mc-compat-evil-prev-state
((normal visual) (evil-force-normal-state))
(t (message "Don't know how to handle previous state: %S"
+evil--mc-compat-evil-prev-state)))
(setq +evil--mc-compat-evil-prev-state nil)
(setq +evil--mc-compat-mark-was-active nil))))
(add-hook 'multiple-cursors-mode-enabled-hook '+evil|mc-compat-switch-to-emacs-state)
(add-hook 'multiple-cursors-mode-disabled-hook '+evil|mc-compat-back-to-previous-state)
(defun +evil|mc-evil-compat-rect-switch-state ()
(if rectangular-region-mode
(+evil|mc-compat-switch-to-emacs-state)
(setq +evil--mc-compat-evil-prev-state nil)))
;; When running edit-lines, point will return (position + 1) as a
;; result of how evil deals with regions
(defadvice mc/edit-lines (before change-point-by-1 activate)
(when (+evil--visual-or-normal-p)
(if (> (point) (mark))
(goto-char (1- (point)))
(push-mark (1- (mark))))))
(add-hook 'rectangular-region-mode-hook '+evil|mc-evil-compat-rect-switch-state)
(defvar mc--default-cmds-to-run-once nil))

View File

@ -3,52 +3,19 @@
(require! :feature evil)
;; `+evil*ex-replace-special-filenames'
;; `evil-ex-replace-special-filenames'
;; NOTE The majority of this function is tested in core/test/core-lib.el, this
;; only tests the evil-mode-specific functionality.
(def-test! file-modifiers
(cl-flet ((do-it #'+evil*ex-replace-special-filenames))
(cl-flet ((do-it #'evil-ex-replace-special-filenames))
(let ((buffer-file-name "~/.emacs.d/test/modules/feature/test-evil.el")
(default-directory "~/.emacs.d/test/modules/"))
(should (equal (do-it "%") "feature/test-evil.el"))
(should (equal (do-it "%:r") "feature/test-evil"))
(should (equal (do-it "%:r.elc") "feature/test-evil.elc"))
(should (equal (do-it "%:e") "el"))
(should (equal (do-it "%:p") (expand-file-name buffer-file-name)))
(should (equal (do-it "%:h") "feature"))
(should (equal (do-it "%:t") "test-evil.el"))
(should (equal (do-it "%:.") "feature/test-evil.el"))
(should (equal (do-it "%:~") "~/.emacs.d/test/modules/feature/test-evil.el"))
(should (equal (do-it "%:s?e?x?") "fxature/test-evil.el"))
(should (equal (do-it "%:gs?e?x?") "fxaturx/txst-xvil.xl"))
(should (equal (file-truename (do-it "%:p"))
(file-truename buffer-file-name))))))
(def-test! nested-file-modifiers
(cl-flet ((do-it #'+evil*ex-replace-special-filenames))
(let ((buffer-file-name "~/vim/src/version.c")
(default-directory "~/vim/"))
(should (equal (do-it "%:p") (expand-file-name "~/vim/src/version.c")))
(should (equal (do-it "%:p:.") "src/version.c"))
(should (equal (do-it "%:p:~") "~/vim/src/version.c"))
(should (equal (do-it "%:h") "src"))
(should (equal (do-it "%:p:h") (expand-file-name "~/vim/src")))
(should (equal (do-it "%:p:h:h") (expand-file-name "~/vim")))
(should (equal (do-it "%:t") "version.c"))
(should (equal (do-it "%:p:t") "version.c"))
(should (equal (do-it "%:r") "src/version"))
(should (equal (do-it "%:p:r") (expand-file-name "~/vim/src/version")))
(should (equal (do-it "%:t:r") "version")))))
(should (equal (do-it "%:gs?e?x?") "fxaturx/txst-xvil.xl")))))
(def-test! empty-file-modifiers
(cl-flet ((do-it #'+evil*ex-replace-special-filenames))
(cl-flet ((do-it #'evil-ex-replace-special-filenames))
(let (buffer-file-name default-directory)
(should (equal (do-it "%") ""))
(should (equal (do-it "%:r") ""))
(should (equal (do-it "%:e") ""))
(should (equal (do-it "%:h") ""))
(should (equal (do-it "%:t") ""))
(should (equal (do-it "%:.") ""))
(should (equal (do-it "%:~") ""))
(should (equal (do-it "%:s?e?x?") ""))
(should (equal (do-it "%:gs?e?x?") ""))
(should (equal (do-it "%:P") "")))))
(should (equal (do-it "%:gs?e?x?") "")))))

View File

@ -4,7 +4,7 @@
(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.")
(def-package! autoinsert ; built-in
:defer 1
@ -12,12 +12,12 @@
(setq auto-insert-query nil ; Don't prompt before insertion
auto-insert-alist nil) ; Tabula rasa
(after! yasnippet
(push '+file-templates-dir yas-snippet-dirs))
:config
(auto-insert-mode 1)
(after! yasnippet
(push '+file-templates-dir yas-snippet-dirs))
(defun +file-templates--expand (key &optional mode project-only)
"Auto insert a snippet of yasnippet into new file."
(when (if project-only (doom-project-p) t)
@ -55,6 +55,7 @@
("-test\\.el$" "__" emacs-ert-mode)
("/.emacs.d/.+\\.el$" "__doom-module" emacs-lisp-mode)
("/.emacs.d/.+/packages\\.el$" "__doom-packages" emacs-lisp-mode)
("/.emacs.d/.+/test\\.el$" "__doom-test" emacs-lisp-mode)
("/.emacs.d/.+/README\\.org$" "__doom-readme" org-mode)
(snippet-mode "__" snippet-mode)
;; Go
@ -72,8 +73,6 @@
("/bower\\.json$" "__bower.json" json-mode)
("/gulpfile\\.js$" "__gulpfile.js" js-mode)
("/webpack\\.config\\.js$" "__webpack.config.js" js-mode)
("\\.lbaction/.+/Info.plist$" "__Info.plst" lb6-mode)
("\\.lbaction/.+/\\(default\\|suggestions\\)\\.js$" "__default.js" lb6-mode)
;; Lua
("/main\\.lua$" "__main.lua" love-mode)
("/conf\\.lua$" "__conf.lua" love-mode)
@ -107,5 +106,6 @@
;; Slim
("/\\(index\\|main\\)\\.slim$" "__" slim-mode)
;; Shell scripts
("\\.z?sh$" "__" sh-mode))))
("\\.z?sh$" "__" sh-mode)
("\\.zunit$" "__zunit" sh-mode))))

View File

@ -0,0 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; `(file-relative-name buffer-file-name doom-modules-dir)`
$0

View File

@ -1,99 +0,0 @@
# -*- mode: snippet -*-
# name: Info.plist File Template
# condition: (eq major-mode 'nxml-mode)
# --
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>io.henrik.launchbar.${1:$(replace-regexp-in-string "[^a-zA-Z0-9]" "" yas-text)}</string>
<key>CFBundleName</key>
<string>${1:Action Name}</string>
<!-- <key>CFBundleShortVersionString</key> -->
<!-- <value>1.0</value> -->
<key>CFBundleVersion</key>
<string>${2:1.0.0}</string>
<key>CFBundleIconFile</key>
<string>icon</string>
<key>LBDebugLogEnabled</key>
<false />
<!-- LB stuff -->
<!-- <key>LSMinimumSystemVersion</key> -->
<!-- <string>10.9.1</string> -->
<!-- Per-action keys -->
<!-- <key>LBTextInputTitle</key> -->
<!-- <string>Rate</string> -->
<key>LBScripts</key>
<dict>
<key>LBDefaultScript</key>
<dict>
<key>LBScriptName</key>
<string>default.js</string>
<key>LBRunInBackground</key>
<false />
<key>LBRequiresArgument</key>
<true />
<key>LBAcceptedArgumentTypes</key>
<array>
<string>string</string>
</array>
<key>LBReturnsResult</key>
<true />
<key>LBReturnType</key>
<string>string</string>
</dict>
</dict>
<key>LBDescription</key>
<dict>
<key>LBAuthor</key>
<string>Henrik Lissner</string>
<key>LBWebsite</key>
<string>http://v0.io/launchbar</string>
<key>LBEmail</key>
<string>contact@henrik.io</string>
<key>LBTwitter</key>
<string>@vnought</string>
<key>LBSummary</key>
<string>${3:A short summary of this action}</string>
<key>LBArgument</key>
<string>${4:Describe the input(s) for this action}</string>${5:
<key>LBResult</key>
<string>${6:Describe the result type}</string>
}${7:<key>LBRequirements</key>
<string>${8:The requirements for this action to work}</string>}
<!-- <key>LBChangelog</key> -->
<!-- <string></string> -->
<key>LBUpdateURL</key>
<string>https://github.com/hlissner/lb6-${2:actions}/blob/master/$1.lbaction/Contents/Info.plist</string>
<key>LBDownloadURL</key>
<string>https://dl.v0.io/launchbar/$1.lbaction</string>
</dict>
</dict>
</plist>

View File

@ -1,40 +0,0 @@
# -*- mode: snippet -*-
# name: default.js file template
# condition: (memq major-mode '(js-mode js2-mode))
# --
/*global Lib*/
${1:include("shared/${2:lib.js}");
}// This function is called when the user runs the action without any argument,
// e.g. by selecting it and hitting enter, space or the right arrow key
// (assuming the action does not specify that it requires an argument in its
// Info.plist). run is also called as a fallback if the more specific runWith...
// function for a given argument type (see below) is not implemented.
function run() {
$0
}
// This function is called after text input or when the user sends text to the
// action using Send To.
function runWithString(string) {
}
// This function is called when a URL item is sent to the action. This is also
// the case when a Action URL is called.
function runWithURL(url, details) {
}
// This function is called when the user sends a result item of a previous
// action run to the action using Send To.
function runWithItem(item) {
}
// This function is called after showing a file open dialog or when the user
// sends one or more files or folders to the action using Send To.
function runWithPaths(paths) {
}

View File

@ -0,0 +1,5 @@
#!/usr/bin/env zunit
@test '...' {
$0
}

View File

@ -1,2 +0,0 @@
;;; feature/hydra/autoload/evil.el -*- lexical-binding: t; -*-

View File

@ -1,2 +0,0 @@
;;; feature/hydra/autoload/hydras.el -*- lexical-binding: t; -*-

View File

@ -1,97 +0,0 @@
;;; feature/hydra/config.el -*- lexical-binding: t; -*-
(def-package! hydra
:commands (+hydra-zoom/body +hydra-window/body defhydra)
:config
(setq lv-use-separator t)
(defhydra +hydra-zoom (:hint t :color red)
"zoom"
("j" text-scale-increase "in")
("k" text-scale-decrease "out")
("0" (text-scale-set 0) "reset"))
(defhydra +hydra-window (:hint nil)
"
Split: _v_ert _s_:horz
Delete: _c_lose _o_nly
Switch Window: _h_:left _j_:down _k_:up _l_:right
Buffers: _p_revious _n_ext _b_:select _f_ind-file
Resize: _H_:splitter left _J_:splitter down _K_:splitter up _L_:splitter right
Move: _a_:up _z_:down _i_menu"
("z" scroll-up-line)
("a" scroll-down-line)
("i" idomenu)
("h" windmove-left)
("j" windmove-down)
("k" windmove-up)
("l" windmove-right)
("p" previous-buffer)
("n" next-buffer)
("b" ido-switch-buffer)
("f" ido-find-file)
("s" split-window-below)
("v" split-window-right)
("c" delete-window)
("o" delete-other-windows)
("H" hydra-move-splitter-left)
("J" hydra-move-splitter-down)
("K" hydra-move-splitter-up)
("L" hydra-move-splitter-right)
("q" nil)))
(def-package! ivy-hydra
:when (featurep! :completion ivy)
:after hydra
:config
(define-key ivy-mode-map (kbd "C-o")
(defhydra coo-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
("j" ivy-next-line)
("k" ivy-previous-line)
("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)
("C-g" keyboard-escape-quit :exit t)
("<escape>" keyboard-escape-quit :exit t)
("C-o" nil)
("i" nil)
("TAB" ivy-alt-done :exit nil)
("C-j" ivy-alt-done :exit nil)
;; ("d" ivy-done :exit t)
("RET" ivy-done :exit t)
("C-m" ivy-done :exit t)
("f" ivy-call)
("c" ivy-toggle-calling)
("m" ivy-toggle-fuzzy)
(">" ivy-minibuffer-grow)
("<" ivy-minibuffer-shrink)
("w" ivy-prev-action)
("s" ivy-next-action)
("a" ivy-read-action)
("t" (setq truncate-lines (not truncate-lines)))
("C" ivy-toggle-case-fold)
("o" ivy-occur :exit t))))

View File

@ -1,7 +0,0 @@
;; -*- no-byte-compile: t; -*-
;;; feature/hydra/packages.el
(package! hydra)
(when (featurep! :completion ivy)
(package! ivy-hydra))

View File

@ -130,5 +130,5 @@ for the provider."
(when (string-empty-p search)
(user-error "The search query is empty"))
(setq +jump--online-last provider)
(browse-url (format url (url-encode-url search))))
(funcall +jump-search-browser-fn (format url (url-encode-url search))))
('error (setq +jump--online-last nil))))

View File

@ -15,18 +15,30 @@
;; `dumb-jump' to find what you want.
(defvar +jump-search-provider-alist
'(("Google" . "https://google.com/search?q=%s")
("DuckDuckGo" . "https://duckduckgo.com/?q=%s")
("DevDocs.io" . "http://devdocs.io/#q=%s")
("StackOverflow" . "https://stackoverflow.com/search?q=%s"))
'(("Google" . "https://google.com/search?q=%s")
("Google images" . "https://google.com/images?q=%s")
("Google maps" . "https://maps.google.com/maps?q=%s")
("Project Gutenberg" . "http://www.gutenberg.org/ebooks/search/?query=%s")
("DuckDuckGo" . "https://duckduckgo.com/?q=%s")
("DevDocs.io" . "http://devdocs.io/#q=%s")
("StackOverflow" . "https://stackoverflow.com/search?q=%s")
("Github" . "https://github.com/search?ref=simplesearch&q=%s")
("Youtube" . "https://youtube.com/results?aq=f&oq=&search_query=%s")
("Wolfram alpha" . "https://wolframalpha.com/input/?i=%s")
("Wikipedia" . "https://wikipedia.org/search-redirect.php?language=en&go=Go&search=%s"))
"An alist that maps online resources to their search url or a function that
produces an url. Used by `+jump/online'.")
(defconst +jump-search-browser-fn #'browse-url
"Function to use to open search urls.")
(defvar +jump-function-alist nil
"TODO")
"An alist mapping major modes to jump function plists, describing what to do
with `+jump/definition', `+jump/references' and `+jump/documentation' are
called.")
(defvar-local +jump-current-functions nil
"TODO")
"The enabled jump functions for the current buffer.")
(def-setting! :jump (modes &rest plist)
"Definies a jump target for major MODES. PLIST accepts the following

View File

@ -0,0 +1,34 @@
;;; feature/services/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +services/create ()
"Interactively create a new prodigy service."
(interactive)
;; TODO
)
;;;###autoload
(defun +services/prodigy-delete (arg)
"Delete service at point. Asks for confirmation."
(interactive "P")
(prodigy-with-refresh
(-when-let (service (prodigy-service-at-pos))
(let ((name (plist-get service :name)))
(cond ((or arg
(y-or-n-p (format "Delete '%s' service?" name)))
(setq prodigy-services (delete service prodigy-services))
(ignore-errors
(prodigy-goto-next-line))
(message "Successfully deleted service: %s" name))
(t
(message "Aborted")))))))
;;;###autoload
(defun +services/cleanup ()
"Delete all services associated with projects that don't exist."
(interactive)
(cl-loop for service in prodigy-services
if (and (plist-member service :project)
(file-directory-p (plist-get service :project)))
collect service into services
finally do (setq prodigy-service services)))

View File

@ -0,0 +1,50 @@
;;; feature/services/config.el -*- lexical-binding: t; -*-
(def-setting! :service (&rest plist)
"TODO"
`(after! prodigy
(prodigy-define-service ,@plist)))
;;
;; Plugins
;;
(def-package! prodigy
:commands (prodigy prodigy-view-mode prodigy-add-filter)
:config
(set! :evil-state 'prodigy-mode 'emacs)
;; Make services, etc persistent between Emacs sessions
(setq prodigy-services (persistent-soft-fetch 'prodigy-services "prodigy")
prodigy-tags (persistent-soft-fetch 'prodigy-tags "prodigy")
prodigy-filters (persistent-soft-fetch 'prodigy-filters "prodigy"))
(defun +services|save ()
"Save all services, tags and filters to files."
(+services/cleanup)
(cl-loop for sym in '(prodigy-services prodigy-tags prodigy-filters)
do (persistent-soft-store sym (symbol-value sym) "prodigy")))
(add-hook 'kill-emacs-hook #'+services|save)
(defun +services*prodigy-services (orig-fn &rest args)
"Adds a new :project property to prodigy services, which hides the service
unless invoked from the relevant project."
(let ((project-root (downcase (doom-project-root)))
(services (apply orig-fn args)))
(if current-prefix-arg
services
(cl-remove-if-not (lambda (service)
(let ((project (plist-get service :project)))
(or (not project)
(file-in-directory-p project-root project))))
services))))
(advice-add #'prodigy-services :around #'+services*prodigy-services)
;; Keybindings
(map! :map prodigy-mode-map "d" #'+services/prodigy-delete)
(when (featurep! :feature evil)
(map! :map prodigy-mode-map
"j" #'prodigy-next
"k" #'prodigy-prev)))

View File

@ -1,4 +1,4 @@
;; -*- no-byte-compile: t; -*-
;;; tools/prodigy/packages.el
;;; feature/services/packages.el
(package! prodigy)

View File

@ -29,14 +29,11 @@
"Refresh git-gutter on ESC. Return nil to prevent shadowing other
`+evil-esc-hook' hooks."
(when git-gutter-mode
(git-gutter)
nil))
(ignore (git-gutter))))
(add-hook '+evil-esc-hook #'+version-control|update-git-gutter t))
(when (featurep! :feature hydra)
(defhydra +hydra-git-gutter (:body-pre (git-gutter-mode 1)
:hint nil)
(def-hydra! +version-control@git-gutter
(:body-pre (git-gutter-mode 1) :hint nil)
"
╭─────────────────┐
Movement Hunk Actions Misc. │ gg: +%-4s(car (git-gutter:statistic))/ -%-3s(cdr (git-gutter:statistic)) │
@ -47,21 +44,17 @@
^↓ ^ [_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))))
("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)))
(def-package! git-timemachine

View File

@ -1,10 +1,7 @@
;;; feature/version-control/config.el -*- lexical-binding: t; -*-
(unless (featurep! -git)
(load! +git))
;; TODO hg support
;; (unless (featurep! -hg)
;; (load! +hg))
(or (featurep! -git) (load! +git))
;; TODO (or (featurep! -hg) (load! +hg))
;;
(setq vc-make-backup-files nil)
@ -26,17 +23,14 @@
:init
(add-hook 'find-file-hook #'+vcs|enable-smerge-mode-maybe)
:config
(when (featurep! :feature hydra)
(require 'hydra)
(when (version< emacs-version "26")
(defalias #'smerge-keep-upper #'smerge-keep-mine)
(defalias #'smerge-keep-lower #'smerge-keep-other)
(defalias #'smerge-diff-base-upper #'smerge-diff-base-mine)
(defalias #'smerge-diff-upper-lower #'smerge-diff-mine-other)
(defalias #'smerge-diff-base-lower #'smerge-diff-base-other))
(when (version< emacs-version "26")
(defalias 'smerge-keep-upper 'smerge-keep-mine)
(defalias 'smerge-keep-lower 'smerge-keep-other)
(defalias 'smerge-diff-base-upper 'smerge-diff-base-mine)
(defalias 'smerge-diff-upper-lower 'smerge-diff-mine-other)
(defalias 'smerge-diff-base-lower 'smerge-diff-base-other))
(defhydra +hydra-smerge (:hint nil
(def-hydra! +hydra-smerge (:hint nil
:pre (smerge-mode 1)
;; Disable `smerge-mode' when quitting hydra if
;; no merge conflicts remain.
@ -51,24 +45,24 @@
^_j_ ↓^ [_a_] all [_H_] hightlight
^_C-j_^ [_RET_] current [_E_] ediff ╭──────────
^_G_^ │ [_q_] quit"
("g" (progn (goto-char (point-min)) (smerge-next)))
("G" (progn (goto-char (point-max)) (smerge-prev)))
("C-j" smerge-next)
("C-k" smerge-prev)
("j" next-line)
("k" previous-line)
("b" smerge-keep-base)
("u" smerge-keep-upper)
("l" smerge-keep-lower)
("a" smerge-keep-all)
("RET" smerge-keep-current)
("\C-m" smerge-keep-current)
("<" smerge-diff-base-upper)
("=" smerge-diff-upper-lower)
(">" smerge-diff-base-lower)
("H" smerge-refine)
("E" smerge-ediff)
("C" smerge-combine-with-next)
("r" smerge-resolve)
("R" smerge-kill-current)
("q" nil :color blue))))
("g" (progn (goto-char (point-min)) (smerge-next)))
("G" (progn (goto-char (point-max)) (smerge-prev)))
("C-j" smerge-next)
("C-k" smerge-prev)
("j" next-line)
("k" previous-line)
("b" smerge-keep-base)
("u" smerge-keep-upper)
("l" smerge-keep-lower)
("a" smerge-keep-all)
("RET" smerge-keep-current)
("\C-m" smerge-keep-current)
("<" smerge-diff-base-upper)
("=" smerge-diff-upper-lower)
(">" smerge-diff-base-lower)
("H" smerge-refine)
("E" smerge-ediff)
("C" smerge-combine-with-next)
("r" smerge-resolve)
("R" smerge-kill-current)
("q" nil :color blue)))

View File

@ -37,7 +37,7 @@
;;;###autoload (autoload '+workspace:delete "feature/workspaces/autoload/evil" nil t)
(evil-define-command +workspace:delete ()
"Ex wrapper around `+workspace/delete'."
(interactive "<a>") (+workspace/delete (+workspace-current-name)))
(interactive) (+workspace/delete (+workspace-current-name)))
;;;###autoload (autoload '+workspace:switch-next "feature/workspaces/autoload/evil" nil t)
(evil-define-command +workspace:switch-next (&optional count)

View File

@ -1,20 +1,70 @@
;;; feature/workspaces/autoload/workspaces.el -*- lexical-binding: t; -*-
(defvar +workspace-workspace-file "_workspaces"
(defvar +workspace-data-file "_workspaces"
"The file basename in which to store single workspace perspectives.")
(defvar +workspace--last nil)
(defvar +workspace--index 0)
(defface +workspace-tab-selected-face
'((t (:inherit 'highlight)))
;;
(defface +workspace-tab-selected-face '((t (:inherit 'highlight)))
"The face for selected tabs displayed by `+workspace/display'"
:group 'doom)
(defface +workspace-tab-face
'((t (:inherit 'default)))
(defface +workspace-tab-face '((t (:inherit 'default)))
"The face for selected tabs displayed by `+workspace/display'"
:group 'doom)
;;
;; 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 #'persp-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."
(cl-assert (stringp name) t)
(member name (+workspace-list-names)))
;;;###autoload
(defun +workspace-contains-buffer-p (buffer &optional workspace)
"Return non-nil if buffer is in workspace (defaults to current workspace)."
(persp-contain-buffer-p buffer (or workspace (+workspace-current)) nil))
;; --- Getters ----------------------------
;;;###autoload
(defun +workspace-get (name &optional noerror)
"Returns a workspace (perspective hash table) named NAME."
(when-let (persp (persp-get-by-name name))
(cond ((+workspace-p persp) persp)
((not noerror) (error "'%s' is an invalid workspace" name)))))
;;;###autoload
(defalias '+workspace-current #'get-current-persp)
;;;###autoload
(defun +workspace-current-name ()
"Get the name of the current workspace."
(safe-persp-name (get-current-persp)))
;;;###autoload
(defun +workspace-list ()
"Return a list of workspace structs."
@ -36,123 +86,92 @@ PERSP can be a string (name of a workspace) or a perspective hash (satisfies
If PERSP is t, then return a list of orphaned buffers associated with no
perspectives."
(unless persp
(setq persp (get-current-persp)))
(if (eq persp t)
(cl-remove-if #'persp--buffer-in-persps (buffer-list))
(when (stringp persp)
(setq persp (+workspace-get persp t)))
(cl-loop for buf in (buffer-list)
if (persp-contain-buffer-p buf persp)
collect buf)))
(let ((persp (or persp (+workspace-current))))
(if (eq persp t)
(cl-remove-if #'persp--buffer-in-persps (buffer-list))
(cl-assert (+workspace-p persp) t)
(cl-loop for buf in (buffer-list)
if (+workspace-contains-buffer-p buf persp)
collect buf))))
;;;###autoload
(defun +workspace-p (obj)
"Return t if OBJ is a perspective hash table."
(and obj
(cl-struct-p obj)
(perspective-p obj)))
;;;###autoload
(defun +workspace-exists-p (name)
"Returns t if NAME is the name of an existing workspace."
(when (symbolp name)
(setq name (symbol-name name)))
(unless (stringp name)
(error "Expected a string, got a %s" (type-of name)))
(member name (+workspace-list-names)))
;;;###autoload
(defun +workspace-get (name &optional noerror)
"Returns a workspace (perspective hash table) named NAME."
(unless (equal name persp-nil-name)
(let ((persp (persp-get-by-name name)))
(when (and (not noerror)
(or (null persp)
(equal persp persp-not-persp)))
(error "%s is not an available workspace" name))
persp)))
;;;###autoload
(defalias '+workspace-current #'get-current-persp)
;;;###autoload
(defun +workspace-current-name ()
"Get the name of the currently active workspace."
(safe-persp-name (get-current-persp)))
;;;###autoload
(defun +workspace-contains-buffer-p (&optional buffer workspace)
"Return non-nil if buffer is in workspace (defaults to current workspace)."
(unless workspace
(setq workspace (+workspace-current)))
(persp-contain-buffer-p buffer workspace nil))
;; --- Actions ----------------------------
;;;###autoload
(defun +workspace-load (name)
"Loads and inserts a single workspace (named NAME) into the current session.
Can only retrieve perspectives that were explicitly saved with
`+workspace-save'.
"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."
(unless (+workspace-exists-p name)
(persp-load-from-file-by-names +workspace-workspace-file *persp-hash* (list name)))
(when (+workspace-exists-p name)
(error "A workspace named '%s' already exists." name))
(persp-load-from-file-by-names
(expand-file-name +workspace-data-file persp-save-dir)
*persp-hash* (list name))
(+workspace-exists-p name))
;;;###autoload
(defun +workspace-load-session (&optional name)
"Replace current session with the entire session named NAME. If NAME is nil,
use `persp-auto-save-fname'."
(persp-load-state-from-file (or name persp-auto-save-fname)))
(persp-load-state-from-file
(expand-file-name (or name persp-auto-save-fname) persp-save-dir)))
;;;###autoload
(defun +workspace-save (name)
"Saves a single workspace (TARGET) from the current session. Can be loaded
again with `+workspace-load'. TARGET can be the string name of a workspace or
its perspective hash table.
"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 not an available workspace" name))
(persp-save-to-file-by-names
+workspace-workspace-file *persp-hash* (list name) t)
(memq name (persp-list-persp-names-in-file (concat persp-save-dir +workspace-workspace-file))))
(error "'%s' is an invalid workspace" name))
(let ((fname (expand-file-name +workspace-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-save-session (&optional name)
"Save a whole session as NAME. If NAME is nil, use `persp-auto-save-fname'.
Return t on success, nil otherwise."
(when (or (not name)
(equal name persp-auto-save-fname))
(setq name persp-auto-save-fname
persp-auto-save-opt 0))
(and (persp-save-state-to-file name) t))
(let ((fname (expand-file-name (or name persp-auto-save-fname)
persp-save-dir))
(persp-auto-save-opt
(if (or (not name)
(equal name persp-auto-save-fname))
0
persp-auto-save-opt)))
(and (persp-save-state-to-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)
(when (+workspace--protected-p name)
(error "Can't create a new '%s' workspace" name))
(unless (+workspace-exists-p name)
(and (persp-add-new name) t)))
(when (+workspace-exists-p name)
(error "A workspace named '%s' already exists" name))
(and (persp-add-new name) t))
;;;###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)
(when (+workspace--protected-p name)
(error "Can't rename '%s' workspace" name))
(persp-rename new-name (+workspace-get name)))
;;;###autoload
(defun +workspace-delete (name &optional inhibit-kill-p)
"Delete the workspace denoted by TARGET, which can be the name of a
perspective or its hash table."
(when (+workspace-protected-p name)
"Delete the workspace denoted by NAME, 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."
(when (+workspace--protected-p name)
(error "Can't delete '%s' workspace" name))
(+workspace-get name) ;; error checking
(persp-kill name inhibit-kill-p))
(+workspace-get name) ; error checking
(persp-kill name inhibit-kill-p)
(not (+workspace-exists-p name)))
;;;###autoload
(defun +workspace-switch (name &optional auto-create-p)
@ -168,17 +187,6 @@ perspective or its hash table."
+workspaces-main)))
(persp-frame-switch 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))
(defun +workspace-protected-p (name)
(or (equal name persp-nil-name)
(equal name +workspaces-main)))
;;
;; Interactive commands
@ -192,9 +200,10 @@ current workspace (by name) from session files."
(list
(if current-prefix-arg
(+workspace-current-name)
(completing-read "Workspace to load: "
(persp-list-persp-names-in-file
(concat persp-save-dir +workspace-workspace-file))))))
(completing-read
"Workspace to load: "
(persp-list-persp-names-in-file
(expand-file-name +workspace-data-file persp-save-dir))))))
(if (not (+workspace-load name))
(+workspace-error (format "Couldn't load workspace %s" name))
(+workspace/switch-to name)
@ -272,20 +281,30 @@ workspace to delete."
nil nil current-name)
current-name))))
(condition-case ex
(if (not (+workspace-delete name))
(error "Couldn't delete %s workspace" name)
(if (+workspace-exists-p +workspace--last)
(+workspace-switch +workspace--last)
(+workspace-switch +workspaces-main t))
(+workspace-message (format "Deleted '%s' workspace" name) 'success))
(+workspace-message
(let ((workspaces (length (+workspace-list-names))))
(cond ((> workspaces 1)
(+workspace-delete name)
(+workspace-switch
(if (+workspace-exists-p +workspace--last)
+workspace--last
(car (+workspace-list-names))))
(format "Deleted '%s' workspace" name))
((= workspaces 1)
(format "Can't delete the last workspace!"))
(t
(+workspace-delete name)
(+workspace-switch +workspaces-main t)
(switch-to-buffer (doom-fallback-buffer))
(format "No workspaces detected! Auto-creating '%s' workspace" +workspaces-main))))
'success)
('error (+workspace-error (cadr ex) t))))
;;;###autoload
(defun +workspace/kill-session ()
"Delete the current session, clears all workspaces, windows and buffers."
(interactive)
(unless (cl-every #'+workspace-delete
(delete +workspaces-main (+workspace-list-names)))
(unless (cl-every #'+workspace-delete (+workspace-list-names))
(+workspace-error "Could not clear session"))
(+workspace-switch +workspaces-main t)
(doom/kill-all-buffers)
@ -388,7 +407,7 @@ the workspace and move to the next."
(if (doom-popup-p)
(doom/popup-close)
(let ((current-persp-name (+workspace-current-name)))
(cond ((or (+workspace-protected-p current-persp-name)
(cond ((or (+workspace--protected-p current-persp-name)
(> (length (doom-visible-windows)) 1))
(if (bound-and-true-p evil-mode)
(evil-window-delete)
@ -397,15 +416,17 @@ the workspace and move to the next."
(+workspace/delete current-persp-name))))))
;;;###autoload
(defun +workspace/cleanup ()
"Clean up orphaned buffers and processes."
(defun +workspace/close-workspace-or-frame ()
"Close the current workspace. If it's the last, delete the frame instead."
(interactive)
(let ((buffers (cl-remove-if #'persp--buffer-in-persps (buffer-list)))
(n (doom-kill-process-buffers)))
(mapc #'kill-buffer buffers)
(when (called-interactively-p 'any)
(message "Cleaned up %d buffers and %d processes"
(length buffers) n))))
(let ((frames (length (frame-list)))
(workspaces (length (+workspace-list-names))))
(cond ((> workspaces 1)
(call-interactively #'+workspace/delete))
((> frames 1)
(call-interactively #'delete-frame))
(t
(error "Can't delete last frame.")))))
;;

View File

@ -1,7 +1,7 @@
;;; lang/cc/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +cc*lineup-arglist (orig-fun &rest args)
(defun +cc*align-lambda-arglist (orig-fun &rest args)
"Improve indentation of continued C++11 lambda function opened as argument."
(if (and (eq major-mode 'c++-mode)
(ignore-errors
@ -21,59 +21,62 @@
(backward-char)
(looking-at-p "[^ \t]>"))
(forward-char)
(call-interactively 'self-insert-command)))
(defun +cc--copy-face (new-face face)
"Define NEW-FACE from existing FACE."
(copy-face face new-face)
(eval `(defvar ,new-face nil))
(set new-face new-face))
;;;###autoload
(defun +cc|extra-fontify-c++ ()
;; We could place some regexes into `c-mode-common-hook', but
;; note that their evaluation order matters.
;; NOTE modern-cpp-font-lock will eventually supercede some of these rules
(font-lock-add-keywords
nil '(;; c++11 string literals
;; L"wide string"
;; L"wide string with UNICODE codepoint: \u2018"
;; u8"UTF-8 string", u"UTF-16 string", U"UTF-32 string"
("\\<\\([LuU8]+\\)\".*?\"" 1 font-lock-keyword-face)
;; R"(user-defined literal)"
;; R"( a "quot'd" string )"
;; R"delimiter(The String Data" )delimiter"
;; R"delimiter((a-z))delimiter" is equivalent to "(a-z)"
("\\(\\<[uU8]*R\"[^\\s-\\\\()]\\{0,16\\}(\\)" 1 font-lock-keyword-face t) ; start delimiter
( "\\<[uU8]*R\"[^\\s-\\\\()]\\{0,16\\}(\\(.*?\\))[^\\s-\\\\()]\\{0,16\\}\"" 1 font-lock-string-face t) ; actual string
( "\\<[uU8]*R\"[^\\s-\\\\()]\\{0,16\\}(.*?\\()[^\\s-\\\\()]\\{0,16\\}\"\\)" 1 font-lock-keyword-face t) ; end delimiter
) t))
;;;###autoload
(defun +cc|extra-fontify-c/c++ ()
(font-lock-add-keywords
nil '(;; PREPROCESSOR_CONSTANT, PREPROCESSORCONSTANT
("\\<[A-Z]*_[A-Z_]+\\>" . font-lock-constant-face)
("\\<[A-Z]\\{3,\\}\\>" . font-lock-constant-face)
;; integer/float/scientific numbers
("\\<\\([\\-+]*[0-9\\.]+\\)\\>" 1 font-lock-constant-face t)
("\\<\\([\\-+]*[0-9\\.]+\\)\\(f\\)\\>"
(1 font-lock-constant-face t)
(2 font-lock-keyword-face t))
("\\<\\([\\-+]*[0-9\\.]+\\)\\([eE]\\)\\([\\-+]?[0-9]+\\)\\>"
(1 font-lock-constant-face t)
(2 font-lock-keyword-face t)
(3 font-lock-constant-face t))
) t))
(call-interactively #'self-insert-command)))
;;;###autoload
(defun +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)
(sp-point-after-word-p id action context)))
;;;###autoload
(defun +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[^<]+"))))
;;;###autoload
(defun +cc-c-lineup-inclass (_langelem)
"Indent privacy keywords at same level as class properties."
(if (memq major-mode '(c-mode c++-mode))
(let ((inclass (assq 'inclass c-syntactic-context)))
(save-excursion
(goto-char (c-langelem-pos inclass))
(if (or (looking-at "struct")
(looking-at "typedef struct"))
'+
'++)))
'+))
;;
;; Hooks
;;
;;;###autoload
(defun +cc|fontify-constants ()
"Better fontification for preprocessor constants"
(font-lock-add-keywords
nil '(("\\<[A-Z]*_[A-Z_]+\\>" . font-lock-constant-face)
("\\<[A-Z]\\{3,\\}\\>" . font-lock-constant-face))
t))
;;;###autoload
(defun +cc|irony-init-compile-options ()
"Initialize compiler options for irony-mode. It searches for the nearest
compilation database and initailizes it. If none was found, it uses
`+cc-c++-compiler-options'.
See https://github.com/Sarcasm/irony-mode#compilation-database for details on
compilation dbs."
(when (memq major-mode '(c-mode c++-mode objc-mode))
(require 'irony-cdb)
(unless (irony-cdb-autosetup-compile-options)
(irony-cdb--update-compile-options
(append (delq nil (cdr-safe (assq major-mode +cc-compiler-options)))
(cl-loop for path in +cc-include-paths
nconc (list "-I" path)))
(doom-project-root)))))

View File

@ -1,12 +1,35 @@
;;; lang/cc/config.el --- c, c++, and obj-c -*- lexical-binding: t; -*-
(defvar +cc-include-paths (list "include/")
"A list of paths, relative to a project root, to search for headers in C/C++.
Paths can be absolute.
The purpose of this variable is to ensure syntax checkers and code-completion
knows where to look for headers.")
(defvar +cc-compiler-options
`((c-mode . nil)
(c++-mode
. ,(list "-std=c++11" ; use C++11 by default
(when IS-MAC
;; NOTE beware: you'll get abi-inconsistencies when passing
;; std-objects to libraries linked with libstdc++ (e.g. if you
;; use boost which wasn't compiled with libc++)
(list "-stdlib=libc++"))))
(objc-mode . nil))
"A list of default compiler options for the C family. These are ignored if a
compilation database is present in the project.")
;;
;; Plugins
;;
(def-package! cc-mode
:commands (c-mode c++-mode objc-mode java-mode)
:mode ("\\.mm" . objc-mode)
:init
(setq-default c-basic-offset tab-width)
(defun +cc--c++-header-file-p ()
:preface
(defun +cc-c++-header-file-p ()
(and buffer-file-name
(equal (file-name-extension buffer-file-name) "h")
(or (file-exists-p (expand-file-name
@ -17,89 +40,68 @@
(projectile-current-project-files))))
(equal (file-name-extension file) "cpp")))))
(defun +cc--objc-header-file-p ()
(defun +cc-objc-header-file-p ()
(and buffer-file-name
(equal (file-name-extension buffer-file-name) "h")
(re-search-forward "@\\<interface\\>" magic-mode-regexp-match-limit t)))
;; Auto-detect C++/Obj-C header files
(push (cons #'+cc--c++-header-file-p 'c++-mode) magic-mode-alist)
(push (cons #'+cc--objc-header-file-p 'objc-mode) magic-mode-alist)
(push (cons #'+cc-c++-header-file-p 'c++-mode) magic-mode-alist)
(push (cons #'+cc-objc-header-file-p 'objc-mode) magic-mode-alist)
:init
(setq-default c-basic-offset tab-width)
:config
(setq c-tab-always-indent nil
c-electric-flag nil)
(set! :electric '(c-mode c++-mode objc-mode java-mode)
:chars '(?\n ?\}))
(set! :company-backend
'(c-mode c++-mode objc-mode)
'(company-irony-c-headers company-irony))
(add-hook 'c-mode-common-hook #'rainbow-delimiters-mode)
(add-hook 'c-mode-hook #'highlight-numbers-mode) ; fontify numbers in C
(add-hook 'c++-mode-hook #'+cc|extra-fontify-c++) ; fontify C++11 string literals
;;; Style/formatting
;; C/C++ style settings
(c-toggle-electric-state -1)
(c-toggle-auto-newline -1)
(c-set-offset 'substatement-open '0) ; don't indent brackets
(c-set-offset 'inline-open '+)
(c-set-offset 'block-open '+)
(c-set-offset 'brace-list-open '+)
(c-set-offset 'case-label '+)
(c-set-offset 'access-label '-)
(c-set-offset 'arglist-intro '+)
(c-set-offset 'arglist-close '0)
;; Indent privacy keywords at same level as class properties
;; (c-set-offset 'inclass #'+cc-c-lineup-inclass)
;;; Better fontification (also see `modern-cpp-font-lock')
(add-hook 'c-mode-common-hook #'rainbow-delimiters-mode)
(add-hook! (c-mode c++-mode) #'highlight-numbers-mode)
(add-hook! (c-mode c++-mode) #'+cc|fontify-constants)
;; Improve indentation of inline lambdas in C++11
(advice-add #'c-lineup-arglist :around #'+cc*align-lambda-arglist)
;;; Keybindings
;; Completely disable electric keys because it interferes with smartparens and
;; custom bindings. We'll do this ourselves.
(setq c-tab-always-indent nil
c-electric-flag nil)
(dolist (key '("#" "{" "}" "/" "*" ";" "," ":" "(" ")"))
(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.
(map! :map c++-mode-map
"<" nil
:i ">" #'+cc/autoclose->-maybe)
;; ...and leave it to smartparens
(sp-with-modes '(c-mode c++-mode objc-mode java-mode)
(sp-local-pair "<" ">" :when '(+cc-sp-point-is-template-p +cc-sp-point-after-include-p))
(sp-local-pair "/*" "*/" :post-handlers '(("||\n[i]" "RET") ("| " "SPC")))
;; Doxygen blocks
(sp-local-pair "/**" "*/" :post-handlers '(("||\n[i]" "RET") ("||\n[i]" "SPC")))
(sp-local-pair "/*!" "*/" :post-handlers '(("||\n[i]" "RET") ("[d-1]< | " "SPC"))))
;; Improve indentation of inline lambdas in C++11
(advice-add #'c-lineup-arglist :around #'+cc*lineup-arglist)
;; C/C++ style settings
(c-toggle-electric-state -1)
(c-toggle-auto-newline -1)
(c-set-offset 'substatement-open '0) ; brackets should be at same indentation level as the statements they open
(c-set-offset 'inline-open '+)
(c-set-offset 'block-open '+)
(c-set-offset 'brace-list-open '+) ; all "opens" should be indented by the c-indent-level
(c-set-offset 'case-label '+) ; indent case labels by c-indent-level, too
(c-set-offset 'access-label '-)
(c-set-offset 'arglist-intro '+)
(c-set-offset 'arglist-close '0)
(defun +cc--c-lineup-inclass (_langelem)
(if (memq major-mode '(c-mode c++-mode))
(let ((inclass (assq 'inclass c-syntactic-context)))
(save-excursion
(goto-char (c-langelem-pos inclass))
(if (or (looking-at "struct")
(looking-at "typedef struct"))
'+
'++)))
'+))
(c-set-offset 'inclass #'+cc--c-lineup-inclass)
;; Certain electric mappings interfere with smartparens and custom bindings,
;; so unbind them
(map! :map c-mode-map
"DEL" nil
"#" #'self-insert-command
"{" #'self-insert-command
"}" #'self-insert-command
"/" #'self-insert-command
"*" #'self-insert-command
";" #'self-insert-command
"," #'self-insert-command
":" #'self-insert-command
"(" #'self-insert-command
")" #'self-insert-command
:map c++-mode-map
"}" 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.
"<" nil
:map (c-mode-base-map c++-mode-map)
:i ">" #'+cc/autoclose->-maybe))
(sp-local-pair "/*!" "*/" :post-handlers '(("||\n[i]" "RET") ("[d-1]< | " "SPC")))))
(def-package! modern-cpp-font-lock
@ -113,29 +115,25 @@
:preface
(setq irony-server-install-prefix (concat doom-etc-dir "irony-server/"))
:init
(defun +cc|init-irony-mode ()
(when (and (memq major-mode '(c-mode c++-mode objc-mode))
(file-directory-p irony-server-install-prefix))
(irony-mode +1)))
(add-hook 'c-mode-common-hook #'+cc|init-irony-mode)
(add-hook! (c-mode c++-mode objc-mode) #'irony-mode)
:config
(add-hook! 'irony-mode-hook #'(irony-eldoc flycheck-mode))
(unless (file-directory-p irony-server-install-prefix)
(warn "irony-mode: server isn't installed; run M-x irony-install-server"))
(defun +cc|init-c++11-clang-options ()
(make-local-variable 'irony-additional-clang-options)
(cl-pushnew "-std=c++11" irony-additional-clang-options :test 'equal))
(add-hook 'c++-mode-hook #'+cc|init-c++11-clang-options)
;; Initialize compilation database, if present. Otherwise, fall back on
;; `+cc-compiler-options'.
(add-hook 'irony-mode-hook #'+cc|irony-init-compile-options))
(map! :map irony-mode-map
[remap completion-at-point] #'counsel-irony
[remap complete-symbol] #'counsel-irony))
(def-package! irony-eldoc :after irony)
(def-package! irony-eldoc
:after irony
:config (add-hook 'irony-mode-hook #'irony-eldoc))
(def-package! flycheck-irony
:when (featurep! :feature syntax-checker)
:after irony
:config (flycheck-irony-setup))
:config
(add-hook 'irony-mode-hook #'flycheck-mode)
(flycheck-irony-setup))
;;
@ -181,7 +179,6 @@
(def-package! company-irony-c-headers :after company-irony)
(def-package! company-glsl
:when (featurep! :completion company)
:after glsl-mode
:config
(if (executable-find "glslangValidator")

View File

@ -29,12 +29,7 @@
(def-package! dockerfile-mode
:mode "/Dockerfile$"
:config
;; TODO
;; (set! :build 'build-image 'dockerfile-mode '+data/dockerfile-build
;; :when '+data-dockerfile-p)
)
:mode "/Dockerfile$")
;; For ROM hacking or debugging

View File

@ -7,13 +7,13 @@
(set! :eval 'emacs-lisp-mode #'+emacs-lisp-eval)
(set! :jump 'emacs-lisp-mode :documentation #'describe-symbol)
(set! :rotate 'emacs-lisp-mode
:symbols '(("t" "nil")
("let" "let*")
("when" "unless")
("append" "prepend")
("advice-add" "advice-remove")
("add-hook" "remove-hook")
("add-hook!" "remove-hook!")))
:symbols '(("t" "nil")
("let" "let*")
("when" "unless")
("append" "prepend")
("advice-add" "advice-remove")
("add-hook" "remove-hook")
("add-hook!" "remove-hook!")))
(add-hook! 'emacs-lisp-mode-hook
#'(;; 3rd-party functionality

View File

@ -1,9 +1,5 @@
;;; lang/go/autoload.el -*- lexical-binding: t; -*-
;;;###autoload
;; TODO (defun +go/build ())
;;
;; Tests
;;

View File

@ -4,45 +4,64 @@
:mode "\\.go$"
:interpreter "go"
:config
(setq gofmt-command "goimports")
(add-hook 'go-mode-hook #'flycheck-mode)
(setq gofmt-command "goimports")
(if (not (executable-find "goimports"))
(warn "go-mode: couldn't find goimports; no code formatting/fixed imports on save")
(add-hook! go-mode (add-hook 'before-save-hook #'gofmt-before-save nil t)))
(set! :build 'go-build 'go-mode #'+go/build)
(set! :repl 'go-mode #'gorepl-run)
(set! :jump 'go-mode
:definition #'go-guru-definition
:references #'go-guru-referrers
:documentation #'godoc-at-point)
(def-menu! +go/refactor-menu
"Refactoring commands for `go-mode' buffers."
'(("Add import" :exec go-import-add :region nil)
("Remove unused imports" :exec go-remove-unused-imports :region nil)
("Format buffer (gofmt)" :exec go-gofmt))
:prompt "Refactor: ")
(def-menu! +go/build-menu
"Build/compilation commands for `go-mode' buffers."
'(("Build project" :exec "go build")
("Build & run project" :exec "go run")
("Clean files" :exec "go clean"))
:prompt "Run test: ")
(def-menu! +go/test-menu
"Test commands for `go-mode' buffers."
'(("Last test" :exec +go/test-rerun)
("All tests" :exec +go/test-all)
("Single test" :exec +go/test-single)
("Nested test" :exec +go/test-nested))
:prompt "Run test: ")
(def-menu! +go/help-menu
"Help and information commands for `go-mode' buffers."
'(("Go to imports" :exec go-goto-imports)
("Lookup in godoc" :exec godoc-at-point)
("Describe this" :exec go-guru-describe)
("List free variables" :exec go-guru-freevars)
("What does this point to" :exec go-guru-pointsto)
("Implements relations for package types" :exec go-guru-implements :region nil)
("List peers for channel" :exec go-guru-peers)
("List references to object" :exec go-guru-referrers)
("Which errors" :exec go-guru-whicerrs)
("What query" :exec go-guru-what)
("Show callers of this function" :exec go-guru-callers :region nil)
("Show callees of this function" :exec go-guru-callees :region nil)))
(map! :map go-mode-map
:n "gd" #'go-guru-definition
:n "gD" #'go-guru-referrers
(:localleader
:n "gr" #'go-play-buffer
:v "gr" #'go-play-region
(:prefix "f"
:n "i" #'go-goto-imports
:n "h" #'godoc-at-point
:n "d" #'go-guru-describe
:n "v" #'go-guru-freevars
:n "I" #'go-guru-implements
:n "p" #'go-guru-peers
:n "r" #'go-guru-referrers
:n "t" #'go-guru-pointsto
:n "s" #'go-guru-callstack
:n "e" #'go-guru-whicherrs
:n "c" #'go-guru-callers
:n "C" #'go-guru-callees)
(:prefix "r"
:n "a" #'go-import-add
:n "i" #'go-remove-unused-imports
:nv "f" #'gofmt)
(:prefix "t"
:n "r" #'+go/test-rerun
:n "a" #'+go/test-all
:n "s" #'+go/test-single
:n "n" #'+go/test-nested))))
:localleader
"r" #'+go/refactor-menu
"b" #'+go/build-menu
"h" #'+go/help-menu
"t" #'+go/test-menu
:n "gr" #'go-play-buffer
:v "gr" #'go-play-region))
(def-package! go-eldoc

View File

@ -9,5 +9,5 @@
(warn "haskell-mode: couldn't find cabal")
(remove-hook 'haskell-mode-hook #'dante-mode))
(add-hook 'dante-mode-hook #'flycheck-mode))
(add-hook 'dante-mode-hook #'flycheck-mode))

View File

@ -12,7 +12,7 @@
(add-hook! 'intero-mode-hook #'(flycheck-mode eldoc-mode))
(set! :popup "^intero:backend:" :regex t :size 12)
(set! :jump :definition #'intero-goto-definition))
(set! :jump 'haskell-mode :definition #'intero-goto-definition))
(def-package! hindent

View File

@ -1,5 +1,13 @@
;;; lang/haskell/config.el -*- lexical-binding: t; -*-
(cond ((featurep! +intero) (load! +intero))
((featurep! +dante) (load! +dante)))
;;
;; Common plugins
;;
(def-package! haskell-mode
:mode "\\.hs$"
:mode ("\\.ghci$" . ghci-script-mode)
@ -30,8 +38,3 @@
(setq company-ghc-show-info 'oneline))
;;
(if (featurep! +dante)
(load! +dante)
(load! +intero))

View File

@ -0,0 +1,58 @@
;;; lang/java/+eclim.el -*- lexical-binding: t; -*-
;; NOTE This submodule is incomplete
(def-package! eclim
:init
(add-hook 'java-mode-hook #'eclim-mode)
:config
(set! :jump 'java-mode
:definition #'eclim-java-find-declaration
:references #'eclim-java-find-references
:documentation #'eclim-java-show-documentation-for-current-element)
(require 'eclimd)
(setq help-at-pt-display-when-idle t
help-at-pt-timer-delay 0.1)
(help-at-pt-set-timer)
;;
(def-menu! +java/refactor-menu
"Refactoring commands for `java-mode' buffers."
'(("Generate constructor" :exec eclim-java-constructor)
("Generate getter & setter" :exec eclim-java-generate-getter-and-setter)
("Organize imports" :exec eclim-java-import-organize)
("Reformat" :exec eclim-java-format)
("Rename symbol at point" :exec eclim-java-refactor-rename-symbol-at-point :region nil)))
(def-menu! +java/help-menu
"Help and information commands for `java-mode' buffers."
'(("Find documentation for current element" :exec eclim-java-show-documentation-for-current-element)
("Find references" :exec eclim-java-find-references)
("View call hierarchy" :exec eclim-java-call-hierarchy)
("View hierarchy" :exec eclim-java-hierarchy)
("View problems" :exec eclim-problems)))
(def-menu! +java/project-menu
"Building/compilation commands for `java-mode' buffers."
'(("Build project" :exec eclim-project-build)
("Create project" :exec eclim-project-create)
("Delete project" :exec eclim-project-delete)
("Go to project" :exec eclim-project-goto)
("Import project" :exec eclim-project-import)
("Close project" :exec eclim-project-close)
("Open project" :exec eclim-project-open)
("Update project" :exec eclim-project-update)))
(map! :map java-mode-map
:localleader
:nv "r" #'+java/refactor-menu
:nv "c" #'+java/compile-menu
:nv "p" #'+java/project-menu))
(def-package! company-emacs-eclim
:when (featurep! :completion company)
:after java
:config
(set! :company-backend 'java-mode '(company-emacs-eclim)))

View File

@ -0,0 +1,46 @@
;;; lang/java/+meghanada.el -*- lexical-binding: t; -*-
(def-package! meghanada
:commands meghanada-mode
:config
(setq meghanada-server-install-dir (concat doom-etc-dir "meghanada-server/")
meghanada-use-company (featurep! :completion company)
meghanada-use-flycheck (featurep! :feature syntax-checker)
meghanada-use-eldoc t
meghanada-use-auto-start t)
(add-hook 'java-mode-hook #'(rainbow-delimiters-mode eldoc-mode))
;; Setup on first use
(meghanada-install-server)
(if (file-exists-p (meghanada--locate-server-jar))
(add-hook! 'java-mode-hook #'(meghanada-mode flycheck-mode))
(warn "java-mode: meghanada-server not installed, java-mode will run with reduced functionality"))
(set! :jump 'java-mode
:definition #'meghanada-jump-declaration
:references #'meghanada-reference)
;;
(def-menu! +java/refactor-menu
"Refactoring commands for `java-mode' buffers."
'(("Add imports for unqualified classes" :exec meghanada-import-all)
("Optimize and clean up imports" :exec meghanada-optimize-import)
("Introduce local variable" :exec meghanada-local-variable)
("Format buffer code" :exec meghanada-code-beautify)))
(def-menu! +java/help-menu
"Help and information commands for `java-mode' buffers."
'(("Find usages" :exec meghanada-reference)
("Show type hierarchives and implemented interfaces" :exec meghanada-typeinfo)))
(def-menu! +java/project-menu
"Project commands for `java-mode' buffers."
'(("Compile current file" :exec meghanada-compile-file)
("Compile project" :exec meghanada-compile-project)))
(map! :map java-mode-map
:localleader
:nv "r" #'+java/refactor-menu
:nv "c" #'+java/compile-menu
:nv "p" #'+java/project-menu))

View File

@ -1,29 +1,14 @@
;;; lang/java/config.el -*- lexical-binding: t; -*-
(def-package! meghanada
:commands meghanada-mode
:config
(setq meghanada-server-install-dir (concat doom-etc-dir "meghanada-server/")
meghanada-use-company (featurep! :completion company)
meghanada-use-flycheck (featurep! :feature syntax-checker)
meghanada-use-eldoc t
meghanada-use-auto-start t)
(cond ((featurep! +meghanada) (load! +meghanada))
((featurep! +eclim) ; FIXME lang/java +eclim
;;(load! +eclim)
(warn "java-mode: eclim support isn't implemented yet")))
;; Setup on first use
(meghanada-install-server)
(if (file-exists-p (meghanada--locate-server-jar))
(add-hook! 'java-mode-hook #'(meghanada-mode flycheck-mode))
(warn "java-mode: meghanada-server not installed, java-mode will run with reduced functionality"))
(add-hook 'java-mode-hook #'(rainbow-delimiters-mode eldoc-mode))
(set! :build 'compile-file 'java-mode #'meghanada-compile-file)
(set! :build 'compile-project 'java-mode #'meghanada-compile-project)
(set! :jump 'java-mode :definition #'meghanada-jump-declaration)
(map! :map meghanada-mode-map :m "gd" #'meghanada-jump-declaration))
;;
;; Common plugins
;;
(def-package! android-mode
:commands android-mode

View File

@ -1,7 +1,14 @@
;; -*- no-byte-compile: t; -*-
;;; lang/java/packages.el
(package! meghanada)
(package! android-mode)
(package! groovy-mode)
(when (featurep! +meghanada)
(package! meghanada))
(when (featurep! +eclim)
(package! eclim)
(when (featurep! :completion company)
(package! company-emacs-eclim)))

View File

@ -169,7 +169,6 @@
(defun +javascript|init-screeps-mode ()
(when (eq major-mode 'js2-mode)
(cl-pushnew 'javascript-jshint flycheck-disabled-checkers)
(push 'javascript-jshint flycheck-disabled-checkers)
(setq js2-additional-externs (append '("_") screeps-objects screeps-constants))))
(add-hook '+javascript-screeps-mode-hook #'+javascript|init-screeps-mode)

View File

@ -18,50 +18,29 @@
;; Conform switch-case indentation to editorconfig's config
(set! :editorconfig :add '(js2-mode js2-basic-offset js-switch-indent-offset))
;; Favor local eslint over global, if available
(defun +javascript|init-flycheck-elint ()
(when (derived-mode-p 'js-mode)
(when-let ((eslint (expand-file-name "node_modules/eslint/bin/eslint.js"
(doom-project-root)))
(exists-p (file-exists-p eslint))
(executable-p (file-executable-p eslint)))
(setq-local flycheck-javascript-eslint-executable eslint))))
(add-hook 'flycheck-mode-hook #'+javascript|init-flycheck-elint)
(sp-with-modes '(js2-mode rjsx-mode)
(sp-local-pair "/* " " */" :post-handlers '(("| " "SPC"))))
;; If it's available globally, use eslint_d
(setq flycheck-javascript-eslint-executable (executable-find "eslint_d"))
(defun +javascript|init-flycheck-eslint ()
"Favor local eslint over global installs and configure flycheck for eslint."
(when (derived-mode-p 'js-mode)
(when-let ((exec-path (list (doom-project-expand "node_modules/.bin")))
(eslint (executable-find "eslint")))
(setq-local flycheck-javascript-eslint-executable eslint))
(when (flycheck-find-checker-executable 'javascript-eslint)
;; Flycheck has it's own trailing command and semicolon warning that was
;; conflicting with the eslint settings.
(setq-local js2-strict-trailing-comma-warning nil)
(setq-local js2-strict-missing-semi-warning nil))))
(add-hook 'flycheck-mode-hook #'+javascript|init-flycheck-eslint)
(map! :map js2-mode-map
:localleader
:n "S" #'+javascript/skewer-this-buffer
:prefix "r"
:n "g" #'js2r-add-to-globals-annotation
:n "ca" #'js2r-arguments-to-object
:n "Xa" #'js2r-contract-array
:n "Xf" #'js2r-contract-function
:n "Xo" #'js2r-contract-object
:nv "d" #'js2r-debug-this
:n "xa" #'js2r-expand-array
:n "xf" #'js2r-expand-function
:n "xo" #'js2r-expand-object
:v "ef" #'js2r-extract-function
:v "em" #'js2r-extract-method
:v "ev" #'js2r-extract-var
:n "F" #'js2r-forward-barf
:n "f" #'js2r-forward-slurp
:v "ii" #'js2r-inject-global-in-iife
:v "iv" #'js2r-inline-var
:v "p" #'js2r-introduce-parameter
:n "p" #'js2r-localize-parameter
:nv "l" #'js2r-log-this
:n "r" #'js2r-rename-var
:n "ss" #'js2r-split-string
:n "sv" #'js2r-split-var-declaration
:n "ct" #'js2r-ternary-to-if
:v "u" #'js2r-unwrap
:n "t" #'js2r-var-to-this
:n "ii" #'js2r-wrap-buffer-in-iife))
"r" #'+javascript/refactor-menu
"S" #'+javascript/skewer-this-buffer))
;; A find-{definition,references} backend for js2-mode. NOTE The xref API is
@ -81,14 +60,43 @@
js2r-add-to-globals-annotation js2r-extract-var js2r-inline-var
js2r-rename-var js2r-var-to-this js2r-arguments-to-object js2r-ternary-to-if
js2r-split-var-declaration js2r-split-string js2r-unwrap js2r-log-this
js2r-debug-this js2r-forward-slurp js2r-forward-barf))
js2r-debug-this js2r-forward-slurp js2r-forward-barf)
:init
(def-menu! +javascript/refactor-menu
"Refactoring commands for `js2-mode' buffers."
'(("Extract into function" :exec js2r-extract-function :region t)
("Extract into method" :exec js2r-extract-method :region t)
("Introduce parameter to function" :exec js2r-introduce-parameter :region t)
("Localize parameter" :exec js2r-localize-parameter :region nil)
("Expand object" :exec js2r-expand-object :region nil)
("Expand function" :exec js2r-expand-function :region nil)
("Expand array" :exec js2r-expand-array :region nil)
("Contract object" :exec js2r-contract-object :region nil)
("Contract function" :exec js2r-contract-function :region nil)
("Contract array" :exec js2r-contract-array :region nil)
("Wrap buffer in IIFE" :exec js2r-wrap-buffer-in-iife :region nil)
("Inject global into IIFE" :exec js2r-inject-global-in-iife :region t)
("Add to globals annotation" :exec js2r-add-to-globals-annotation :region nil)
("Extract variable" :exec js2r-extract-var :region t)
("Inline variable" :exec js2r-inline-var :region t)
("Rename variable" :exec js2r-rename-var :region nil)
("Replace var with this" :exec js2r-var-to-this :region nil)
("Arguments to object" :exec js2r-arguments-to-object :region nil)
("Ternary to if" :exec js2r-ternary-to-if :region nil)
("Split var declaration" :exec js2r-split-var-declaration :region nil)
("Split string" :exec js2r-split-string :region nil)
("Unwrap" :exec js2r-unwrap :region t)
("Log this" :exec js2r-log-this)
("Debug this" :exec js2r-debug-this)
("Reformat buffer (eslint_d)" :exec eslintd-fix :region nil :when (fboundp 'eslintd-fix)))
:prompt "Refactor: "))
(def-package! tern
:commands tern-mode
:init (add-hook 'js2-mode-hook #'tern-mode)
:config
(advice-add #'tern-project-dir :override #'doom*project-root))
(advice-add #'tern-project-dir :override #'doom-project-root))
(def-package! company-tern
@ -103,17 +111,15 @@
:mode "\\.jsx$"
:mode "components/.+\\.js$"
:init
;; auto-detect JSX file
(push (cons (lambda ()
(and buffer-file-name
(equal (file-name-extension buffer-file-name) "js")
(re-search-forward "\\(^\\s-*import React\\|\\( from \\|require(\\)[\"']react\\)"
magic-mode-regexp-match-limit t)
(progn
(goto-char (match-beginning 1))
(not (sp-point-in-string-or-comment)))))
'rjsx-mode)
magic-mode-alist)
(defun +javascript-jsx-file-p ()
(and buffer-file-name
(equal (file-name-extension buffer-file-name) "js")
(re-search-forward "\\(^\\s-*import React\\|\\( from \\|require(\\)[\"']react\\)"
magic-mode-regexp-match-limit t)
(progn (goto-char (match-beginning 1))
(not (sp-point-in-string-or-comment)))))
(push (cons #'+javascript-jsx-file-p 'rjsx-mode) magic-mode-alist)
:config
(set! :electric 'rjsx-mode :chars '(?\} ?\) ?. ?>))
@ -138,6 +144,10 @@
(map! :map* (json-mode js2-mode-map) :n "gQ" #'web-beautify-js))
(def-package! eslintd-fix
:commands (eslintd-fix-mode eslintd-fix))
;;
;; Skewer-mode
;;
@ -174,28 +184,27 @@
;;
(def-project-mode! +javascript-screeps-mode
:match "/screeps/.+$"
:match "/screeps\\(-ai\\)?/.+$"
:modes (+javascript-npm-mode)
:init (load! +screeps))
:add-hooks (+javascript|init-screeps-mode)
:on-load (load! +screeps))
(def-project-mode! +javascript-gulp-mode
:files "gulpfile.js")
(def-project-mode! +javascript-npm-mode
:modes (html-mode css-mode web-mode js2-mode markdown-mode)
:files "package.json")
:files "package.json"
:on-enter
(when (make-local-variable 'exec-path)
(push (doom-project-expand "node_modules/.bin")
exec-path)))
(def-project-mode! +javascript-lb6-mode
:modes (web-mode js2-mode nxml-mode markdown-mode)
:match "\\.lb\\(action\\|ext\\)/"
:init
;; TODO
;; (when IS-MAC
;; (set! :build 'launchbar-action '+javascript-lb6-mode
;; (lambda ()
;; (when-let (dir (f-traverse-upwards (lambda (f) (f-ext? f "lbaction"))))
;; (shell-command (format "open '%s'" dir))))
;; :when
;; (lambda () (f-traverse-upwards (lambda (f) (f-ext? f "lbaction"))))))
)
;;
;; Tools
;;
(def-project-mode! +javascript-eslintd-fix-mode
:add-hooks (eslintd-fix-mode))

View File

@ -11,6 +11,7 @@
(package! tern)
(package! web-beautify)
(package! skewer-mode)
(package! eslintd-fix)
(when (featurep! :completion company)
(package! company-tern))

View File

@ -3,15 +3,13 @@
(def-package! ledger-mode
:mode "\\.ledger$"
:commands ledger-mode
:config
(setq ledger-clear-whole-transactions 1))
:config (setq ledger-clear-whole-transactions 1))
(def-package! evil-ledger
:when (featurep! :feature evil)
:after ledger-mode
:config
(add-hook 'ledger-mode-hook #'evil-ledger-mode))
:config (add-hook 'ledger-mode-hook #'evil-ledger-mode))
(def-package! flycheck-ledger

View File

@ -7,3 +7,13 @@
(lua-start-process "lua" "lua")
(pop-to-buffer lua-process-buffer))
;;;###autoload
(defun +lua/run-love-game ()
"Run the current project with Love2D."
(interactive)
(async-shell-command
(format "%s %s"
(or (executable-find "love")
(if IS-MAC "open -a love.app"))
(shell-quote-argument (doom-project-root)))))

View File

@ -10,7 +10,16 @@
(set! :repl 'lua-mode #'+lua/repl)
;; sp's lua-specific rules are obnoxious, so we disable them
(setq sp-pairs (delete (assq 'lua-mode sp-pairs) sp-pairs)))
(setq sp-pairs (delete (assq 'lua-mode sp-pairs) sp-pairs))
(def-menu! +lua/build-menu
"Build/compilation commands for `lua-mode' buffers."
'(("Run Love app" :exec +lua/run-love-game :when +lua-love-mode))
:prompt "Build tasks: ")
(map! :map lua-mode-map
:localleader
"b" #'+lua/build-menu))
(def-package! company-lua
@ -32,9 +41,5 @@
(def-project-mode! +lua-love-mode
:modes (lua-mode markdown-mode json-mode)
:files (and "main.lua" "conf.lua")
:init
(set! :build 'run-love-app '+lua-love-mode
(lambda ()
(async-shell-command (format "open -a love.app '%s'" (doom-project-root))))))
:files (and "main.lua" "conf.lua"))

View File

@ -37,15 +37,15 @@
:m "]p" #'markdown-demote
:m "[l" #'markdown-next-link
:m "]l" #'markdown-previous-link
:i "M--" #'markdown-insert-hr)
:i "M--" #'markdown-insert-hr
(:localleader
:nv "o" #'markdown-open
:nv "b" #'markdown-preview
(:prefix "i"
:nv "t" #'markdown-toc-generate-toc
:nv "i" #'markdown-insert-image
:nv "l" #'markdown-insert-link))))
(:localleader
:nv "o" #'markdown-open
:nv "b" #'markdown-preview
(:prefix "i"
:nv "t" #'markdown-toc-generate-toc
:nv "i" #'markdown-insert-image
:nv "l" #'markdown-insert-link)))))
(def-package! markdown-toc

View File

@ -1,5 +1,20 @@
;;; lang/python/config.el -*- lexical-binding: t; -*-
(defvar +python-pyenv-root nil
"The path to pyenv's root directory. This is automatically set when `python'
is loaded.")
(defvar +python-pyenv-versions nil
"Available versions of python in pyenv.")
(defvar-local +python-current-version nil
"The currently active pyenv version.")
;;
;; Plugins
;;
(def-package! python
:commands python-mode
:init
@ -24,6 +39,33 @@
python-shell-completion-string-code
"';'.join(get_ipython().Completer.all_completions('''%s'''))\n"))
;; Version management with pyenv
(defun +python|add-version-to-modeline ()
"Add version string to the major mode in the modeline."
(setq mode-name
(if +python-current-version
(format "Python %s" +python-current-version)
"Python")))
(add-hook 'python-mode-hook #'+python|add-version-to-modeline)
(if (not (executable-find "pyenv"))
(setq +python-current-version (string-trim (shell-command-to-string "python --version 2>&1 | cut -d' ' -f2")))
(setq +python-pyenv-root (string-trim (shell-command-to-string "pyenv root"))
+python-pyenv-versions (split-string (shell-command-to-string "pyenv versions --bare") "\n" t))
(defun +python|detect-pyenv-version ()
"Detect the pyenv version for the current project and set the relevant
environment variables."
(when-let (version-str (shell-command-to-string "python --version 2>&1 | cut -d' ' -f2"))
(setq version-str (string-trim version-str)
+python-current-version version-str)
(let ((pyenv-current-path (concat +python-pyenv-root "/versions/" version-str)))
(when (file-directory-p pyenv-current-path)
(setq pythonic-environment pyenv-current-path)))
(when (member version-str +python-pyenv-versions)
(setenv "PYENV_VERSION" version-str))))
(add-hook 'python-mode-hook #'+python|detect-pyenv-version))
(define-key python-mode-map (kbd "DEL") nil) ; interferes with smartparens
(sp-with-modes 'python-mode
(sp-local-pair "'" nil :unless '(sp-point-before-word-p sp-point-after-word-p sp-point-before-same-p))))

View File

@ -1,5 +1,16 @@
;;; lang/ruby/config.el -*- lexical-binding: t; -*-
(defvar +ruby-rbenv-versions nil
"Available versions of ruby in rbenv.")
(defvar-local +ruby-current-version nil
"The currently active ruby version.")
;;
;; Plugins
;;
(def-package! ruby-mode
:mode ("\\.rb$" "\\.rake$" "\\.gemspec$" "\\.?pryrc$"
"/\\(Gem\\|Cap\\|Vagrant\\|Rake\\|Pod\\|Puppet\\|Berks\\)file$")
@ -13,6 +24,29 @@
;; Don't interfere with my custom RET behavior
(define-key ruby-mode-map [?\n] nil)
;; Version management with rbenv
(defun +ruby|add-version-to-modeline ()
"Add version string to the major mode in the modeline."
(setq mode-name
(if +python-current-version
(format "Ruby %s" +ruby-current-version)
"Ruby")))
(add-hook 'ruby-mode-hook #'+ruby|add-version-to-modeline)
(if (not (executable-find "rbenv"))
(setq +ruby-current-version (string-trim (shell-command-to-string "ruby --version 2>&1 | cut -d' ' -f2")))
(setq +ruby-rbenv-versions (split-string (shell-command-to-string "rbenv versions --bare") "\n" t))
(defun +ruby|detect-rbenv-version ()
"Detect the rbenv version for the current project and set the relevant
environment variables."
(when-let (version-str (shell-command-to-string "ruby --version 2>&1 | cut -d' ' -f2"))
(setq version-str (string-trim version-str)
+ruby-current-version version-str)
(when (member version-str +ruby-rbenv-versions)
(setenv "RBENV_VERSION" version-str))))
(add-hook 'ruby-mode-hook #'+ruby|detect-rbenv-version))
(map! :map ruby-mode-map
:localleader
:prefix "r"
@ -72,12 +106,7 @@
:config (set! :company-backend 'inf-ruby-mode '(company-inf-ruby)))
;;
;; TODO Frameworks
;;
(def-package! rake
:commands (rake rake-find-task rake-rerun)
:config (setq rake-completion-system 'default))
;; (def-project-mode! +ruby-rake-mode
;; :files "Rakefile"
;; :init
;; (set! :build 'rake '+ruby-rake-mode '+ruby/rake
;; :when (lambda () (doom-project-has! "Rakefile"))))

View File

@ -7,6 +7,7 @@
(package! rspec-mode)
(package! ruby-refactor)
(package! yard-mode)
(package! rake)
(when (featurep! :completion company)
(package! company-inf-ruby))

View File

@ -2,4 +2,7 @@
;; TODO (defun +rust/run-cargo () (interactive))
;; TODO (defun +rust-cargo-project-p ())
;;;###autoload
(defun +rust-cargo-project-p ()
"Return t if this is a cargo project."
(doom-project-has! "Cargo.toml"))

View File

@ -1,15 +1,21 @@
;;; lang/rust/config.el -*- lexical-binding: t; -*-
(defvar +rust-ext-dir (concat doom-etc-dir "rust/")
"TODO")
(defvar +rust-src-dir (concat doom-etc-dir "rust/")
"The path to Rust source library. Required by racer.")
;;
;; Plugins
;;
(def-package! rust-mode
:mode "\\.rs$"
:init
(add-hook 'rust-mode-hook #'flycheck-mode)
:config
(set! :build 'run-cargo '(rust-mode toml-mode) #'+rust/run-cargo
:when #'+rust-cargo-project-p))
(def-menu! +rust/build-menu
"TODO"
'(("run" :exec "cargo run" :cwd t :when (+rust-cargo-project-p))
("build" :exec "cargo build" :cwd t :when (+rust-cargo-project-p)))
:prompt "Cargo: "))
(def-package! racer
@ -18,14 +24,13 @@
:init
(add-hook! 'rust-mode-hook #'(racer-mode eldoc-mode flycheck-rust-setup))
:config
(setq racer-cmd (expand-file-name "racer/target/release/racer" +rust-ext-dir)
racer-rust-src-path (expand-file-name "rust/src/" +rust-ext-dir))
(setq racer-cmd (expand-file-name "racer/target/release/racer" +rust-src-dir)
racer-rust-src-path (expand-file-name "rust/src/" +rust-src-dir))
(set! :jump 'rust-mode :definition #'racer-find-definition)
(unless (file-exists-p racer-cmd)
(warn "rust-mode: racer binary can't be found; auto-completion is disabled"))
;; TODO Unit test keybinds
(map! :map rust-mode-map :m "gd" #'racer-find-definition))
(warn "rust-mode: racer binary can't be found; auto-completion is disabled")))
(def-package! company-racer
@ -36,5 +41,6 @@
(def-package! flycheck-rust
:when (featurep! :feature syntax-checker)
:after rust-mode)
:after rust-mode
:config (add-hook 'rust-mode-hook #'flycheck-mode))

View File

@ -14,6 +14,7 @@
(def-package! sh-script ; built-in
:mode ("\\.zsh$" . sh-mode)
:mode ("\\.zunit$" . sh-mode)
:mode ("/bspwmrc$" . sh-mode)
:init
(add-hook! sh-mode #'(flycheck-mode highlight-numbers-mode))

View File

@ -16,9 +16,7 @@
;; '(tide-jump-to-definition "jump to definition")
;; '(tide-documentation-at-point "current type documentation")
;; '(tide-restart-server "restart tide server"))
(map! :localleader
:m "fh" #'tide-documentation-at-point))
)
(def-package! tide
@ -41,8 +39,7 @@
(equal (file-name-extension buffer-file-name) "tsx")))
(tide-setup)
(flycheck-mode +1)
(eldoc-mode +1)))
(add-hook! (typescript-mode web-mode) #'+typescript|init-tide)
(advice-add #'tide-project-root :override #'doom*project-root))
(eldoc-mode +1)
(setq tide-project-root (doom-project-root))))
(add-hook! (typescript-mode web-mode) #'+typescript|init-tide))

View File

@ -13,6 +13,7 @@
(:localleader
:n "rb" #'+css/toggle-inline-or-block))
;;
;; Packages
;;
@ -36,15 +37,14 @@
:mode ("\\.scss$" . scss-mode)
:config
(set! :company-backend '(css-mode scss-mode) '(company-css company-yasnippet))
(set! :build 'compile-to-css 'scss-mode #'+css/scss-build))
(map! :map scss-mode-map :localleader "b" #'+css/scss-build))
(def-package! sass-mode
:mode "\\.sass$"
:config
(setq sass-command-options '("--style compressed"))
(set! :company-backend 'sass-mode '(company-css company-yasnippet))
(set! :build 'compile-to-css 'sass-mode #'+css/sass-build))
(map! :map scss-mode-map :localleader "b" #'+css/sass-build))
(def-package! less-css-mode

View File

@ -41,7 +41,4 @@
:mode "\\.jade$"
:mode "\\.pug$"
:config
(set! :company-backend 'pug-mode '(company-yasnippet))
(map! :map pug-mode-map
:i [tab] #'doom/dumb-indent
:i [backtab] #'doom/dumb-dedent))
(set! :company-backend 'pug-mode '(company-yasnippet)))

View File

@ -1,6 +1,5 @@
;;; lang/web/autoload/html.el -*- lexical-binding: t; -*-
;;;###autoload
(defvar +web-entities-list
[["&nbsp;" " "] ["&ensp;" ""] ["&emsp;" ""] ["&thinsp;" ""]
["&rlm;" ""] ["&lrm;" ""] ["&zwj;" ""] ["&zwnj;" ""]

View File

@ -38,11 +38,9 @@
(def-project-mode! +web-jekyll-mode
:modes (web-mode js-mode coffee-mode css-mode haml-mode pug-mode)
:files (and "config.yml" (or "_layouts/" "_posts/"))
:init
(defun +web|init-jekyll-mode ()
(when (eq major-mode 'web-mode)
(web-mode-set-engine "django")))
(add-hook '+web-jekyll-mode-hook #'+web|init-jekyll-mode))
:on-enter
(when (eq major-mode 'web-mode)
(web-mode-set-engine "django")))
(def-project-mode! +web-wordpress-mode
:modes (php-mode web-mode css-mode haml-mode pug-mode)

View File

@ -9,11 +9,12 @@
(package! counsel-css :recipe (:fetcher github :repo "hlissner/emacs-counsel-css")))
;; +html.el
(package! company-web)
(package! emmet-mode)
(package! haml-mode)
(package! pug-mode)
(package! web-mode)
(when (featurep! :completion company)
(package! company-web))
;; +css.el
(package! less-css-mode)

View File

@ -91,7 +91,7 @@ the cursor."
(or ext (file-name-extension filename))))
;;;###autoload
(defun +org-attach*insert-link (link filename)
(defun +org-attach*insert-link (_link filename)
"TODO"
(if (looking-back "^[ \t]+" (line-beginning-position))
(delete-region (match-beginning 0) (match-end 0))

View File

@ -1,10 +1,10 @@
;;; org/org-capture/autoload/evil.el -*- lexical-binding: t; -*-
;;;###autoload (autoload '+org-capture:dwim "org/org-capture/autoload/evil" nil t)
(evil-define-operator +org-capture:dwim (&optional beg end)
;;;###autoload (autoload '+org-capture:open "org/org-capture/autoload/evil" nil t)
(evil-define-operator +org-capture:open (&optional beg end)
"Evil ex interface to `+org-capture/dwim'."
:move-point nil :type inclusive
(interactive "<r>")
(+org-capture/dwim
(+org-capture/open
(unless (or (evil-normal-state-p) (evil-insert-state-p))
(buffer-substring beg end))))

View File

@ -1,7 +1,7 @@
;;; org/org-capture/autoload/org-capture.el -*- lexical-binding: t; -*-
;;;###autoload
(defun +org-capture/dwim (&optional string key)
(defun +org-capture/open (&optional string key)
"Sends STRING, the current selection or prompted input to `org-capture'.
Uses the capture template specified by KEY. Otherwise, prompts you for one."
@ -15,3 +15,57 @@ Uses the capture template specified by KEY. Otherwise, prompts you for one."
(region-end)))))
(org-capture-string string key)
(org-capture nil key))))
;; --- External frame ---------------------
(defvar +org-capture-window-params
`((name . "org-capture")
(width . 70)
(height . 25)
(window-system . ,(cond (IS-MAC 'ns)
(IS-LINUX 'x)
(t 'w32)))
,(if IS-LINUX '(display . ":0")))
"TODO")
;;;###autoload
(defun +org-capture|cleanup-frame ()
"Closes the org-capture frame once done adding an entry."
(when (+org-capture-frame-p)
(delete-frame nil t)))
;;;###autoload
(defun +org-capture-frame-p (&rest _)
"Return t if the current frame is an org-capture frame opened by
`+org-capture/open-frame'."
(equal "org-capture" (frame-parameter nil 'name)))
;;;###autoload
(defun +org-capture/open-frame (&optional string key)
"Opens the org-capture window in a floating frame that cleans itself up once
you're done. This can be called from an external shell script."
(interactive)
(require 'org)
(let (after-make-frame-functions before-make-frame-hook)
(let ((frame (if (+org-capture-frame-p)
(selected-frame)
(make-frame +org-capture-window-params))))
(with-selected-frame frame
(condition-case ex
(cl-letf (((symbol-function #'pop-to-buffer)
(symbol-function #'switch-to-buffer)))
(if (and (stringp string)
(not (string-empty-p string)))
(org-capture-string string key)
(org-capture nil key))
(when (featurep 'solaire-mode)
(solaire-mode +1)))
('error
(message "org-capture: %s" (error-message-string ex))
(delete-frame frame)))))))
;;;###autoload
(defun +org-capture-available-keys ()
"TODO"
(string-join (mapcar #'car org-capture-templates) ""))

View File

@ -12,56 +12,21 @@
;; anywhere I can call org-capture (whether or not Emacs is open/running),
;; like, say, from qutebrowser, vimperator, dmenu or a global keybinding.
(setq org-default-notes-file (concat +org-dir "notes.org")
org-capture-templates
'(("t" "Todo" entry
(file+headline (expand-file-name "todo.org" +org-dir) "Inbox")
"* [ ] %?\n%i" :prepend t :kill-buffer t)
("n" "Notes" entry
(file+headline org-default-notes-file "Inbox")
"* %u %?\n%i" :prepend t :kill-buffer t)))
(defun +org-capture|init ()
"Set up a sane `org-capture' workflow."
(setq org-default-notes-file (concat +org-dir "notes.org")
;; FIXME This is incomplete!
org-capture-templates
'(;; TODO: New vocabulary word
("t" "Todo" entry
(file+headline (expand-file-name "todo.org" +org-dir) "Inbox")
"* [ ] %?")
("c" "Changelog" entry
(file+headline (expand-file-name "CHANGELOG.org" (doom-project-root)) "Unreleased")
"* %?")
;; ("p" "Project Notes" entry
;; (file+headline org-default-notes-file "Inbox")
;; "* %u %?\n%i" :prepend t)
;; ("m" "Major-mode Notes" entry
;; (file+headline org-default-notes-file "Inbox")
;; "* %u %?\n%i" :prepend t)
("n" "Notes" entry
(file+headline (concat +org-dir "notes.org") "Inbox")
"* %u %?\n%i" :prepend t)
;; ("v" "Vocab" entry
;; (file+headline (concat org-directory "topics/vocab.org") "Unsorted")
;; "** %i%?\n")
))
(add-hook 'org-capture-after-finalize-hook #'+org-capture|cleanup-frame)
(when (featurep! :feature evil)
(add-hook 'org-capture-mode-hook #'evil-insert-state))
;; Allows the Emacs mini-frame (opened from an external shell script to run
;; and clean up properly) if the frame is named "org-capture".
(require 'org-capture)
(require 'org-protocol)
(defun +org-capture*init (&rest _)
"Makes sure the org-capture window is the only window in the frame."
(when (equal "org-capture" (frame-parameter nil 'name))
(setq mode-line-format nil)
(delete-other-windows)))
(advice-add #'org-capture :after #'+org-capture*init)
(defun +org-capture|finalize ()
"Closes the frame once org-capture is done."
(when (equal "org-capture" (frame-parameter nil 'name))
(when (and (featurep 'persp-mode) persp-mode)
(+workspace/delete (+workspace-current-name)))
(delete-frame)))
(add-hook 'org-capture-after-finalize-hook #'+org-capture|finalize))
(when (featurep! :ui doom-dashboard)
(add-hook '+doom-dashboard-inhibit-functions #'+org-capture-frame-p)))

View File

@ -1,36 +0,0 @@
;;; org/org-notebook/autoload.el -*- lexical-binding: t; -*-
(defun +org-notebook--explore-notes (dir)
(unless (file-directory-p dir)
(error "Directory doesn't exist: %s" dir))
(if (fboundp '+evil/neotree)
(neotree-dir dir)
(let ((default-directory dir))
(call-interactively (command-remapping 'find-file)))))
;;;###autoload
(defun +org-notebook/find-major-mode-notes ()
"Browse org notes in `+org-notebook-code-dir' in neotree, ido, ivy or helm --
whichever is available."
(interactive)
(let ((dir (expand-file-name
(concat (or (cdr (assq major-mode +org-notebook-code-alist))
(replace-regexp-in-string
"+" "p"
(string-remove-suffix "-mode" (symbol-name major-mode))
nil t))
"/")
+org-notebook-code-dir)))
(unless (file-in-directory-p dir +org-notebook-code-dir)
(error "Invalid location for %s notes: %s"
major-mode (abbreviate-file-name dir)))
(unless (file-directory-p dir)
(make-directory dir t))
(+org-notebook--explore-notes dir)))
;;;###autoload
(defun +org-notebook/find-project-notes ()
"Browse org notes in `+org-notebook-project-dir' in neotree, ido, ivy or helm --
whichever is available."
(interactive)
(+org-notebook--explore-notes +org-notebook-project-dir))

View File

@ -1,27 +0,0 @@
;;; org/org-notebook/config.el -*- lexical-binding: t; -*-
;; (add-hook 'org-load-hook '+org|init-notebook t)
;; While I program, write or plan, I want easy access to notes of various kinds,
;; such as major-mode/language specific notes, or project-specific notes. They
;; can be accessed via `+org-notebook/find-major-mode-notes' and
;; `+org-notebook/find-project-notes'.
(defvar +org-notebook-dir (concat +org-dir "notes/")
"The directory where the notes are kept.")
(defvar +org-notebook-code-dir (concat +org-notebook-dir "code/")
"The directory where programming notes and snippets are kept.")
(defvar +org-notebook-project-dir (concat +org-notebook-dir "projects/")
"The directory where project notes are kept.")
(defvar +org-notebook-code-alist
'((js2-mode . "javascript"))
"An alist mapping certain modes (symbols) to their org notes directory name.
If a mode isn't here, it's guessed by stripping out the -mode suffix and
replacing '+' characters with 'p's.")
;; (defun +org|init-notebook ())

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