diff --git a/core/autoload/help.el b/core/autoload/help.el index 6b792ed80..1aadc057a 100644 --- a/core/autoload/help.el +++ b/core/autoload/help.el @@ -1,7 +1,8 @@ ;;; core/autoload/help.el -*- lexical-binding: t; -*- -(defvar doom--module-mode-alist +(defvar doom--help-major-mode-module-alist '((dockerfile-mode :tools docker) + (agda2-mode :lang agda) (haxor-mode :lang assembly) (mips-mode :lang assembly) (nasm-mode :lang assembly) @@ -27,12 +28,14 @@ (go-mode :lang go) (haskell-mode :lang haskell) (hy-mode :lang hy) + (idris-mode :lang idris) (java-mode :lang java) (js2-mode :lang javascript) (rjsx-mode :lang javascript) (typescript-mode :lang javascript) (coffee-mode :lang javascript) (julia-mode :lang julia) + (kotlin-mode :lang kotlin) (latex-mode :lang latex) (LaTeX-mode :lang latex) (ledger-mode :lang ledger) @@ -61,12 +64,14 @@ (scss-mode :lang web) (sass-mode :lang web) (less-css-mode :lang web) - (stylus-mode :lang web)) + (stylus-mode :lang web) + (terra-mode :lang terra) + (vala-mode :lang vala)) "TODO") ;; -;; Helpers +;;; Helpers ;;;###autoload (defun doom-active-minor-modes () @@ -77,16 +82,131 @@ ;; -;; Commands +;;; Custom describe commands + +;;;###autoload (defalias 'doom/describe-autodefs #'doom/help-autodefs) +;;;###autoload (defalias 'doom/describe-module #'doom/help-modules) +;;;###autoload (defalias 'doom/describe-package #'doom/help-packages) ;;;###autoload -(defun doom/describe-autodefs (autodef) - "Open the documentation of Doom autodefs. +(defun doom/describe-active-minor-mode (mode) + "Get information on an active minor mode. Use `describe-minor-mode' for a +selection of all minor-modes, active or not." + (interactive + (list (completing-read "Minor mode: " (doom-active-minor-modes)))) + (describe-minor-mode-from-symbol + (cond ((stringp mode) (intern mode)) + ((symbolp mode) mode) + ((error "Expected a symbol/string, got a %s" (type-of mode)))))) -What is an autodef? It's a function or macro that is always defined, even if its -containing module is disabled (in which case it will safely no-op). This -syntactic sugar lets you use them without needing to check if they are -available." +;;;###autoload +(defun doom/describe-symbol (symbol) + "Show help for SYMBOL, a variable, function or macro." + (interactive + (list (helpful--read-symbol "Symbol: " #'helpful--bound-p))) + (let* ((sym (intern-soft symbol)) + (bound (boundp sym)) + (fbound (fboundp sym))) + (cond ((and sym bound (not fbound)) + (helpful-variable sym)) + ((and sym fbound (not bound)) + (helpful-callable sym)) + ((apropos (format "^%s\$" symbol))) + ((apropos (format "%s" symbol)))))) + + +;; +;;; Documentation commands + +(defun doom--org-headings (files &optional depth prompt include-files) + (let ((org-agenda-files (doom-enlist files)) + (default-directory doom-docs-dir)) + (unwind-protect + (delq nil + (org-map-entries + (lambda () + (let* ((components (org-heading-components)) + (path (org-get-outline-path)) + (level (nth 0 components)) + (text (nth 4 components)) + (tags (nth 5 components))) + (when (and (or (not depth) + (and (integerp depth) + (<= level depth))) + (or (not tags) + (not (string-match-p ":TOC" tags)))) + (list + (mapconcat + 'identity + (list (mapconcat 'identity + (append (when include-files + (list (or (+org-get-property "TITLE") + (file-relative-name buffer-file-name)))) + path + (list text)) + " > ") + tags) + " ") + buffer-file-name + (point))))) + nil + 'agenda)) + (mapc #'kill-buffer org-agenda-new-buffers) + (setq org-agenda-new-buffers nil)))) + +;;;###autoload +(defun doom/help () + "Open Doom's user manual." + (interactive) + (find-file (expand-file-name "index.org" doom-docs-dir))) + +;;;###autoload +(defun doom/help-search () + "Search Doom's documentation and jump to a headline." + (interactive) + (let (ivy-sort-functions-alist) + (completing-read "Find in Doom help: " + (doom--org-headings (list "getting_started.org" + "contributing.org" + "troubleshooting.org" + "tutorials.org" + "faq.org") + 2 nil t)))) + +;;;###autoload +(defun doom/help-faq () + "Search Doom's FAQ and jump to a question." + (interactive) + (completing-read "Find in FAQ: " + (doom--org-headings (list "faq.org")))) + +;;;###autoload +(defun doom/help-news () + "Open a Doom newsletter. +The latest newsletter will be selected by default." + (interactive) + (let* ((default-directory (expand-file-name "news/" doom-docs-dir)) + (news-files (doom-files-in default-directory))) + (find-file + (read-file-name (format "Open Doom newsletter (current: v%s): " + doom-version) + default-directory + (if (member doom-version news-files) + doom-version + (concat (mapconcat #'number-to-string + (nbutlast (version-to-list doom-version) 1) + ".") + ".x")) + t doom-version)))) + +;;;###autoload +(defun doom/help-autodefs (autodef) + "Open the documentation of an autodef. + +An autodef is a Doom concept. It is a function or macro that is always defined, +whether or not its containing module is disabled (in which case it will safely +no-op). This syntactic sugar lets you use them without needing to check if they +are available." (interactive (let* ((settings (cl-loop with case-fold-search = nil @@ -133,26 +253,15 @@ available." (describe-function fn)))) ;;;###autoload -(defun doom/describe-active-minor-mode (mode) - "Get information on an active minor mode. Use `describe-minor-mode' for a -selection of all minor-modes, active or not." - (interactive - (list (completing-read "Minor mode: " (doom-active-minor-modes)))) - (describe-minor-mode-from-symbol - (cond ((stringp mode) (intern mode)) - ((symbolp mode) mode) - ((error "Expected a symbol/string, got a %s" (type-of mode)))))) - -;;;###autoload -(defun doom/describe-module (category module) - "Open the documentation of CATEGORY MODULE. +(defun doom/help-modules (category module) + "Open the documentation for a Doom module. CATEGORY is a keyword and MODULE is a symbol. e.g. :editor and 'evil. Automatically selects a) the module at point (in private init files), b) the module derived from a `featurep!' or `require!' call, c) the module that the current file is in, or d) the module associated with the current major mode (see -`doom--module-mode-alist')." +`doom--help-major-mode-module-alist')." (interactive (let* ((module (cond ((and buffer-file-name @@ -173,7 +282,7 @@ current file is in, or d) the module associated with the current major mode (see ((and buffer-file-name (when-let* ((mod (doom-module-from-path buffer-file-name))) (format "%s %s" (car mod) (cdr mod))))) - ((when-let* ((mod (cdr (assq major-mode doom--module-mode-alist)))) + ((when-let* ((mod (cdr (assq major-mode doom--help-major-mode-module-alist)))) (format "%s %s" (symbol-name (car mod)) (symbol-name (cadr mod))))))) @@ -185,7 +294,7 @@ current file is in, or d) the module associated with the current major mode (see for format = (format "%s %s" cat mod) if (doom-module-p cat mod) collect format - else + else if (and cat mod) collect (propertize format 'face 'font-lock-comment-face)) nil t nil nil module)) (key (split-string module-string " "))) @@ -203,6 +312,10 @@ current file is in, or d) the module associated with the current major mode (see (doom-project-browse path) (user-error "Aborted module lookup"))))) + +;; +;;; `doom/help-packages' + (defun doom--describe-package-insert-button (label path &optional regexp) (declare (indent defun)) (insert-text-button @@ -223,49 +336,61 @@ current file is in, or d) the module associated with the current major mode (see (recenter) (message "Couldn't find the config block")))))))) -;;;###autoload -(global-set-key [remap describe-package] #'doom/describe-package) +(defun doom--completing-package (&optional prompt refresh) + (let* (guess + (sym (symbol-at-point)) + (package-list (or (unless refresh (doom-cache-get 'help-packages)) + (doom-cache-set 'help-packages (doom-package-list 'all)))) + (pkg-alist (seq-group-by #'car package-list)) + (packages + ;; TODO Refactor me + (cl-loop for (pkg . descs) in (cl-sort pkg-alist #'string-lessp :key #'car) + for str = + (format "%-40s %s" + pkg + (cl-loop for (desc . plist) in descs + for module = (car (plist-get plist :modules)) + collect + (format "%s%s" + (car module) + (if (cdr module) (format " %s" (cdr module)) "")) + into modules + finally return + (propertize (string-join modules ", ") + 'face 'font-lock-comment-face))) + collect str + and do + (when (eq pkg sym) + (setq guess str))))) + (intern + (car (split-string + (completing-read + (or prompt + (if guess + (format "Describe package (default %s): " + (car (split-string guess " "))) + "Describe package: ")) + packages nil t nil nil + (if guess guess)) + " "))))) -(defvar doom--describe-package-list-cache nil) ;;;###autoload -(defun doom/describe-package (package) - "Like `describe-packages', but is Doom aware. +(defun doom/help-packages (package) + "Like `describe-package', but for packages installed by Doom modules. Only shows installed packages. Includes information about where packages are defined and configured. -If prefix arg is prsent, refresh the cache." +If prefix arg is present, refresh the cache." (interactive - (list - (let* ((guess (or (function-called-at-point) - (symbol-at-point)))) - (require 'finder-inf nil t) - (require 'core-packages) - (doom-initialize-packages) - (let ((packages - (or (unless current-prefix-arg doom--describe-package-list-cache) - (cl-loop for pkg - in (cl-delete-duplicates - (sort (append (mapcar #'car package-alist) - (mapcar #'car package-archive-contents) - (mapcar #'car package--builtins)) - #'string-greaterp)) - if (assq pkg package-alist) - collect (symbol-name pkg) - else - collect (propertize (symbol-name pkg) 'face 'font-lock-comment-face))))) - (unless (memq guess packages) - (setq guess nil)) - (setq doom--describe-package-list-cache packages) - (intern - (completing-read - (if guess - (format "Describe package (default %s): " - guess) - "Describe package: ") - packages nil t nil nil - (if guess (symbol-name guess)))))))) - (describe-package package) + (list (doom--completing-package nil current-prefix-arg))) + (if (or (package-desc-p package) + (and (symbolp package) + (or (assq package package-alist) + (assq package package-archive-contents) + (assq package package--builtins)))) + (describe-package package) + (doom--describe-package package)) (save-excursion (with-current-buffer (help-buffer) (let ((inhibit-read-only t)) @@ -275,7 +400,7 @@ If prefix arg is prsent, refresh the cache." (end-of-line) (let ((indent (make-string (length (match-string 0)) ? ))) (insert "\n" indent "Installed by the following Doom modules:\n") - (dolist (m (get package 'doom-module)) + (dolist (m (doom-package-prop package :modules)) (insert indent) (doom--describe-package-insert-button (format " %s %s" (car m) (or (cdr m) "")) @@ -293,45 +418,25 @@ If prefix arg is prsent, refresh the cache." (insert "\n") (package--print-help-section "Configs") - (dolist (file (get package 'doom-files)) - (doom--describe-package-insert-button - (abbreviate-file-name file) - file - (format "\\((\\(:?after!\\|def-package!\\)[ \t\n]*%s\\|^[ \t]*;; `%s'$\\)" - package package)) - (insert "\n" indent)) - (delete-char -1))))))) + (insert-text-button + "See where this package is configured" + 'face 'link + 'follow-link t + 'action + `(lambda (_) (doom/help-package-config ',package))))))))) ;;;###autoload -(defun doom/describe-symbol (symbol) - "Show help for SYMBOL, a variable, function or macro." +(defun doom/help-package-config (package) + "Jump to a configuration block for PACKAGE." (interactive - (list (helpful--read-symbol "Symbol: " #'helpful--bound-p))) - (let* ((sym (intern-soft symbol)) - (bound (boundp sym)) - (fbound (fboundp sym))) - (cond ((and sym bound (not fbound)) - (helpful-variable sym)) - ((and sym fbound (not bound)) - (helpful-callable sym)) - ((apropos (format "^%s\$" symbol))) - ((apropos (format "%s" symbol)))))) - -;;;###autoload -(defalias 'doom/help 'doom/open-manual) - -;;;###autoload -(defun doom/open-manual () - "TODO" - (interactive) - (user-error "This command isn't implemented yet") - ;; (find-file (expand-file-name "index.org" doom-docs-dir)) - ) - -;;;###autoload -(defun doom/open-news () - "TODO" - (interactive) - (user-error "This command isn't implemented yet") - ;; (find-file (expand-file-name (concat "news/" doom-version) doom-docs-dir)) - ) + (list (doom--completing-package "Select package to search for: " current-prefix-arg))) + (let* ((default-directory doom-emacs-dir) + (results (shell-command-to-string + (format "git grep --no-break --no-heading --line-number '%s %s\\($\\| \\)'" + "\\(^;;;###package\\|(after!\\|(def-package!\\)" + package))) + (select (completing-read "Jump to config: " (split-string results "\n" t)))) + (cl-destructuring-bind (file line _match) + (split-string select ":") + (pop-to-buffer file) + (goto-line line)))) diff --git a/core/autoload/packages.el b/core/autoload/packages.el index 77fee86d9..d1a4e51fe 100644 --- a/core/autoload/packages.el +++ b/core/autoload/packages.el @@ -219,9 +219,9 @@ files." (push (cons name (plist-put plist :modules (cond ((file-in-directory-p file doom-private-dir) - (list :private)) + '((:private))) ((file-in-directory-p file doom-core-dir) - (list :core)) + '((:core))) ((doom-module-from-path file))))) doom-packages)))))) ((debug error) diff --git a/modules/config/default/config.el b/modules/config/default/config.el index 85272c466..89eb7a63a 100644 --- a/modules/config/default/config.el +++ b/modules/config/default/config.el @@ -183,9 +183,8 @@ (define-key! help-map ;; new keybinds "'" #'describe-char - "A" #'doom/describe-autodefs "B" #'doom/open-bug-report - "D" #'doom/open-manual + "D" #'doom/help "E" #'doom/open-vanilla-sandbox "M" #'doom/describe-active-minor-mode "O" #'+lookup/online @@ -195,7 +194,6 @@ "C-k" #'describe-key-briefly "C-l" #'describe-language-environment "C-m" #'info-emacs-manual - "C-v" #'doom/version ;; Unbind `help-for-help'. Conflicts with which-key's help command for the ;; h prefix. It's already on ? and F1 anyway. @@ -210,22 +208,34 @@ "rf" #'doom/reload-font "re" #'doom/reload-env + ;; replaces `apropos-documentation' b/c `apropos' covers this + "d" nil + "d/" #'doom/help-search + "da" #'doom/help-autodefs + "dd" #'doom/toggle-debug-mode + "df" #'doom/help-faq + "dh" #'doom/help + "dm" #'doom/help-modules + "dn" #'doom/help-news + "dp" #'doom/help-packages + "dc" #'doom/help-package-config + "dt" #'doom/toggle-profiler + "dv" #'doom/version + ;; replaces `apropos-command' "a" #'apropos ;; replaces `describe-copying' b/c not useful "C-c" #'describe-coding-system - ;; replaces `apropos-documentation' b/c `apropos' covers this - "d" #'doom/describe-module ;; replaces `Info-got-emacs-command-node' b/c redundant w/ `Info-goto-node' "F" #'describe-face ;; replaces `view-hello-file' b/c annoying - "h" #'doom/describe-symbol + "h" #'doom/help ;; replaces `describe-language-environment' b/c remapped to C-l "L" #'global-command-log-mode ;; replaces `view-emacs-news' b/c it's on C-n too - "n" #'doom/open-news + "n" #'doom/help-news ;; replaces `finder-by-keyword' - ;; "p" #'doom/describe-package + "p" #'doom/describe-package ;; replaces `describe-package' b/c redundant w/ `doom/describe-package' "P" #'find-library)