Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

470 lines
20 KiB

преди 4 години
  1. ;;; helm-elisp-package.el --- helm interface for package.el -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2012 ~ 2019 Thierry Volpiatto <thierry.volpiatto@gmail.com>
  3. ;; This program is free software; you can redistribute it and/or modify
  4. ;; it under the terms of the GNU General Public License as published by
  5. ;; the Free Software Foundation, either version 3 of the License, or
  6. ;; (at your option) any later version.
  7. ;; This program is distributed in the hope that it will be useful,
  8. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. ;; GNU General Public License for more details.
  11. ;; You should have received a copy of the GNU General Public License
  12. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ;;; Code:
  14. (require 'cl-lib)
  15. (require 'helm)
  16. (require 'helm-help)
  17. (require 'package)
  18. (defgroup helm-el-package nil
  19. "helm elisp packages."
  20. :group 'helm)
  21. (defcustom helm-el-package-initial-filter 'all
  22. "Show only installed, upgraded or all packages at startup."
  23. :group 'helm-el-package
  24. :type '(radio :tag "Initial filter for elisp packages"
  25. (const :tag "Show all packages" all)
  26. (const :tag "Show installed packages" installed)
  27. (const :tag "Show not installed packages" uninstalled)
  28. (const :tag "Show upgradable packages" upgrade)))
  29. (defcustom helm-el-truncate-lines t
  30. "Truncate lines in helm-buffer when non--nil."
  31. :group 'helm-el-package
  32. :type 'boolean)
  33. ;; internals vars
  34. (defvar helm-el-package--show-only 'all)
  35. (defvar helm-el-package--initialized-p nil)
  36. (defvar helm-el-package--tabulated-list nil)
  37. (defvar helm-el-package--upgrades nil)
  38. (defvar helm-el-package--removable-packages nil)
  39. ;; Shutup bytecompiler for emacs-24*
  40. (defvar package-menu-async) ; Only available on emacs-25.
  41. (declare-function async-byte-recompile-directory "ext:async-bytecomp.el")
  42. (defun helm-el-package--init ()
  43. ;; In emacs-27 package-show-package-list returns an empty buffer
  44. ;; until package-initialize have been called.
  45. (unless (or package--initialized
  46. (null (boundp 'package-quickstart)))
  47. (package-initialize))
  48. (let (package-menu-async
  49. (inhibit-read-only t))
  50. (when (null package-alist)
  51. (setq helm-el-package--show-only 'all))
  52. (when (setq helm-el-package--removable-packages
  53. (package--removable-packages))
  54. (package-autoremove))
  55. (unwind-protect
  56. (progn
  57. (save-selected-window
  58. (if helm-el-package--initialized-p
  59. ;; Use this as `list-packages' doesn't work
  60. ;; properly (empty buffer) when called from lisp
  61. ;; with 'no-fetch (emacs-25 WA).
  62. (package-show-package-list)
  63. (when helm--force-updating-p (message "Refreshing packages list..."))
  64. (list-packages helm-el-package--initialized-p))
  65. (setq helm-el-package--initialized-p t)
  66. (message nil))
  67. (helm-init-candidates-in-buffer
  68. 'global
  69. (with-current-buffer (get-buffer "*Packages*")
  70. (setq helm-el-package--tabulated-list tabulated-list-entries)
  71. (remove-text-properties (point-min) (point-max)
  72. '(read-only button follow-link category))
  73. (goto-char (point-min))
  74. (while (re-search-forward "^[ \t]+" nil t)
  75. (replace-match ""))
  76. (buffer-string)))
  77. (setq helm-el-package--upgrades (helm-el-package-menu--find-upgrades))
  78. (if helm--force-updating-p
  79. (if helm-el-package--upgrades
  80. (message "Refreshing packages list done, [%d] package(s) to upgrade"
  81. (length helm-el-package--upgrades))
  82. (message "Refreshing packages list done, no upgrades available"))
  83. (setq helm-el-package--show-only (if helm-el-package--upgrades
  84. 'upgrade
  85. helm-el-package-initial-filter))))
  86. (kill-buffer "*Packages*"))))
  87. (defun helm-el-package-describe (candidate)
  88. (let ((id (get-text-property 0 'tabulated-list-id candidate)))
  89. (describe-package (package-desc-name id))))
  90. (defun helm-el-package-visit-homepage (candidate)
  91. (let* ((id (get-text-property 0 'tabulated-list-id candidate))
  92. (pkg (package-desc-name id))
  93. (desc (cadr (assoc pkg package-archive-contents)))
  94. (extras (package-desc-extras desc))
  95. (url (and (listp extras) (cdr-safe (assoc :url extras)))))
  96. (if (stringp url)
  97. (browse-url url)
  98. (message "Package %s has no homepage"
  99. (propertize (symbol-name pkg)
  100. 'face 'font-lock-keyword-face)))))
  101. (defun helm-el-run-visit-homepage ()
  102. (interactive)
  103. (with-helm-alive-p
  104. (helm-exit-and-execute-action 'helm-el-package-visit-homepage)))
  105. (put 'helm-el-run-visit-homepage 'helm-only t)
  106. (defun helm-elisp-package--pkg-name (pkg)
  107. (if (package-desc-p pkg)
  108. (package-desc-name pkg)
  109. pkg))
  110. (defun helm-el-package-install-1 (pkg-list)
  111. (cl-loop with mkd = pkg-list
  112. for p in mkd
  113. for id = (get-text-property 0 'tabulated-list-id p)
  114. for name = (helm-elisp-package--pkg-name id)
  115. do (package-install id t)
  116. when (helm-aand (assq name package-alist)
  117. (package-desc-dir (cadr it))
  118. (file-exists-p it))
  119. collect id into installed-list and
  120. do (unless (package--user-selected-p name)
  121. (package--save-selected-packages
  122. (cons name package-selected-packages)))
  123. finally do (message (format "%d packages installed:\n(%s)"
  124. (length installed-list)
  125. (mapconcat #'package-desc-full-name
  126. installed-list ", ")))))
  127. (defun helm-el-package-install (_candidate)
  128. (helm-el-package-install-1 (helm-marked-candidates)))
  129. (defun helm-el-run-package-install ()
  130. (interactive)
  131. (with-helm-alive-p
  132. (helm-exit-and-execute-action 'helm-el-package-install)))
  133. (put 'helm-el-run-package-install 'helm-only t)
  134. (defun helm-el-package-uninstall-1 (pkg-list &optional force)
  135. (cl-loop with mkd = pkg-list
  136. for p in mkd
  137. for id = (get-text-property 0 'tabulated-list-id p)
  138. do
  139. (condition-case-unless-debug err
  140. (package-delete id force)
  141. (error (message (cadr err))))
  142. ;; Seems like package-descs are symbols with props instead of
  143. ;; vectors in emacs-27, use package-desc-name to ensure
  144. ;; compatibility in all emacs versions.
  145. unless (assoc (package-desc-name id) package-alist)
  146. collect id into delete-list
  147. finally do (if delete-list
  148. (message (format "%d packages deleted:\n(%s)"
  149. (length delete-list)
  150. (mapconcat #'package-desc-full-name
  151. delete-list ", ")))
  152. "No package deleted")))
  153. (defun helm-el-package-uninstall (_candidate)
  154. (helm-el-package-uninstall-1 (helm-marked-candidates) helm-current-prefix-arg))
  155. (defun helm-el-run-package-uninstall ()
  156. (interactive)
  157. (with-helm-alive-p
  158. (helm-exit-and-execute-action 'helm-el-package-uninstall)))
  159. (put 'helm-el-run-package-uninstall 'helm-only t)
  160. (defun helm-el-package-menu--find-upgrades ()
  161. (cl-loop for entry in helm-el-package--tabulated-list
  162. for pkg-desc = (car entry)
  163. for status = (package-desc-status pkg-desc)
  164. ;; A dependency.
  165. when (string= status "dependency")
  166. collect pkg-desc into dependencies
  167. ;; An installed package used as dependency (user have
  168. ;; installed this package explicitely).
  169. when (package--used-elsewhere-p pkg-desc)
  170. collect pkg-desc into installed-as-dep
  171. ;; An installed package.
  172. when (member status '("installed" "unsigned"))
  173. collect pkg-desc into installed
  174. when (member status '("available" "new"))
  175. collect (cons (package-desc-name pkg-desc) pkg-desc) into available
  176. finally return
  177. ;; Always try to upgrade dependencies before installed.
  178. (cl-loop with all = (append dependencies installed-as-dep installed)
  179. for pkg in all
  180. for name = (package-desc-name pkg)
  181. for avail-pkg = (assq name available)
  182. when (and avail-pkg
  183. (version-list-<
  184. (package-desc-version pkg)
  185. (package-desc-version (cdr avail-pkg))))
  186. collect avail-pkg)))
  187. (defun helm-el-package--user-installed-p (package)
  188. "Return non-nil if PACKAGE is a user-installed package."
  189. (let* ((assoc (assq package package-alist))
  190. (pkg-desc (and assoc (cadr assoc)))
  191. (dir (and pkg-desc (package-desc-dir pkg-desc))))
  192. (when dir
  193. (file-in-directory-p dir package-user-dir))))
  194. (defun helm-el-package-upgrade-1 (pkg-list)
  195. (cl-loop for p in pkg-list
  196. for pkg-desc = (car p)
  197. for pkg-name = (package-desc-name pkg-desc)
  198. for upgrade = (cdr (assq pkg-name
  199. helm-el-package--upgrades))
  200. do
  201. (cond (;; Install.
  202. (equal pkg-desc upgrade)
  203. (message "Installing package `%s'" pkg-name)
  204. (package-install pkg-desc t))
  205. (;; Do nothing.
  206. (or (null upgrade)
  207. ;; This may happen when a Elpa version of pkg
  208. ;; is installed and need upgrade and pkg is as
  209. ;; well a builtin package.
  210. (package-built-in-p pkg-name))
  211. (ignore))
  212. (;; Delete.
  213. t
  214. (message "Deleting package `%s'" pkg-name)
  215. (package-delete pkg-desc t t)))))
  216. (defun helm-el-package-upgrade (_candidate)
  217. (helm-el-package-upgrade-1
  218. (cl-loop with pkgs = (helm-marked-candidates)
  219. for p in helm-el-package--tabulated-list
  220. for pkg = (car p)
  221. if (member (symbol-name (package-desc-name pkg)) pkgs)
  222. collect p)))
  223. (defun helm-el-run-package-upgrade ()
  224. (interactive)
  225. (with-helm-alive-p
  226. (helm-exit-and-execute-action 'helm-el-package-upgrade)))
  227. (put 'helm-el-run-package-upgrade 'helm-only t)
  228. (defun helm-el-package-upgrade-all ()
  229. (if helm-el-package--upgrades
  230. (with-helm-display-marked-candidates
  231. helm-marked-buffer-name (helm-fast-remove-dups
  232. (mapcar (lambda (x) (symbol-name (car x)))
  233. helm-el-package--upgrades)
  234. :test 'equal)
  235. (when (y-or-n-p "Upgrade all packages? ")
  236. (helm-el-package-upgrade-1 helm-el-package--tabulated-list)))
  237. (message "No packages to upgrade actually!")))
  238. (defun helm-el-package-upgrade-all-action (_candidate)
  239. (helm-el-package-upgrade-all))
  240. (defun helm-el-run-package-upgrade-all ()
  241. (interactive)
  242. (with-helm-alive-p
  243. (helm-exit-and-execute-action 'helm-el-package-upgrade-all-action)))
  244. (put 'helm-el-run-package-upgrade-all 'helm-only t)
  245. (defun helm-el-package--transformer (candidates _source)
  246. (cl-loop for c in candidates
  247. for disp = (concat " " c)
  248. for id = (get-text-property 0 'tabulated-list-id c)
  249. for name = (and id (package-desc-name id))
  250. for desc = (package-desc-status id)
  251. for built-in-p = (and (package-built-in-p name)
  252. (not (member desc '("available" "new"
  253. "installed" "dependency"))))
  254. for installed-p = (member desc '("installed" "dependency"))
  255. for upgrade-p = (assq name helm-el-package--upgrades)
  256. for user-installed-p = (memq name package-selected-packages)
  257. do (when (and user-installed-p (not upgrade-p))
  258. (put-text-property 0 2 'display "S " disp))
  259. do (when (or (memq name helm-el-package--removable-packages)
  260. (and upgrade-p installed-p))
  261. (put-text-property 0 2 'display "U " disp)
  262. (put-text-property
  263. 2 (+ (length (symbol-name name)) 2)
  264. 'face 'font-lock-variable-name-face disp))
  265. do (when (and upgrade-p (not installed-p) (not built-in-p))
  266. (put-text-property 0 2 'display "I " disp))
  267. for cand = (cons disp (car (split-string disp)))
  268. when (or (and built-in-p
  269. (eq helm-el-package--show-only 'built-in))
  270. (and upgrade-p
  271. (eq helm-el-package--show-only 'upgrade))
  272. (and installed-p
  273. (eq helm-el-package--show-only 'installed))
  274. (and (not installed-p)
  275. (not built-in-p)
  276. (eq helm-el-package--show-only 'uninstalled))
  277. (eq helm-el-package--show-only 'all))
  278. collect cand))
  279. (defun helm-el-package-show-built-in ()
  280. (interactive)
  281. (with-helm-alive-p
  282. (setq helm-el-package--show-only 'built-in)
  283. (helm-update)))
  284. (put 'helm-el-package-show-built-in 'helm-only t)
  285. (defun helm-el-package-show-upgrade ()
  286. (interactive)
  287. (with-helm-alive-p
  288. (setq helm-el-package--show-only 'upgrade)
  289. (helm-update)))
  290. (put 'helm-el-package-show-upgrade 'helm-only t)
  291. (defun helm-el-package-show-installed ()
  292. (interactive)
  293. (with-helm-alive-p
  294. (setq helm-el-package--show-only 'installed)
  295. (helm-update)))
  296. (put 'helm-el-package-show-installed 'helm-only t)
  297. (defun helm-el-package-show-all ()
  298. (interactive)
  299. (with-helm-alive-p
  300. (setq helm-el-package--show-only 'all)
  301. (helm-update)))
  302. (put 'helm-el-package-show-all 'helm-only t)
  303. (defun helm-el-package-show-uninstalled ()
  304. (interactive)
  305. (with-helm-alive-p
  306. (setq helm-el-package--show-only 'uninstalled)
  307. (helm-update)))
  308. (put 'helm-el-package-show-uninstalled 'helm-only t)
  309. (defvar helm-el-package-map
  310. (let ((map (make-sparse-keymap)))
  311. (set-keymap-parent map helm-map)
  312. (define-key map (kbd "M-I") 'helm-el-package-show-installed)
  313. (define-key map (kbd "M-O") 'helm-el-package-show-uninstalled)
  314. (define-key map (kbd "M-U") 'helm-el-package-show-upgrade)
  315. (define-key map (kbd "M-B") 'helm-el-package-show-built-in)
  316. (define-key map (kbd "M-A") 'helm-el-package-show-all)
  317. (define-key map (kbd "C-c i") 'helm-el-run-package-install)
  318. (define-key map (kbd "C-c r") 'helm-el-run-package-reinstall)
  319. (define-key map (kbd "C-c d") 'helm-el-run-package-uninstall)
  320. (define-key map (kbd "C-c u") 'helm-el-run-package-upgrade)
  321. (define-key map (kbd "C-c U") 'helm-el-run-package-upgrade-all)
  322. (define-key map (kbd "C-c @") 'helm-el-run-visit-homepage)
  323. map))
  324. (defvar helm-source-list-el-package nil)
  325. (defclass helm-list-el-package-source (helm-source-in-buffer)
  326. ((init :initform 'helm-el-package--init)
  327. (get-line :initform 'buffer-substring)
  328. (filtered-candidate-transformer :initform 'helm-el-package--transformer)
  329. (action-transformer :initform 'helm-el-package--action-transformer)
  330. (help-message :initform 'helm-el-package-help-message)
  331. (keymap :initform helm-el-package-map)
  332. (update :initform 'helm-el-package--update)
  333. (candidate-number-limit :initform 9999)
  334. (action :initform '(("Describe package" . helm-el-package-describe)
  335. ("Visit homepage" . helm-el-package-visit-homepage)))
  336. (group :initform 'helm-el-package)))
  337. (defun helm-el-package--action-transformer (actions candidate)
  338. (let* ((pkg-desc (get-text-property 0 'tabulated-list-id candidate))
  339. (status (package-desc-status pkg-desc))
  340. (pkg-name (package-desc-name pkg-desc))
  341. (built-in (and (package-built-in-p pkg-name)
  342. (not (member status '("available" "new"
  343. "installed" "dependency")))))
  344. (acts (if helm-el-package--upgrades
  345. (append actions '(("Upgrade all packages"
  346. . helm-el-package-upgrade-all-action)))
  347. actions)))
  348. (cond (built-in '(("Describe package" . helm-el-package-describe)))
  349. ((and (package-installed-p pkg-name)
  350. (cdr (assq pkg-name helm-el-package--upgrades))
  351. (member status '("installed" "dependency")))
  352. (append '(("Upgrade package(s)" . helm-el-package-upgrade)
  353. ("Uninstall package(s)" . helm-el-package-uninstall))
  354. acts))
  355. ((and (package-installed-p pkg-name)
  356. (cdr (assq pkg-name helm-el-package--upgrades))
  357. (string= status "available"))
  358. (append '(("Upgrade package(s)" . helm-el-package-upgrade))
  359. acts))
  360. ((and (package-installed-p pkg-name)
  361. (or (null (package-built-in-p pkg-name))
  362. (and (package-built-in-p pkg-name)
  363. (assq pkg-name package-alist))))
  364. (append acts '(("Reinstall package(s)" . helm-el-package-reinstall)
  365. ("Recompile package(s)" . helm-el-package-recompile)
  366. ("Uninstall package(s)" . helm-el-package-uninstall))))
  367. (t (append acts '(("Install packages(s)" . helm-el-package-install)))))))
  368. (defun helm-el-package--update ()
  369. (setq helm-el-package--initialized-p nil))
  370. (defun helm-el-package-recompile (_pkg)
  371. (cl-loop for p in (helm-marked-candidates)
  372. do (helm-el-package-recompile-1 p)))
  373. (defun helm-el-package-recompile-1 (pkg)
  374. (let* ((pkg-desc (get-text-property 0 'tabulated-list-id pkg))
  375. (dir (package-desc-dir pkg-desc)))
  376. (async-byte-recompile-directory dir)))
  377. (defun helm-el-package-reinstall (_pkg)
  378. (cl-loop for p in (helm-marked-candidates)
  379. for pkg-desc = (get-text-property 0 'tabulated-list-id p)
  380. do (helm-el-package-reinstall-1 pkg-desc)))
  381. (defun helm-el-package-reinstall-1 (pkg-desc)
  382. (let ((name (package-desc-name pkg-desc)))
  383. (package-delete pkg-desc 'force 'nosave)
  384. ;; pkg-desc contain the description
  385. ;; of the installed package just removed
  386. ;; and is BTW no more valid.
  387. ;; Use the entry in package-archive-content
  388. ;; which is the non--installed package entry.
  389. ;; For some reason `package-install'
  390. ;; need a pkg-desc (package-desc-p) for the build-in
  391. ;; packages already installed, the name (as symbol)
  392. ;; fails with such packages.
  393. (package-install
  394. (cadr (assq name package-archive-contents)) t)))
  395. (defun helm-el-run-package-reinstall ()
  396. (interactive)
  397. (with-helm-alive-p
  398. (helm-exit-and-execute-action 'helm-el-package-reinstall)))
  399. (put 'helm-el-run-package-reinstall 'helm-only t)
  400. ;;;###autoload
  401. (defun helm-list-elisp-packages (arg)
  402. "Preconfigured helm for listing and handling emacs packages."
  403. (interactive "P")
  404. (when arg (setq helm-el-package--initialized-p nil))
  405. (unless helm-source-list-el-package
  406. (setq helm-source-list-el-package
  407. (helm-make-source "list packages" 'helm-list-el-package-source)))
  408. (helm :sources 'helm-source-list-el-package
  409. :truncate-lines helm-el-truncate-lines
  410. :full-frame t
  411. :buffer "*helm list packages*"))
  412. ;;;###autoload
  413. (defun helm-list-elisp-packages-no-fetch (arg)
  414. "Preconfigured helm for emacs packages.
  415. Same as `helm-list-elisp-packages' but don't fetch packages on remote.
  416. Called with a prefix ARG always fetch packages on remote."
  417. (interactive "P")
  418. (let ((helm-el-package--initialized-p (null arg)))
  419. (helm-list-elisp-packages nil)))
  420. (provide 'helm-elisp-package)
  421. ;;; helm-elisp-package.el ends here