Codebase list eshell-prompt-extras / 5c8e273
Update upstream source from tag 'upstream/1.0' Update to upstream version '1.0' with Debian dir d58e3cf1ee9a1a6d49c096a59fdb9fa6e56ceda5 Lev Lamberov 4 years ago
2 changed file(s) with 181 addition(s) and 47 deletion(s). Raw diff Collapse all Expand all
6464
6565 epe-theme-lambda
6666 epe-theme-dakrone
67 epe-theme-multiline-with-status
6768
6869 Custom Variables
6970 ----------------
8384 epe-git-face
8485 epe-symbol-face
8586 epe-sudo-symbol-face
87 epe-status-face
8688
8789 Screenshot
8890 ----------
00 ;;; eshell-prompt-extras.el --- Display extra information for your eshell prompt.
11
2 ;; Copyright (C) 2014-2016 Wei Zhao
3
4 ;; Author: Wei Zhao <kaihaosw@gmail.com>
2 ;; Copyright (C) 2014-2019 Wei Zhao
3
4 ;; Author: zwild <judezhao@outlook.com>
55 ;; Contributors: Lee Hinman
66 ;; Maintainer: Chunyang Xu <mail@xuchunyang.me>
7 ;; URL: https://github.com/hiddenlotus/eshell-prompt-extras
8 ;; Version: 0.96
7 ;; URL: https://github.com/zwild/eshell-prompt-extras
8 ;; Version: 1.0
99 ;; Created: 2014-08-16
1010 ;; Keywords: eshell, prompt
11 ;; Package-Requires: ((emacs "25"))
1112
1213 ;; This file is NOT part of GNU Emacs.
1314
3536 ;; number for eshell prompt.
3637
3738 ;; If you want to display the python virtual environment info, you
38 ;; need to install `virtualenvwrapper' and `virtualenvwrapper.el'.
39 ;; pip install virtualenvwrapper
39 ;; need to install `virtualenvwrapper.el'.
4040 ;; M-x: package-install: virtualenvwrapper
4141
4242 ;; Installation
8080 (require 'em-dirs)
8181 (require 'esh-ext)
8282 (require 'tramp)
83 (require 'subr-x)
84 (require 'seq)
8385 (autoload 'cl-reduce "cl-lib")
8486 (autoload 'vc-git-branches "vc-git")
8587 (autoload 'vc-find-root "vc-hooks")
8688
87 (when (require 'virtualenvwrapper nil t)
88 (defun epe-venv-p ()
89 "If you are `workon'ing some virtual environment."
90 (and (eshell-search-path "virtualenvwrapper.sh")
91 (string-match venv-location (eshell-search-path "python")))))
92
9389 (defgroup epe nil
9490 "Eshell extras"
9591 :group 'eshell-prompt)
120116 :type '(choice (const :tag "fish-style-dir-name" fish)
121117 (const :tag "single-dir-name" single)
122118 (const :tag "full-path-name" full)))
119
120 (defcustom epe-fish-path-max-len 30
121 "Default maximum length for path in `epe-fish-path'."
122 :group 'epe
123 :type 'number)
123124
124125 (defface epe-remote-face
125126 '((t (:inherit font-lock-comment-face)))
138139 "Face of directory in prompt."
139140 :group 'epe)
140141
142 (defface epe-git-dir-face
143 `((t (:foreground "gold")))
144 "Face of git path component in prompt."
145 :group 'epe)
146
141147 (defface epe-git-face
142148 '((t (:inherit font-lock-constant-face)))
143149 "Face of git info in prompt."
173179 (defface epe-pipeline-time-face
174180 '((t :foreground "yellow"))
175181 "Face for time in pipeline theme."
182 :group 'epe)
183
184 (defface epe-status-face
185 '((t (:inherit font-lock-keyword-face)))
186 "Face of command status line (duration, termination timestamp)."
176187 :group 'epe)
177188
178189 ;; help definations
191202 (replace-regexp-in-string "\n$" "" string))
192203
193204 ;; https://www.emacswiki.org/emacs/EshellPrompt
194 (defun epe-fish-path (path)
205 (defun epe-fish-path (path &optional max-len)
195206 "Return a potentially trimmed-down version of the directory PATH, replacing
196207 parent directories with their initial characters to try to get the character
197208 length of PATH (sans directory slashes) down to MAX-LEN."
198209 (let* ((components (split-string (abbreviate-file-name path) "/"))
199 (max-len 30)
210 (max-len (or max-len epe-fish-path-max-len))
200211 (len (+ (1- (length components))
201212 (cl-reduce '+ components :key 'length)))
202213 (str ""))
216227 components (cdr components)))
217228 (concat str (cl-reduce (lambda (a b) (concat a "/" b)) components))))
218229
230 (defun epe-extract-git-component (path)
231 "Extract and return the tuple (prefix git-component) from PATH."
232 (let ((prefix path)
233 git-component)
234 (when (epe-git-p)
235 ;; We need "--show-prefix and not "--top-level" when we don't follow symlinks.
236 (let* ((git-file-path (abbreviate-file-name
237 (string-trim-right
238 (with-output-to-string
239 (with-current-buffer standard-output
240 (call-process "git" nil t nil
241 "rev-parse"
242 "--show-prefix"))))))
243 (common-folder (car (split-string git-file-path "/"))))
244 (setq prefix (string-join (seq-take-while
245 (lambda (s)
246 (not (string= s common-folder)))
247 (split-string path "/"))
248 "/"))
249 (setq git-component
250 (substring-no-properties path
251 (min (length path) (1+ (length prefix)))))))
252 (list prefix git-component)))
253
219254 (defun epe-user-name ()
220255 "User information."
221256 (if (epe-remote-p)
226261 "Date time information."
227262 (format-time-string (or format "%Y-%m-%d %H:%M") (current-time)))
228263
264 (defun epe-status-formatter (timestamp duration)
265 "Return the status display for `epe-status'.
266 TIMESTAMP is the value returned by `current-time' and DURATION is the floating
267 time the command took to complete in seconds."
268 (format "#[STATUS] End time %s, duration %.3fs\n"
269 (format-time-string "%F %T" timestamp)
270 duration))
271
272 (defcustom epe-status-min-duration 1
273 "If a command takes more time than this, display its status with `epe-status'."
274 :group 'epe
275 :type 'number)
276
277 (defvar epe-status--last-command-time nil)
278 (make-variable-buffer-local 'epe-status--last-command-time)
279
280 (defun epe-status--record ()
281 (setq epe-status--last-command-time (current-time)))
282
283 (defun epe-status (&optional formatter min-duration)
284 "Termination timestamp and duration of command.
285 Status is only returned if command duration was longer than
286 MIN-DURATION \(defaults to `epe-status-min-duration'). FORMATTER
287 is a function of two arguments, TIMESTAMP and DURATION, that
288 returns a string."
289 (if epe-status--last-command-time
290 (let ((duration (time-to-seconds
291 (time-subtract (current-time) epe-status--last-command-time))))
292 (setq epe-status--last-command-time nil)
293 (if (> duration (or min-duration
294 epe-status-min-duration))
295 (funcall (or formatter
296 #'epe-status-formatter)
297 (current-time)
298 duration)
299 ""))
300 (progn
301 (add-hook 'eshell-pre-command-hook #'epe-status--record)
302 "")))
229303
230304 ;; tramp info
231305 (defun epe-remote-p ()
253327
254328 (defun epe-git-p ()
255329 "If you installed git and in a git project."
256 (and (eshell-search-path "git")
257 (vc-find-root (eshell/pwd) ".git")))
330 (unless (epe-remote-p) ; Work-around for issue #20
331 (and (eshell-search-path "git")
332 (vc-find-root (eshell/pwd) ".git"))))
258333
259334 (defun epe-git-short-sha1 ()
260335 (epe-trim-newline (shell-command-to-string "git rev-parse --short HEAD")))
274349 ((string-match "^(HEAD detached at \\([[:word:]]+\\)" branch)
275350 (concat epe-git-detached-HEAD-char (match-string 1 branch)))
276351 (t branch))))
352
353 (defun epe-git-tag (&optional rev with-distance)
354 ;; Inspired by `magit-get-current-tag'.
355 "Return the closest tag reachable from REV.
356
357 If optional REV is nil, then default to `HEAD'.
358 If optional WITH-DISTANCE is non-nil then return (TAG COMMITS),
359 if it is `dirty' return (TAG COMMIT DIRTY). COMMITS is the number
360 of commits in `HEAD' but not in TAG and DIRTY is t if there are
361 uncommitted changes, nil otherwise."
362 (let ((it (with-output-to-string
363 (with-current-buffer standard-output
364 (apply #'call-process "git" nil t nil "describe" "--long" "--tags"
365 (delq nil (list (and (eq with-distance 'dirty) "--dirty") rev)))))))
366 (unless (string-empty-p it)
367 (save-match-data
368 (string-match
369 "\\(.+\\)-\\(?:0[0-9]*\\|\\([0-9]+\\)\\)-g[0-9a-z]+\\(-dirty\\)?$" it)
370 (if with-distance
371 `(,(match-string 1 it)
372 ,(string-to-number (or (match-string 2 it) "0"))
373 ,@(and (match-string 3 it) (list t)))
374 (match-string 1 it))))))
277375
278376 (defun epe-git-dirty ()
279377 "Return if your git is 'dirty'."
339437 (epe-colorize-with-face
340438 (concat (epe-remote-user) "@" (epe-remote-host) " ")
341439 'epe-remote-face))
342 (when epe-show-python-info
343 (when (fboundp 'epe-venv-p)
344 (when (and (epe-venv-p) venv-current-name)
345 (epe-colorize-with-face (concat "(" venv-current-name ") ") 'epe-venv-face))))
440 (when (and epe-show-python-info (bound-and-true-p venv-current-name))
441 (epe-colorize-with-face (concat "(" venv-current-name ") ") 'epe-venv-face))
346442 (let ((f (cond ((eq epe-path-style 'fish) 'epe-fish-path)
347443 ((eq epe-path-style 'single) 'epe-abbrev-dir-name)
348444 ((eq epe-path-style 'full) 'abbreviate-file-name))))
393489 (epe-colorize-with-face
394490 (concat (epe-remote-user) "@" (epe-remote-host) " ")
395491 'epe-remote-face))
396 (when epe-show-python-info
397 (when (fboundp 'epe-venv-p)
398 (when (and (epe-venv-p) venv-current-name)
399 (epe-colorize-with-face (concat "(" venv-current-name ") ") 'epe-venv-face))))
492 (when (and epe-show-python-info (bound-and-true-p venv-current-name))
493 (epe-colorize-with-face (concat "(" venv-current-name ") ") 'epe-venv-face))
400494 (epe-colorize-with-face (funcall
401495 shrink-paths
402496 (split-string
422516 (concat
423517 (if (epe-remote-p)
424518 (progn
425 (concat
426 (epe-colorize-with-face "┌─[" 'epe-pipeline-delimiter-face)
427 (epe-colorize-with-face (epe-remote-user) 'epe-pipeline-user-face)
428 (epe-colorize-with-face "@" 'epe-pipeline-delimiter-face)
429 (epe-colorize-with-face (epe-remote-host) 'epe-pipeline-host-face))
430 )
519 (concat
520 (epe-colorize-with-face "┌─[" 'epe-pipeline-delimiter-face)
521 (epe-colorize-with-face (epe-remote-user) 'epe-pipeline-user-face)
522 (epe-colorize-with-face "@" 'epe-pipeline-delimiter-face)
523 (epe-colorize-with-face (epe-remote-host) 'epe-pipeline-host-face)))
431524 (progn
432525 (concat
433 (epe-colorize-with-face "┌─[" 'epe-pipeline-delimiter-face)
434 (epe-colorize-with-face (user-login-name) 'epe-pipeline-user-face)
435 (epe-colorize-with-face "@" 'epe-pipeline-delimiter-face)
436 (epe-colorize-with-face (system-name) 'epe-pipeline-host-face)))
437 )
526 (epe-colorize-with-face "┌─[" 'epe-pipeline-delimiter-face)
527 (epe-colorize-with-face (user-login-name) 'epe-pipeline-user-face)
528 (epe-colorize-with-face "@" 'epe-pipeline-delimiter-face)
529 (epe-colorize-with-face (system-name) 'epe-pipeline-host-face))))
438530 (concat
439531 (epe-colorize-with-face "]──[" 'epe-pipeline-delimiter-face)
440532 (epe-colorize-with-face (format-time-string "%H:%M" (current-time)) 'epe-pipeline-time-face)
441533 (epe-colorize-with-face "]──[" 'epe-pipeline-delimiter-face)
442534 (epe-colorize-with-face (concat (eshell/pwd)) 'epe-dir-face)
443535 (epe-colorize-with-face "]\n" 'epe-pipeline-delimiter-face)
444 (epe-colorize-with-face "└─>" 'epe-pipeline-delimiter-face)
445 )
446 (when epe-show-python-info
447 (when (fboundp 'epe-venv-p)
448 (when (and (epe-venv-p) venv-current-name)
449 (epe-colorize-with-face (concat "(" venv-current-name ") ") 'epe-venv-face))))
536 (epe-colorize-with-face "└─>" 'epe-pipeline-delimiter-face))
537 (when (and epe-show-python-info (bound-and-true-p venv-current-name))
538 (epe-colorize-with-face (concat "(" venv-current-name ") ") 'epe-venv-face))
450539 (when (epe-git-p)
451540 (concat
452541 (epe-colorize-with-face ":" 'epe-dir-face)
453542 (epe-colorize-with-face
454543 (concat (epe-git-branch)
455 (epe-git-dirty)
456 (epe-git-untracked)
457 (let ((unpushed (epe-git-unpushed-number)))
458 (unless (= unpushed 0)
459 (concat ":" (number-to-string unpushed)))))
544 (epe-git-dirty)
545 (epe-git-untracked)
546 (let ((unpushed (epe-git-unpushed-number)))
547 (unless (= unpushed 0)
548 (concat ":" (number-to-string unpushed)))))
460549 'epe-git-face)))
461550 (epe-colorize-with-face " λ" 'epe-symbol-face)
462551 (epe-colorize-with-face (if (= (user-uid) 0) "#" "") 'epe-sudo-symbol-face)
463552 " "))
553
554 (defun epe-theme-multiline-with-status ()
555 "A simple eshell-prompt theme with information on its own line
556 and status display on command termination."
557 ;; If the prompt spans over multiple lines, the regexp should match
558 ;; last line only.
559 (setq eshell-prompt-regexp "^> ")
560 (concat
561 (epe-colorize-with-face (epe-status) 'epe-status-face)
562 (when (epe-remote-p)
563 (epe-colorize-with-face
564 (concat "(" (epe-remote-user) "@" (epe-remote-host) ")")
565 'epe-remote-face))
566 (when (and epe-show-python-info (bound-and-true-p venv-current-name))
567 (epe-colorize-with-face (concat "(" venv-current-name ") ") 'epe-venv-face))
568 (let ((f (cond ((eq epe-path-style 'fish) 'epe-fish-path)
569 ((eq epe-path-style 'single) 'epe-abbrev-dir-name)
570 ((eq epe-path-style 'full) 'abbreviate-file-name))))
571 (pcase (epe-extract-git-component (funcall f (eshell/pwd)))
572 (`(,prefix nil)
573 (format
574 (propertize "[%s]" 'face '(:weight bold))
575 (propertize prefix 'face 'epe-dir-face)))
576 (`(,prefix ,git-component)
577 (format
578 (epe-colorize-with-face "[%s%s@%s]" '(:weight bold))
579 (epe-colorize-with-face prefix 'epe-dir-face)
580 (if (string-empty-p git-component)
581 ""
582 (concat "/"
583 (epe-colorize-with-face git-component 'epe-git-dir-face)))
584 (epe-colorize-with-face
585 (concat (or (epe-git-branch)
586 (epe-git-tag))
587 (epe-git-dirty)
588 (epe-git-untracked)
589 (let ((unpushed (epe-git-unpushed-number)))
590 (unless (= unpushed 0)
591 (concat ":" (number-to-string unpushed)))))
592 'epe-git-face)))))
593 (epe-colorize-with-face "\n>" '(:weight bold))
594 " "))
595
464596 (provide 'eshell-prompt-extras)
465597
466598 ;;; eshell-prompt-extras.el ends here