eshell-starship.el 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261
  1. ;;; eshell-starship.el --- Starship-like (https://starship.rs) prompt for eshell -*- lexical-binding: t; -*-
  2. ;;; Commentary:
  3. ;;; Code:
  4. (require 'vc)
  5. (require 'vc-git)
  6. (require 'eshell)
  7. (require 'cl-lib)
  8. (require 'tramp)
  9. (eval-when-compile (require 'rx))
  10. ;;; Configuration options
  11. (defgroup eshell-starship nil
  12. "Starship-like (starship.rs) prompt for `eshell'."
  13. :version "0.0.1"
  14. :prefix 'eshell-starship-
  15. :group 'eshell)
  16. (defvar-local eshell-starship--current-explain-buffer nil
  17. "The eshell-starship explain buffer for this eshell buffer.")
  18. (defcustom eshell-starship-explain-auto-update t
  19. "Non-nil if eshell-starship explain buffers shoul auto-update."
  20. :group 'eshell-starship
  21. :tag "Auto-update explain buffers"
  22. :type 'boolean)
  23. (defun eshell-starship--defcustom-setter (sym val)
  24. "Set SYM to VAL (using `set-default-toplevel-value').
  25. This will also update all eshell-starship explain buffers that need updating."
  26. (set-default-toplevel-value sym val)
  27. (dolist (buffer (buffer-list))
  28. (with-current-buffer buffer
  29. (when (and (derived-mode-p 'eshell-mode)
  30. (buffer-live-p eshell-starship--current-explain-buffer))
  31. (with-current-buffer eshell-starship--current-explain-buffer
  32. (when eshell-starship-explain-auto-update
  33. (revert-buffer)))))))
  34. (defcustom eshell-starship-module-order
  35. '("remote" "root" "cwd" "git" "vc" t "cmd-time" "newline" "container" "arrow")
  36. "The order of modules for eshell-starship.
  37. This is a list with each element being a module name. The special value t can
  38. appear at most once to denote \"all remaining modules\"."
  39. :group 'eshell-starship
  40. :tag "Module order"
  41. :type '(repeat (choice (const :tag "Remaining modules" t)
  42. (string :tag "Module")))
  43. :set 'eshell-starship--defcustom-setter)
  44. (defcustom eshell-starship-disabled-modules '()
  45. "List of disabled eshell-starship modules."
  46. :group 'eshell-starship
  47. :tag "Disabled modules"
  48. :type '(repeat (string :tag "Module"))
  49. :set 'eshell-starship--defcustom-setter)
  50. (defcustom eshell-starship-explain-suppress-refresh-messages nil
  51. "Weather to suppress messages during eshell-starship explore refreshes."
  52. :group 'eshell-starship
  53. :tag "Suppress eshell-starship explore refresh messages"
  54. :type 'boolean)
  55. (defcustom eshell-starship-overridden-remote-methods
  56. '("docker" "podman" "kubernetes" "doas" "su" "sudo" "sudoedit")
  57. "List of `file-remote-p' mwthods that should NOT be considered remote.
  58. Any eshell buffer with a `default-directory' managed by one of these methods
  59. will not be considered remote and all modules that would be disabled because of
  60. the remote directory will work as usual."
  61. :group 'eshell-starship
  62. :tag "Overridden Remote Methods"
  63. :type '(repeat (string :tag "Method")))
  64. (defcustom eshell-starship-verbose-tramp 1
  65. "Tramp verbosity level when rendering the prompt."
  66. :group 'eshell-starship
  67. :tag "Tramp Verbosity Level"
  68. :type 'integer)
  69. (defface eshell-starship-icon-face '((t :inherit default))
  70. "Face to use when drawing module icons.
  71. Note that the foreground color will be overridden by the module."
  72. :group 'eshell-starship
  73. :tag "Icon face")
  74. (defvar eshell-starship--modules-by
  75. (list :extensions (make-hash-table :test 'equal)
  76. :dirs (make-hash-table :test 'equal)
  77. :files (make-hash-table :test 'equal))
  78. "An alist hash tables that map various fields to lists of modules.")
  79. (defun eshell-starship--module-by (field key)
  80. "Lookup a list of modules with a FIELD corresponding to KEY.
  81. FIELD should be one of the keys in `eshell-starship--modules-by'."
  82. (when-let ((table (plist-get eshell-starship--modules-by field)))
  83. (gethash key table)))
  84. (defvar eshell-starship--extra-module-files ()
  85. "A list of (NAME IS-DIR MODULE).
  86. These represent files that cannot be stored in `eshell-starship--module-by'.")
  87. ;;; Module API
  88. (defvar eshell-starship-modules (make-hash-table :test 'equal)
  89. "List of modules used by eshell-starship.")
  90. (defvar-local eshell-starship--module-cache nil
  91. "Hash table mapping modules to a list of their last output.
  92. The entries of this hash table are of the the form (VALID OUTPUT LAST-TIMES).
  93. LAST-TIMES is a list of the 10 last execution times for this module.")
  94. (defclass eshell-starship-module ()
  95. ((name :initarg :name
  96. :accessor eshell-starship-module-name
  97. :type string
  98. :documentation "The name of this module.")
  99. (precmd-action :initarg :precmd-action
  100. :initform nil
  101. :accessor eshell-starship-module-precmd-action
  102. :type (or function null)
  103. :documentation
  104. "A function to run before each command is run.")
  105. (postcmd-action :initarg :postcmd-action
  106. :initform nil
  107. :accessor eshell-starship-module-postcmd-action
  108. :type (or function null)
  109. :documentation
  110. "A function to run after each command is run.")
  111. (predicate :initarg :predicate
  112. :initform 'ignore
  113. :accessor eshell-starship-module-predicate
  114. :type function
  115. :documentation
  116. "A function that should return non-nil if the module should be run.")
  117. (files :initarg :files
  118. :initform nil
  119. :accessor eshell-starship-module-files
  120. :type list
  121. :documentation
  122. "A list of files that indicate that the module should be run.")
  123. (dirs :initarg :dirs
  124. :initform nil
  125. :accessor eshell-starship--module-dirs
  126. :type list
  127. :documentation
  128. "A list of directories that indicate that the module should be run.")
  129. (extensions :initarg :extensions
  130. :initform nil
  131. :accessor eshell-starship-module-extensions
  132. :type list
  133. :documentation
  134. "A list of extensions that indicate that the module should be run.")
  135. (prefix :initarg :prefix
  136. :initform ""
  137. :accessor eshell-starship-module-prefix
  138. :type string
  139. :documentation "Text to be placed before the module's icon. This is
  140. not colored.")
  141. (icon :initarg :icon
  142. :initform ""
  143. :accessor eshell-starship-module-icon
  144. :type string
  145. :documentation "The modules icon. This is colored.")
  146. (postfix :initarg :postfix
  147. :initform ""
  148. :accessor eshell-starship-module-prefix
  149. :type string
  150. :documentation "Text to be placed after the module's content. This is
  151. not colored.")
  152. (color :initarg :color
  153. :initform nil
  154. :accessor eshell-starship-module-color
  155. :type (or null string)
  156. :documentation "The color to give the module's icon and main text.
  157. Use `list-colors-display' to get a list of some possible values. This can also
  158. be nil.")
  159. (allow-remote :initarg :allow-remote
  160. :initform t
  161. :accessor eshell-starship-module-allow-remote-p
  162. :type boolean
  163. :documentation "Weather the module should be run if
  164. `default-directory' is a `file-remote-p'.")
  165. (action :initarg :action
  166. :initform 'string
  167. :accessor eshell-starship-module-action
  168. :type function
  169. :documentation "A function that produces the main text for the
  170. module.")
  171. (reload-on :initarg :reload-on
  172. :initform 'never
  173. :accessor eshell-starship-module-reload-on
  174. :type (or (member never always cwd)
  175. (list-of (member never always cwd)))
  176. :documentation "A list of times when this module should be
  177. reloaded. Current possible values are:
  178. - never (or an empty list): don't ever re-run this module
  179. - always: re-run this module every time the prompt is updated
  180. - cwd: re-run this module when the CWD changes")
  181. (doc :initarg :doc
  182. :initform "No documentation provided."
  183. :accessor eshell-starship-module-doc
  184. :type string
  185. :documentation "The documentation for this module."))
  186. (:documentation "Class for eshell-starship modules."))
  187. (defun eshell-starship--remove-module-lookups (name)
  188. "Unregistered module named NAME (a string) in `eshell-starship--modules-by'."
  189. (cl-loop
  190. for table in (cdr eshell-starship--modules-by) by 'cddr
  191. do (maphash (lambda (k v)
  192. (puthash k (cl-delete-if (lambda (module)
  193. (with-slots ((cur-mod-name name))
  194. module
  195. (equal cur-mod-name name)))
  196. v)
  197. table))
  198. table))
  199. (setq eshell-starship--extra-module-files
  200. (cl-delete-if (lambda (entry)
  201. (with-slots ((cur-mod-name name)) (cl-third entry)
  202. (equal cur-mod-name name)))
  203. eshell-starship--extra-module-files)))
  204. (defun eshell-starship--register-module-lookups (module)
  205. "Register MODULE in `eshell-starship--modules-by'."
  206. (let ((exts-table (plist-get eshell-starship--modules-by :extensions))
  207. (dirs-table (plist-get eshell-starship--modules-by :dirs))
  208. (files-table (plist-get eshell-starship--modules-by :files)))
  209. (with-slots (name dirs extensions files) module
  210. (eshell-starship--remove-module-lookups name)
  211. (dolist (dir dirs)
  212. (if (cl-find ?/ dir)
  213. (push (list dir t module) eshell-starship--extra-module-files))
  214. (push module (gethash dir dirs-table)))
  215. (dolist (file files)
  216. (if (cl-find ?/ file)
  217. (push (list file nil module) eshell-starship--extra-module-files)
  218. (push module (gethash file files-table))))
  219. (dolist (ext extensions)
  220. (push module (gethash ext exts-table))))))
  221. (defun eshell-starship--defmodule-real (name &rest opts)
  222. "Do the work of `eshell-starship-defmodule'.
  223. NAME is the module name (a symbol) and OPTS is the list of options to pass to
  224. the module's constructor."
  225. (let ((module (apply 'make-instance 'eshell-starship-module
  226. :name (symbol-name name) opts))
  227. (str-name (symbol-name name)))
  228. (dolist (buffer (buffer-list))
  229. (with-current-buffer buffer
  230. (when (hash-table-p eshell-starship--module-cache)
  231. (remhash str-name eshell-starship--module-cache))))
  232. (eshell-starship--register-module-lookups module)
  233. ;; This returns module
  234. (puthash str-name module eshell-starship-modules)))
  235. (defmacro eshell-starship-defmodule (name &rest opts)
  236. "Create a new eshell-starship named NAME module with OPTS."
  237. (declare (indent defun))
  238. `(eshell-starship--defmodule-real ',name ,@opts))
  239. ;;; Utility functions
  240. (cl-defmacro eshell-starship-find-version-function (command pattern
  241. &rest format)
  242. "Return a version finding function for COMMAND.
  243. COMMAND is in the form of (exec args...). The temp buffer that was used to run
  244. COMMAND will then have `re-search-forward' run with PATTERN. FORMAT will then
  245. be passed verbatim as the arguments to `concat'."
  246. (declare (indent defun))
  247. `(lambda ()
  248. (with-temp-buffer
  249. (when (zerop (process-file ,(car command) nil t nil ,@(cdr command)))
  250. (goto-char (point-min))
  251. (when (re-search-forward ,pattern nil t)
  252. (concat ,@format))))))
  253. (cl-defun eshell-starship-format-span (span &optional (places 0))
  254. "Format SPAN (in seconds) as \"XhXmXs\".
  255. The number is rounded to PLACES before being rendered."
  256. (let* ((ispan (round span))
  257. (hours (/ ispan 3600))
  258. (mins (% (/ ispan 60) 60))
  259. (secs (mod span 60)))
  260. (concat (when (/= hours 0)
  261. (format "%dh" hours))
  262. (when (or (/= mins 0) (/= hours 0))
  263. (format "%dm" mins))
  264. (format (format "%%.%dfs" places) secs))))
  265. (defun eshell-starship-clear-cache-for (modules)
  266. "Clear the cache for each of MODULES.
  267. MODULES can also be a single module."
  268. (dolist (module (ensure-list modules))
  269. (when (symbolp module) (setq module (symbol-name module)))
  270. (when-let ((cur-entry (gethash module eshell-starship--module-cache)))
  271. (setf (car cur-entry) nil))))
  272. ;;; CWD Module
  273. (defun eshell-starship--replace-home-with-tilda (path)
  274. "If PATH beings with $HOME (the environment variable), replace it with ~."
  275. (let ((home (getenv "HOME")))
  276. (if (equal home path)
  277. "~"
  278. (setq home (file-name-as-directory home))
  279. (if (string-prefix-p home path)
  280. (concat "~/" (seq-subseq path (length home)))
  281. path))))
  282. (defun eshell-starship--limit-path-parts (num path)
  283. "Cut PATH down to NUM components.
  284. Example:
  285. /this/is/a/path 3-> is/a/path"
  286. (let ((parts (string-split path "/" t nil)))
  287. (concat
  288. (when (and (file-name-absolute-p path)
  289. (not (equal "~" (car parts)))
  290. (<= (length parts) num))
  291. "/")
  292. (string-join (last parts num) "/"))))
  293. (defun eshell-starship--get-current-dir ()
  294. "Get dir for `eshell-starship--prompt-function'."
  295. (concat
  296. (propertize
  297. (eshell-starship--limit-path-parts
  298. 3 (let ((cwd (or (file-remote-p default-directory 'localname)
  299. default-directory)))
  300. (if-let ((worktree (vc-root-dir))
  301. (parent (file-name-parent-directory worktree)))
  302. (file-relative-name cwd (or (file-remote-p parent 'localname)
  303. parent))
  304. (eshell-starship--replace-home-with-tilda cwd))))
  305. 'face '(:foreground "dark turquoise"))
  306. (unless (file-writable-p default-directory)
  307. " ")))
  308. (eshell-starship-defmodule cwd
  309. :predicate 'always
  310. :reload-on 'cwd
  311. :action 'eshell-starship--get-current-dir
  312. :doc "The current working directory.")
  313. ;;; Git module
  314. (defun eshell-starship--git-parse-status-headers ()
  315. "Parse the status headers (read from the current buffer).
  316. The headers are as described in the porcelain v2 section of the git-status(3)
  317. man page.
  318. The return value is a list of the form (oid head upstream ahead behind stash)"
  319. (let ((oid nil)
  320. (head nil)
  321. (upstream nil)
  322. (ahead nil)
  323. (behind nil)
  324. (stash nil))
  325. (while (and (char-after) (= (char-after) ?#))
  326. (forward-char 2)
  327. (cond
  328. ((looking-at "branch\\.oid ")
  329. (setq oid (buffer-substring-no-properties
  330. (match-end 0)
  331. (pos-eol))))
  332. ((looking-at "branch\\.head ")
  333. (setq head (buffer-substring-no-properties
  334. (match-end 0)
  335. (pos-eol))))
  336. ((looking-at "branch\\.upstream ")
  337. (setq upstream (buffer-substring-no-properties
  338. (match-end 0)
  339. (pos-eol))))
  340. ((looking-at "branch\\.ab ")
  341. (let ((ab-str (buffer-substring-no-properties
  342. (match-end 0)
  343. (pos-eol))))
  344. (when (string-match "\\(+[0-9]+\\) \\(-[0-9]+\\)$"
  345. ab-str)
  346. (setq ahead (string-to-number (match-string 1 ab-str))
  347. behind (string-to-number (match-string 2 ab-str))))))
  348. ((looking-at "stash ")
  349. (setq stash (string-to-number (buffer-substring-no-properties
  350. (match-end 0)
  351. (pos-eol))))))
  352. (forward-line))
  353. (list oid head upstream (or ahead 0) (or behind 0) stash)))
  354. (defun eshell-starship--git-interpret-file-status (x y)
  355. "Return the prompt character for the status X and Y.
  356. A description of X and Y can be found in the git-status(3) man page."
  357. (cond
  358. ((or (= x ?D) (= y ?D))
  359. ?)
  360. ((or (= x ?R) (= y ?R))
  361. ?»)
  362. ((= y ?M)
  363. ?!)
  364. ((or (= x ?A) (= x ?M))
  365. ?+)))
  366. (defun eshell-starship--git-interpret-branch-status (ahead behind)
  367. "Get the status char for the current branch and its remote.
  368. AHEAD should evaluate to t if the current branch is ahead of its remote, and
  369. BEHIND should evaluate to t if the current branch is behind its remote."
  370. (cond
  371. ((and ahead behind) "󰹺")
  372. (ahead "󰜷")
  373. (behind "󰜮")))
  374. (defun eshell-starship--git-file-status (stash ahead behind)
  375. "Get the file status string for the git prompt module.
  376. STASH should be t if there is current stashed data stash. AHEAD and BEHIND
  377. should be as for `eshell-starship--git-interpret-branch-status'."
  378. (let ((merge-conflicts nil)
  379. (status-chars nil))
  380. (while (not (eobp))
  381. (cond
  382. ((= (char-after) ??)
  383. (push ?? status-chars))
  384. ((= (char-after) ?u)
  385. (setq merge-conflicts t))
  386. ((or (= (char-after) ?1)
  387. (= (char-after) ?2))
  388. (push (eshell-starship--git-interpret-file-status
  389. (char-after (+ 2 (point)))
  390. (char-after (+ 3 (point))))
  391. status-chars)))
  392. (forward-line))
  393. (concat (eshell-starship--git-interpret-branch-status (not (zerop ahead))
  394. (not (zerop behind)))
  395. (when merge-conflicts "=")
  396. (when stash "$")
  397. (apply 'string (sort (seq-uniq status-chars) #'<)))))
  398. (defun eshell-starship--git-current-operation ()
  399. "Return the current git operation.
  400. For example, a revert. If there is no current operation, return nil."
  401. (let ((git-dir (expand-file-name ".git" (vc-git-root default-directory))))
  402. (cond
  403. ((file-exists-p (expand-file-name "rebase-apply/applying" git-dir))
  404. "AM")
  405. ((file-exists-p (expand-file-name "rebase-apply/rebasing" git-dir))
  406. "REBASE")
  407. ((file-exists-p (expand-file-name "rebase-apply" git-dir))
  408. "AM/REBASE")
  409. ((file-exists-p (expand-file-name "rebase-merge" git-dir))
  410. "REBASING")
  411. ((file-exists-p (expand-file-name "CHERRY_PICK_HEAD" git-dir))
  412. "CHERRY-PICKING")
  413. ((file-exists-p (expand-file-name "MERGE_HEAD" git-dir))
  414. "MERGING")
  415. ((file-exists-p (expand-file-name "BISECT_LOG" git-dir))
  416. "BISECTING")
  417. ((file-exists-p (expand-file-name "REVERT_HEAD" git-dir))
  418. "REVERTING"))))
  419. (defun eshell-starship--git-status ()
  420. "Return the text for the git module for `eshell-starship--prompt-function'."
  421. (with-temp-buffer
  422. (when (zerop (vc-git-command t nil nil "status" "--porcelain=v2"
  423. "--branch" "--show-stash"))
  424. (goto-char (point-min))
  425. (cl-destructuring-bind (oid head _upstream ahead behind stash)
  426. (eshell-starship--git-parse-status-headers)
  427. (let* ((file-status (eshell-starship--git-file-status stash ahead
  428. behind))
  429. (operation (eshell-starship--git-current-operation))
  430. (output
  431. (concat
  432. (if (string= "(detached)" head)
  433. (propertize (concat " (" (substring oid 0 7) ")")
  434. 'face '(:foreground "lawn green"))
  435. (propertize (concat head)
  436. 'face '(:foreground "medium purple")))
  437. (unless (string-empty-p file-status)
  438. (propertize (concat " [" file-status "]")
  439. 'face '(:foreground "red")))
  440. (when operation
  441. (concat " (" (propertize
  442. operation 'face
  443. '(:inherit bold :foreground "yellow"))
  444. ")")))))
  445. (unless (zerop (length output))
  446. output))))))
  447. (eshell-starship-defmodule git
  448. :predicate (lambda ()
  449. (eq (vc-responsible-backend default-directory t) 'Git))
  450. :color "medium purple"
  451. :icon "󰊢 "
  452. :reload-on 'always
  453. :action 'eshell-starship--git-status
  454. :doc "The working directory's status as a git repository.")
  455. ;;; Non-git VC module
  456. (defun eshell-starship--vc-status ()
  457. "Get vc status for `eshell-starship--prompt-function'."
  458. (when-let ((backend (vc-responsible-backend default-directory t))
  459. ((not (eq backend 'Git))))
  460. (downcase (symbol-name backend))))
  461. (eshell-starship-defmodule vc
  462. :predicate 'always
  463. :allow-remote nil
  464. :reload-on 'always
  465. :color "purple"
  466. :icon " "
  467. :action 'eshell-starship--vc-status
  468. :doc "The working directory's version control status (other than git).")
  469. ;;; Timer module
  470. (defvar-local eshell-starship--last-start-time nil
  471. "Start time of last eshell command.")
  472. (defvar-local eshell-starship--last-end-time nil
  473. "End time of last eshell command.")
  474. (defun eshell-starship--last-command-time ()
  475. "Return the prompt component for the time of the last command."
  476. (prog1
  477. (and eshell-starship--last-start-time
  478. eshell-starship--last-end-time
  479. ;; this must be here (not in a predicate) to ensure these values get
  480. ;; cleared
  481. (<= 3 (- eshell-starship--last-end-time
  482. eshell-starship--last-start-time))
  483. (eshell-starship-format-span (- eshell-starship--last-end-time
  484. eshell-starship--last-start-time)))
  485. (setq eshell-starship--last-start-time nil
  486. eshell-starship--last-end-time nil)))
  487. (eshell-starship-defmodule cmd-time
  488. :predicate 'always
  489. :prefix "took "
  490. :color "gold1"
  491. :reload-on 'always
  492. :precmd-action (lambda ()
  493. (setq eshell-starship--last-start-time (float-time)))
  494. :postcmd-action (lambda ()
  495. (setq eshell-starship--last-end-time (float-time)))
  496. :action 'eshell-starship--last-command-time
  497. :doc "The amount of time it took the last command to execute.")
  498. ;;; Language modules
  499. (eshell-starship-defmodule cc
  500. :extensions '("c" "h")
  501. :prefix "via "
  502. :icon "C "
  503. :color "spring green"
  504. :allow-remote nil
  505. :reload-on 'cwd
  506. :action (eshell-starship-find-version-function
  507. ("cc" "-v")
  508. "^\\([-a-zA-Z]+\\) version \\([0-9]+\\.[0-9]+\\.[0-9]+\\)"
  509. "v" (match-string 2) "-" (match-string 1))
  510. :doc "Your C compiler version.")
  511. (eshell-starship-defmodule c++
  512. :extensions '("cpp" "cc" "cxx" "hpp" "hh" "hxx")
  513. :prefix "via "
  514. :icon " "
  515. :color "royal blue"
  516. :allow-remote nil
  517. :reload-on 'cwd
  518. :action (eshell-starship-find-version-function
  519. ("cpp" "--version")
  520. "\\(GCC\\|clang\\).+?\\([0-9]+\\.[0-9]+\\.[0-9]+\\)"
  521. "v" (match-string 2) "-" (downcase (match-string 1)))
  522. :doc "Your C++ compiler version.")
  523. (eshell-starship-defmodule rust
  524. :extensions '("rs")
  525. :files '("Cargo.toml")
  526. :prefix "via "
  527. :icon "🦀 "
  528. :color "red"
  529. :allow-remote nil
  530. :reload-on 'cwd
  531. :action (eshell-starship-find-version-function
  532. ("rustc" "--version")
  533. "^rustc \\([0-9]+\\.[0-9]+\\.[0-9]+\\)"
  534. "v" (match-string 1))
  535. :doc "Your Rust compiler version.")
  536. (eshell-starship-defmodule cmake
  537. :files '("CMakeLists.txt" "CMakeCache.txt")
  538. :extensions '("cmake")
  539. :prefix "via "
  540. :icon "󰔶 "
  541. :color "blue"
  542. :allow-remote nil
  543. :reload-on 'cwd
  544. :action (eshell-starship-find-version-function
  545. ("cmake" "--version")
  546. "cmake version \\([0-9]+\\.[0-9]+\\.[0-9]+\\)"
  547. "v" (match-string 1))
  548. :doc "Your CMake version.")
  549. (require 'inf-lisp nil t)
  550. (when (featurep 'inf-lisp)
  551. (eshell-starship-defmodule common-lisp
  552. :extensions '("asd" "lisp")
  553. :prefix "via "
  554. :icon " "
  555. :color "green yellow"
  556. :allow-remote nil
  557. :reload-on 'cwd
  558. :action (eshell-starship-find-version-function
  559. (inferior-lisp-program "--version")
  560. "[a-zA-Z]+ [0-9.]+"
  561. (match-string 0))
  562. :doc "Your current inferior-lisp program."))
  563. (eshell-starship-defmodule elisp
  564. :extensions '("el" "elc" "eln")
  565. :prefix "via "
  566. :icon " "
  567. :color "dark orchid"
  568. :allow-remote nil
  569. :reload-on 'never
  570. :action (lambda ()
  571. emacs-version)
  572. :doc "The current emacs-version.")
  573. (eshell-starship-defmodule java
  574. :extensions '("java" "class" "gradle" "jar" "clj" "cljc")
  575. :files '("pom.xml" "build.gradle.kts" "build.sbt" ".java-version" "deps.edn"
  576. "project.clj" "build.boot" ".sdkmanrc")
  577. :prefix "via "
  578. :icon "☕ "
  579. :color "dark red"
  580. :allow-remote nil
  581. :reload-on 'cwd
  582. :action (eshell-starship-find-version-function
  583. ("java" "-version")
  584. "version \"\\([0-9]+\\)\""
  585. "v" (match-string 1))
  586. :doc "Your Java version.")
  587. (eshell-starship-defmodule zig
  588. :extensions '("zig")
  589. :prefix "via "
  590. :icon "↯ "
  591. :color "yellow"
  592. :allow-remote nil
  593. :reload-on 'cwd
  594. :action (eshell-starship-find-version-function
  595. ("zig" "version")
  596. ".+"
  597. "v" (match-string 0))
  598. :doc "Your Zig version.")
  599. (defun eshell-starship--current-venv ()
  600. "Return the name of the prompt string for the current venv.
  601. This requires pyvenv.el or pyenv-mode to work."
  602. (concat
  603. (and (bound-and-true-p pyvenv-virtual-env-name)
  604. (format " (%s)" pyvenv-virtual-env-name))
  605. (and (fboundp 'pyenv-mode-version)
  606. (when-let ((ver (pyenv-mode-version)))
  607. (format " (%s)" ver)))))
  608. (defun eshell-starship--python-status ()
  609. "Return the prompt string for the python module."
  610. (when-let
  611. ((python-exec (or (bound-and-true-p python-interpreter) "python"))
  612. (output (process-lines-ignore-status python-exec "--version"))
  613. ((string-match "^Python \\([0-9.]+\\)" (car output))))
  614. (concat "v" (match-string 1 (car output)) (eshell-starship--current-venv))))
  615. (defvar-local eshell-starship--python-last-pyvenv nil
  616. "The previous `pyvenv-virtual-env' value.
  617. This does not mean anything if pyvenv.el is not installed.")
  618. (defvar-local eshell-starship--python-last-pyenv nil
  619. "The return value of the last `pyenv-mode-version'.
  620. This does not mean anything if pyenv-mode is not installed.")
  621. (defun eshell-starship--python-postcmd-action ()
  622. "The postcmd action for the python module."
  623. (let ((need-clear nil))
  624. (when (and (boundp 'pyvenv-virtual-env)
  625. (not (equal eshell-starship--python-last-pyvenv
  626. pyvenv-virtual-env)))
  627. (setq eshell-starship--python-last-pyvenv pyvenv-virtual-env
  628. need-clear t))
  629. (when (fboundp 'pyenv-mode-version)
  630. (let ((cur-ver (pyenv-mode-version)))
  631. (when (not (equal eshell-starship--python-last-pyenv cur-ver))
  632. (setq eshell-starship--python-last-pyenv cur-ver
  633. need-clear t))))
  634. (when need-clear
  635. (eshell-starship-clear-cache-for 'python))))
  636. (eshell-starship-defmodule python
  637. :extensions '("py" "ipynb")
  638. :predicate (lambda ()
  639. (or (bound-and-true-p pyvenv-virtual-env)
  640. (and (fboundp 'pyenv-mode-version)
  641. (pyenv-mode-version))))
  642. :files '(".python-version" "Pipfile" "__init__.py" "pyproject.toml"
  643. "requirements.txt" "setup.py" "tox.ini" "pixi.toml")
  644. :prefix "via "
  645. :icon "🐍 "
  646. :color "#CECB00"
  647. :allow-remote nil
  648. :reload-on 'cwd
  649. :action #'eshell-starship--python-status
  650. :postcmd-action #'eshell-starship--python-postcmd-action
  651. :doc "Your current system-wide Python version.")
  652. (eshell-starship-defmodule php
  653. :extensions '("php")'
  654. :files '("composer.json" ".php-version")
  655. :prefix "via "
  656. :icon "🐘 "
  657. :color "#AFAFFF"
  658. :allow-remote nil
  659. :reload-on 'cwd
  660. :action (eshell-starship-find-version-function
  661. ("php" "--version")
  662. "^PHP \\([0-9.]+\\)"
  663. "v" (match-string 1))
  664. :doc "Your current PHP version.")
  665. (eshell-starship-defmodule node
  666. :extensions '("js" "mjs" "cjs" "ts" "mts" "cts")
  667. :files '("package.json" ".node-version" ".nvmrc")
  668. :dirs '("node_modules")
  669. :prefix "via "
  670. :icon " "
  671. :color "green"
  672. :allow-remote nil
  673. :reload-on 'cwd
  674. :action (eshell-starship-find-version-function
  675. ("node" "--version")
  676. ".+" (match-string 0))
  677. :doc "Your current NodeJS version.")
  678. ;;; Misc modules
  679. (eshell-starship-defmodule remote
  680. :icon "🌐 "
  681. :color "light blue"
  682. :predicate
  683. (lambda ()
  684. (eshell-starship--remote-for-modules-p default-directory))
  685. :action
  686. (lambda ()
  687. (or (file-remote-p default-directory 'host) ""))
  688. :reload-on 'cwd
  689. :doc "A small icon if the working directory is remote.")
  690. (eshell-starship-defmodule root
  691. :predicate
  692. (lambda ()
  693. (member (file-remote-p default-directory 'method)
  694. '("doas" "sudo" "su" "sudoedit")))
  695. :action
  696. (lambda ()
  697. (format "%s in"
  698. (propertize (file-remote-p default-directory 'user)
  699. 'face '(:weight bold :foreground "red"))))
  700. :reload-on 'cwd
  701. :doc "Show the current sudo or doas user.")
  702. (eshell-starship-defmodule newline
  703. :predicate 'always
  704. :action (lambda () (propertize "\n" 'read-only t 'rear-nonsticky t))
  705. :doc "A newline in the prompt.")
  706. (eshell-starship-defmodule container
  707. :icon "⬢ "
  708. :color "firebrick"
  709. :predicate (lambda ()
  710. (member (file-remote-p default-directory 'method)
  711. '("docker" "podman" "kubernetes")))
  712. :action (lambda ()
  713. (format "[%s]" (file-remote-p default-directory 'host)))
  714. :reload-on 'cwd
  715. :doc "The name of the current container.")
  716. (eshell-starship-defmodule arrow
  717. :predicate 'always
  718. :reload-on 'always
  719. :action (lambda ()
  720. (propertize
  721. "❯ " 'face `(:foreground
  722. ,(if (= eshell-last-command-status 0)
  723. "lime green"
  724. "red"))
  725. 'rear-nonsticky t))
  726. :doc "An arrow that appears next to where you type.")
  727. ;;; Driver code
  728. (defun eshell-starship-clear-cache (&rest flags)
  729. "Clear each cache entry with a \\=:reload-on of FLAGS.
  730. If any of flags is t, clear all caches."
  731. (interactive '(t) eshell-starship)
  732. (cl-loop with force-clear = (member t flags)
  733. for module being the hash-values of eshell-starship-modules
  734. do (with-slots (name reload-on) module
  735. (when (or force-clear
  736. (cl-intersection (ensure-list reload-on) flags))
  737. (eshell-starship-clear-cache-for name)))))
  738. (defun eshell-starship--cwd-clear-caches ()
  739. "Clear caches that should be cleared on cwd for eshell-starship."
  740. (eshell-starship-clear-cache 'cwd))
  741. (defun eshell-starship--permute-extension (ext)
  742. "Permute EXT for lookup up modules.
  743. That is, if EXT is \"pkg.tar.gz\", this will return
  744. \(\"pkg.tar.gz\" \"tar.gz\" \"gz\")."
  745. (cl-maplist (lambda (parts)
  746. (mapconcat 'identity parts "."))
  747. (string-split ext "\\.")))
  748. (defun eshell-starship--file-name-extension (name)
  749. "Return the extension for a file name NAME."
  750. (if-let ((start (if (string-prefix-p "." name) 1 0))
  751. (idx (cl-position ?. name :start start :test '=)))
  752. (substring name (1+ idx))
  753. ""))
  754. (defun eshell-starship--remote-for-modules-p (file)
  755. "Return non-nil if FILE is remote for the purpose of running modules."
  756. (let ((method (file-remote-p file 'method)))
  757. (and method
  758. (not (member method eshell-starship-overridden-remote-methods)))))
  759. (defun eshell-starship--modules-for-dir (dir)
  760. "Return a list of modules that are applicable to DIR."
  761. (let ((is-remote (eshell-starship--remote-for-modules-p dir)))
  762. (seq-uniq
  763. (nconc
  764. (cl-delete-if
  765. (lambda (module)
  766. (and is-remote (not (eshell-starship-module-allow-remote-p module))))
  767. (mapcan
  768. (lambda (entry)
  769. (let ((name (car entry))
  770. (is-dir (eq t (file-attribute-type (cdr entry)))))
  771. (if is-dir
  772. (copy-sequence (eshell-starship--module-by :dirs name))
  773. (apply 'nconc
  774. (eshell-starship--module-by :files name)
  775. (mapcar (lambda (ext)
  776. (copy-sequence (eshell-starship--module-by
  777. :extensions ext)))
  778. (eshell-starship--permute-extension
  779. (eshell-starship--file-name-extension name)))))))
  780. (directory-files-and-attributes dir nil nil t)))
  781. (let ((default-directory dir))
  782. (cl-loop for (name is-dir module) in eshell-starship--extra-module-files
  783. when (and (or (not is-remote)
  784. (eshell-starship-module-allow-remote-p module))
  785. is-dir (file-directory-p name))
  786. collect module
  787. when (and (or (not is-remote)
  788. (eshell-starship-module-allow-remote-p module))
  789. (not is-dir) (file-exists-p name))
  790. collect module))
  791. (let ((default-directory dir))
  792. (cl-loop for module being the hash-values of eshell-starship-modules
  793. for predicate = (eshell-starship-module-predicate module)
  794. when (and (or (not is-remote)
  795. (eshell-starship-module-allow-remote-p module))
  796. (funcall predicate))
  797. collect module)))
  798. 'eq)))
  799. (defun eshell-starship--propertize-face (str append &rest faces)
  800. "Copy STR and add FACES to its text properties.
  801. This uses `add-face-text-property' internally, so it will add to existing `face'
  802. properties. If STR is nil, return an empty string. If APPEND, give priority to
  803. existing faces."
  804. (if (not str)
  805. ""
  806. (let ((copy (copy-sequence str)))
  807. (dolist (face faces copy)
  808. (add-face-text-property 0 (length copy) face append copy)))))
  809. (defun eshell-starship--execute-module (module)
  810. "Run the module MODULE and return its output.
  811. Also cache the time it took to run it and its output."
  812. (with-slots (name action prefix postfix icon color) module
  813. (let ((oldtimes (cl-third (gethash name eshell-starship--module-cache)))
  814. start-time result end-time)
  815. (setq start-time (float-time)
  816. result (funcall action)
  817. end-time (float-time))
  818. (when-let ((result)
  819. (output
  820. (concat prefix
  821. (eshell-starship--propertize-face
  822. icon t
  823. (when color
  824. (list (list :foreground color)))
  825. 'eshell-starship-icon-face)
  826. (if color
  827. (eshell-starship--propertize-face
  828. result t (list :foreground color))
  829. result)
  830. postfix)))
  831. (puthash name (list t output (cons (- end-time start-time)
  832. (take 9 oldtimes)))
  833. eshell-starship--module-cache)
  834. output))))
  835. (defvar-local eshell-starship--last-prompt-modules nil
  836. "The list of the modules that where shown in the last prompt.")
  837. (defun eshell-starship--execute-modules ()
  838. "Execute all the modules in `eshell-starship-modules'.
  839. Return a hash table mapping module names to their output."
  840. (setq eshell-starship--last-prompt-modules nil)
  841. (cl-loop
  842. with output = (make-hash-table :test 'equal)
  843. for module in (eshell-starship--modules-for-dir default-directory)
  844. for name = (eshell-starship-module-name module)
  845. for reload-on = (ensure-list (eshell-starship-module-reload-on module))
  846. for cache-entry = (gethash name eshell-starship--module-cache)
  847. do (if (and (not (member 'always reload-on)) (car cache-entry))
  848. (puthash name (cl-second cache-entry) output)
  849. (puthash name (eshell-starship--execute-module module) output))
  850. do (push module eshell-starship--last-prompt-modules)
  851. finally (maphash (lambda (k v)
  852. (unless v
  853. (remhash k output)))
  854. output)
  855. finally return output))
  856. (defun eshell-starship--run-module-precmd-actions ()
  857. "Run the pre-command action for each module."
  858. (cl-loop for module being the hash-values of eshell-starship-modules
  859. for precmd-action = (eshell-starship-module-precmd-action module)
  860. when precmd-action
  861. do (funcall precmd-action)))
  862. (defun eshell-starship--run-module-postcmd-actions ()
  863. "Run the post-command action for each module."
  864. (cl-loop for module being the hash-values of eshell-starship-modules
  865. for postcmd-action = (eshell-starship-module-postcmd-action module)
  866. when postcmd-action
  867. do (funcall postcmd-action)))
  868. (defun eshell-starship--build-module-string ()
  869. "Build a space-separated string of module outputs."
  870. (let ((output (eshell-starship--execute-modules))
  871. pre post found-rest)
  872. (dolist (cur-name (mapcar (lambda (name)
  873. (if (and (not (eq name t)) (symbolp name))
  874. (symbol-name name)
  875. name))
  876. eshell-starship-module-order))
  877. (cond
  878. ((and (eq cur-name t) found-rest)
  879. (warn "t appears more than once in `eshell-starship-module-order"))
  880. ((eq cur-name t)
  881. (setq found-rest t))
  882. ((not (gethash cur-name output))
  883. ;; skip
  884. )
  885. (found-rest
  886. (push (gethash cur-name output) post)
  887. (remhash cur-name output))
  888. (t
  889. (push (gethash cur-name output) pre)
  890. (remhash cur-name output))))
  891. (cl-loop for (part . rest) = (nconc (nreverse pre)
  892. (hash-table-values output)
  893. (nreverse post))
  894. then rest
  895. while part
  896. concat part
  897. unless (or (string-suffix-p "\n" part)
  898. (string-empty-p part)
  899. (not (car rest))
  900. (string-prefix-p "\n" (car rest)))
  901. concat " ")))
  902. (defun eshell-starship--render-prompt ()
  903. "Actually produce the prompt."
  904. (concat
  905. (unless (<= (line-number-at-pos) 3)
  906. "\n")
  907. (eshell-starship--build-module-string)))
  908. (defvar-local eshell-starship--last-prompt-info nil
  909. "A list of the last prompt and the time it took to render it.")
  910. (defun eshell-starship--prompt-function ()
  911. "Function for `eshell-prompt-function'."
  912. (let ((tramp-verbose eshell-starship-verbose-tramp)
  913. start-time prompt end-time)
  914. (setq start-time (float-time)
  915. prompt (eshell-starship--render-prompt)
  916. end-time (float-time)
  917. eshell-starship--last-prompt-info
  918. (list prompt (- end-time start-time)))
  919. (when (buffer-live-p eshell-starship--current-explain-buffer)
  920. (with-current-buffer eshell-starship--current-explain-buffer
  921. (when eshell-starship-explain-auto-update
  922. (let ((eshell-starship-explain-suppress-refresh-messages t))
  923. (revert-buffer)))))
  924. prompt))
  925. (defvar-local eshell-starship--restore-state nil
  926. "State of various variables set by `eshell-starship-prompt-mode'.")
  927. (defvar-local eshell-starship--explain-eshell-buffer nil
  928. "The eshell buffer backing this eshell-starship-explain buffer.")
  929. (defun eshell-starship--enable ()
  930. "Enable eshell-starship."
  931. (setq-local eshell-starship--restore-state
  932. (buffer-local-set-state
  933. eshell-prompt-function
  934. 'eshell-starship--prompt-function
  935. ;; temporary fix until the next version where eshell uses fields
  936. eshell-prompt-regexp (rx bol (? "⬢ [" (+ any) "] ") "❯ ")
  937. eshell-highlight-prompt nil)
  938. eshell-starship--module-cache (make-hash-table :test 'equal))
  939. (add-hook 'eshell-pre-command-hook
  940. #'eshell-starship--run-module-precmd-actions nil t)
  941. (add-hook 'eshell-post-command-hook
  942. #'eshell-starship--run-module-postcmd-actions nil t)
  943. (add-hook 'eshell-directory-change-hook #'eshell-starship--cwd-clear-caches
  944. nil t))
  945. (defun eshell-starship--disable ()
  946. "Disable eshell-starship."
  947. (when eshell-starship--current-explain-buffer
  948. (with-current-buffer eshell-starship--current-explain-buffer
  949. (setq eshell-starship--explain-eshell-buffer nil)))
  950. (setq-local eshell-starship--module-cache nil
  951. eshell-starship--current-explain-buffer nil)
  952. (buffer-local-restore-state eshell-starship--restore-state)
  953. (remove-hook 'eshell-pre-command-hook
  954. #'eshell-starship--run-module-precmd-actions t)
  955. (remove-hook 'eshell-post-command-hook
  956. #'eshell-starship--run-module-postcmd-actions t)
  957. (remove-hook 'eshell-directory-change-hook #'eshell-starship--cwd-clear-caches
  958. t))
  959. ;;;###autoload
  960. (define-minor-mode eshell-starship-prompt-mode
  961. "Minor mode to make eshell prompts look like starship (https://starship.rs)."
  962. :global nil
  963. :init-value nil
  964. :interactive (eshell-mode)
  965. (if eshell-starship-prompt-mode
  966. (eshell-starship--enable)
  967. (eshell-starship--disable)))
  968. ;;; Explain buffer
  969. (defface eshell-starship--heading
  970. '((t (:height 1.2 :weight bold) ))
  971. "Face for showing headings in `eshell-starship-explain' buffers.")
  972. (defun eshell-starship--insert-prompt-string (str &optional prefix first-prefix)
  973. "Insert STR, a prompt string, into the current buffer.
  974. This just cleans up STR a bit before inserting it. Also, if PREFIX is non-nil,
  975. it will be inserted at the start of each line. If FIRST-PREFIX is non-nil, it
  976. will be used specially as the first line's prefix."
  977. (cl-loop with first-line = t
  978. with blank-count = 0
  979. with found-start = nil
  980. for line in (string-lines str)
  981. for empty = (zerop (length line))
  982. while line
  983. do
  984. (if empty
  985. (when found-start
  986. (cl-incf blank-count))
  987. (setq found-start t)
  988. (dotimes (_ blank-count)
  989. (insert "\n"))
  990. (if (and first-line first-prefix)
  991. (progn
  992. (insert first-prefix)
  993. (setq first-line nil))
  994. (insert prefix))
  995. (insert line)
  996. (insert "\n"))))
  997. (defun eshell-starship--explain-insert-module (module &optional no-output)
  998. "Insert information about MODULE at point.
  999. If NO-OUTPUT is non-nil, don't insert the modules previous output."
  1000. (with-slots (name doc) module
  1001. (let ((bullet-char (if (char-displayable-p ?\•)
  1002. ?\•
  1003. ?\-))
  1004. (cache-entry (gethash
  1005. name (buffer-local-value
  1006. 'eshell-starship--module-cache
  1007. eshell-starship--explain-eshell-buffer))))
  1008. (insert (format " %c %s - %s\n"
  1009. bullet-char
  1010. (propertize name
  1011. 'face 'font-lock-keyword-face)
  1012. doc))
  1013. (unless no-output
  1014. (unless (member module
  1015. (buffer-local-value 'eshell-starship--last-prompt-modules
  1016. eshell-starship--explain-eshell-buffer))
  1017. (insert " (This module is hidden.)\n"))
  1018. (if (not (cl-first cache-entry))
  1019. (insert " This module has no cached output.\n")
  1020. (insert " Last output was:\n")
  1021. (eshell-starship--insert-prompt-string (cl-second cache-entry)
  1022. " "
  1023. " \"")
  1024. (forward-line -1)
  1025. (end-of-line)
  1026. (insert "\"")
  1027. (forward-line)
  1028. (insert (format " It took %s.\n"
  1029. (eshell-starship-format-span
  1030. (car (cl-third cache-entry)) 3))))
  1031. (insert "\n")))))
  1032. (defun eshell-starship--explain-insert-enabled ()
  1033. "Insert an explanation of enabled modules at point."
  1034. (let ((rest-modules (copy-hash-table eshell-starship-modules))
  1035. rest-point)
  1036. (dolist (cur-name eshell-starship-module-order)
  1037. (unless (cl-member cur-name eshell-starship-disabled-modules
  1038. :test 'equal)
  1039. (if (eq cur-name t)
  1040. (setq rest-point (point))
  1041. (when-let ((module (gethash cur-name eshell-starship-modules)))
  1042. (eshell-starship--explain-insert-module module)
  1043. (remhash cur-name rest-modules)))))
  1044. (save-excursion
  1045. (goto-char rest-point)
  1046. (cl-loop for module being the hash-values of rest-modules
  1047. using (hash-keys name)
  1048. unless (cl-member name eshell-starship-disabled-modules
  1049. :test 'equal)
  1050. do (eshell-starship--explain-insert-module module)))))
  1051. (defun eshell-starship--explain-format-buffer ()
  1052. "Fill the current buffer with content for `eshell-starship-explain'."
  1053. (unless (buffer-live-p eshell-starship--explain-eshell-buffer)
  1054. (error "Parent Eshell buffer is gone (or no longer using eshell-starship)"))
  1055. (erase-buffer)
  1056. (cl-flet ((heading (txt)
  1057. (propertize txt 'face 'eshell-starship--heading)))
  1058. (cl-destructuring-bind (&optional last-prompt last-time)
  1059. (buffer-local-value 'eshell-starship--last-prompt-info
  1060. eshell-starship--explain-eshell-buffer)
  1061. (when (and last-prompt last-time)
  1062. (insert "The last prompt was:\n")
  1063. (eshell-starship--insert-prompt-string last-prompt " ")
  1064. (insert
  1065. (format "\nIt was rendered in %s.\n\n"
  1066. (eshell-starship-format-span last-time 3)))))
  1067. (insert (heading "The following modules are enabled:\n"))
  1068. (eshell-starship--explain-insert-enabled)
  1069. (if (null eshell-starship-disabled-modules)
  1070. (insert (heading "There are no disabled modules."))
  1071. (insert (heading "The following modules are disabled:\n"))
  1072. (dolist (name eshell-starship-disabled-modules)
  1073. (when-let ((module (gethash name eshell-starship-modules)))
  1074. (eshell-starship--explain-insert-module module t)))
  1075. ;; get rid of newline
  1076. (delete-char -1))))
  1077. (defun eshell-starship--explain-revert (_ignore-auto _noconfirm)
  1078. "Revert function for eshell-starship explain buffers.
  1079. _IGNORE-AUTO and _NOCONFIRM are ignored."
  1080. (let ((save (point))
  1081. (inhibit-read-only t))
  1082. (eshell-starship--explain-format-buffer)
  1083. (goto-char save))
  1084. (unless eshell-starship-explain-suppress-refresh-messages
  1085. (message "Refreshed eshell-starship explain buffer")))
  1086. ;;;###autoload
  1087. (defun eshell-starship-explain-toggle-auto-update-mode (&optional arg)
  1088. "Toggle `eshell-starship-explain-auto-update' in the current buffer.
  1089. If ARG is negative, disable it. If ARG is positive, enable it. Otherwise,
  1090. toggle it."
  1091. (interactive "P" eshell-starship-explain-mode)
  1092. (unless (derived-mode-p 'eshell-starship-explain-mode)
  1093. (error "Not an eshell-starship explain buffer"))
  1094. (if (not arg)
  1095. (cl-callf not eshell-starship-explain-auto-update)
  1096. (let ((num (prefix-numeric-value arg)))
  1097. (setq eshell-starship-explain-auto-update (<= 0 num))))
  1098. (when eshell-starship-explain-auto-update
  1099. (revert-buffer))
  1100. (force-mode-line-update)
  1101. (message "%s auto-updating."
  1102. (if eshell-starship-explain-auto-update
  1103. "Enabled"
  1104. "Disabled")))
  1105. ;;;###autoload
  1106. (defvar-keymap eshell-starship-explain-mode-map
  1107. :doc "Keymap for `eshell-starship-explain-mode'."
  1108. :parent special-mode-map
  1109. :suppress t
  1110. "a" #'eshell-starship-explain-toggle-auto-update-mode
  1111. "r" #'revert-buffer)
  1112. (define-derived-mode eshell-starship-explain-mode nil
  1113. "Eshell-Starship Explain"
  1114. "Major mode for `eshell-starship-explain' buffers."
  1115. :group 'eshell-starship
  1116. :interactive nil
  1117. (setq-local mode-name
  1118. '("Eshell-Starship Explain"
  1119. (eshell-starship-explain-auto-update "/a"))
  1120. display-line-numbers nil
  1121. revert-buffer-function
  1122. 'eshell-starship--explain-revert))
  1123. ;;;###autoload
  1124. (defun eshell-starship-setup-evil-keybindings ()
  1125. "Setup keybindings for `evil-mode' for eshell-starship."
  1126. (require 'evil)
  1127. (when (fboundp 'evil-define-key*)
  1128. (evil-define-key* '(motion normal) eshell-starship-explain-mode-map
  1129. "a" #'eshell-starship-explain-toggle-auto-update-mode
  1130. "r" #'revert-buffer)))
  1131. ;;;###autoload
  1132. (defun eshell-starship-explain ()
  1133. "Show some information about the current prompt."
  1134. (interactive nil eshell-mode)
  1135. (unless (derived-mode-p 'eshell-mode)
  1136. (error "Current buffer is not in eshell-mode. Nothing to explain"))
  1137. (let ((eshell-buffer (current-buffer))
  1138. (explain-buffer (get-buffer-create "*Eshell-Starship Explain*")))
  1139. (dolist (buffer (buffer-list))
  1140. (with-current-buffer buffer
  1141. (cond
  1142. ((eq buffer eshell-buffer)
  1143. (setq eshell-starship--current-explain-buffer explain-buffer))
  1144. ((derived-mode-p 'eshell-mode)
  1145. (setq eshell-starship--current-explain-buffer nil)))))
  1146. (with-current-buffer explain-buffer
  1147. (unless (derived-mode-p 'eshell-starship-explain-mode)
  1148. (eshell-starship-explain-mode))
  1149. (setq eshell-starship--explain-eshell-buffer eshell-buffer)
  1150. (save-excursion
  1151. (let ((inhibit-read-only t))
  1152. (eshell-starship--explain-format-buffer)))
  1153. (pop-to-buffer (current-buffer)))))
  1154. (provide 'eshell-starship)
  1155. ;;; eshell-starship.el ends here