guix-ui-profile.el 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. ;;; guix-ui-profile.el --- Interface for displaying profiles -*- lexical-binding: t -*-
  2. ;; Copyright © 2016–2019 Alex Kost <alezost@gmail.com>
  3. ;; This file is part of Emacs-Guix.
  4. ;; Emacs-Guix is free software; you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation, either version 3 of the License, or
  7. ;; (at your option) any later version.
  8. ;;
  9. ;; Emacs-Guix is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with Emacs-Guix. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;; This file provides a 'list' interface for displaying Guix profiles
  18. ;; with `guix-profiles' command.
  19. ;;
  20. ;; `guix-profiles' variable controls what profiles are displayed.
  21. ;;; Code:
  22. (require 'dash)
  23. (require 'bui)
  24. (require 'guix nil t)
  25. (require 'guix-profiles)
  26. (require 'guix-read)
  27. (require 'guix-utils)
  28. (require 'guix-misc)
  29. (guix-define-groups profile)
  30. (bui-define-entry-type guix-profile
  31. :get-entries-function 'guix-profile-get-entries
  32. :message-function 'guix-profile-message
  33. :titles '((number-of-packages . "Packages")
  34. (number-of-generations . "Generations")))
  35. (defvar guix-profiles nil
  36. "List of profiles displayed by '\\[guix-profiles]' command.
  37. This variable is set automatically when it is needed (on the
  38. first use).
  39. If you need to add more profiles to it, see Info
  40. node `(emacs-guix) Profile Commands' to learn how to do it
  41. properly.")
  42. (defun guix-all-profiles ()
  43. "Return a list of all profiles."
  44. (or guix-profiles
  45. (setq guix-profiles
  46. (--filter
  47. (and it (file-exists-p it))
  48. (delete-dups
  49. (-cons* guix-default-user-profile
  50. guix-default-pulled-profile
  51. guix-system-profile
  52. (--when-let (getenv "GUIX_PROFILE")
  53. (guix-file-name it))
  54. (guix-eval-read "(user-profiles)")))))))
  55. (defun guix-profile->entry (profile)
  56. "Return 'guix-profile' entry by PROFILE file-name."
  57. (let* ((profile (guix-profile profile))
  58. (number-of-packages (guix-profile-number-of-packages
  59. profile)))
  60. (if number-of-packages
  61. `((id . ,profile)
  62. (profile . ,profile)
  63. (current . ,(guix-current-profile? profile))
  64. (number-of-packages . ,number-of-packages)
  65. (number-of-generations . ,(guix-profile-number-of-generations
  66. profile)))
  67. (error "No packages in '%s'. Is it a real profile?" profile))))
  68. (defun guix-profile-get-entries (&optional search-type &rest args)
  69. "Return 'guix-profile' entries."
  70. (let ((profiles (cond
  71. ((or (null search-type)
  72. (eq search-type 'all))
  73. (guix-all-profiles))
  74. ((memq search-type '(id profile file-name))
  75. args)
  76. (t (error "Wrong search-type: %S" search-type)))))
  77. (mapcar #'guix-profile->entry profiles)))
  78. (defun guix-profile-message (entries &rest _)
  79. "Display a message after showing profile ENTRIES."
  80. (unless entries
  81. (message "Oops, Guix profiles not found.
  82. Please check `guix-profiles' variable.")))
  83. (defun guix-read-profile-from-entries (&optional entries)
  84. "Return profile file name from ENTRIES (current entries by default).
  85. If there is only one entry, return its profile name. If there
  86. are multiple entries, prompt for a profile name and return it."
  87. (or entries (setq entries (bui-current-entries)))
  88. (if (cdr entries)
  89. (completing-read "Profile: "
  90. (--map (bui-entry-value it 'profile)
  91. entries))
  92. (bui-entry-value (car entries) 'profile)))
  93. ;;; Profile 'list'
  94. (bui-define-interface guix-profile list
  95. :mode-name "Profile-List"
  96. :buffer-name "*Guix Profiles*"
  97. :describe-function 'guix-profile-list-describe
  98. :format '((current guix-profile-list-get-current 10 t)
  99. (profile bui-list-get-file-name 40 t)
  100. (number-of-packages nil 11 bui-list-sort-numerically-2
  101. :right-align t)
  102. (number-of-generations nil 14 bui-list-sort-numerically-3
  103. :right-align t))
  104. :hint 'guix-profile-list-hint
  105. :sort-key '(profile))
  106. (let ((map guix-profile-list-mode-map))
  107. (define-key map (kbd "E") 'guix-profile-list-show-search-paths)
  108. (define-key map (kbd "P") 'guix-profile-list-show-packages)
  109. (define-key map (kbd "G") 'guix-profile-list-show-generations)
  110. (define-key map (kbd "M") 'guix-profile-list-apply-manifest)
  111. (define-key map (kbd "c") 'guix-profile-list-set-current))
  112. (defvar guix-profile-list-default-hint
  113. '(("\\[guix-profile-list-show-packages]") " show packages;\n"
  114. ("\\[guix-profile-list-show-generations]") " show generations;\n"
  115. ("\\[guix-profile-list-show-search-paths]") " show search paths;\n"
  116. ("\\[guix-profile-list-set-current]") " set current profile;\n"
  117. ("\\[guix-profile-list-apply-manifest]") " apply manifest;\n"))
  118. (defun guix-profile-list-hint ()
  119. (bui-format-hints
  120. guix-profile-list-default-hint
  121. (bui-default-hint)))
  122. (defun guix-profile-list-describe (&rest ids)
  123. "Describe profiles with IDS (list of identifiers)."
  124. (bui-display-entries
  125. (bui-entries-by-ids (bui-current-entries) ids)
  126. 'guix-profile 'info (cons 'id ids)))
  127. (defun guix-profile-list-current-profile ()
  128. "Return file name of the current profile."
  129. ;; (bui-entry-value (bui-list-current-entry) 'profile)
  130. ;; Just get the ID, as currently ID is the profile file name.
  131. (bui-list-current-id))
  132. (defun guix-profile-list-marked-profiles ()
  133. "Return a list of file names of the marked profiles.
  134. If nothing is marked, return a list with profile at point."
  135. (bui-list-marked-or-current))
  136. (declare-function guix-installed-packages "guix-ui-package" t)
  137. (declare-function guix-generations "guix-ui-generation" t)
  138. (defun guix-profile-list-show-packages ()
  139. "Display packages installed in the current profile."
  140. (interactive)
  141. (guix-installed-packages (guix-profile-list-current-profile)))
  142. (defun guix-profile-list-show-generations ()
  143. "Display generations of the current profile."
  144. (interactive)
  145. (guix-generations (guix-profile-list-current-profile)))
  146. (defun guix-profile-list-show-search-paths (&optional type)
  147. "Display 'search paths' environment variables for the marked profiles.
  148. If nothing is marked, use profile on the current line."
  149. (interactive (list (guix-read-search-paths-type)))
  150. (guix-show-search-paths (guix-profile-list-marked-profiles) type))
  151. (defun guix-profile-list-apply-manifest (file)
  152. "Apply manifest from FILE to the current profile."
  153. (interactive (list (guix-read-manifest-file-name)))
  154. (guix-apply-manifest (guix-package-profile
  155. (guix-profile-list-current-profile))
  156. file (current-buffer)))
  157. (defun guix-profile-list-get-current (value &optional _)
  158. "Return string from VALUE showing whether this profile is current."
  159. (if value "(current)" ""))
  160. (defun guix-profile-list-set-current ()
  161. "Set `guix-current-profile' to the profile on the current line."
  162. (interactive)
  163. (guix-set-current-profile (guix-profile-list-current-profile))
  164. ;; Now updating "Current" column is needed. It can be done simply by
  165. ;; reverting the buffer, but it should be more effective to reset
  166. ;; 'current' parameter for all entries and to redisplay the buffer
  167. ;; instead.
  168. (let* ((current-id (bui-list-current-id))
  169. (new-entries (mapcar
  170. (lambda (entry)
  171. (let ((id (bui-entry-id entry)))
  172. (cons `(current . ,(equal id current-id))
  173. (--remove-first (eq (car it) 'current)
  174. entry))))
  175. (bui-current-entries))))
  176. (setf (bui-item-entries bui-item)
  177. new-entries))
  178. (bui-redisplay))
  179. ;;; Profile 'info'
  180. (bui-define-interface guix-profile info
  181. :mode-name "Profile-Info"
  182. :buffer-name "*Guix Profile Info*"
  183. :format '((profile nil (simple bui-file))
  184. nil
  185. guix-profile-info-insert-buttons
  186. (current format guix-profile-info-insert-current)
  187. (number-of-packages
  188. format guix-profile-info-insert-number-of-packages)
  189. (number-of-generations
  190. format guix-profile-info-insert-number-of-generations))
  191. :hint 'guix-profile-info-hint)
  192. (let ((map guix-profile-info-mode-map))
  193. (define-key map (kbd "E") 'guix-profile-info-show-search-paths)
  194. (define-key map (kbd "P") 'guix-profile-info-show-packages)
  195. (define-key map (kbd "G") 'guix-profile-info-show-generations)
  196. (define-key map (kbd "M") 'guix-profile-info-apply-manifest)
  197. (define-key map (kbd "c") 'guix-profile-info-set-current))
  198. (defvar guix-profile-info-default-hint
  199. '(("\\[guix-profile-info-show-packages]") " show packages;\n"
  200. ("\\[guix-profile-info-show-generations]") " show generations;\n"
  201. ("\\[guix-profile-info-show-search-paths]") " show search paths;\n"
  202. ("\\[guix-profile-info-set-current]") " set current profile;\n"
  203. ("\\[guix-profile-info-apply-manifest]") " apply manifest;\n"))
  204. (defun guix-profile-info-hint ()
  205. (bui-format-hints
  206. guix-profile-info-default-hint
  207. (bui-default-hint)))
  208. (defface guix-profile-info-current
  209. '((t :inherit guix-true))
  210. "Face used if a profile is the current one."
  211. :group 'guix-profile-info-faces)
  212. (defface guix-profile-info-not-current
  213. '((t :inherit guix-false))
  214. "Face used if a profile is not the current one."
  215. :group 'guix-profile-info-faces)
  216. (defun guix-profile-info-insert-search-paths-button (profile)
  217. "Insert 'Search paths' button for PROFILE."
  218. (bui-insert-action-button
  219. "Search paths"
  220. (lambda (btn)
  221. (guix-show-search-paths (list (button-get btn 'profile))
  222. (guix-read-search-paths-type)))
  223. (format "Show 'search paths' environment variables for profile '%s'"
  224. profile)
  225. 'profile profile))
  226. (defun guix-profile-info-insert-apply-manifest-button (profile)
  227. "Insert 'Apply manifest' button for PROFILE."
  228. (bui-insert-action-button
  229. "Apply manifest"
  230. (lambda (btn)
  231. (guix-apply-manifest (button-get btn 'profile)
  232. (guix-read-manifest-file-name)
  233. (current-buffer)))
  234. (format "Apply manifest file to profile '%s'"
  235. profile)
  236. 'profile profile))
  237. (defun guix-profile-info-insert-buttons (entry)
  238. "Insert some buttons for profile ENTRY at point."
  239. (let ((profile (bui-entry-non-void-value entry 'profile)))
  240. (guix-profile-info-insert-search-paths-button profile)
  241. (unless (guix-system-profile? profile)
  242. (bui-insert-indent)
  243. (guix-profile-info-insert-apply-manifest-button profile))
  244. (bui-newline)))
  245. (defun guix-profile-info-insert-current (value entry)
  246. "Insert boolean VALUE showing whether this profile is current."
  247. (if value
  248. (bui-info-insert-value-format "Yes" 'guix-profile-info-current)
  249. (bui-info-insert-value-format "No" 'guix-profile-info-not-current)
  250. (bui-insert-indent)
  251. (let ((profile (bui-entry-non-void-value entry 'profile)))
  252. (bui-insert-action-button
  253. "Set"
  254. (lambda (btn)
  255. (guix-set-current-profile (button-get btn 'profile))
  256. (bui-revert nil t))
  257. (format "Make '%s' the current profile" profile)
  258. 'profile profile))))
  259. (defun guix-profile-info-insert-number-of-packages (number entry)
  260. "Insert the NUMBER of packages and button to display packages."
  261. (bui-format-insert number)
  262. (bui-insert-indent)
  263. (let ((profile (bui-entry-non-void-value entry 'profile)))
  264. (bui-insert-action-button
  265. "Show"
  266. (lambda (btn)
  267. (guix-installed-packages (button-get btn 'profile)))
  268. (format "Show packages installed in profile '%s'" profile)
  269. 'profile profile)))
  270. (defun guix-profile-info-insert-number-of-generations (number entry)
  271. "Insert the NUMBER of generations and button to display generations."
  272. (bui-format-insert number)
  273. (bui-insert-indent)
  274. (let ((profile (bui-entry-non-void-value entry 'profile)))
  275. (bui-insert-action-button
  276. "Show"
  277. (lambda (btn)
  278. (guix-generations (button-get btn 'profile)))
  279. (format "Show generations of profile '%s'" profile)
  280. 'profile profile)))
  281. (defun guix-profile-info-show-packages (profile)
  282. "Display packages installed in PROFILE."
  283. (interactive (list (guix-read-profile-from-entries)))
  284. (guix-installed-packages profile))
  285. (defun guix-profile-info-show-generations (profile)
  286. "Display generations of PROFILE."
  287. (interactive (list (guix-read-profile-from-entries)))
  288. (guix-generations profile))
  289. (defun guix-profile-info-show-search-paths (profile &optional type)
  290. "Display 'search paths' environment variables for PROFILE."
  291. (interactive
  292. (list (guix-read-profile-from-entries)
  293. (guix-read-search-paths-type)))
  294. (guix-show-search-paths (list profile) type))
  295. (defun guix-profile-info-apply-manifest (profile &optional file)
  296. "Apply manifest from FILE to PROFILE."
  297. (interactive
  298. (list (guix-read-profile-from-entries)
  299. (guix-read-manifest-file-name)))
  300. (guix-apply-manifest profile file (current-buffer)))
  301. (defun guix-profile-info-set-current (profile)
  302. "Set `guix-current-profile' to PROFILE."
  303. (interactive (list (guix-read-profile-from-entries)))
  304. (guix-set-current-profile profile)
  305. (bui-revert nil t))
  306. ;;; Interactive commands
  307. (defun guix-profiles-show ()
  308. "Display Guix profiles.
  309. Unlike `guix-profiles', this command always recreates
  310. `guix-profile-list-buffer-name' buffer."
  311. (interactive)
  312. (bui-list-get-display-entries 'guix-profile))
  313. ;;;###autoload
  314. (defun guix-profiles ()
  315. "Display Guix profiles.
  316. Switch to the `guix-profile-list-buffer-name' buffer if it
  317. already exists.
  318. Modify `guix-profiles' variable to add more profiles."
  319. (interactive)
  320. (guix-switch-to-buffer-or-funcall
  321. guix-profile-list-buffer-name #'guix-profiles-show 'message))
  322. ;;;###autoload
  323. (defun guix-system-profile ()
  324. "Display interface for `guix-system-profile'."
  325. (interactive)
  326. (bui-get-display-entries 'guix-profile 'info
  327. (list 'profile guix-system-profile)))
  328. ;;;###autoload
  329. (defun guix-current-profile ()
  330. "Display interface for `guix-current-profile'."
  331. (interactive)
  332. (bui-get-display-entries 'guix-profile 'info
  333. (list 'profile guix-current-profile)))
  334. (provide 'guix-ui-profile)
  335. ;;; guix-ui-profile.el ends here