diff --git a/.travis.yml b/.travis.yml index ce714c4e1..d1ba2d6a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/CHANGELOG.org b/CHANGELOG.org index e84a62ab0..bd68c8d85 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -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~. diff --git a/Makefile b/Makefile index a8925cd12..669ba1a05 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/bin/org-capture b/bin/org-capture index c987e0908..7f2998481 100755 --- a/bin/org-capture +++ b/bin/org-capture @@ -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 diff --git a/core/autoload/buffers.el b/core/autoload/buffers.el index 2ce241650..a052c2dd6 100644 --- a/core/autoload/buffers.el +++ b/core/autoload/buffers.el @@ -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)) diff --git a/core/autoload/debug.el b/core/autoload/debug.el index 99262a8e3..a6a7597e3 100644 --- a/core/autoload/debug.el +++ b/core/autoload/debug.el @@ -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 "\\_" 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/" diff --git a/core/autoload/editor.el b/core/autoload/editor.el index ae86b38de..3188c27c9 100644 --- a/core/autoload/editor.el +++ b/core/autoload/editor.el @@ -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)) + diff --git a/core/autoload/help.el b/core/autoload/help.el index fccb5c14c..739f7f25c 100644 --- a/core/autoload/help.el +++ b/core/autoload/help.el @@ -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 diff --git a/core/autoload/menu.el b/core/autoload/menu.el new file mode 100644 index 000000000..cd2e2db23 --- /dev/null +++ b/core/autoload/menu.el @@ -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"))))))) + diff --git a/core/autoload/packages.el b/core/autoload/packages.el index c10790b82..c6bc37c11 100644 --- a/core/autoload/packages.el +++ b/core/autoload/packages.el @@ -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) diff --git a/core/autoload/popups.el b/core/autoload/popups.el index dc1acfe8d..9cb8b26d7 100644 --- a/core/autoload/popups.el +++ b/core/autoload/popups.el @@ -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))))))) diff --git a/core/autoload/scratch.el b/core/autoload/scratch.el new file mode 100644 index 000000000..b0bd510c5 --- /dev/null +++ b/core/autoload/scratch.el @@ -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))) + diff --git a/core/autoload/test.el b/core/autoload/test.el index c926163bf..2c1b948d1 100644 --- a/core/autoload/test.el +++ b/core/autoload/test.el @@ -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))) diff --git a/core/autoload/ui.el b/core/autoload/ui.el index 7773e8e40..2336d5fbe 100644 --- a/core/autoload/ui.el +++ b/core/autoload/ui.el @@ -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 diff --git a/core/core-editor.el b/core/core-editor.el index 0597aa95a..3355619f6 100644 --- a/core/core-editor.el +++ b/core/core-editor.el @@ -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 diff --git a/core/core-keybinds.el b/core/core-keybinds.el index 21c390e5a..f2d70468e 100644 --- a/core/core-keybinds.el +++ b/core/core-keybinds.el @@ -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. diff --git a/core/core-lib.el b/core/core-lib.el index 81e6ae883..0ffbca94d 100644 --- a/core/core-lib.el +++ b/core/core-lib.el @@ -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) diff --git a/core/core-packages.el b/core/core-packages.el index d03916593..7ba141790 100644 --- a/core/core-packages.el +++ b/core/core-packages.el @@ -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 diff --git a/core/core-popups.el b/core/core-popups.el index 40515b7cc..cdb74c6b7 100644 --- a/core/core-popups.el +++ b/core/core-popups.el @@ -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) diff --git a/core/core-projects.el b/core/core-projects.el index c0a241e33..bee087f9f 100644 --- a/core/core-projects.el +++ b/core/core-projects.el @@ -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-directories)) + 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) - "Don't traverse the file system if a remote connection." + "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))) - (projectile-ignored-directories)) + (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-project-p (&optional strict-p) +(defun doom/reload-project () + "Reload the project root cache." + (interactive) + (projectile-invalidate-cache nil) + (projectile-reset-cached-project-root) + (dolist (fn projectile-project-root-files-functions) + (remhash (format "%s-%s" fn default-directory) projectile-project-root-cache))) + +(defun doom-project-p () "Whether or not this buffer is currently in a project or not." - (let ((projectile-require-project-root strict-p)) + (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)) - (ignore-errors (projectile-project-root)))) +(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." - (doom-project-root)) +(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." - (doom--resolve-paths files (doom-project-root))) + "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 ;; (defvar-local doom-project nil - "A list of project mode symbols to enable. Used for .dir-locals.el.") + "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) - (funcall mode))) + (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, -keymaps, hooks, snippets, etc. + "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) - :init FORM -- FORM to run the first time this project mode is enabled. + :add-hooks HOOKS -- HOOKS is a list of hooks to add this mode's hook. + + :on-load FORM -- FORM to run the first time this project mode is enabled. + + :on-enter FORM -- FORM is run each time the mode is activated. + + :on-exit FORM -- FORM is run each time the mode is disabled. Relevant: `doom-project-hook'." - (declare (indent 1)) - (let ((modes (plist-get plist :modes)) + (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)) - (keymap-sym (intern (format "%s-map" name)))) + (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) - ,(concat "Keymap for `" (symbol-name name) "'")) + ,(if load-form `(defvar ,init-var nil)) (define-minor-mode ,name - "A project minor mode." + ,doc-string :init-value nil - :keymap ,keymap-sym) + :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)) - ,init-form))))) + :when ,when))))) (provide 'core-projects) ;;; core-projects.el ends here diff --git a/core/core-ui.el b/core/core-ui.el index 8772d2da8..9913c9db0 100644 --- a/core/core-ui.el +++ b/core/core-ui.el @@ -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 diff --git a/core/core.el b/core/core.el index d9a864e43..9dfbe3533 100644 --- a/core/core.el +++ b/core/core.el @@ -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 diff --git a/core/packages.el b/core/packages.el index 4e3a882f8..5adea83c4 100644 --- a/core/packages.el +++ b/core/packages.el @@ -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) diff --git a/core/test/core-lib.el b/core/test/core-lib.el index 6f2483985..fba4c22e5 100644 --- a/core/test/core-lib.el +++ b/core/test/core-lib.el @@ -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 ----------------------------- diff --git a/init.example.el b/init.example.el index da78ac985..86169ab31 100644 --- a/init.example.el +++ b/init.example.el @@ -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) diff --git a/init.test.el b/init.test.el index fad51de56..9cba1cfc6 100644 --- a/init.test.el +++ b/init.test.el @@ -15,5 +15,8 @@ :lang web + :org + org + :private hlissner) diff --git a/modules/completion/ivy/autoload/evil.el b/modules/completion/ivy/autoload/evil.el index 74246641e..f8789be6a 100644 --- a/modules/completion/ivy/autoload/evil.el +++ b/modules/completion/ivy/autoload/evil.el @@ -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" diff --git a/modules/completion/ivy/config.el b/modules/completion/ivy/config.el index 7dd495b01..7c0bbe755 100644 --- a/modules/completion/ivy/config.el +++ b/modules/completion/ivy/config.el @@ -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) + ("" 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))) diff --git a/modules/completion/ivy/packages.el b/modules/completion/ivy/packages.el index 067451a87..8ec066214 100644 --- a/modules/completion/ivy/packages.el +++ b/modules/completion/ivy/packages.el @@ -6,3 +6,4 @@ (package! counsel-projectile) (package! smex) (package! swiper) +(package! ivy-hydra) diff --git a/modules/feature/debug/autoload/debug.el b/modules/feature/debug/autoload/debug.el deleted file mode 100644 index 4fa5c59be..000000000 --- a/modules/feature/debug/autoload/debug.el +++ /dev/null @@ -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)) - diff --git a/modules/feature/debug/autoload/evil.el b/modules/feature/debug/autoload/evil.el deleted file mode 100644 index 847fa739a..000000000 --- a/modules/feature/debug/autoload/evil.el +++ /dev/null @@ -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 "") - (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))) diff --git a/modules/feature/debugger/autoload/debug.el b/modules/feature/debugger/autoload/debug.el new file mode 100644 index 000000000..650e43170 --- /dev/null +++ b/modules/feature/debugger/autoload/debug.el @@ -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))) + diff --git a/modules/feature/debugger/autoload/evil.el b/modules/feature/debugger/autoload/evil.el new file mode 100644 index 000000000..c1b9c0a5b --- /dev/null +++ b/modules/feature/debugger/autoload/evil.el @@ -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 "") + ;; 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))) diff --git a/modules/feature/debug/config.el b/modules/feature/debugger/config.el similarity index 92% rename from modules/feature/debug/config.el rename to modules/feature/debugger/config.el index 66b89194d..384975d2f 100644 --- a/modules/feature/debug/config.el +++ b/modules/feature/debugger/config.el @@ -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)) diff --git a/modules/feature/debug/packages.el b/modules/feature/debugger/packages.el similarity index 60% rename from modules/feature/debug/packages.el rename to modules/feature/debugger/packages.el index b5da02cc9..e16548605 100644 --- a/modules/feature/debug/packages.el +++ b/modules/feature/debugger/packages.el @@ -1,4 +1,4 @@ ;; -*- no-byte-compile: t; -*- -;;; feature/debug/packages.el +;;; feature/debugger/packages.el (package! realgud) diff --git a/modules/feature/eval/README.org b/modules/feature/eval/README.org index ff669433d..eb9cf3084 100644 --- a/modules/feature/eval/README.org +++ b/modules/feature/eval/README.org @@ -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) + = 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) - + = 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. diff --git a/modules/feature/eval/autoload/build.el b/modules/feature/eval/autoload/build.el deleted file mode 100644 index cdf9c181b..000000000 --- a/modules/feature/eval/autoload/build.el +++ /dev/null @@ -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)))) - diff --git a/modules/feature/eval/autoload/eval.el b/modules/feature/eval/autoload/eval.el index d9d185c52..45e4ebc34 100644 --- a/modules/feature/eval/autoload/eval.el +++ b/modules/feature/eval/autoload/eval.el @@ -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) diff --git a/modules/feature/eval/autoload/evil.el b/modules/feature/eval/autoload/evil.el index 8116867d3..00b87245c 100644 --- a/modules/feature/eval/autoload/evil.el +++ b/modules/feature/eval/autoload/evil.el @@ -18,5 +18,5 @@ :move-point nil (interactive "") (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))) diff --git a/modules/feature/eval/autoload/repl.el b/modules/feature/eval/autoload/repl.el index d24cc564e..8b396b172 100644 --- a/modules/feature/eval/autoload/repl.el +++ b/modules/feature/eval/autoload/repl.el @@ -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") diff --git a/modules/feature/eval/config.el b/modules/feature/eval/config.el index 0d4501f9e..3f8176757 100644 --- a/modules/feature/eval/config.el +++ b/modules/feature/eval/config.el @@ -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)) diff --git a/modules/feature/evil/autoload/evil.el b/modules/feature/evil/autoload/evil.el index 129b8230f..18c4d5147 100644 --- a/modules/feature/evil/autoload/evil.el +++ b/modules/feature/evil/autoload/evil.el @@ -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)) diff --git a/modules/feature/evil/config.el b/modules/feature/evil/config.el index 0584d24af..907d47678 100644 --- a/modules/feature/evil/config.el +++ b/modules/feature/evil/config.el @@ -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 "" :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 "" #'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)) diff --git a/modules/feature/evil/test/evil.el b/modules/feature/evil/test/evil.el index cf8ba496f..7fd0626a3 100644 --- a/modules/feature/evil/test/evil.el +++ b/modules/feature/evil/test/evil.el @@ -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?") ""))))) diff --git a/modules/feature/file-templates/config.el b/modules/feature/file-templates/config.el index d1f6977a5..8c55c349c 100644 --- a/modules/feature/file-templates/config.el +++ b/modules/feature/file-templates/config.el @@ -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)))) diff --git a/modules/feature/file-templates/templates/emacs-lisp-mode/__doom-test b/modules/feature/file-templates/templates/emacs-lisp-mode/__doom-test new file mode 100644 index 000000000..9cd77f6ac --- /dev/null +++ b/modules/feature/file-templates/templates/emacs-lisp-mode/__doom-test @@ -0,0 +1,4 @@ +;; -*- no-byte-compile: t; -*- +;;; `(file-relative-name buffer-file-name doom-modules-dir)` + +$0 \ No newline at end of file diff --git a/modules/feature/file-templates/templates/lb6-mode/__Info.plist b/modules/feature/file-templates/templates/lb6-mode/__Info.plist deleted file mode 100644 index 74a6d0d1d..000000000 --- a/modules/feature/file-templates/templates/lb6-mode/__Info.plist +++ /dev/null @@ -1,99 +0,0 @@ -# -*- mode: snippet -*- -# name: Info.plist File Template -# condition: (eq major-mode 'nxml-mode) -# -- - - - - - - CFBundleIdentifier - io.henrik.launchbar.${1:$(replace-regexp-in-string "[^a-zA-Z0-9]" "" yas-text)} - - CFBundleName - ${1:Action Name} - - - - - CFBundleVersion - ${2:1.0.0} - - CFBundleIconFile - icon - - LBDebugLogEnabled - - - - - - - - - - - LBScripts - - LBDefaultScript - - LBScriptName - default.js - - LBRunInBackground - - - LBRequiresArgument - - - LBAcceptedArgumentTypes - - string - - - LBReturnsResult - - - LBReturnType - string - - - - LBDescription - - LBAuthor - Henrik Lissner - - LBWebsite - http://v0.io/launchbar - - LBEmail - contact@henrik.io - - LBTwitter - @vnought - - LBSummary - ${3:A short summary of this action} - - LBArgument - ${4:Describe the input(s) for this action}${5: - - LBResult - ${6:Describe the result type} - - }${7:LBRequirements - ${8:The requirements for this action to work}} - - - - - LBUpdateURL - https://github.com/hlissner/lb6-${2:actions}/blob/master/$1.lbaction/Contents/Info.plist - - LBDownloadURL - https://dl.v0.io/launchbar/$1.lbaction - - - - \ No newline at end of file diff --git a/modules/feature/file-templates/templates/lb6-mode/__default.js b/modules/feature/file-templates/templates/lb6-mode/__default.js deleted file mode 100644 index e0a2a28b0..000000000 --- a/modules/feature/file-templates/templates/lb6-mode/__default.js +++ /dev/null @@ -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) { - -} diff --git a/modules/feature/file-templates/templates/sh-mode/__zunit b/modules/feature/file-templates/templates/sh-mode/__zunit new file mode 100644 index 000000000..6abced490 --- /dev/null +++ b/modules/feature/file-templates/templates/sh-mode/__zunit @@ -0,0 +1,5 @@ +#!/usr/bin/env zunit + +@test '...' { + $0 +} diff --git a/modules/feature/hydra/autoload/evil.el b/modules/feature/hydra/autoload/evil.el deleted file mode 100644 index 92aa9cdef..000000000 --- a/modules/feature/hydra/autoload/evil.el +++ /dev/null @@ -1,2 +0,0 @@ -;;; feature/hydra/autoload/evil.el -*- lexical-binding: t; -*- - diff --git a/modules/feature/hydra/autoload/hydras.el b/modules/feature/hydra/autoload/hydras.el deleted file mode 100644 index 002f91420..000000000 --- a/modules/feature/hydra/autoload/hydras.el +++ /dev/null @@ -1,2 +0,0 @@ -;;; feature/hydra/autoload/hydras.el -*- lexical-binding: t; -*- - diff --git a/modules/feature/hydra/config.el b/modules/feature/hydra/config.el deleted file mode 100644 index f2b83baef..000000000 --- a/modules/feature/hydra/config.el +++ /dev/null @@ -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) - ("" 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)))) diff --git a/modules/feature/hydra/packages.el b/modules/feature/hydra/packages.el deleted file mode 100644 index 08cbb525d..000000000 --- a/modules/feature/hydra/packages.el +++ /dev/null @@ -1,7 +0,0 @@ -;; -*- no-byte-compile: t; -*- -;;; feature/hydra/packages.el - -(package! hydra) -(when (featurep! :completion ivy) - (package! ivy-hydra)) - diff --git a/modules/feature/jump/autoload/jump.el b/modules/feature/jump/autoload/jump.el index b9defcecf..cd75034b8 100644 --- a/modules/feature/jump/autoload/jump.el +++ b/modules/feature/jump/autoload/jump.el @@ -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)))) diff --git a/modules/feature/jump/config.el b/modules/feature/jump/config.el index ba68aa485..e763d4718 100644 --- a/modules/feature/jump/config.el +++ b/modules/feature/jump/config.el @@ -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 diff --git a/modules/feature/services/autoload.el b/modules/feature/services/autoload.el new file mode 100644 index 000000000..dd2e7a8f3 --- /dev/null +++ b/modules/feature/services/autoload.el @@ -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))) diff --git a/modules/feature/services/config.el b/modules/feature/services/config.el new file mode 100644 index 000000000..6430a8864 --- /dev/null +++ b/modules/feature/services/config.el @@ -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))) + diff --git a/modules/tools/prodigy/packages.el b/modules/feature/services/packages.el similarity index 60% rename from modules/tools/prodigy/packages.el rename to modules/feature/services/packages.el index bf9396134..dcca97f5d 100644 --- a/modules/tools/prodigy/packages.el +++ b/modules/feature/services/packages.el @@ -1,4 +1,4 @@ ;; -*- no-byte-compile: t; -*- -;;; tools/prodigy/packages.el +;;; feature/services/packages.el (package! prodigy) diff --git a/modules/feature/version-control/+git.el b/modules/feature/version-control/+git.el index 408b68667..f16a47d1d 100644 --- a/modules/feature/version-control/+git.el +++ b/modules/feature/version-control/+git.el @@ -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 diff --git a/modules/feature/version-control/config.el b/modules/feature/version-control/config.el index 7d9339355..d99b54b4f 100644 --- a/modules/feature/version-control/config.el +++ b/modules/feature/version-control/config.el @@ -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))) diff --git a/modules/feature/workspaces/autoload/evil.el b/modules/feature/workspaces/autoload/evil.el index 502652fc4..a4aee5e15 100644 --- a/modules/feature/workspaces/autoload/evil.el +++ b/modules/feature/workspaces/autoload/evil.el @@ -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 "") (+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) diff --git a/modules/feature/workspaces/autoload/workspaces.el b/modules/feature/workspaces/autoload/workspaces.el index cdd5d2534..b44a8ea45 100644 --- a/modules/feature/workspaces/autoload/workspaces.el +++ b/modules/feature/workspaces/autoload/workspaces.el @@ -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."))))) ;; diff --git a/modules/lang/cc/autoload.el b/modules/lang/cc/autoload.el index a6fd1c5ec..046ed54db 100644 --- a/modules/lang/cc/autoload.el +++ b/modules/lang/cc/autoload.el @@ -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))))) + diff --git a/modules/lang/cc/config.el b/modules/lang/cc/config.el index 2b2e3fec4..4a8e6369b 100644 --- a/modules/lang/cc/config.el +++ b/modules/lang/cc/config.el @@ -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 "@\\" 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") diff --git a/modules/lang/data/config.el b/modules/lang/data/config.el index 1b46a8505..71f7078e5 100644 --- a/modules/lang/data/config.el +++ b/modules/lang/data/config.el @@ -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 diff --git a/modules/lang/emacs-lisp/config.el b/modules/lang/emacs-lisp/config.el index 59b99007b..7cdf28216 100644 --- a/modules/lang/emacs-lisp/config.el +++ b/modules/lang/emacs-lisp/config.el @@ -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 diff --git a/modules/lang/go/autoload.el b/modules/lang/go/autoload.el index 993de0f08..18a3e1ca6 100644 --- a/modules/lang/go/autoload.el +++ b/modules/lang/go/autoload.el @@ -1,9 +1,5 @@ ;;; lang/go/autoload.el -*- lexical-binding: t; -*- -;;;###autoload -;; TODO (defun +go/build ()) - - ;; ;; Tests ;; diff --git a/modules/lang/go/config.el b/modules/lang/go/config.el index 7d43b6b78..e2d3f37df 100644 --- a/modules/lang/go/config.el +++ b/modules/lang/go/config.el @@ -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 diff --git a/modules/lang/haskell/+dante.el b/modules/lang/haskell/+dante.el index 45ca105ab..bf2d61fa1 100644 --- a/modules/lang/haskell/+dante.el +++ b/modules/lang/haskell/+dante.el @@ -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)) diff --git a/modules/lang/haskell/+intero.el b/modules/lang/haskell/+intero.el index a27ab768c..5adb63d25 100644 --- a/modules/lang/haskell/+intero.el +++ b/modules/lang/haskell/+intero.el @@ -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 diff --git a/modules/lang/haskell/config.el b/modules/lang/haskell/config.el index 975f84259..8cc1fc152 100644 --- a/modules/lang/haskell/config.el +++ b/modules/lang/haskell/config.el @@ -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)) diff --git a/modules/lang/java/+eclim.el b/modules/lang/java/+eclim.el new file mode 100644 index 000000000..7e8059480 --- /dev/null +++ b/modules/lang/java/+eclim.el @@ -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))) diff --git a/modules/lang/java/+meghanada.el b/modules/lang/java/+meghanada.el new file mode 100644 index 000000000..8107c1847 --- /dev/null +++ b/modules/lang/java/+meghanada.el @@ -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)) diff --git a/modules/lang/java/config.el b/modules/lang/java/config.el index ce079ade2..e96aaee00 100644 --- a/modules/lang/java/config.el +++ b/modules/lang/java/config.el @@ -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 diff --git a/modules/lang/java/packages.el b/modules/lang/java/packages.el index abcc37749..03deb4911 100644 --- a/modules/lang/java/packages.el +++ b/modules/lang/java/packages.el @@ -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))) + diff --git a/modules/lang/javascript/+screeps.el b/modules/lang/javascript/+screeps.el index 9daaf2f3c..179ebb54b 100644 --- a/modules/lang/javascript/+screeps.el +++ b/modules/lang/javascript/+screeps.el @@ -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) diff --git a/modules/lang/javascript/config.el b/modules/lang/javascript/config.el index 47972d434..abcca44ab 100644 --- a/modules/lang/javascript/config.el +++ b/modules/lang/javascript/config.el @@ -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)) diff --git a/modules/lang/javascript/packages.el b/modules/lang/javascript/packages.el index 5d9b56cbd..c89f3bd09 100644 --- a/modules/lang/javascript/packages.el +++ b/modules/lang/javascript/packages.el @@ -11,6 +11,7 @@ (package! tern) (package! web-beautify) (package! skewer-mode) +(package! eslintd-fix) (when (featurep! :completion company) (package! company-tern)) diff --git a/modules/lang/ledger/config.el b/modules/lang/ledger/config.el index 07e823130..f76966619 100644 --- a/modules/lang/ledger/config.el +++ b/modules/lang/ledger/config.el @@ -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 diff --git a/modules/lang/lua/autoload.el b/modules/lang/lua/autoload.el index a15b4ce08..2fc165a41 100644 --- a/modules/lang/lua/autoload.el +++ b/modules/lang/lua/autoload.el @@ -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))))) + diff --git a/modules/lang/lua/config.el b/modules/lang/lua/config.el index 51a35af72..dd5d423cb 100644 --- a/modules/lang/lua/config.el +++ b/modules/lang/lua/config.el @@ -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")) diff --git a/modules/lang/markdown/config.el b/modules/lang/markdown/config.el index 80255e3c2..9c7293b0c 100644 --- a/modules/lang/markdown/config.el +++ b/modules/lang/markdown/config.el @@ -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 diff --git a/modules/lang/python/config.el b/modules/lang/python/config.el index 3b624da8a..b92eb752b 100644 --- a/modules/lang/python/config.el +++ b/modules/lang/python/config.el @@ -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)))) diff --git a/modules/lang/ruby/config.el b/modules/lang/ruby/config.el index 543965616..865d1ea37 100644 --- a/modules/lang/ruby/config.el +++ b/modules/lang/ruby/config.el @@ -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")))) diff --git a/modules/lang/ruby/packages.el b/modules/lang/ruby/packages.el index 97353a6cf..5328f19fa 100644 --- a/modules/lang/ruby/packages.el +++ b/modules/lang/ruby/packages.el @@ -7,6 +7,7 @@ (package! rspec-mode) (package! ruby-refactor) (package! yard-mode) +(package! rake) (when (featurep! :completion company) (package! company-inf-ruby)) diff --git a/modules/lang/rust/autoload.el b/modules/lang/rust/autoload.el index 21ceaf0ff..2b94b95de 100644 --- a/modules/lang/rust/autoload.el +++ b/modules/lang/rust/autoload.el @@ -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")) diff --git a/modules/lang/rust/config.el b/modules/lang/rust/config.el index 6dfa517eb..22e5b8bf9 100644 --- a/modules/lang/rust/config.el +++ b/modules/lang/rust/config.el @@ -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)) diff --git a/modules/lang/sh/config.el b/modules/lang/sh/config.el index 0c06ab1f3..f5c93a8bc 100644 --- a/modules/lang/sh/config.el +++ b/modules/lang/sh/config.el @@ -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)) diff --git a/modules/lang/typescript/config.el b/modules/lang/typescript/config.el index 9ee2f38db..255357382 100644 --- a/modules/lang/typescript/config.el +++ b/modules/lang/typescript/config.el @@ -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)) diff --git a/modules/lang/web/+css.el b/modules/lang/web/+css.el index 630a989c1..181d1a5d8 100644 --- a/modules/lang/web/+css.el +++ b/modules/lang/web/+css.el @@ -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 diff --git a/modules/lang/web/+html.el b/modules/lang/web/+html.el index 1e1642f8b..8b66dd9e1 100644 --- a/modules/lang/web/+html.el +++ b/modules/lang/web/+html.el @@ -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))) diff --git a/modules/lang/web/autoload/html.el b/modules/lang/web/autoload/html.el index c3dd1aad0..b9945da9f 100644 --- a/modules/lang/web/autoload/html.el +++ b/modules/lang/web/autoload/html.el @@ -1,6 +1,5 @@ ;;; lang/web/autoload/html.el -*- lexical-binding: t; -*- -;;;###autoload (defvar +web-entities-list [[" " " "] [" " " "] [" " " "] [" " " "] ["‏" "‏"] ["‎" "‎"] ["‍" "‍"] ["‌" "‌"] diff --git a/modules/lang/web/config.el b/modules/lang/web/config.el index a7da62b0a..c31022e4c 100644 --- a/modules/lang/web/config.el +++ b/modules/lang/web/config.el @@ -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) diff --git a/modules/lang/web/packages.el b/modules/lang/web/packages.el index 1d4c28811..b8e748133 100644 --- a/modules/lang/web/packages.el +++ b/modules/lang/web/packages.el @@ -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) diff --git a/modules/org/org-attach/autoload/org-attach.el b/modules/org/org-attach/autoload/org-attach.el index 2d6a670bc..783ee2d99 100644 --- a/modules/org/org-attach/autoload/org-attach.el +++ b/modules/org/org-attach/autoload/org-attach.el @@ -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)) diff --git a/modules/org/org-capture/autoload/evil.el b/modules/org/org-capture/autoload/evil.el index d1b01841e..7becec046 100644 --- a/modules/org/org-capture/autoload/evil.el +++ b/modules/org/org-capture/autoload/evil.el @@ -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 "") - (+org-capture/dwim + (+org-capture/open (unless (or (evil-normal-state-p) (evil-insert-state-p)) (buffer-substring beg end)))) diff --git a/modules/org/org-capture/autoload/org-capture.el b/modules/org/org-capture/autoload/org-capture.el index ad1c72a71..2c884357a 100644 --- a/modules/org/org-capture/autoload/org-capture.el +++ b/modules/org/org-capture/autoload/org-capture.el @@ -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) "")) diff --git a/modules/org/org-capture/config.el b/modules/org/org-capture/config.el index a4baf2f96..d52f60b7e 100644 --- a/modules/org/org-capture/config.el +++ b/modules/org/org-capture/config.el @@ -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))) diff --git a/modules/org/org-notebook/autoload.el b/modules/org/org-notebook/autoload.el deleted file mode 100644 index 2f98210fb..000000000 --- a/modules/org/org-notebook/autoload.el +++ /dev/null @@ -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)) diff --git a/modules/org/org-notebook/config.el b/modules/org/org-notebook/config.el deleted file mode 100644 index 3d6e1f891..000000000 --- a/modules/org/org-notebook/config.el +++ /dev/null @@ -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 ()) diff --git a/modules/org/org/autoload/org.el b/modules/org/org/autoload/org.el index 18a1c6df5..9c293b075 100644 --- a/modules/org/org/autoload/org.el +++ b/modules/org/org/autoload/org.el @@ -8,8 +8,9 @@ :group 'evil-org (setq org-hide-emphasis-markers +org-pretty-mode) (org-toggle-pretty-entities) - ;; In case the above un-align tables - (org-table-map-tables 'org-table-align t)) + (org-with-silent-modifications + ;; In case the above un-align tables + (org-table-map-tables 'org-table-align t))) ;;;###autoload (defun +org|realign-table-maybe () @@ -45,6 +46,10 @@ If on a: (let* ((scroll-pt (window-start)) (context (org-element-context)) (type (org-element-type context))) + ;; skip over unimportant contexts + (while (and context (memq type '(verbatim code bold italic underline strike-through subscript superscript))) + (setq context (org-element-property :parent context) + type (org-element-type context))) (pcase type ((guard (org-element-property :checkbox (org-element-lineage context '(item) t))) (let ((match (and (org-at-item-checkbox-p) (match-string 1)))) @@ -180,8 +185,10 @@ wrong places)." (- (point) (line-beginning-position))))) (pcase direction ('below - (goto-char (line-end-position)) - (insert (concat "\n" (make-string pad ? ) marker))) + (org-end-of-item) + (goto-char (line-beginning-position)) + (insert (make-string pad 32) (or marker "")) + (save-excursion (insert "\n"))) ('above (goto-char (line-beginning-position)) (insert (make-string pad 32) (or marker "")) @@ -217,7 +224,6 @@ wrong places)." (org-back-to-heading) (org-insert-heading) (when (= level 1) - (save-excursion (evil-open-above 1)) (save-excursion (insert "\n"))))) (when (org-element-property :todo-type context) (org-todo 'todo)))) @@ -276,3 +282,18 @@ with `org-cycle'). Also: (let ((window-beg (window-start))) (org-cycle) (set-window-start nil window-beg)))))) + +;;;###autoload +(defun +org/remove-link () + "Unlink the text at point." + (interactive) + (unless (org-in-regexp org-bracket-link-regexp 1) + (user-error "No link at point")) + (save-excursion + (let ((remove (list (match-beginning 0) (match-end 0))) + (description (if (match-end 3) + (org-match-string-no-properties 3) + (org-match-string-no-properties 1)))) + (apply #'delete-region remove) + (insert description)))) + diff --git a/modules/org/org/config.el b/modules/org/org/config.el index de0ed06c3..69afdc5dd 100644 --- a/modules/org/org/config.el +++ b/modules/org/org/config.el @@ -2,7 +2,8 @@ ;; Ensure ELPA org is prioritized above built-in org. (when-let (path (locate-library "org" nil doom--package-load-path)) - (cl-pushnew (file-name-directory path) load-path :test #'equal)) + (setq load-path (delete path load-path)) + (push (file-name-directory path) load-path)) ;; Custom variables (defvar +org-dir (expand-file-name "~/work/org/") @@ -55,6 +56,7 @@ ;; (setq line-spacing 1) (visual-line-mode +1) + (org-indent-mode +1) (doom|disable-line-numbers) ;; show-paren-mode causes problems for org-indent-mode, so disable it @@ -97,7 +99,6 @@ org-agenda-skip-unavailable-files nil org-cycle-include-plain-lists t org-cycle-separator-lines 1 - ;; org-ellipsis "  " org-entities-user '(("flat" "\\flat" nil "" "" "266D" "♭") ("sharp" "\\sharp" nil "" "" "266F" "♯")) org-ellipsis "  " org-fontify-done-headline t @@ -113,6 +114,10 @@ org-indent-mode-turns-on-hiding-stars t org-pretty-entities nil org-pretty-entities-include-sub-superscripts t + org-priority-faces + `((?a . ,(face-foreground 'error)) + (?b . ,(face-foreground 'warning)) + (?c . ,(face-foreground 'success))) org-startup-folded t org-startup-indented t org-startup-with-inline-images nil @@ -143,7 +148,10 @@ "Sets up org-mode and evil keybindings. Tries to fix the idiosyncrasies between the two." (map! (:map org-mode-map - "RET" #'org-return-indent) + "RET" #'org-return-indent + "C-c C-S-l" #'+org/remove-link + :n "j" "gj" + :n "k" "gk") (:map +org-evil-mode-map :n "RET" #'+org/dwim-at-point @@ -213,9 +221,9 @@ between the two." (defun +org|remove-occur-highlights () "Remove org occur highlights on ESC in normal mode." - (when (derived-mode-p 'org-mode) - (org-remove-occur-highlights) - t)) + (when (and (derived-mode-p 'org-mode) + org-occur-highlights) + (org-remove-occur-highlights))) (add-hook '+evil-esc-hook #'+org|remove-occur-highlights) (after! recentf diff --git a/modules/org/org/test/autoload-org.el b/modules/org/org/test/autoload-org.el new file mode 100644 index 000000000..d9ac443d0 --- /dev/null +++ b/modules/org/org/test/autoload-org.el @@ -0,0 +1,42 @@ +;; -*- no-byte-compile: t; -*- +;;; org/org/test/autoload-org.el + +(defmacro should-org-buffer! (source expected &rest body) + `(should-buffer! ,source ,expected + (org-mode) + ,@body)) + + +;; `+org/insert-item' +(def-test! insert-item-h1 + "Should append/prepend new first-level headers with an extra newline." + (should-org-buffer! ("* {0}Header") ("* Header\n\n* {|}") + (+org/insert-item 'below)) + (should-org-buffer! ("* {0}Header") ("* {|}\n\n* Header") + (+org/insert-item 'above))) + +(def-test! insert-item-h2 + "Should append/prepend new second-level (and higher) headers without an extra +newline." + (should-org-buffer! ("** {0}Header") ("** Header\n** {|}") + (+org/insert-item 'below)) + (should-org-buffer! ("** {0}Header") ("** {|}\n** Header") + (+org/insert-item 'above))) + +(def-test! insert-item-plain-list + "Should append/prepend new second-level (and higher) headers without an extra +newline." + (should-org-buffer! ("+ {0}List item") ("+ List item\n+ {|}") + (+org/insert-item 'below)) + (should-org-buffer! ("+ {0}List item" + " + Sub item") + ("+ List item" + " + Sub item" + "+ {|}") + (+org/insert-item 'below)) + (should-org-buffer! ("+ {0}List item" + "+ Next item") + ("+ List item" + "+ {|}" + "+ Next item") + (+org/insert-item 'below))) diff --git a/modules/org/org/test/org.el b/modules/org/org/test/org.el new file mode 100644 index 000000000..d91044ff9 --- /dev/null +++ b/modules/org/org/test/org.el @@ -0,0 +1,5 @@ +;;; org/org/test/org.el -*- lexical-binding: t; -*- + +(when (featurep 'org) (unload-feature 'org t)) +(require! :org org) +(require 'org (locate-library "org" nil doom--package-load-path)) diff --git a/modules/private/hlissner/+bindings.el b/modules/private/hlissner/+bindings.el index 2aafddac0..3ca86b7b1 100644 --- a/modules/private/hlissner/+bindings.el +++ b/modules/private/hlissner/+bindings.el @@ -23,18 +23,21 @@ :nvime "M-x" #'execute-extended-command :nvime "A-x" #'execute-extended-command ;; Emacs debug utilities - "M-;" #'eval-expression - "A-;" #'eval-expression + "M-;" #'eval-expression + :nvime "M-;" #'eval-expression + "M-:" #'doom/open-scratch-buffer + :nvime "M-:" #'doom/open-scratch-buffer ;; Text-scaling "M-+" (λ! (text-scale-set 0)) "M-=" #'text-scale-increase "M--" #'text-scale-decrease ;; Simple window navigation/manipulation "C-`" #'doom/popup-toggle + "C-~" #'doom/popup-raise "M-t" #'+workspace/new "M-T" #'+workspace/display "M-w" #'delete-window - "M-W" #'delete-frame + "M-W" #'+workspace/close-workspace-or-frame "M-n" #'evil-buffer-new "M-N" #'make-frame "M-1" (λ! (+workspace/switch-to 0)) @@ -75,8 +78,8 @@ (:leader :desc "Ex command" :nv ";" #'evil-ex :desc "M-x" :nv ":" #'execute-extended-command - :desc "Pop up scratch buffer" :nv "x" #'doom/scratch-buffer - :desc "Org Capture" :nv "X" #'+org/capture + :desc "Pop up scratch buffer" :nv "x" #'doom/open-scratch-buffer + :desc "Org Capture" :nv "X" #'+org-capture/open ;; Most commonly used :desc "Find file in project" :n "SPC" #'projectile-find-file @@ -151,7 +154,7 @@ :desc "Kill buffer" :n "k" #'doom/kill-this-buffer :desc "Kill other buffers" :n "o" #'doom/kill-other-buffers :desc "Save buffer" :n "s" #'save-buffer - :desc "Pop scratch buffer" :n "x" #'doom/scratch-buffer + :desc "Pop scratch buffer" :n "x" #'doom/open-scratch-buffer :desc "Bury buffer" :n "z" #'bury-buffer :desc "Next buffer" :n "]" #'doom/next-buffer :desc "Previous buffer" :n "[" #'doom/previous-buffer @@ -165,7 +168,7 @@ :desc "Build tasks" :nv "b" #'+eval/build :desc "Jump to definition" :n "d" #'+jump/definition :desc "Jump to references" :n "D" #'+jump/references - :desc "Open REPL" :n "r" #'+eval/repl + :desc "Open REPL" :n "r" #'+eval/open-repl :v "r" #'+eval:repl) (:desc "file" :prefix "f" @@ -188,6 +191,7 @@ :desc "Git blame" :n "b" #'magit-blame :desc "Git time machine" :n "t" #'git-timemachine-toggle :desc "Git revert hunk" :n "r" #'git-gutter:revert-hunk + :desc "Git revert buffer" :n "R" #'vc-revert :desc "List gists" :n "g" #'+gist:list :desc "Next hunk" :nv "]" #'git-gutter:next-hunk :desc "Previous hunk" :nv "[" #'git-gutter:previous-hunk) @@ -222,18 +226,18 @@ (:desc "notes" :prefix "n" :desc "Find file in notes" :n "n" #'+hlissner/find-in-notes :desc "Browse notes" :n "N" #'+hlissner/browse-notes - :desc "Org capture" :n "x" #'+org/capture + :desc "Org capture" :n "x" #'+org-capture/open :desc "Browse mode notes" :n "m" #'+org/browse-notes-for-major-mode :desc "Browse project notes" :n "p" #'+org/browse-notes-for-project) (:desc "open" :prefix "o" :desc "Default browser" :n "b" #'browse-url-of-file :desc "Debugger" :n "d" #'+debug/open - :desc "REPL" :n "r" #'+eval/repl + :desc "REPL" :n "r" #'+eval/open-repl :v "r" #'+eval:repl :desc "Neotree" :n "n" #'+neotree/toggle - :desc "Terminal" :n "t" #'+term/popup - :desc "Terminal in project" :n "T" #'+term/popup-in-project + :desc "Terminal" :n "t" #'+term/open-popup + :desc "Terminal in project" :n "T" #'+term/open-popup-in-project ;; applications :desc "APP: elfeed" :n "E" #'=rss @@ -257,7 +261,7 @@ :desc "Switch project" :n "p" #'projectile-switch-project :desc "Recent project files" :n "r" #'projectile-recentf :desc "List project tasks" :n "t" #'+ivy/tasks - :desc "Pop term in project" :n "o" #'+term/popup-in-project + :desc "Pop term in project" :n "o" #'+term/open-popup-in-project :desc "Invalidate cache" :n "x" #'projectile-invalidate-cache) (:desc "quit" :prefix "q" @@ -381,8 +385,6 @@ ;; counsel (:after counsel - (:map ivy-mode-map - "C-o" #'ivy-dispatching-done) (:map counsel-ag-map [backtab] #'+ivy/wgrep-occur ; search/replace on results "C-SPC" #'counsel-git-grep-recenter ; preview @@ -407,12 +409,14 @@ (:prefix "gz" :nv "m" #'evil-mc-make-all-cursors :nv "u" #'evil-mc-undo-all-cursors - :nv "z" #'+evil/mc-toggle-cursors - :nv "c" #'+evil/mc-make-cursor-here + :nv "z" #'+evil/mc-make-cursor-here + :nv "t" #'+evil/mc-toggle-cursors :nv "n" #'evil-mc-make-and-goto-next-cursor :nv "p" #'evil-mc-make-and-goto-prev-cursor :nv "N" #'evil-mc-make-and-goto-last-cursor - :nv "P" #'evil-mc-make-and-goto-first-cursor) + :nv "P" #'evil-mc-make-and-goto-first-cursor + :nv "d" #'evil-mc-make-and-goto-next-match + :nv "D" #'evil-mc-make-and-goto-prev-match) (:after evil-mc :map evil-mc-key-map :nv "C-n" #'evil-mc-make-and-goto-next-cursor @@ -726,7 +730,6 @@ ;; properly, more like vim, or how I like it. (map! (:map input-decode-map - [?\C-i] [C-i] [S-iso-lefttab] [backtab] (:unless window-system "TAB" [tab])) ; Fix TAB in terminal @@ -763,14 +766,23 @@ :i "C-e" #'org-end-of-line :i "C-a" #'org-beginning-of-line)) - ;; Make ESC quit all the things + ;; Restore common editing keys (and ESC) in minibuffer (:map (minibuffer-local-map minibuffer-local-ns-map minibuffer-local-completion-map minibuffer-local-must-match-map - minibuffer-local-isearch-map) + minibuffer-local-isearch-map + evil-ex-completion-map + evil-ex-search-keymap + read-expression-map) [escape] #'abort-recursive-edit - "C-r" #'evil-paste-from-register) + "C-r" #'evil-paste-from-register + "C-a" #'move-beginning-of-line + "C-w" #'doom/minibuffer-kill-word + "C-u" #'doom/minibuffer-kill-line + "C-b" #'backward-word + "C-f" #'forward-word + "M-z" #'doom/minibuffer-undo) (:map messages-buffer-mode-map "M-;" #'eval-expression @@ -779,13 +791,5 @@ (:map tabulated-list-mode-map [remap evil-record-macro] #'doom/popup-close-maybe) - (:map (evil-ex-completion-map evil-ex-search-keymap read-expression-map) - "C-a" #'move-beginning-of-line - "C-w" #'doom/minibuffer-kill-word - "C-u" #'doom/minibuffer-kill-line - "C-b" #'backward-word - "C-f" #'forward-word - "M-z" #'doom/minibuffer-undo) - (:after view (:map view-mode-map "" #'View-quit-all))) diff --git a/modules/private/hlissner/+commands.el b/modules/private/hlissner/+commands.el index fe6a9c09f..260a4d761 100644 --- a/modules/private/hlissner/+commands.el +++ b/modules/private/hlissner/+commands.el @@ -7,17 +7,13 @@ ;;(ex! "g[lobal]" #'+evil:global) ;;; Custom commands -;; Emacs utilities -(ex! "bc[omp]" #'+hlissner:byte-compile) -(ex! "re[load]" #'doom/reload) -(ex! "re[load]au" #'doom/reload-autoloads) - ;; Editing (ex! "@" #'+evil:macro-on-all-lines) ; TODO Test me (ex! "al[ign]" #'+evil:align) (ex! "enhtml" #'+web:encode-html-entities) (ex! "dehtml" #'+web:decode-html-entities) (ex! "mc" #'+evil:mc) +(ex! "iedit" #'evil-multiedit-ex-match) (ex! "na[rrow]" #'+evil:narrow-buffer) (ex! "retab" #'+evil:retab) @@ -32,9 +28,7 @@ (ex! "sh[ell]" #'+eshell:run) (ex! "t[mux]" #'+tmux:run) ; send to tmux (ex! "tcd" #'+tmux:cd-here) ; cd to default-directory in tmux - -(evil-set-command-properties #'doom/scratch-buffer :ex-bang t) -(ex! "x" #'doom/scratch-buffer) +(ex! "x" #'doom/open-project-scratch-buffer) ;; GIT (ex! "gist" #'+gist:send) ; send current buffer/region to gist @@ -48,9 +42,7 @@ (ex! "grevert" #'git-gutter:revert-hunk) ;; Dealing with buffers -(evil-set-command-properties #'+workspace/cleanup :ex-bang t) - -(ex! "clean[up]" #'+workspace/cleanup) +(ex! "clean[up]" #'doom/cleanup-buffers) (ex! "k[ill]" #'doom/kill-this-buffer) (ex! "k[ill]all" #'+hlissner:kill-all-buffers) (ex! "k[ill]m" #'+hlissner:kill-matching-buffers) @@ -103,4 +95,4 @@ (ex! "tabsave" #'+workspace:save) ;; Org-mode -(ex! "org" #'+org:capture) +(ex! "cap" #'+org-capture/dwim) diff --git a/modules/private/hlissner/config.el b/modules/private/hlissner/config.el index 31ffa9019..469399338 100644 --- a/modules/private/hlissner/config.el +++ b/modules/private/hlissner/config.el @@ -8,7 +8,8 @@ (defvar +hlissner-snippets-dir (expand-file-name "snippets/" +hlissner-dir)) (setq epa-file-encrypt-to user-mail-address - auth-sources (list (expand-file-name ".authinfo.gpg" +hlissner-dir))) + auth-sources (list (expand-file-name ".authinfo.gpg" +hlissner-dir)) + +doom-modeline-buffer-file-name-style 'relative-from-project) (defun +hlissner*no-authinfo-for-tramp (orig-fn &rest args) "Don't look into .authinfo for local sudo TRAMP buffers." diff --git a/modules/tools/eshell/autoload/eshell.el b/modules/tools/eshell/autoload/eshell.el index 33d219037..7029f74ba 100644 --- a/modules/tools/eshell/autoload/eshell.el +++ b/modules/tools/eshell/autoload/eshell.el @@ -1,27 +1,39 @@ ;;; tools/eshell/autoload/eshell.el -*- lexical-binding: t; -*- -(require 'eshell) +(defvar +eshell-buffers () + "List of open eshell buffers.") + +(defvar +eshell-buffer-name "*doom:eshell*" + "The name to use for custom eshell buffers. This only affects `+eshell/open', +`+eshell/open-popup' and `+eshell/open-workspace'.") + + +;; --- Commands --------------------------- ;;;###autoload -(defun +eshell/run () +(defun +eshell/open (&optional command) "Open eshell in the current buffer." (interactive) - (let ((buf (generate-new-buffer eshell-buffer-name))) + (let ((buf (generate-new-buffer +eshell-buffer-name))) (with-current-buffer buf (unless (eq major-mode 'eshell-mode) (eshell-mode))) - (pop-to-buffer-same-window buf))) + (switch-to-buffer buf) + (when command + (+eshell-run-command command)))) ;;;###autoload -(defun +eshell/popup () +(defun +eshell/open-popup (&optional command) "Open eshell in a popup window." (interactive) - (let ((buf (get-buffer-create "*eshell:popup*"))) + (let ((buf (get-buffer-create +eshell-buffer-name))) (with-current-buffer buf (unless (eq major-mode 'eshell-mode) (eshell-mode))) - (doom-popup-buffer buf))) + (doom-popup-buffer buf '(:autokill t) t) + (when command + (+eshell-run-command command)))) ;;;###autoload -(defun +eshell/tab () +(defun +eshell/open-workspace (&optional command) "Open eshell in a separate workspace. Requires the (:feature workspaces) module to be loaded." (interactive) @@ -29,11 +41,42 @@ module to be loaded." (user-error ":feature workspaces is required, but disabled")) (unless (+workspace-get "eshell" t) (+workspace/new "eshell")) - (if-let (buf (cl-find-if (lambda (it) (string-match-p "^\\*eshell" (buffer-name (window-buffer it)))) + (if-let (buf (cl-find-if (lambda (it) (string-match-p "^\\*doom:eshell" (buffer-name (window-buffer it)))) (doom-visible-windows))) (select-window (get-buffer-window buf)) - (+eshell/run)) - (doom/workspace-display)) + (+eshell/open)) + (doom/workspace-display) + (when command + (+eshell-run-command command))) + +(defun +eshell-run-command (command) + (unless (cl-remove-if-not #'buffer-live-p +eshell-buffers) + (user-error "No living eshell buffers available")) + (with-current-buffer (car +eshell-buffers) + (goto-char eshell-last-output-end) + (when (bound-and-true-p evil-mode) + (call-interactively #'evil-append-line)) + (insert command) + (eshell-send-input nil t))) + + +;; --- Hooks ------------------------------ + +;;;###autoload +(defun +eshell|init () + "Keep track of eshell buffers." + (cl-pushnew (current-buffer) +eshell-buffers :test #'eq)) + +;;;###autoload +(defun +eshell|cleanup () + "Close window (or workspace) on quit." + (setq +eshell-buffers (delete (current-buffer) +eshell-buffers)) + (when (and (featurep! :feature workspaces) + (string= "eshell" (+workspace-current-name))) + (+workspace/delete "eshell"))) + + +;; --- Keybindings ------------------------ ;;;###autoload (defun +eshell/quit-or-delete-char (arg) @@ -57,16 +100,16 @@ module to be loaded." (defun +eshell/split () (interactive) (select-window (split-window-vertically)) - (+eshell:run)) + (+eshell/open)) ;;;###autoload (defun +eshell/vsplit () (interactive) (select-window (split-window-horizontally)) - (+eshell:run)) + (+eshell/open)) ;;;###autoload -(defun +eshell/prompt () +(defun +eshell-prompt () (concat (propertize (abbreviate-file-name (eshell/pwd)) 'face 'eshell-prompt) (propertize (+eshell--current-git-branch) 'face 'font-lock-function-name-face) (propertize " λ " 'face 'font-lock-constant-face))) diff --git a/modules/tools/eshell/autoload/evil.el b/modules/tools/eshell/autoload/evil.el index 2b7902a2a..5ef5f8ee0 100644 --- a/modules/tools/eshell/autoload/evil.el +++ b/modules/tools/eshell/autoload/evil.el @@ -1,10 +1,10 @@ ;;; tools/eshell/autoload/evil.el -*- lexical-binding: t; -*- -;;;###autoload (autoload '+eshell:run "emacs/eshell/autoload/evil" nil t) -(evil-define-command +eshell:run (_command bang) +;;;###autoload (autoload '+eshell:run "tools/eshell/autoload/evil" nil t) +(evil-define-command +eshell:run (command bang) ;; TODO Add COMMAND support (interactive "") (if bang - (+eshell/run) - (+eshell/popup))) + (+eshell/open command) + (+eshell/open-popup command))) diff --git a/modules/tools/eshell/config.el b/modules/tools/eshell/config.el index c999c4996..207d2f873 100644 --- a/modules/tools/eshell/config.el +++ b/modules/tools/eshell/config.el @@ -3,14 +3,12 @@ ;; This is highly experimental. I don't use eshell often, so this may need work. ;; see: -;; + `+eshell/run': open in current buffer -;; + `+eshell/tab': open in separate tab (requires :feature workspaces) -;; + `+eshell/popup': open in a popup - -(defvar +eshell-buffers '() - "TODO") +;; + `+eshell/open': open in current buffer +;; + `+eshell/open-popup': open in a popup +;; + `+eshell/open-workspace': open in separate tab (requires :feature workspaces) (def-package! eshell ; built-in + :commands eshell-mode :init (setq eshell-directory-name (concat doom-cache-dir "/eshell") eshell-scroll-to-bottom-on-input 'all @@ -19,7 +17,7 @@ eshell-kill-processes-on-exit t ;; em-prompt eshell-prompt-regexp "^.* λ " - eshell-prompt-function #'+eshell/prompt + eshell-prompt-function #'+eshell-prompt ;; em-glob eshell-glob-case-insensitive t eshell-error-if-no-glob t @@ -27,17 +25,20 @@ eshell-aliases-file (concat doom-local-dir ".eshell-aliases")) :config - (set! :popup "^\\*eshell:popup\\*$" :regexp t :size 25) (set! :evil-state 'eshell-mode 'insert) + ;; Keep track of open eshell buffers + (add-hook 'eshell-mode-hook #'+eshell|init) + (add-hook 'eshell-exit-hook #'+eshell|cleanup) + (after! em-term ;; Visual commands require a proper terminal. Eshell can't handle that, so it ;; delegates these commands to a term buffer. (nconc eshell-visual-commands '("tmux" "htop" "bash" "zsh" "fish" "vim" "nvim")) (setq eshell-visual-subcommands '(("git" "log" "l" "diff" "show")))) - (defun +eshell|keymap-setup () - "Setup eshell keybindings. This must be done in a hook because eshell + (defun +eshell|init-keymap () + "Setup eshell keybindings. This must be done in a hook because eshell-mode redefines its keys every time `eshell-mode' is enabled." (map! :map eshell-mode-map :n "i" #'+eshell/evil-prepend-maybe @@ -48,37 +49,23 @@ redefines its keys every time `eshell-mode' is enabled." :n "R" #'+eshell/evil-replace-state-maybe :n "c" #'+eshell/evil-change :n "C" #'+eshell/evil-change-line - :i "" #'eshell-pcomplete - :i "C-u" #'eshell-kill-input + :i [tab] #'eshell-pcomplete :i "SPC" #'self-insert-command + :i "C-u" #'eshell-kill-input :i "C-a" #'eshell-bol :i "C-d" #'+eshell/quit-or-delete-char :i "C-k" #'kill-line :i "C-p" #'eshell-previous-input :i "" #'eshell-previous-input - :i "C-n" #'eshell-previous-input - :i "" #'eshell-previous-input + :i "C-n" #'eshell-next-input + :i "" #'eshell-next-input :m "" #'+eshell/evil-append - :n [remap evil-window-split] #'+eshell/split - :n [remap evil-window-vsplit] #'+eshell/vsplit - :n [remap evil-record-macro] #'eshell-life-is-too-much - [remap doom/close-window-or-tab] #'eshell-life-is-too-much)) - (add-hook 'eshell-mode-hook #'+eshell|keymap-setup) - - (defun +eshell|cleanup () - "Close window (or workspace) on quit." - (setq +eshell-buffers (delete (current-buffer) +eshell-buffers)) - (cond ((doom-popup-p) - (delete-window)) - ((and (featurep! :feature workspaces) - (string= "eshell" (+workspace-current-name))) - (+workspace/close-window-or-workspace)))) - (add-hook 'eshell-exit-hook #'+eshell|cleanup) - - (defun +eshell|init () - "Keep track of eshell buffers." - (add-to-list '+eshell-buffers (current-buffer))) - (add-hook 'eshell-mode-hook #'+eshell|init) + :n [remap evil-window-split] #'+eshell/split + :n [remap evil-window-vsplit] #'+eshell/vsplit + :n [remap evil-record-macro] #'eshell-life-is-too-much + [remap kill-this-buffer] #'eshell-life-is-too-much + [remap +workspace/close-window-or-workspace] #'eshell-life-is-too-much)) + (add-hook 'eshell-mode-hook #'+eshell|init-keymap) (add-hook! eshell-mode (add-hook 'evil-insert-state-exit-hook #'hl-line-mode nil t) @@ -91,15 +78,5 @@ redefines its keys every time `eshell-mode' is enabled." ("ll" "ls -l") ("la" "ls -la") ("g" "hub") - ("gs" "hub status --oneline .") - ("gss" "hub status --oneline"))) - - ;; Custom commands - ;; (defun eshell/e (file) - ;; (eshell-eval (cond ((doom/popup-p) - ;; (doom/popup-save (find-file file)) - ;; 0) - ;; (t (find-file file) - ;; 0)))) - ) + ("gs" "hub status --short .")))) diff --git a/modules/tools/imenu/config.el b/modules/tools/imenu/config.el new file mode 100644 index 000000000..cbef99643 --- /dev/null +++ b/modules/tools/imenu/config.el @@ -0,0 +1,25 @@ +;;; tools/imenu/config.el -*- lexical-binding: t; -*- + +(def-package! imenu-anywhere + :commands (ido-imenu-anywhere ivy-imenu-anywhere helm-imenu-anywhere) + :config (setq imenu-anywhere-delimiter ": ")) + + +(def-package! imenu-list + :commands imenu-list-minor-mode + :config + (setq imenu-list-focus-after-activation t) + (set! :popup imenu-list-buffer-name :size 35 :align 'right) + + ;; use popups + (defun doom*imenu-list-show () + (doom-popup-buffer (get-buffer imenu-list-buffer-name))) + (advice-add #'imenu-list-show :override #'doom*imenu-list-show) + (advice-add #'imenu-list-show-noselect :override #'doom*imenu-list-show) + + ;; auto kill imenu-list on deactivation + (defun doom|kill-imenu-list () + (when (and (not imenu-list-minor-mode) + (get-buffer imenu-list-buffer-name)) + (kill-buffer (get-buffer imenu-list-buffer-name)))) + (add-hook 'imenu-list-minor-mode-hook #'doom|kill-imenu-list)) diff --git a/modules/tools/imenu/packages.el b/modules/tools/imenu/packages.el new file mode 100644 index 000000000..0328b7016 --- /dev/null +++ b/modules/tools/imenu/packages.el @@ -0,0 +1,5 @@ +;; -*- no-byte-compile: t; -*- +;;; tools/imenu/packages.el + +(package! imenu-anywhere) +(package! imenu-list) diff --git a/modules/tools/make/autoload.el b/modules/tools/make/autoload.el new file mode 100644 index 000000000..ce8de9d3a --- /dev/null +++ b/modules/tools/make/autoload.el @@ -0,0 +1,14 @@ +;;; tools/make/autoload.el -*- lexical-binding: t; -*- + +;;;###autoload +(defun +make/run () + "Run a make task in the current project." + (interactive) + (require 'makefile-executor) + (let* ((buffer-file (or buffer-file-name default-directory)) + (makefile-dir (locate-dominating-file buffer-file "Makefile"))) + (unless makefile-dir + (user-error "No makefile found in this project.")) + (let ((default-directory makefile-dir)) + (makefile-executor-execute-target + (expand-file-name "Makefile"))))) diff --git a/modules/tools/make/packages.el b/modules/tools/make/packages.el new file mode 100644 index 000000000..422d37597 --- /dev/null +++ b/modules/tools/make/packages.el @@ -0,0 +1,4 @@ +;; -*- no-byte-compile: t; -*- +;;; tools/make/packages.el + +(package! makefile-executor) diff --git a/modules/tools/neotree/autoload.el b/modules/tools/neotree/autoload.el index 74358725c..cfc12e91c 100644 --- a/modules/tools/neotree/autoload.el +++ b/modules/tools/neotree/autoload.el @@ -13,7 +13,8 @@ ((not (and (neo-global--window-exists-p) (equal (file-truename (neo-global--with-buffer neo-buffer--start-node)) (file-truename project-root)))) - (neotree-dir project-root)) + (neotree-dir project-root) + (neotree-find path project-root)) (t (neotree-find path project-root))))) ;;;###autoload diff --git a/modules/tools/prodigy/config.el b/modules/tools/prodigy/config.el deleted file mode 100644 index 318807883..000000000 --- a/modules/tools/prodigy/config.el +++ /dev/null @@ -1,9 +0,0 @@ -;;; tools/prodigy/config.el -*- lexical-binding: t; -*- - -;; -;; Plugins -;; - -(def-package! prodigy - :config - (set! :evil-state 'prodigy-mode 'emacs)) diff --git a/modules/tools/rgb/config.el b/modules/tools/rgb/config.el index 70132f8e3..b69768ce7 100644 --- a/modules/tools/rgb/config.el +++ b/modules/tools/rgb/config.el @@ -10,15 +10,13 @@ (def-package! kurecolor :after rainbow-mode :config - (when (featurep! :feature hydra) - (defhydra hydra-kurecolor (:color pink - :hint nil) - " + (def-hydra! +rgb@kurecolor (:color pink :hint nil) + " Inc/Dec _w_/_W_ brightness _d_/_D_ saturation _e_/_E_ hue " - ("w" kurecolor-decrease-brightness-by-step) - ("W" kurecolor-increase-brightness-by-step) - ("d" kurecolor-decrease-saturation-by-step) - ("D" kurecolor-increase-saturation-by-step) - ("e" kurecolor-decrease-hue-by-step) - ("E" kurecolor-increase-hue-by-step) - ("q" nil "cancel" :color blue)))) + ("w" kurecolor-decrease-brightness-by-step) + ("W" kurecolor-increase-brightness-by-step) + ("d" kurecolor-decrease-saturation-by-step) + ("D" kurecolor-increase-saturation-by-step) + ("e" kurecolor-decrease-hue-by-step) + ("E" kurecolor-increase-hue-by-step) + ("q" nil "cancel" :color blue))) diff --git a/modules/tools/term/autoload.el b/modules/tools/term/autoload.el index 88bec697a..efab41c95 100644 --- a/modules/tools/term/autoload.el +++ b/modules/tools/term/autoload.el @@ -1,7 +1,7 @@ ;;; tools/term/autoload.el -*- lexical-binding: t; -*- ;;;###autoload -(defun +term (&optional project-root) +(defun +term/open (&optional project-root) "Open a terminal buffer in the current window. If PROJECT-ROOT (C-u) is non-nil, cd into the current project's root." (interactive "P") @@ -9,7 +9,7 @@ non-nil, cd into the current project's root." (call-interactively #'multi-term))) ;;;###autoload -(defun +term/popup (&optional project-root) +(defun +term/open-popup (&optional project-root) "Open a terminal popup window. If PROJECT-ROOT (C-u) is non-nil, cd into the current project's root." (interactive "P") @@ -21,7 +21,7 @@ current project's root." (multi-term-internal))) ;;;###autoload -(defun +term/popup-in-project () +(defun +term/open-popup-in-project () "Open a terminal popup window in the root of the current project." (interactive) - (+term/popup t)) + (+term/open-popup t)) diff --git a/modules/ui/doom-dashboard/config.el b/modules/ui/doom-dashboard/config.el index 565724ea5..6ce0ff134 100644 --- a/modules/ui/doom-dashboard/config.el +++ b/modules/ui/doom-dashboard/config.el @@ -1,10 +1,7 @@ ;;; ui/doom-dashboard/config.el -*- lexical-binding: t; -*- (defvar +doom-dashboard-name " *doom*" - "TODO") - -(defvar +doom-dashboard-modeline nil - "TODO") + "The name to use for the dashboard buffer.") (defvar +doom-dashboard-inhibit-refresh nil "If non-nil, the doom buffer won't be refreshed.") @@ -12,26 +9,26 @@ (defvar +doom-dashboard-widgets '(banner shortmenu loaded) "List of widgets to display in a blank scratch buffer.") -(define-derived-mode +doom-dashboard-mode special-mode - (concat "v" doom-version) - "Major mode for the DOOM dashboard buffer." - (read-only-mode +1) - (setq truncate-lines t - mode-line-format +doom-dashboard-modeline) - (cl-loop for (car . _cdr) in fringe-indicator-alist - collect (cons car nil) into alist - finally do (setq fringe-indicator-alist alist))) +(defvar +doom-dashboard-inhibit-functions () + "A list of functions that determine whether to inhibit the dashboard the +loading.") - -(defvar +doom-dashboard--width 0) +(defvar +doom-dashboard--width 80) (defvar +doom-dashboard--height 0) (defvar +doom-dashboard--old-fringe-indicator fringe-indicator-alist) -(defvar +doom-dashboard--old-modeline nil) (setq doom-fallback-buffer +doom-dashboard-name) -;; +(define-derived-mode +doom-dashboard-mode special-mode + (format "DOOM v%s" doom-version) + "Major mode for the DOOM dashboard buffer." + (read-only-mode +1) + (setq truncate-lines t) + (cl-loop for (car . _cdr) in fringe-indicator-alist + collect (cons car nil) into alist + finally do (setq fringe-indicator-alist alist))) + (map! :map +doom-dashboard-mode-map "n" #'+doom-dashboard/next-button "p" #'+doom-dashboard/previous-button @@ -53,9 +50,11 @@ "Initialize doom-dashboard and set up its hooks; possibly open the dashboard if in a GUI/non-daemon session." (add-hook 'window-configuration-change-hook #'+doom-dashboard-reload) + (add-hook 'focus-in-hook #'+doom-dashboard-reload) (add-hook 'kill-buffer-query-functions #'+doom-dashboard|kill-buffer-query-fn) (when (and (display-graphic-p) (not (daemonp))) - (+doom-dashboard/open (selected-frame)))) + (let ((default-directory doom-emacs-dir)) + (+doom-dashboard/open (selected-frame))))) (defun +doom-dashboard|kill-buffer-query-fn () (or (not (+doom-dashboard-p)) @@ -74,17 +73,19 @@ whose dimensions may not be fully initialized by the time this is run." (add-hook 'window-setup-hook #'+doom-dashboard|init) (add-hook 'after-make-frame-functions #'+doom-dashboard|make-frame) (add-hook 'server-visit-hook #'+doom-dashboard|server-visit) -(add-hook '+doom-dashboard-mode-hook #'doom|disable-vi-tilde-fringe) +(when (featurep! :ui vi-tilde-fringe) + (add-hook '+doom-dashboard-mode-hook #'+vi-tilde-fringe|disable)) ;; (defun +doom-dashboard/open (frame) (interactive (list (selected-frame))) - (unless +doom-dashboard-inhibit-refresh - (with-selected-frame frame - (switch-to-buffer (doom-fallback-buffer)) - (+doom-dashboard-reload))) - (setq +doom-dashboard-inhibit-refresh nil)) + (unless (run-hook-with-args-until-success '+doom-dashboard-inhibit-functions) + (unless +doom-dashboard-inhibit-refresh + (with-selected-frame frame + (switch-to-buffer (doom-fallback-buffer)) + (+doom-dashboard-reload))) + (setq +doom-dashboard-inhibit-refresh nil))) (defun +doom-dashboard-p (&optional buffer) "Returns t if BUFFER is the dashboard buffer." @@ -96,46 +97,39 @@ whose dimensions may not be fully initialized by the time this is run." (concat (make-string (ceiling (max 0 (- len (length s))) 2) ? ) s)) -(defun +doom-dashboard-deferred-reload (frame) - "Reload the dashboard after a brief pause. This is necessary for new frames, -whose dimensions may not be fully initialized by the time this is run." - (run-with-timer 0.05 nil - (lambda (frame) - (with-selected-frame frame - (+doom-dashboard/open t))) - frame)) - (defun +doom-dashboard-reload (&optional dir) "Update the DOOM scratch buffer (or create it, if it doesn't exist)." - (when (and (not +doom-dashboard-inhibit-refresh) - (not (window-minibuffer-p (frame-selected-window))) - (get-buffer-window (doom-fallback-buffer))) - (unless +doom-dashboard-modeline - (setq +doom-dashboard--old-modeline mode-line-format - +doom-dashboard-modeline - (or (and (featurep! :ui doom-modeline) - (doom-modeline 'project)) - mode-line-format))) - (let ((+doom-dashboard--width 80) - (old-pwd (or dir default-directory)) - (fallback-buffer (doom-fallback-buffer))) - (dolist (win (get-buffer-window-list fallback-buffer nil t)) - (set-window-fringes win 0 0) - (set-window-margins - win (max 0 (/ (- (window-total-width win) +doom-dashboard--width) 2)))) - (with-current-buffer fallback-buffer - (with-silent-modifications - (unless (eq major-mode '+doom-dashboard-mode) - (+doom-dashboard-mode)) - (erase-buffer) - (setq default-directory old-pwd) - (let ((+doom-dashboard--height (window-height))) - (insert (make-string (max 0 (- (truncate (/ +doom-dashboard--height 2)) 16)) ?\n)) - (dolist (widget-name +doom-dashboard-widgets) - (funcall (intern (format "doom-dashboard-widget--%s" widget-name))) - (insert "\n"))) - (unless (button-at (point)) - (goto-char (next-button (point-min)))))))) + (when (get-buffer-window (doom-fallback-buffer)) + (unless (or +doom-dashboard-inhibit-refresh + (window-minibuffer-p (frame-selected-window))) + (let ((old-pwd (or dir default-directory)) + (fallback-buffer (doom-fallback-buffer))) + (with-current-buffer fallback-buffer + (with-silent-modifications + (unless (eq major-mode '+doom-dashboard-mode) + (+doom-dashboard-mode)) + (erase-buffer) + (setq default-directory old-pwd) + (let ((+doom-dashboard--height (window-height (get-buffer-window fallback-buffer))) + (lines 1) + content) + (with-temp-buffer + (dolist (widget-name +doom-dashboard-widgets) + (funcall (intern (format "doom-dashboard-widget--%s" widget-name))) + (insert "\n")) + (setq content (buffer-string) + lines (count-lines (point-min) (point-max)))) + (insert (make-string (max 0 (- (/ +doom-dashboard--height 2) + (/ lines 2))) + ?\n) + content)) + (unless (button-at (point)) + (goto-char (next-button (point-min)))))))) + ;; Update all dashboard windows + (dolist (win (get-buffer-window-list (doom-fallback-buffer) nil t)) + (set-window-fringes win 0 0) + (set-window-margins + win (max 0 (/ (- (window-total-width win) +doom-dashboard--width) 2))))) t) ;; widgets @@ -166,11 +160,13 @@ whose dimensions may not be fully initialized by the time this is run." (defun doom-dashboard-widget--loaded () (insert + "\n" (propertize (+doom-dashboard-center +doom-dashboard--width - (format "Loaded %d packages in %.03fs " + (format "Loaded %d packages in %d modules in %.02fs" (- (length load-path) (length doom--base-load-path)) + (hash-table-size doom-modules) (if (floatp doom-init-time) doom-init-time 0.0))) 'face 'font-lock-comment-face) "\n")) @@ -178,10 +174,8 @@ whose dimensions may not be fully initialized by the time this is run." (defvar all-the-icons-scale-factor) (defvar all-the-icons-default-adjust) (defun doom-dashboard-widget--shortmenu () - (let ((all-the-icons-scale-factor 1.3) - (all-the-icons-default-adjust -0.05) - (last-session-p (and (and (featurep 'persp-mode) persp-mode) - (file-exists-p (expand-file-name persp-auto-save-fname persp-save-dir))))) + (let ((all-the-icons-scale-factor 1.45) + (all-the-icons-default-adjust -0.02)) (mapc (lambda (btn) (when btn (cl-destructuring-bind (label icon fn) btn @@ -192,16 +186,20 @@ whose dimensions may not be fully initialized by the time this is run." (propertize (concat " " label) 'face 'font-lock-keyword-face)) 'action `(lambda (_) ,fn) 'follow-link t) - (+doom-dashboard-center (1- +doom-dashboard--width) (buffer-string))) + (+doom-dashboard-center (- +doom-dashboard--width 2) (buffer-string))) "\n\n")))) `(("Homepage" "mark-github" - (browse-url "https://github.com/hlissner/.emacs.d")) - ,(when last-session-p + (browse-url "https://github.com/hlissner/doom-emacs")) + ,(when (and (featurep! :feature workspaces) + (file-exists-p (expand-file-name persp-auto-save-fname persp-save-dir))) '("Reload last session" "history" (+workspace/load-session))) + ,(when (featurep! :org org) + '("See agenda for this week" "calendar" + (call-interactively 'org-agenda-list))) ("Recently opened files" "file-text" - (call-interactively (command-remapping 'recentf))) - ("Recent opened projects" "briefcase" + (call-interactively (command-remapping 'recentf-open-files))) + ("Open project" "briefcase" (call-interactively (command-remapping 'projectile-switch-project))) ("Jump to bookmark" "bookmark" (call-interactively (command-remapping 'bookmark-jump))) diff --git a/modules/ui/doom-modeline/config.el b/modules/ui/doom-modeline/config.el index 90ade36f6..8f03143b6 100644 --- a/modules/ui/doom-modeline/config.el +++ b/modules/ui/doom-modeline/config.el @@ -87,6 +87,7 @@ Given ~/Projects/FOSS/emacs/lisp/comint.el truncate-upto-project => ~/P/F/emacs/lisp/comint.el truncate-upto-root => ~/P/F/e/lisp/comint.el truncate-all => ~/P/F/e/l/comint.el +relative-from-project => emacs/lisp/comint.el relative-to-project => lisp/comint.el file-name => comint.el") @@ -169,27 +170,6 @@ active." :group '+doom-modeline) -;; -;; Bootstrap -;; - -;; Show version string for multi-version managers like rvm, rbenv, pyenv, etc. -(defvar-local +doom-modeline-env-version nil) -(defvar-local +doom-modeline-env-command nil) -(add-hook! '(focus-in-hook find-file-hook) #'+doom-modeline|update-env) -(defun +doom-modeline|update-env () - (when +doom-modeline-env-command - (let* ((default-directory (doom-project-root)) - (s (shell-command-to-string +doom-modeline-env-command))) - (setq +doom-modeline-env-version (if (string-match "[ \t\n\r]+\\'" s) - (replace-match "" t t s) - s))))) - -;; Only support python and ruby for now -(add-hook! 'python-mode-hook (setq +doom-modeline-env-command "python --version 2>&1 | cut -d' ' -f2")) -(add-hook! 'ruby-mode-hook (setq +doom-modeline-env-command "ruby --version 2>&1 | cut -d' ' -f2")) - - ;; ;; Modeline helpers ;; @@ -232,6 +212,7 @@ active." ('truncate-upto-root (+doom-modeline--buffer-file-name-truncate)) ('truncate-all (+doom-modeline--buffer-file-name-truncate t)) ('relative-to-project (+doom-modeline--buffer-file-name-relative)) + ('relative-from-project (+doom-modeline--buffer-file-name-relative 'include-project)) ('file-name (propertize (file-name-nondirectory buffer-file-name) 'face (let ((face (or (and (buffer-modified-p) @@ -239,18 +220,18 @@ active." (and (active) 'doom-modeline-buffer-file)))) (when face `(:inherit ,face)))))) - 'help-echo (+doom-modeline--buffer-file-name nil))) + 'help-echo buffer-file-truename)) (defun +doom-modeline--buffer-file-name-truncate (&optional truncate-tail) "Propertized `buffer-file-name' that truncates every dir along path. If TRUNCATE-TAIL is t also truncate the parent directory of the file." (let ((dirs (shrink-path-prompt (file-name-directory (or buffer-file-truename - (file-truename buffer-file-name)))))) + (file-truename buffer-file-name))))) + (active (active))) (if (null dirs) - "%b" - (let ((modified-faces (if (buffer-modified-p) 'doom-modeline-buffer-modified)) - (active (active))) + (propertize "%b" 'face (if active 'doom-modeline-buffer-file)) + (let ((modified-faces (if (buffer-modified-p) 'doom-modeline-buffer-modified))) (let ((dirname (car dirs)) (basename (cdr dirs)) (dir-faces (or modified-faces (if active 'doom-modeline-project-root-dir))) @@ -262,14 +243,15 @@ If TRUNCATE-TAIL is t also truncate the parent directory of the file." (propertize (file-name-nondirectory buffer-file-name) 'face (if file-faces `(:inherit ,file-faces))))))))) -(defun +doom-modeline--buffer-file-name-relative () +(defun +doom-modeline--buffer-file-name-relative (&optional include-project) "Propertized `buffer-file-name' showing directories relative to project's root only." - (let ((root (doom-project-root))) + (let ((root (doom-project-root)) + (active (active))) (if (null root) - "%b" + (propertize "%b" 'face (if active 'doom-modeline-buffer-file)) (let* ((modified-faces (if (buffer-modified-p) 'doom-modeline-buffer-modified)) - (active (active)) - (relative-dirs (file-relative-name (file-name-directory buffer-file-name) root)) + (relative-dirs (file-relative-name (file-name-directory buffer-file-name) + (if include-project (concat root "../") root))) (relative-faces (or modified-faces (if active 'doom-modeline-buffer-path))) (file-faces (or modified-faces (if active 'doom-modeline-buffer-file)))) (if (equal "./" relative-dirs) (setq relative-dirs "")) @@ -289,12 +271,12 @@ Example: (file-name-directory (or buffer-file-truename (file-truename buffer-file-name))) - (file-truename buffer-file-name)))) + (file-truename buffer-file-name))) + (active (active))) (if (null file-name-split) - "%b" + (propertize "%b" 'face (if active 'doom-modeline-buffer-file)) (pcase-let ((`(,root-path-parent ,project ,relative-path ,filename) file-name-split)) - (let ((modified-faces (if (buffer-modified-p) 'doom-modeline-buffer-modified)) - (active (active))) + (let ((modified-faces (if (buffer-modified-p) 'doom-modeline-buffer-modified))) (let ((sp-faces (or modified-faces (if active 'font-lock-comment-face))) (project-faces (or modified-faces (if active 'font-lock-string-face))) (relative-faces (or modified-faces (if active 'doom-modeline-buffer-path))) @@ -316,8 +298,8 @@ Example: ;; Segments ;; -(def-modeline-segment! buffer-project - "Displays `doom-project-root'. This is for special buffers like the scratch +(def-modeline-segment! buffer-default-directory + "Displays `default-directory'. This is for special buffers like the scratch buffer where knowing the current project directory is important." (let ((face (if (active) 'doom-modeline-buffer-path))) (concat (if (display-graphic-p) " ") @@ -326,7 +308,7 @@ buffer where knowing the current project directory is important." :face face :v-adjust -0.05 :height 1.25) - (propertize (concat " " (abbreviate-file-name (doom-project-root))) + (propertize (concat " " (abbreviate-file-name default-directory)) 'face face)))) ;; @@ -365,8 +347,12 @@ directory, the file name, and its state (modified, read-only or non-existent)." ;; (def-modeline-segment! buffer-info-simple - "Return the current buffer name only, but with fontification." - (propertize "%b" 'face (if (active) 'doom-modeline-buffer-file))) + "Display only the current buffer's name, but with fontification." + (propertize + "%b" + 'face (cond ((and buffer-file-name (buffer-modified-p)) + 'doom-modeline-buffer-modified) + ((active) 'doom-modeline-buffer-file)))) ;; (def-modeline-segment! buffer-encoding @@ -388,8 +374,6 @@ directory, the file name, and its state (modified, read-only or non-existent)." (concat (format-mode-line mode-name) (when (stringp mode-line-process) mode-line-process) - (when +doom-modeline-env-version - (concat " " +doom-modeline-env-version)) (and (featurep 'face-remap) (/= text-scale-mode-amount 0) (format " (%+d)" text-scale-mode-amount))) @@ -614,7 +598,7 @@ Returns \"\" to not break --no-window-system." (buffer-encoding major-mode flycheck)) (def-modeline! project - (bar buffer-project) + (bar buffer-default-directory) (major-mode)) (def-modeline! media @@ -641,12 +625,17 @@ Returns \"\" to not break --no-window-system." (defun +doom-modeline|set-media-modeline () (doom-set-modeline 'media)) +(defun +doom-modeline|set-project-modeline () + (doom-set-modeline 'project)) + ;; ;; Bootstrap ;; (add-hook 'doom-init-ui-hook #'+doom-modeline|init) +(add-hook 'doom-scratch-buffer-hook #'+doom-modeline|set-special-modeline) +(add-hook '+doom-dashboard-mode-hook #'+doom-modeline|set-project-modeline) (add-hook 'org-src-mode-hook #'+doom-modeline|set-special-modeline) (add-hook 'image-mode-hook #'+doom-modeline|set-media-modeline) diff --git a/modules/ui/doom/config.el b/modules/ui/doom/config.el index 7cb145cc6..75dbcc8b0 100644 --- a/modules/ui/doom/config.el +++ b/modules/ui/doom/config.el @@ -11,7 +11,7 @@ :config (set! :theme 'doom-one) - ;; Ensure `doom/reload' reloads common faces + ;; Ensure `doom/reload-load-path' reloads common faces (defun +doom|reload-theme () (load "doom-themes-common.el" nil t)) (add-hook 'doom-pre-reload-theme-hook #'+doom|reload-theme) diff --git a/modules/ui/hl-todo/autoload.el b/modules/ui/hl-todo/autoload.el new file mode 100644 index 000000000..21f09e6b7 --- /dev/null +++ b/modules/ui/hl-todo/autoload.el @@ -0,0 +1,18 @@ +;;; ui/hl-todo/autoload.el -*- lexical-binding: t; -*- + +;;;###autoload +(defun +hl-todo|use-face-detection () + "Use a different, more primitive method of locating todo keywords. + +This is useful for major modes that don't use or have a valid syntax-table entry +for comment start/end characters." + (set (make-local-variable 'hl-todo-keywords) + '(((lambda (limit) + (let (case-fold-search) + (and (re-search-forward hl-todo-regexp limit t) + (memq 'font-lock-comment-face (doom-enlist (get-text-property (point) 'face)))))) + (1 (hl-todo-get-face) t t)))) + (when hl-todo-mode + (hl-todo-mode -1) + (hl-todo-mode +1))) + diff --git a/modules/ui/hl-todo/config.el b/modules/ui/hl-todo/config.el index 49521b7eb..8321cfc90 100644 --- a/modules/ui/hl-todo/config.el +++ b/modules/ui/hl-todo/config.el @@ -7,4 +7,10 @@ (setq hl-todo-keyword-faces `(("TODO" . ,(face-foreground 'warning)) ("FIXME" . ,(face-foreground 'error)) - ("NOTE" . ,(face-foreground 'success))))) + ("NOTE" . ,(face-foreground 'success)))) + + ;; Use a more primitive todo-keyword detection method in major modes that + ;; don't use/have a valid syntax table entry for comments. + (add-hook! + (pug-mode haml-mode) + #'+hl-todo|use-face-detection)) diff --git a/modules/ui/nav-flash/autoload.el b/modules/ui/nav-flash/autoload.el index 9284c9fcc..53d7bfb60 100644 --- a/modules/ui/nav-flash/autoload.el +++ b/modules/ui/nav-flash/autoload.el @@ -6,9 +6,10 @@ (let ((point (save-excursion (goto-char (window-start)) (point-marker)))) (apply orig-fn args) - (unless (equal point - (save-excursion (goto-char (window-start)) - (point-marker))) + (unless (or (derived-mode-p 'term-mode) + (equal point + (save-excursion (goto-char (window-start)) + (point-marker)))) (+doom/blink-cursor)))) ;;;###autoload diff --git a/modules/ui/vi-tilde-fringe/config.el b/modules/ui/vi-tilde-fringe/config.el new file mode 100644 index 000000000..3467a770d --- /dev/null +++ b/modules/ui/vi-tilde-fringe/config.el @@ -0,0 +1,11 @@ +;;; ui/vi-tilde-fringe/config.el -*- lexical-binding: t; -*- + +;; 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) + :config + (defun +vi-tilde-fringe|disable () + (vi-tilde-fringe-mode -1))) + diff --git a/modules/ui/vi-tilde-fringe/packages.el b/modules/ui/vi-tilde-fringe/packages.el new file mode 100644 index 000000000..10b52d811 --- /dev/null +++ b/modules/ui/vi-tilde-fringe/packages.el @@ -0,0 +1,4 @@ +;; -*- no-byte-compile: t; -*- +;;; ui/vi-tilde-fringe/packages.el + +(package! vi-tilde-fringe)