diff options
| author | Philip Kaludercic | 2025-09-02 14:51:09 +0200 |
|---|---|---|
| committer | Philip Kaludercic | 2025-09-02 14:51:09 +0200 |
| commit | 055ee29eb43221385b30182643d627512ca99ef7 (patch) | |
| tree | cdc3021f6f6ad11376140ec15cf1b51bfb4878d4 | |
| parent | 218eab3f99a53bbb4f4efc8cfa1b78c268238e07 (diff) | |
| parent | 2a88c6dc3c36f726275554f1b93e32fd726e415c (diff) | |
| download | emacs-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.el | 81 | ||||
| -rw-r--r-- | doc/emacs/package.texi | 10 | ||||
| -rw-r--r-- | etc/NEWS | 11 | ||||
| -rw-r--r-- | etc/package-autosuggest.eld | 188 | ||||
| -rw-r--r-- | lisp/package/package-autosuggest.el | 226 |
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. | ||
| 30 | This file will automatically update \"package-autosuggest.eld\", but not | ||
| 31 | save it. You should invoke this command with built GNU ELPA and NonGNU | ||
| 32 | ELPA checkouts (i.e. having run \"make autoloads\" in both directories). | ||
| 33 | Please 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 | |||
| 439 | access. Such local archives are mainly useful for testing. | 439 | access. 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 | ||
| 446 | types. If Emacs opens a file with no specific mode, you can use the | ||
| 447 | @code{package-autosuggest} command to install the recommended packages | ||
| 448 | from ELPA. After enabling @code{package-autosuggest-mode}, Emacs will | ||
| 449 | display a clickable hint in the mode-line if it there is a suggested | ||
| 450 | package. | ||
| 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 |
| @@ -2258,6 +2258,17 @@ version-list of a given package symbol. These functions provide public | |||
| 2258 | interfaces for external tools to query information about built-in | 2258 | interfaces for external tools to query information about built-in |
| 2259 | packages. | 2259 | packages. |
| 2260 | 2260 | ||
| 2261 | +++ | ||
| 2262 | *** New command 'package-autosuggest' | ||
| 2263 | Using a built-in database of package suggestions from ELPA, this command | ||
| 2264 | will install viable packages if no specific major mode is available. | ||
| 2265 | |||
| 2266 | +++ | ||
| 2267 | *** New minor mode 'package-autosuggest-mode' | ||
| 2268 | When enabled, this displays a hint in the mode line indicating the | ||
| 2269 | availability of a suggested package. You can customise the presentation | ||
| 2270 | of 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. | ||
| 45 | Each hint has the form (PACKAGE TYPE DATA), where PACKAGE is a symbol | ||
| 46 | denoting the package and major-mode the hint applies to, TYPE is one of | ||
| 47 | `auto-mode-alist', `magic-mode-alist' or `interpreter-mode-alist' | ||
| 48 | indicating the type of check to be made and DATA is the value to check | ||
| 49 | against TYPE in the intuitive way (e.g. for `auto-mode-alist' DATA is a | ||
| 50 | regular expression matching a file name that PACKAGE should be suggested | ||
| 51 | for). If the package name and the major mode name differ, then an | ||
| 52 | optional forth element MAJOR-MODE can indicate what command to invoke to | ||
| 53 | enable the package.") | ||
| 54 | |||
| 55 | (defcustom package-autosuggest-style 'mode-line | ||
| 56 | "How to draw attention to `package-autosuggest-mode' suggestions. | ||
| 57 | You can set this value to `mode-line' (default) to indicate the | ||
| 58 | availability of a package suggestion in the minor mode, `always' to | ||
| 59 | prompt the user in the minibuffer every time a suggestion is available | ||
| 60 | in a `fundamenta-mode' buffer, `once' to do only prompt the user once | ||
| 61 | for each suggestion or `message' to just display a message hinting at | ||
| 62 | the 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. | ||
| 78 | The elements of this list should be a subset of elements from | ||
| 79 | `package-autosuggest-database'. Suggestions found in this list will not | ||
| 80 | count 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. | ||
| 86 | SUG 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. | ||
| 115 | The 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. | ||
| 126 | SUG 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. | ||
| 175 | This 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. | ||
| 218 | Emacs will remember if you have previously rejected a suggestion during | ||
| 219 | a session and won't mention it afterwards. If you have made a mistake | ||
| 220 | or would like to reconsider this, use this command to want to reset the | ||
| 221 | suggestions." | ||
| 222 | (interactive) | ||
| 223 | (setq package--autosuggest-suggested nil)) | ||
| 224 | |||
| 225 | (provide 'package-autosuggest) | ||
| 226 | ;;; package-autosuggest.el ends here | ||