aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilip Kaludercic2025-09-02 14:51:09 +0200
committerPhilip Kaludercic2025-09-02 14:51:09 +0200
commit055ee29eb43221385b30182643d627512ca99ef7 (patch)
treecdc3021f6f6ad11376140ec15cf1b51bfb4878d4
parent218eab3f99a53bbb4f4efc8cfa1b78c268238e07 (diff)
parent2a88c6dc3c36f726275554f1b93e32fd726e415c (diff)
downloademacs-055ee29eb43221385b30182643d627512ca99ef7.tar.gz
emacs-055ee29eb43221385b30182643d627512ca99ef7.zip
Merge remote-tracking branch 'origin/feature/package-autosuggest' into scratch/split-package.el
-rw-r--r--admin/scrape-elpa.el81
-rw-r--r--doc/emacs/package.texi10
-rw-r--r--etc/NEWS11
-rw-r--r--etc/package-autosuggest.eld188
-rw-r--r--lisp/package/package-autosuggest.el226
5 files changed, 516 insertions, 0 deletions
diff --git a/admin/scrape-elpa.el b/admin/scrape-elpa.el
new file mode 100644
index 00000000000..bf3846c0fcb
--- /dev/null
+++ b/admin/scrape-elpa.el
@@ -0,0 +1,81 @@
1;;; scrape-elpa.el --- Collect ELPA package suggestions -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2024 Free Software Foundation, Inc.
4
5;; Author: Philip Kaludercic <philipk@posteo.net>
6;; Keywords: tools
7
8;; This program is free software; you can redistribute it and/or modify
9;; it under the terms of the GNU General Public License as published by
10;; the Free Software Foundation, either version 3 of the License, or
11;; (at your option) any later version.
12
13;; This program is distributed in the hope that it will be useful,
14;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;; GNU General Public License for more details.
17
18;; You should have received a copy of the GNU General Public License
19;; along with this program. If not, see <https://www.gnu.org/licenses/>.
20
21;;; Commentary:
22
23;; This file defines an administrative command to update the
24;; `package-autosuggest' database.
25
26;;; Code:
27
28(defun scrape-elpa (&rest directories)
29 "Scrape autoload files in DIRECTORIES for package suggestions.
30This file will automatically update \"package-autosuggest.eld\", but not
31save it. You should invoke this command with built GNU ELPA and NonGNU
32ELPA checkouts (i.e. having run \"make autoloads\" in both directories).
33Please review the results before updating the autosuggest database!"
34 (interactive (completing-read-multiple
35 "ELPA directories to scrape: "
36 #'completion-file-name-table
37 #'file-directory-p))
38 (with-current-buffer
39 (find-file (expand-file-name "package-autosuggest.eld" data-directory))
40 (erase-buffer)
41 (lisp-data-mode)
42 (insert ";; The contents of this file are loaded into `package-autosuggest-database'
43;; and were automatically generate by scraping ELPA for auto-loaded
44;; code using the `scrape-elpa' command. Please avoid updating this
45;; file manually!
46
47")
48 (fill-paragraph)
49 (insert "(")
50 (let ((standard-output (current-buffer)))
51 (dolist-with-progress-reporter
52 (file (mapcan
53 (lambda (dir)
54 (directory-files-recursively
55 dir "-autoloads\\.el\\'"))
56 directories))
57 "Scraping files..."
58 (and-let* (((string-match "/\\([^/]+?\\)-autoloads\\.el\\'" file))
59 (pkg (intern (match-string 1 file)))
60 (inhibit-message t))
61 (with-temp-buffer
62 (insert-file-contents file)
63 (condition-case nil
64 (while t
65 (dolist (exp (macroexp-unprogn (read (current-buffer))))
66 (pcase exp
67 (`(add-to-list
68 ',(and (or 'interpreter-mode-alist
69 'magic-mode-alist
70 'auto-mode-alist)
71 variable)
72 '(,(and (pred stringp) regexp) .
73 ,(and (pred symbolp) mode)))
74 (terpri)
75 (prin1 (append (list pkg variable regexp)
76 (and (not (eq pkg mode)) (list mode))))))))
77 (end-of-file nil))))))
78 (insert "\n)\n")))
79
80(provide 'scrape-elpa)
81;;; scrape-elpa.el ends here
diff --git a/doc/emacs/package.texi b/doc/emacs/package.texi
index c29beea3b08..5aa9f9a74bf 100644
--- a/doc/emacs/package.texi
+++ b/doc/emacs/package.texi
@@ -439,6 +439,16 @@ case, Emacs retrieves packages from this archive via ordinary file
439access. Such local archives are mainly useful for testing. 439access. Such local archives are mainly useful for testing.
440@end defopt 440@end defopt
441 441
442@cindex suggestions
443@findex package-autosuggest
444@findex package-autosuggest-mode
445 Emacs has a built-in database of suggested packages for certain file
446types. If Emacs opens a file with no specific mode, you can use the
447@code{package-autosuggest} command to install the recommended packages
448from ELPA. After enabling @code{package-autosuggest-mode}, Emacs will
449display a clickable hint in the mode-line if it there is a suggested
450package.
451
442@anchor{Package Signing} 452@anchor{Package Signing}
443@cindex package security 453@cindex package security
444@cindex package signing 454@cindex package signing
diff --git a/etc/NEWS b/etc/NEWS
index 4a193484591..83f52df69cb 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2258,6 +2258,17 @@ version-list of a given package symbol. These functions provide public
2258interfaces for external tools to query information about built-in 2258interfaces for external tools to query information about built-in
2259packages. 2259packages.
2260 2260
2261+++
2262*** New command 'package-autosuggest'
2263Using a built-in database of package suggestions from ELPA, this command
2264will install viable packages if no specific major mode is available.
2265
2266+++
2267*** New minor mode 'package-autosuggest-mode'
2268When enabled, this displays a hint in the mode line indicating the
2269availability of a suggested package. You can customise the presentation
2270of these hints using 'package-autosuggest-style'.
2271
2261** Rcirc 2272** Rcirc
2262 2273
2263+++ 2274+++
diff --git a/etc/package-autosuggest.eld b/etc/package-autosuggest.eld
new file mode 100644
index 00000000000..cf8b8288e27
--- /dev/null
+++ b/etc/package-autosuggest.eld
@@ -0,0 +1,188 @@
1;; The contents of this file are loaded into `package-autosuggest-database'
2;; and were automatically generate by scraping ELPA for auto-loaded
3;; code using the `scrape-elpa' command. Please avoid updating this
4;; file manually!
5
6(
7(ada-mode auto-mode-alist "\\.ad[abs]\\'")
8(arbitools auto-mode-alist "\\.trf?\\'" arbitools-mode)
9(auctex auto-mode-alist "\\.hva\\'" LaTeX-mode)
10(bnf-mode auto-mode-alist "\\.bnf\\'")
11(chess auto-mode-alist "\\.pgn\\'" chess-pgn-mode)
12(cobol-mode auto-mode-alist "\\.c\\(ob\\|bl\\|py\\)\\'")
13(code-cells auto-mode-alist "\\.ipynb\\'" code-cells-convert-ipynb)
14(csharp-mode auto-mode-alist "\\.cs\\'")
15(csv-mode auto-mode-alist "\\.[Cc][Ss][Vv]\\'")
16(csv-mode auto-mode-alist "\\.tsv\\'" tsv-mode)
17(dismal auto-mode-alist "\\.dis\\'" dismal-mode)
18(djvu auto-mode-alist "\\.djvu\\'" djvu-init-mode)
19(dts-mode auto-mode-alist "\\.dtsi?\\'")
20(ess auto-mode-alist "\\.[Bb][Uu][Gg]\\'" ess-bugs-mode)
21(ess auto-mode-alist "\\.[Bb][Oo][Gg]\\'" ess-bugs-mode)
22(ess auto-mode-alist "\\.[Bb][Mm][Dd]\\'" ess-bugs-mode)
23(ess auto-mode-alist "\\.[Jj][Aa][Gg]\\'" ess-jags-mode)
24(ess auto-mode-alist "/R/.*\\.q\\'" ess-r-mode)
25(ess auto-mode-alist "\\.[rR]\\'" ess-r-mode)
26(ess auto-mode-alist "\\.[rR]profile\\'" ess-r-mode)
27(ess auto-mode-alist "NAMESPACE\\'" ess-r-mode)
28(ess auto-mode-alist "CITATION\\'" ess-r-mode)
29(ess auto-mode-alist "\\.[Rr]out\\'" ess-r-transcript-mode)
30(ess interpreter-mode-alist "Rscript" ess-r-mode)
31(ess interpreter-mode-alist "r" ess-r-mode)
32(ess auto-mode-alist "/Makevars\\(\\.win\\)?\\'" makefile-mode)
33(ess auto-mode-alist "DESCRIPTION\\'" conf-colon-mode)
34(ess auto-mode-alist "\\.Rd\\'" Rd-mode)
35(ess auto-mode-alist "\\.[Ss]t\\'" S-transcript-mode)
36(ess auto-mode-alist "\\.Sout\\'" S-transcript-mode)
37(ess auto-mode-alist "\\.[Ss][Aa][Ss]\\'" SAS-mode)
38(gle-mode auto-mode-alist "\\.gle\\'")
39(gpr-mode auto-mode-alist "\\.gpr\\'")
40(html5-schema auto-mode-alist "\\.html?\\'" nxml-mode)
41(jgraph-mode auto-mode-alist "\\.jgr\\'")
42(json-mode auto-mode-alist "\\.json\\'")
43(lmc auto-mode-alist "\\.elmc\\'" lmc-asm-mode)
44(matlab-mode auto-mode-alist "\\.tlc\\'" tlc-mode)
45(matlab auto-mode-alist "\\.tlc\\'" tlc-mode)
46(muse auto-mode-alist "\\.muse\\'" muse-mode-choose-mode)
47(auctex auto-mode-alist "\\.drv\\'" latex-mode)
48(auctex auto-mode-alist "\\.dtx\\'" doctex-mode)
49(nftables-mode auto-mode-alist "\\.nft\\(?:ables\\)?\\'")
50(nftables-mode auto-mode-alist "/etc/nftables.conf")
51(nftables-mode interpreter-mode-alist "nft\\(?:ables\\)?")
52(omn-mode auto-mode-alist "\\.pomn\\'")
53(omn-mode auto-mode-alist "\\.omn\\'")
54(poke-mode auto-mode-alist "\\.pk\\'")
55(pspp-mode auto-mode-alist "\\.sps\\'")
56(python auto-mode-alist "/\\(?:Pipfile\\|\\.?flake8\\)\\'" conf-mode)
57(rec-mode auto-mode-alist "\\.rec\\'")
58(rnc-mode auto-mode-alist "\\.rnc\\'")
59(sed-mode auto-mode-alist "\\.sed\\'")
60(sed-mode interpreter-mode-alist "sed")
61(shen-mode auto-mode-alist "\\.shen\\'")
62(sisu-mode auto-mode-alist "\\.ss[imt]\\'")
63(smalltalk-mode auto-mode-alist "\\.st\\'")
64(sml-mode auto-mode-alist "\\.s\\(ml\\|ig\\)\\'")
65(sml-mode auto-mode-alist "\\.cm\\'" sml-cm-mode)
66(sml-mode auto-mode-alist "\\.grm\\'" sml-yacc-mode)
67(sql-cassandra auto-mode-alist "\\.cql\\'" sql-mode)
68(sxhkdrc-mode auto-mode-alist "sxhkdrc\\'")
69(systemd auto-mode-alist "\\.automount\\'" systemd-automount-mode)
70(systemd auto-mode-alist "\\.mount\\'" systemd-mount-mode)
71(systemd auto-mode-alist "\\.path\\'" systemd-path-mode)
72(systemd auto-mode-alist "\\.service\\'" systemd-service-mode)
73(systemd auto-mode-alist "\\.socket\\'" systemd-socket-mode)
74(systemd auto-mode-alist "\\.swap\\'" systemd-swap-mode)
75(systemd auto-mode-alist "\\.timer\\'" systemd-timer-mode)
76(vcard auto-mode-alist "\\.[Vv][Cc][Ff]\\'" vcard-mode)
77(wisi auto-mode-alist "\\.parse_table.*\\'" wisitoken-parse_table-mode)
78(wisitoken-grammar-mode auto-mode-alist "\\.wy\\'" simple-indent-mode)
79(wisitoken-grammar-mode auto-mode-alist "\\.wy\\'")
80(adoc-mode auto-mode-alist "\\.a\\(?:scii\\)?doc\\'")
81(apache-mode auto-mode-alist "/\\.htaccess\\'")
82(apache-mode auto-mode-alist "/\\(?:access\\|httpd\\|srm\\)\\.conf\\'")
83(apache-mode auto-mode-alist "/apache2/.+\\.conf\\'")
84(apache-mode auto-mode-alist "/httpd/conf/.+\\.conf\\'")
85(apache-mode auto-mode-alist "/apache2/sites-\\(?:available\\|enabled\\)/")
86(arduino-mode auto-mode-alist "\\.pde\\'")
87(arduino-mode auto-mode-alist "\\.ino\\'")
88(beancount auto-mode-alist "\\.beancount\\'" beancount-mode)
89(bison-mode auto-mode-alist "\\.y\\'")
90(bison-mode auto-mode-alist "\\.l\\'" flex-mode)
91(bison-mode auto-mode-alist "\\.jison\\'" jison-mode)
92(bqn-mode auto-mode-alist "\\.bqn\\'")
93(bqn-mode interpreter-mode-alist "bqn")
94(clojure-mode auto-mode-alist "\\.\\(clj\\|cljd\\|dtm\\|edn\\|lpy\\)\\'")
95(clojure-mode auto-mode-alist "\\.cljc\\'" clojurec-mode)
96(clojure-mode auto-mode-alist "\\.cljs\\'" clojurescript-mode)
97(clojure-mode auto-mode-alist "\\(?:build\\|profile\\)\\.boot\\'")
98(clojure-mode interpreter-mode-alist "bb")
99(clojure-mode interpreter-mode-alist "nbb" clojurescript-mode)
100(coffee-mode auto-mode-alist "\\.coffee\\'")
101(coffee-mode auto-mode-alist "\\.iced\\'")
102(coffee-mode auto-mode-alist "Cakefile\\'")
103(coffee-mode auto-mode-alist "\\.cson\\'")
104(coffee-mode interpreter-mode-alist "coffee")
105(d-mode auto-mode-alist "\\.d[i]?\\'")
106(dart-mode auto-mode-alist "\\.dart\\'")
107(dockerfile-mode auto-mode-alist "\\.dockerfile\\'")
108(drupal-mode auto-mode-alist "[^/]\\.\\(module\\|test\\|install\\|profile\\|tpl\\.php\\|theme\\|inc\\)\\'" php-mode)
109(drupal-mode auto-mode-alist "[^/]\\.info\\'" conf-windows-mode)
110(drupal-mode auto-mode-alist "[^/]\\.make\\'" drush-make-mode)
111(editorconfig auto-mode-alist "\\.editorconfig\\'" editorconfig-conf-mode)
112(elixir-mode auto-mode-alist "\\.elixir\\'")
113(elixir-mode auto-mode-alist "\\.ex\\'")
114(elixir-mode auto-mode-alist "\\.exs\\'")
115(elixir-mode auto-mode-alist "mix\\.lock")
116(ett auto-mode-alist "\\.ett\\'" ett-mode)
117(forth-mode auto-mode-alist "\\.\\(f\\|fs\\|fth\\|4th\\)\\'")
118(geiser-racket auto-mode-alist "\\.rkt\\'" scheme-mode)
119(gnu-apl-mode auto-mode-alist "\\.apl\\'")
120(gnu-apl-mode interpreter-mode-alist "apl")
121(go-mode auto-mode-alist "go\\.mod\\'" go-dot-mod-mode)
122(go-mode auto-mode-alist "go\\.work\\'" go-dot-work-mode)
123(graphql-mode auto-mode-alist "\\.graphql\\'")
124(graphql-mode auto-mode-alist "\\.gql\\'")
125(haml-mode auto-mode-alist "\\.haml\\'")
126(haskell-mode auto-mode-alist "\\.hcr\\'" ghc-core-mode)
127(haskell-mode auto-mode-alist "\\.dump-simpl\\'" ghc-core-mode)
128(haskell-mode auto-mode-alist "\\.ghci\\'" ghci-script-mode)
129(haskell-mode auto-mode-alist "\\.chs\\'" haskell-c2hs-mode)
130(haskell-mode auto-mode-alist "\\.cabal\\'\\|/cabal\\.project\\|/\\.cabal/config\\'" haskell-cabal-mode)
131(haskell-mode auto-mode-alist "\\.[gh]s\\'")
132(haskell-mode auto-mode-alist "\\.hsig\\'")
133(haskell-mode auto-mode-alist "\\.l[gh]s\\'" haskell-literate-mode)
134(haskell-mode auto-mode-alist "\\.hsc\\'")
135(haskell-mode interpreter-mode-alist "runghc")
136(haskell-mode interpreter-mode-alist "runhaskell")
137(j-mode auto-mode-alist "\\.ij[rsp]$")
138(j-mode auto-mode-alist "\\.ijt$" j-lab-mode)
139(jade-mode auto-mode-alist "\\.jade\\'")
140(jade-mode auto-mode-alist "\\.pug\\'")
141(jade-mode auto-mode-alist "\\.styl\\'" stylus-mode)
142(jinja2-mode auto-mode-alist "\\.jinja2\\'")
143(jinja2-mode auto-mode-alist "\\.j2\\'")
144(julia-mode auto-mode-alist "\\.jl\\'")
145(lua-mode auto-mode-alist "\\.lua\\'")
146(lua-mode interpreter-mode-alist "lua")
147(markdown-mode auto-mode-alist "\\.\\(?:md\\|markdown\\|mkd\\|mdown\\|mkdn\\|mdwn\\)\\'")
148(nginx-mode auto-mode-alist "nginx\\.conf\\'")
149(nginx-mode auto-mode-alist "/nginx/.+\\.conf\\'")
150(nix-mode auto-mode-alist "^/nix/store/.+\\.drv\\'" nix-drv-mode)
151(nix-mode auto-mode-alist "\\flake.lock\\'" js-mode)
152(nix-mode auto-mode-alist "\\.nix\\'")
153(php-mode auto-mode-alist "/\\.php_cs\\(?:\\.dist\\)?\\'")
154(php-mode auto-mode-alist "\\.\\(?:php\\.inc\\|stub\\)\\'")
155(php-mode auto-mode-alist "\\.\\(?:php[s345]?\\|phtml\\)\\'" php-mode-maybe)
156(proof auto-mode-alist "\\.v\\'" coq-mode)
157(racket-mode auto-mode-alist "\\.rkt\\'")
158(racket-mode auto-mode-alist "\\.rktd\\'")
159(racket-mode auto-mode-alist "\\.rktl\\'")
160(racket-mode interpreter-mode-alist "racket")
161(raku-mode interpreter-mode-alist "perl6\\|raku")
162(raku-mode auto-mode-alist "\\.p[lm]?6\\'")
163(raku-mode auto-mode-alist "\\.nqp\\'")
164(raku-mode auto-mode-alist "\\.raku\\(?:mod\\|test\\)?\\'")
165(rfc-mode auto-mode-alist "/rfc[0-9]+\\.txt\\'")
166(rust-mode auto-mode-alist "\\.rs\\'")
167(sass-mode auto-mode-alist "\\.sass\\'")
168(scad-mode auto-mode-alist "\\.scad\\'")
169(scala-mode auto-mode-alist "\\.\\(scala\\|sbt\\|worksheet\\.sc\\)\\'")
170(stylus-mode auto-mode-alist "\\.jade\\'" jade-mode)
171(stylus-mode auto-mode-alist "\\.pug\\'" jade-mode)
172(stylus-mode auto-mode-alist "\\.styl\\'")
173(subed auto-mode-alist "\\.ass\\'" subed-ass-mode)
174(subed auto-mode-alist "\\.srt\\'" subed-srt-mode)
175(subed auto-mode-alist "\\.vtt\\'" subed-vtt-mode)
176(swift-mode auto-mode-alist "\\.swift\\(interface\\)?\\'")
177(systemd auto-mode-alist "\\.nspawn\\'" systemd-mode)
178(tuareg auto-mode-alist "\\.ml[ip]?\\'" tuareg-mode)
179(tuareg auto-mode-alist "\\.eliomi?\\'" tuareg-mode)
180(tuareg interpreter-mode-alist "ocamlrun" tuareg-mode)
181(tuareg interpreter-mode-alist "ocaml" tuareg-mode)
182(tuareg auto-mode-alist "\\.mly\\'" tuareg-menhir-mode)
183(tuareg auto-mode-alist "[./]opam_?\\'" tuareg-opam-mode)
184(typescript-mode auto-mode-alist "\\.ts\\'")
185(yaml-mode auto-mode-alist "\\.\\(e?ya?\\|ra\\)ml\\'")
186(yaml-mode magic-mode-alist "^%YAML\\s-+[0-9]+\\.[0-9]+\\(\\s-+#\\|\\s-*$\\)")
187(zig-mode auto-mode-alist "\\.\\(zig\\|zon\\)\\'")
188)
diff --git a/lisp/package/package-autosuggest.el b/lisp/package/package-autosuggest.el
new file mode 100644
index 00000000000..b0a6e863191
--- /dev/null
+++ b/lisp/package/package-autosuggest.el
@@ -0,0 +1,226 @@
1;;; package-autosuggest.el --- Automatic suggestion of relevant packages -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2023-2025 Free Software Foundation, Inc.
4
5;; Author: Philip Kaludercic <philipk@posteo.net>
6
7;; This program is free software; you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; This program is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with this program. If not, see <https://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;; Packages are suggested to users depending on the entries in the
23;; "auto-suggestion" database that is bundled with Emacs. By enabling
24;; the `package-autosuggest-mode' minor mode, Emacs will hint that files
25;; without any special support, but the user can manually query
26;; suggestions using the `package-autosuggest' command as well.
27
28;;; Code:
29
30(require 'package-core)
31(require 'package-install)
32
33(defgroup package-autosuggest nil
34 "Automatic suggestion of relevant packages."
35 :group 'package
36 :version "31.1")
37
38(defconst package-autosuggest-database
39 (eval-when-compile
40 (with-temp-buffer
41 (insert-file-contents
42 (expand-file-name "package-autosuggest.eld" data-directory))
43 (read (current-buffer))))
44 "List of hints for packages to suggest installing.
45Each hint has the form (PACKAGE TYPE DATA), where PACKAGE is a symbol
46denoting the package and major-mode the hint applies to, TYPE is one of
47`auto-mode-alist', `magic-mode-alist' or `interpreter-mode-alist'
48indicating the type of check to be made and DATA is the value to check
49against TYPE in the intuitive way (e.g. for `auto-mode-alist' DATA is a
50regular expression matching a file name that PACKAGE should be suggested
51for). If the package name and the major mode name differ, then an
52optional forth element MAJOR-MODE can indicate what command to invoke to
53enable the package.")
54
55(defcustom package-autosuggest-style 'mode-line
56 "How to draw attention to `package-autosuggest-mode' suggestions.
57You can set this value to `mode-line' (default) to indicate the
58availability of a package suggestion in the minor mode, `always' to
59prompt the user in the minibuffer every time a suggestion is available
60in a `fundamenta-mode' buffer, `once' to do only prompt the user once
61for each suggestion or `message' to just display a message hinting at
62the existence of a suggestion."
63 :type '(choice (const :tag "Indicate in mode line" mode-line)
64 (const :tag "Always prompt" always)
65 (const :tag "Prompt only once" once)
66 (const :tag "Indicate with message" message)))
67
68;;;###autoload
69(define-minor-mode package-autosuggest-mode
70 "Enable the automatic suggestion and installation of packages."
71 :global t
72 (funcall (if package-autosuggest-mode #'add-hook #'remove-hook)
73 'after-change-major-mode-hook
74 #'package--autosuggest-after-change-mode))
75
76(defvar package--autosuggest-suggested '()
77 "List of packages that have already been suggested.
78The elements of this list should be a subset of elements from
79`package-autosuggest-database'. Suggestions found in this list will not
80count as suggestions (e.g. if `package-autosuggest-style' is set to
81`mode-line', a suggestion found in here will inhibit
82`package-autosuggest-mode' from displaying a hint in the mode line).")
83
84(defun package--suggestion-applies-p (sug)
85 "Check if a suggestion SUG is applicable to the current buffer.
86SUG should be an element of `package-autosuggest-database'."
87 (pcase sug
88 (`(,(or (pred (lambda (e) (assq e package--autosuggest-suggested)))
89 (pred package-installed-p))
90 . ,_)
91 nil)
92 ((or `(,_ auto-mode-alist ,ext ,_)
93 `(,_ auto-mode-alist ,ext))
94 (and (string-match-p ext (buffer-name)) t))
95 ((or `(,_ magic-mode-alist ,mag ,_)
96 `(,_ magic-mode-alist ,mag))
97 (save-restriction
98 (widen)
99 (save-excursion
100 (goto-char (point-min))
101 (looking-at-p mag))))
102 ((or `(,_ interpreter-mode-alist ,magic ,_)
103 `(,_ interpreter-mode-alist ,magic))
104 (save-restriction
105 (widen)
106 (save-excursion
107 (goto-char (point-min))
108 (and (looking-at auto-mode-interpreter-regexp)
109 (string-match-p
110 (concat "\\`" (file-name-nondirectory (match-string 2)) "\\'")
111 magic)))))))
112
113(defun package--autosuggest-find-candidates ()
114 "Return a list of suggestions that might be interesting the current buffer.
115The elements of the returned list will be a subset of the elements of
116`package--autosuggest-suggested'."
117 (and package-autosuggest-mode (eq major-mode 'fundamental-mode)
118 (let (suggetions)
119 (dolist (sug package-autosuggest-database)
120 (when (package--suggestion-applies-p sug)
121 (push sug suggetions)))
122 suggetions)))
123
124(defun package--autosuggest-install-and-enable (sug)
125 "Install and enable a package suggestion PKG-ENT.
126SUG should be an element of `package-autosuggest-database'."
127 (let ((buffers-to-update '()))
128 (dolist (buf (buffer-list))
129 (with-current-buffer buf
130 (when (and (eq major-mode 'fundamental-mode) (buffer-file-name)
131 (package--suggestion-applies-p sug))
132 (push buf buffers-to-update))))
133 (with-demoted-errors "Failed to install package: %S"
134 (package-install (car sug))
135 (dolist (buf buffers-to-update)
136 (with-demoted-errors "Failed to enable major mode: %S"
137 (with-current-buffer buf
138 (funcall-interactively (or (cadddr sug) (car sug)))))))))
139
140(defvar package--autosugest-line-format
141 '(:eval (package--autosugest-line-format)))
142(put 'package--autosugest-line-format 'risky-local-variable t)
143
144(defface package-autosuggest-face
145 '((t :inherit (success)))
146 "Face to use in the mode line to highlight suggested packages."
147 :version "30.1")
148
149(defun package--autosugest-line-format ()
150 "Generate a mode-line string to indicate a suggested package."
151 `(,@(and-let* (((not (null package-autosuggest-mode)))
152 ((eq package-autosuggest-style 'mode-line))
153 (avail (package--autosuggest-find-candidates)))
154 (propertize
155 (format "Install %s?"
156 (mapconcat
157 #'symbol-name
158 (delete-dups (mapcar #'car avail))
159 ", "))
160 'face 'package-autosuggest-face
161 'mouse-face 'mode-line-highlight
162 'help-echo "Click to install suggested package."
163 'keymap (let ((map (make-sparse-keymap)))
164 (define-key map [mode-line down-mouse-1] #'package-autosuggest)
165 map)))))
166
167;;;###autoload
168(progn
169 (add-to-list
170 'mode-line-misc-info
171 '(package-autosuggest-mode ("" package--autosugest-line-format))))
172
173(defun package--autosuggest-after-change-mode ()
174 "Display package suggestions for the current buffer.
175This function should be added to `after-change-major-mode-hook'."
176 (when-let* ((avail (package--autosuggest-find-candidates))
177 (pkgs (mapconcat #'symbol-name
178 (delete-dups (mapcar #'car avail))
179 ", ")))
180 (pcase-exhaustive package-autosuggest-style
181 ('mode-line
182 (force-mode-line-update t))
183 ('always
184 (when (yes-or-no-p (format "Install suggested packages (%s)?" pkgs))
185 (mapc #'package--autosuggest-install-and-enable avail)))
186 ('once
187 (when (yes-or-no-p (format "Install suggested packages (%s)?" pkgs))
188 (mapc #'package--autosuggest-install-and-enable avail))
189 (setq package--autosuggest-suggested (append avail package--autosuggest-suggested)))
190 ('message
191 (message
192 (substitute-command-keys
193 (format "Found suggested packages: %s. Install using \\[package-autosuggest]"
194 pkgs)))))))
195
196;;;###autoload
197(defun package-autosuggest ()
198 "Prompt the user to install the suggested packages."
199 (interactive)
200 (let* ((avail (or (package--autosuggest-find-candidates)
201 (user-error "No suggestions found")))
202 (use-dialog-box t)
203 (prompt (concat
204 "Install "
205 (mapconcat
206 #'symbol-name
207 (delete-dups (mapcar #'car avail))
208 ", ")
209 "?")))
210 (if (yes-or-no-p prompt)
211 (mapc #'package--autosuggest-install-and-enable avail)
212 (setq package--autosuggest-suggested (append avail package--autosuggest-suggested))
213 (when (eq package-autosuggest-style 'mode-line)
214 (force-mode-line-update t)))))
215
216(defun package-reset-suggestions ()
217 "Forget previous package suggestions.
218Emacs will remember if you have previously rejected a suggestion during
219a session and won't mention it afterwards. If you have made a mistake
220or would like to reconsider this, use this command to want to reset the
221suggestions."
222 (interactive)
223 (setq package--autosuggest-suggested nil))
224
225(provide 'package-autosuggest)
226;;; package-autosuggest.el ends here