├── README.org ├── eshell-git-prompt.el └── img ├── eshell-git-prompt-git-radar.png ├── eshell-git-prompt-powerline.png ├── eshell-git-prompt-robbyrussell.png └── eshell-git-prompt-usage.png /README.org: -------------------------------------------------------------------------------- 1 | * eshell-git-prompt [[https://melpa.org/#/eshell-git-prompt][https://melpa.org/packages/eshell-git-prompt-badge.svg]] [[https://stable.melpa.org/#/eshell-git-prompt][https://stable.melpa.org/packages/eshell-git-prompt-badge.svg]] 2 | 3 | This package provides some themes of Emacs Shell (Eshell) prompt. 4 | 5 | ** Usage 6 | 7 | In Eshell, type ~use-theme~ to list and preview available themes, then 8 | type ~use-theme name~ to choose a theme. 9 | 10 | [[./img/eshell-git-prompt-usage.png]] 11 | 12 | You can also choose a theme in your init file by using 13 | ~eshell-git-prompt-use-theme~, then Eshell will use theme at the 14 | startup. For example, put the following in you init file 15 | 16 | #+BEGIN_SRC emacs-lisp 17 | (eshell-git-prompt-use-theme 'powerline) 18 | #+END_SRC 19 | 20 | ** Recover 21 | 22 | (Just in case some theme breaks your Eshell) 23 | 24 | To recover the Eshell default prompt, type 25 | 26 | #+BEGIN_SRC shell 27 | $ use-theme default 28 | #+END_SRC 29 | 30 | or ~M-x eshell-git-prompt-use-theme RET default RET~. 31 | 32 | ** How does this package work? 33 | 34 | You can customize Eshell's prompt via something like the following 35 | 36 | #+BEGIN_SRC emacs-lisp 37 | (setq eshell-prompt-function (lambda () "A simple prompt." "$ ") 38 | eshell-prompt-regexp "^$ ") 39 | #+END_SRC 40 | 41 | this package uses the same way. 42 | -------------------------------------------------------------------------------- /eshell-git-prompt.el: -------------------------------------------------------------------------------- 1 | ;;; eshell-git-prompt.el --- Some Eshell prompt for Git users -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015-2017 Chunyang Xu 4 | 5 | ;; Author: Chunyang Xu 6 | ;; URL: https://github.com/xuchunyang/eshell-git-prompt 7 | ;; Package-Requires: ((emacs "24.1") (cl-lib "0.5") (dash "2.11.0")) 8 | ;; Keywords: eshell git 9 | ;; Version: 0.1.3 10 | ;; Created: 09/11/2015 11 | 12 | ;; This program is free software; you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; This program is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with this program. If not, see . 24 | 25 | ;;; Commentary: 26 | ;; This package provides some themes of Emacs Shell (Eshell) prompt. 27 | ;; 28 | ;; Usage: 29 | ;; In Eshell, type ~use-theme~ to list and preview available themes, then 30 | ;; type ~use-theme name~ to choose a theme. 31 | ;; 32 | ;; You can also choose a theme in your init file by using 33 | ;; ~eshell-git-prompt-use-theme~, then Eshell will use theme at the 34 | ;; startup. For example, put the following in you init file 35 | ;; 36 | ;; TODO 37 | ;; - [ ] For `eshell-prompt-regexp' hack, replace '$' with '' ('\x06') 38 | ;; - [ ] Make it easier to make new theme (that is, improve API) 39 | ;; - [ ] Test with recent version Emacs and in text-base Emacs 40 | ;; 41 | ;; Note 42 | ;; 1 You must kill all Eshell buffers and re-enter Eshell to make your new 43 | ;; prompt take effect. 44 | ;; 2 You must set `eshell-prompt-regexp' to FULLY match your Eshell prompt, 45 | ;; sometimes it is impossible, especially when you don't want use any special 46 | ;; characters (e.g., '$') to state the end of your Eshell prompt 47 | ;; 3 If you set `eshell-prompt-function' or `eshell-prompt-regexp' incorrectly, 48 | ;; Eshell may crashes, if it happens, you can M-x `eshell-git-prompt-use-theme' 49 | ;; to revert to the default Eshell prompt, then read Note #1. 50 | 51 | ;;; Code: 52 | 53 | (require 'cl-lib) 54 | (require 'dash) 55 | 56 | (declare-function eshell/pwd "em-dirs" (&rest args)) 57 | (declare-function eshell-printn "esh-io" (object)) 58 | 59 | (eval-when-compile 60 | (defvar eshell-last-command-status) 61 | (defvar eshell-prompt-function) 62 | (defvar eshell-prompt-regexp)) 63 | 64 | 65 | ;;; * Customization 66 | (defcustom eshell-git-prompt-themes 67 | '((robbyrussell 68 | eshell-git-prompt-robbyrussell 69 | eshell-git-prompt-robbyrussell-regexp) 70 | (git-radar 71 | eshell-git-prompt-git-radar 72 | eshell-git-prompt-git-radar-regexp) 73 | (powerline 74 | eshell-git-prompt-powerline 75 | eshell-git-prompt-powerline-regexp) 76 | (multiline 77 | eshell-git-prompt-multiline 78 | eshell-git-prompt-multiline-regexp) 79 | (multiline2 80 | eshell-git-prompt-multiline2 81 | eshell-git-prompt-multiline2-regexp) 82 | ;; Only a single $ 83 | (simple 84 | eshell-git-prompt-simple 85 | eshell-git-prompt-simple-regexp) 86 | (default 87 | eshell-git-prompt-default-func 88 | eshell-git-prompt-default-regexp)) 89 | "A list of all available themes. 90 | You can add your own theme to this list, then run 91 | `eshell-git-prompt-use-theme' to use it." 92 | :group 'eshell-prompt 93 | :type '(repeat (list symbol symbol symbol))) 94 | 95 | (defface eshell-git-prompt-exit-success-face 96 | '((((class color) (background light)) :foreground "forest green") 97 | (((class color) (background dark)) :foreground "green")) 98 | "Face for the success char" 99 | :group 'eshell-faces) 100 | 101 | (defface eshell-git-prompt-exit-fail-face 102 | '((((class color) (background light)) :foreground "red") 103 | (((class color) (background dark)) :foreground "red")) 104 | "Face for the fail char" 105 | :group 'eshell-faces) 106 | 107 | (defface eshell-git-prompt-directory-face 108 | '((((class color) (background light)) :foreground "steel blue") 109 | (((class color) (background dark)) :foreground "cyan")) 110 | "Face for the directory" 111 | :group 'eshell-faces) 112 | 113 | (defface eshell-git-prompt-modified-face 114 | '((((class color) (background light)) :foreground "dark orange") 115 | (((class color) (background dark)) :foreground "red")) 116 | "Face for the git 'modified' char" 117 | :group 'eshell-faces) 118 | 119 | (defface eshell-git-prompt-add-face 120 | '((((class color) (background light)) :foreground "dim gray") 121 | (((class color) (background dark)) :foreground "white")) 122 | "Face for the git 'add' char" 123 | :group 'eshell-faces) 124 | 125 | (defface eshell-git-prompt-branch-face 126 | '((((class color) (background light)) :foreground "dim gray") 127 | (((class color) (background dark)) :foreground "dark gray")) 128 | "Face for the git branch" 129 | :group 'eshell-faces) 130 | 131 | (defface eshell-git-prompt-robyrussell-git-face 132 | '((((class color) (background light)) :foreground "blue") 133 | (((class color) (background dark)) :foreground "blue")) 134 | "Face for the git indicator" 135 | :group 'eshell-faces) 136 | 137 | (defface eshell-git-prompt-robyrussell-branch-face 138 | '((((class color) (background light)) :foreground "red") 139 | (((class color) (background dark)) :foreground "red")) 140 | "Face for the git branch" 141 | :group 'eshell-faces) 142 | 143 | (defface eshell-git-prompt-robyrussell-git-dirty-face 144 | '((((class color) (background light)) :foreground "dark orange") 145 | (((class color) (background dark)) :foreground "yellow")) 146 | "Face for the git dirty" 147 | :group 'eshell-faces) 148 | 149 | (defface eshell-git-prompt-powerline-dir-face 150 | '((t :background "steel blue")) 151 | "Face for directory name in eshell git prompt theme `powerline`" 152 | :group 'eshell-faces) 153 | 154 | (defface eshell-git-prompt-powerline-clean-face 155 | '((t :background "forest green")) 156 | "Face for git branch (clean) in eshell git prompt theme `powerline`" 157 | :group 'eshell-faces) 158 | 159 | (defface eshell-git-prompt-powerline-not-clean-face 160 | '((t :background "indian red")) 161 | "Face for git branch (not clean) in eshell git prompt theme `powerline`" 162 | :group 'eshell-faces) 163 | 164 | (defface eshell-git-prompt-multiline-secondary-face 165 | '((((class color) (background light)) :foreground "light gray") 166 | (((class color) (background dark)) :foreground "dim gray")) 167 | "Face for secondary part in eshell git prompt theme `multiline`. e.g. separator, horizontal line, date." 168 | :group 'eshell-faces) 169 | 170 | (defface eshell-git-prompt-multiline-command-face 171 | '((((class color) (background light)) :foreground "slate blue") 172 | (((class color) (background dark)) :foreground "gold")) 173 | "Face for command user typed in eshell git prompt theme `multiline`." 174 | :group 'eshell-faces) 175 | 176 | (defface eshell-git-prompt-multiline-sign-face 177 | '((t :foreground "deep pink")) 178 | "Face for prompt sign in eshell git prompt theme `multiline`." 179 | :group 'eshell-faces) 180 | 181 | (defface eshell-git-prompt-multiline2-secondary-face 182 | '((t (:foreground "#51afef" :weight ultra-bold))) 183 | "Face for secondary part in eshell git prompt theme `multiline`. e.g. separator, horizontal line, date." 184 | :group 'eshell-faces) 185 | 186 | (defface eshell-git-prompt-multiline2-usr-face 187 | '((t (:foreground "magenta" :weight ultra-bold))) 188 | "Face for prompt sign in eshell git prompt theme `multiline`." 189 | :group 'eshell-faces) 190 | 191 | (defface eshell-git-prompt-multiline2-dir-face 192 | '((t (:foreground "white" :weight ultra-bold))) 193 | "Face for prompt sign in eshell git prompt theme `multiline`." 194 | :group 'eshell-faces) 195 | 196 | (defface eshell-git-prompt-multiline2-host-face 197 | '((t (:foreground "green" :weight ultra-bold))) 198 | "Face for prompt sign in eshell git prompt theme `multiline`." 199 | :group 'eshell-faces) 200 | 201 | (defface eshell-git-prompt-multiline2-git-face 202 | '((t (:foreground "red" :weight ultra-bold))) 203 | "Face for prompt sign in eshell git prompt theme `multiline`." 204 | :group 'eshell-faces) 205 | 206 | (defface eshell-git-prompt-multiline2-fail-face 207 | '((t (:foreground "red" :weight ultra-bold))) 208 | "Face for prompt sign in eshell git prompt theme `multiline`." 209 | :group 'eshell-faces) 210 | 211 | (defface eshell-git-prompt-multiline2-command-face 212 | '((((class color) (background light)) (:foreground "slate blue" :weight ultra-bold)) 213 | (((class color) (background dark)) (:foreground "gold" :weight ultra-bold))) 214 | "Face for command user typed in eshell git prompt theme `multiline`." 215 | :group 'eshell-faces) 216 | 217 | 218 | ;;; * Internal 219 | 220 | (defmacro with-face (str &rest properties) 221 | "Add face PROPERTIES to STR." 222 | (declare (indent 1)) 223 | `(propertize ,str 'face (list ,@properties))) 224 | 225 | (defun eshell-git-prompt---str-read-only (str) 226 | "Make STR read-only and return the string." 227 | (add-text-properties 0 (length str) 228 | '(read-only t 229 | front-sticky (read-only) 230 | rear-nonsticky (read-only)) 231 | str) 232 | str) 233 | 234 | (defmacro eshell-git-prompt-str-append (str suffix) 235 | "Append STR with SUFFIX and set the value of symbol STR to the result." 236 | (if (symbolp str) 237 | (list 'setq str (list 'concat str suffix)) 238 | (list 'cl-callf 'concat str suffix))) 239 | 240 | (cl-defun eshell-git-prompt--git-root-dir 241 | (&optional (directory default-directory)) 242 | "Return Git root directory name if exist, otherwise, return nil." 243 | (let ((root (locate-dominating-file directory ".git"))) 244 | (and root (file-name-as-directory root)))) 245 | 246 | (cl-defun eshell-git-prompt--shorten-directory-name 247 | (&optional (directory default-directory)) 248 | "Return only current directory name (without ending slash). 249 | DIRECTORY must end with slash. 250 | 251 | For example: 252 | 253 | \"~/foo/bar\" => \"bar\" 254 | \"~\" => \"~\" 255 | \"/\" => \"/\"" 256 | (let ((dir (abbreviate-file-name directory))) 257 | (if (> (length dir) 1) 258 | (file-name-nondirectory (substring dir 0 -1)) 259 | dir))) 260 | 261 | (defun eshell-git-prompt--slash-str (str) 262 | "Make sure STR is ended with one slash, return it." 263 | (if (string-suffix-p "/" str) 264 | str 265 | (concat str "/"))) 266 | 267 | (defun eshell-git-prompt-last-command-status () 268 | "Return Eshell last command execution status. 269 | When Eshell just launches, `eshell-last-command-status' is not defined yet, 270 | return 0 (i.e., success)." 271 | (if (not (boundp 'eshell-last-command-status)) 272 | 0 273 | eshell-last-command-status)) 274 | 275 | (defun eshell-git-prompt-exit-success-p () 276 | "Replacement of `eshell-exit-success-p'. 277 | Should return nil only if a external command fails, otherwise return non-nil. 278 | 279 | It is unable to distinguish if a elisp command fails in practice, for example, 280 | `eshell/cd' return nil whether it successes or not." 281 | (= (eshell-git-prompt-last-command-status) 0)) 282 | 283 | (defconst eshell-git-prompt---git-global-arguments 284 | '("--no-pager" "--literal-pathspecs" "-c" "core.preloadindex=true") 285 | "Global git arguments.") 286 | 287 | (defun eshell-git-prompt--process-git-arguments (args) 288 | "Prepare ARGS for a function that invokes Git." 289 | (setq args (-flatten args)) 290 | (append eshell-git-prompt---git-global-arguments args)) 291 | 292 | (defun eshell-git-prompt--git-insert (&rest args) 293 | "Execute Git with ARGS, inserting its output at point." 294 | (setq args (eshell-git-prompt--process-git-arguments args)) 295 | (apply #'process-file "git" nil (list t nil) nil args)) 296 | 297 | (defun eshell-git-prompt--git-string (&rest args) 298 | "Execute Git with ARGS, returning the first line of its output. 299 | If there is no output return nil. If the output begins with a 300 | newline return an empty string." 301 | (with-temp-buffer 302 | (apply #'eshell-git-prompt--git-insert args) 303 | (unless (bobp) 304 | (goto-char (point-min)) 305 | (buffer-substring-no-properties (point) (line-end-position))))) 306 | 307 | (defun eshell-git-prompt--git-lines (&rest args) 308 | "Execute Git with ARGS, returning its output as a list of lines. 309 | Empty lines anywhere in the output are omitted." 310 | (with-temp-buffer 311 | (apply #'eshell-git-prompt--git-insert args) 312 | (split-string (buffer-string) "\n" t))) 313 | 314 | (defun eshell-git-prompt--collect-status () 315 | "Return working directory status as a plist. 316 | If working directory is clean, return nil." 317 | (let ((untracked 0) ; next git-add, then git-commit 318 | (modified 0) ; next git-commit 319 | (modified-updated 0) ; next git-commit 320 | (new-added 0) ; next git-commit 321 | (deleted 0) ; next git-rm, then git-commit 322 | (deleted-updated 0) ; next git-commit 323 | (renamed-updated 0) ; next git-commit 324 | ) 325 | (-when-let (status-items (eshell-git-prompt--git-lines "status" "--porcelain")) 326 | (--each status-items 327 | (pcase (substring it 0 2) 328 | ("??" (cl-incf untracked)) 329 | ("MM" (progn (cl-incf modified) 330 | (cl-incf modified-updated))) 331 | (" M" (cl-incf modified)) 332 | ("M " (cl-incf modified-updated)) 333 | ("A " (cl-incf new-added)) 334 | (" D" (cl-incf deleted)) 335 | ("D " (cl-incf deleted-updated)) 336 | ("R " (cl-incf renamed-updated)))) 337 | (list :untracked untracked 338 | :modified modified 339 | :modified-updated modified-updated 340 | :new-added new-added 341 | :deleted deleted 342 | :deleted-updated deleted-updated 343 | :renamed-updated renamed-updated)))) 344 | 345 | (defun eshell-git-prompt--branch-name () 346 | "Return current branch name." 347 | (eshell-git-prompt--git-string "symbolic-ref" "HEAD" "--short")) 348 | 349 | (defvar eshell-git-prompt-branch-name nil) 350 | 351 | (defun eshell-git-prompt--commit-short-sha () 352 | (eshell-git-prompt--git-string "rev-parse" "--short" "HEAD")) 353 | 354 | (defun eshell-git-prompt--readable-branch-name () 355 | (-if-let (branch-name eshell-git-prompt-branch-name) 356 | branch-name 357 | (concat "detached@" (eshell-git-prompt--commit-short-sha)))) 358 | 359 | (defun eshell-git-prompt--remote-branch-name () 360 | (eshell-git-prompt--git-string "for-each-ref" "--format=%(upstream:short)" 361 | (format "refs/heads/%s" 362 | eshell-git-prompt-branch-name))) 363 | 364 | (defvar eshell-git-prompt-remote-branch-name nil) 365 | 366 | (defun eshell-git-prompt--commits-ahead-of-remote () 367 | (-if-let (remote-branch-name eshell-git-prompt-remote-branch-name) 368 | (string-to-number 369 | (eshell-git-prompt--git-string "rev-list" "--right-only" "--count" 370 | (format "%s...HEAD" remote-branch-name))) 371 | 0)) 372 | 373 | (defun eshell-git-prompt--commits-behind-of-remote () 374 | (-if-let (remote-branch-name eshell-git-prompt-remote-branch-name) 375 | (string-to-number 376 | (eshell-git-prompt--git-string "rev-list" "--left-only" "--count" 377 | (format "%s...HEAD" remote-branch-name))) 378 | 0)) 379 | 380 | 381 | ;;; * Themes 382 | 383 | (defun eshell-git-prompt-simple () 384 | (eshell-git-prompt---str-read-only 385 | (if (= (user-uid) 0) "# " "$ "))) 386 | 387 | (defconst eshell-git-prompt-simple-regexp "^[#$] ") 388 | 389 | ;; the Eshell default prompt 390 | (defun eshell-git-prompt-default-func () 391 | (eshell-git-prompt---str-read-only 392 | (concat (abbreviate-file-name (eshell/pwd)) 393 | (if (= (user-uid) 0) " # " " $ ")))) 394 | 395 | (defconst eshell-git-prompt-default-regexp "^[^#$\n]* [#$] ") 396 | 397 | ;; oh-my-zsh's robbyrussell theme 398 | ;; see https://github.com/robbyrussell/oh-my-zsh/wiki/Themes#robbyrussell 399 | (defun eshell-git-prompt-robbyrussell () 400 | "Eshell Git prompt with oh-my-zsh's robbyrussell theme. 401 | 402 | It looks like: 403 | 404 | ➜ eshell-git-prompt git:(master) ✗ git status " 405 | ;; Prompt components 406 | (let (beg dir git-branch git-dirty end) 407 | ;; Beg: start symbol 408 | (setq beg 409 | (with-face "➜" (if (eshell-git-prompt-exit-success-p) 410 | 'eshell-git-prompt-exit-success-face 'eshell-git-prompt-exit-fail-face))) 411 | 412 | ;; Dir: current working directory 413 | (setq dir (with-face (eshell-git-prompt--shorten-directory-name) 414 | 'eshell-git-prompt-directory-face)) 415 | 416 | ;; Git: branch/detached head, dirty status 417 | (when (eshell-git-prompt--git-root-dir) 418 | (setq eshell-git-prompt-branch-name (eshell-git-prompt--branch-name)) 419 | 420 | (setq git-branch 421 | (concat 422 | (with-face "git:(" 'eshell-git-prompt-robyrussell-git-face) 423 | (with-face (eshell-git-prompt--readable-branch-name) 'eshell-git-prompt-robyrussell-branch-face) 424 | (with-face ")" 'eshell-git-prompt-robyrussell-git-face))) 425 | 426 | (setq git-dirty 427 | (when (eshell-git-prompt--collect-status) 428 | (with-face "✗" 'eshell-git-prompt-robyrussell-git-dirty-face)))) 429 | 430 | ;; End: To make it possible to let `eshell-prompt-regexp' to match the full prompt 431 | (setq end (propertize "$" 'invisible t)) 432 | 433 | ;; Build prompt 434 | (eshell-git-prompt---str-read-only 435 | (concat (mapconcat #'identity (-non-nil (list beg dir git-branch git-dirty)) " ") 436 | end 437 | " ")))) 438 | 439 | (defconst eshell-git-prompt-robbyrussell-regexp "^[^$\n]*\\\$ ") 440 | 441 | ;; git-radar 442 | ;; see https://github.com/michaeldfallen/git-radar 443 | 444 | (defun eshell-git-prompt-git-radar () 445 | "Eshell Git prompt inspired by git-radar." 446 | (eshell-git-prompt---str-read-only 447 | (concat 448 | (with-face "➜" 449 | (if (eshell-git-prompt-exit-success-p) 450 | 'eshell-git-prompt-exit-success-face 'eshell-git-prompt-exit-fail-face)) 451 | " " 452 | (with-face (eshell-git-prompt--shorten-directory-name) 453 | 'eshell-git-prompt-directory-face) 454 | ;; Yo, we are in a Git repo, display some information about it 455 | (when (eshell-git-prompt--git-root-dir) 456 | (setq eshell-git-prompt-branch-name 457 | (eshell-git-prompt--branch-name) 458 | eshell-git-prompt-remote-branch-name 459 | (eshell-git-prompt--remote-branch-name)) 460 | (concat 461 | " " 462 | (with-face "git:(" 'eshell-git-prompt-branch-face) 463 | 464 | ;; Branch name 465 | (with-face (eshell-git-prompt--readable-branch-name) 466 | 'eshell-git-prompt-branch-face) 467 | 468 | ;; Local commits 469 | (when eshell-git-prompt-remote-branch-name 470 | (let ((local-behind (eshell-git-prompt--commits-behind-of-remote)) 471 | (local-ahead (eshell-git-prompt--commits-ahead-of-remote))) 472 | (cond ((and (> local-ahead 0) (> local-behind 0)) 473 | (concat " " 474 | (number-to-string local-behind) 475 | (with-face "⇵" :foreground "yellow") 476 | (number-to-string local-ahead))) 477 | ((> local-behind 0) 478 | (concat " " 479 | (number-to-string local-behind) 480 | (with-face "↓" :foreground "red"))) 481 | ((> local-ahead 0) 482 | (concat " " 483 | (number-to-string local-ahead) 484 | (with-face "↑" :foreground "LimeGreen")))))) 485 | 486 | (with-face ")" 'eshell-git-prompt-branch-face) 487 | 488 | ;; File status 489 | (-when-let (git-status (eshell-git-prompt--collect-status)) 490 | (-let [(&plist :untracked untracked 491 | :new-added new-added 492 | :modified-updated modified-updated 493 | :modified modified) 494 | git-status] 495 | (concat 496 | ;; Updated to index changes 497 | (let (group1) 498 | (setq group1 499 | (concat 500 | (when (> new-added 0) 501 | (concat 502 | (number-to-string new-added) 503 | (with-face "A" 'eshell-git-prompt-add-face))) 504 | (when (> modified-updated 0) 505 | (concat 506 | (number-to-string modified-updated) 507 | (with-face "M" 'eshell-git-prompt-modified-face))))) 508 | (when (> (length group1) 0) 509 | (concat " " group1))) 510 | ;; Modified but not updated 511 | (when (> modified 0) 512 | (concat " " (number-to-string modified) 513 | (with-face "M" 'eshell-git-prompt-modified-face))) 514 | ;; Untracked file 515 | (when (> untracked 0) 516 | (concat " " (number-to-string untracked) 517 | (with-face "A" 'eshell-git-prompt-add-face)))))))) 518 | ;; To make it possible to let `eshell-prompt-regexp' to match the full prompt 519 | (propertize "$" 'invisible t) " "))) 520 | 521 | (defconst eshell-git-prompt-git-radar-regexp "^[^$\n]*\\\$ ") 522 | 523 | 524 | ;; Powerline 525 | 526 | ;; For https://github.com/xuchunyang/eshell-git-prompt/issues/8 527 | (defun eshell-git-prompt-powerline-dir () 528 | (abbreviate-file-name (eshell/pwd))) 529 | 530 | (defun eshell-git-prompt-powerline () 531 | (let ((segment-separator "\xe0b0") 532 | (branch "\xe0a0") 533 | (detached "\x27a6") 534 | (cross "\x2718") 535 | dir git git-face) 536 | (setq dir 537 | (propertize 538 | (concat 539 | " " 540 | (unless (eshell-git-prompt-exit-success-p) 541 | (concat cross " ")) 542 | (eshell-git-prompt-powerline-dir) 543 | " ") 544 | 'face 'eshell-git-prompt-powerline-dir-face)) 545 | (setq git 546 | (when (eshell-git-prompt--git-root-dir) 547 | (setq git-face 548 | (if (eshell-git-prompt--collect-status) 549 | 'eshell-git-prompt-powerline-not-clean-face 550 | 'eshell-git-prompt-powerline-clean-face)) 551 | (setq eshell-git-prompt-branch-name (eshell-git-prompt--branch-name)) 552 | (propertize 553 | (concat " " 554 | (-if-let (branch-name eshell-git-prompt-branch-name) 555 | (concat branch " " branch-name) 556 | (concat detached " "(eshell-git-prompt--commit-short-sha))) 557 | " ") 558 | 'face git-face))) 559 | (eshell-git-prompt---str-read-only 560 | (concat 561 | (if git 562 | (concat dir 563 | (with-face segment-separator 564 | :foreground (face-background 'eshell-git-prompt-powerline-dir-face) 565 | :background (face-background git-face)) 566 | git 567 | (with-face segment-separator 568 | :foreground (face-background git-face))) 569 | (concat dir 570 | (with-face segment-separator 571 | :foreground (face-background 'eshell-git-prompt-powerline-dir-face)))) 572 | (propertize "$" 'invisible t) " ")))) 573 | 574 | (defconst eshell-git-prompt-powerline-regexp "^[^$\n]*\\\$ ") 575 | 576 | (defun eshell-git-prompt-multiline () 577 | "Eshell Git prompt inspired by spaceship-prompt." 578 | (let (separator hr dir git git-dirty time sign command) 579 | (setq separator (with-face " | " 'eshell-git-prompt-multiline-secondary-face)) 580 | (setq hr (with-face (concat "\n" (make-string (/ (window-total-width) 2) ?─) "\n") 'eshell-git-prompt-multiline-secondary-face)) 581 | (setq dir 582 | (concat 583 | (with-face "🗀" 'eshell-git-prompt-directory-face) 584 | (concat (abbreviate-file-name (eshell/pwd))))) 585 | (setq git 586 | (concat (with-face "⎇" 'eshell-git-prompt-exit-success-face) 587 | (concat (eshell-git-prompt--branch-name)))) 588 | (setq git-dirty 589 | (when (eshell-git-prompt--branch-name) 590 | (if (eshell-git-prompt--collect-status) 591 | (with-face " ✎" 'eshell-git-prompt-modified-face) 592 | (with-face " ✔" 'eshell-git-prompt-exit-success-face)))) 593 | (setq time (with-face (format-time-string "%I:%M:%S %p") 'eshell-git-prompt-multiline-secondary-face)) 594 | (setq sign 595 | (if (= (user-uid) 0) 596 | (with-face "\n#" 'eshell-git-prompt-multiline-sign-face) 597 | (with-face "\nλ" 'eshell-git-prompt-multiline-sign-face))) 598 | (setq command (with-face " " 'eshell-git-prompt-multiline-command-face)) 599 | 600 | ;; Build prompt 601 | (eshell-git-prompt---str-read-only 602 | (concat hr dir separator git git-dirty separator time sign command)))) 603 | 604 | (defconst eshell-git-prompt-multiline-regexp "^[^$\n]*λ ") 605 | 606 | (defun eshell-git-prompt-multiline2 () 607 | "Eshell Git prompt inspired by spaceship-prompt." 608 | (let (beg separator usr at host dir git time sign command) 609 | (setq beg (with-face "\n┌─(" 'eshell-git-prompt-multiline2-secondary-face)) 610 | (setq separator (with-face ")──(" 'eshell-git-prompt-multiline2-secondary-face)) 611 | (setq usr (with-face (user-login-name) ''eshell-git-prompt-multiline2-usr-face)) 612 | (setq at (with-face "@" 'eshell-git-prompt-multiline2-secondary-face)) 613 | (setq host (with-face (system-name) 'eshell-git-prompt-multiline2-host-face)) 614 | (setq dir 615 | (with-face (eshell-git-prompt--shorten-directory-name) 616 | 'eshell-git-prompt-multiline2-dir-face)) 617 | (setq git 618 | (concat 619 | (if (eshell-git-prompt--branch-name) 620 | (concat 621 | separator 622 | (with-face "⎇ " 'eshell-git-prompt-exit-success-face) 623 | (with-face (eshell-git-prompt--branch-name) 624 | 'eshell-git-prompt-multiline2-git-face)) 625 | "") 626 | (when (eshell-git-prompt--branch-name) 627 | (if (eshell-git-prompt--collect-status) 628 | (with-face " ✗" 'eshell-git-prompt-modified-face) 629 | (with-face " ✔" 'eshell-git-prompt-exit-success-face))))) 630 | (setq time (with-face (format-time-string "%I:%M:%S %p") 631 | 'eshell-git-prompt-multiline2-command-face)) 632 | (setq sign 633 | (concat 634 | (with-face ")\n└─" 'eshell-git-prompt-multiline2-secondary-face) 635 | (if (not (eshell-git-prompt-exit-success-p)) 636 | (with-face ">>" 'eshell-git-prompt-multiline2-fail-face) 637 | (with-face ">>" 'eshell-git-prompt-multiline2-secondary-face)))) 638 | (setq command (with-face " " 'eshell-git-prompt-multiline2-command-face)) 639 | 640 | ;; Build prompt 641 | (eshell-git-prompt---str-read-only 642 | (concat beg usr at host separator dir git separator time sign command)))) 643 | 644 | (defconst eshell-git-prompt-multiline2-regexp "^[^$\n]*└─>> ") 645 | 646 | (defvar eshell-git-prompt-current-theme nil) 647 | 648 | ;;;###autoload 649 | (defun eshell-git-prompt-use-theme (theme) 650 | "Pick up a Eshell prompt theme from `eshell-git-prompt-themes' to use." 651 | (interactive 652 | (let ((theme 653 | (completing-read "Use theme: " 654 | (--map (symbol-name (car it)) 655 | eshell-git-prompt-themes) 656 | nil t))) 657 | (list (intern theme)))) 658 | (when (stringp theme) 659 | (setq theme (intern theme))) 660 | (-if-let (func-regexp (assoc-default theme eshell-git-prompt-themes)) 661 | (progn 662 | (setq eshell-prompt-function (symbol-function (car func-regexp)) 663 | eshell-prompt-regexp (symbol-value (cadr func-regexp))) 664 | (setq eshell-git-prompt-current-theme theme) 665 | (when (called-interactively-p 'interactive) 666 | (message 667 | "Now kill all Eshell buffers and re-enter Eshell to use %s theme" 668 | (symbol-name theme)))) 669 | (error "Theme \"%s\" is not available" theme))) 670 | 671 | ;; TODO: Support the --help option 672 | ;; TODO: Support completing (via pcomplete) 673 | ;;;###autoload 674 | (defun eshell/use-theme (&optional theme) 675 | "List all available themes and pick one from Eshell." 676 | (if (null theme) 677 | (progn 678 | (eshell-printn "") 679 | (eshell-printn (make-string 60 ?-)) 680 | (eshell-printn (format "%-20s%s" "Name" "Preview")) 681 | (eshell-printn (make-string 60 ?-)) 682 | (eshell-printn 683 | (mapconcat (lambda (theme) 684 | (format "%-20s%s" 685 | (symbol-name (car theme)) 686 | (funcall (cadr theme)))) 687 | eshell-git-prompt-themes "\n")) 688 | (eshell-printn (make-string 60 ?-)) 689 | (eshell-printn "") 690 | (eshell-printn "Type 'use-theme theme-name' to use a theme.")) 691 | (when (numberp theme) 692 | (setq theme (number-to-string theme))) 693 | (setq theme (intern theme)) 694 | (-if-let (func-regexp (assoc-default theme eshell-git-prompt-themes)) 695 | (progn 696 | (setq eshell-prompt-function (symbol-function (car func-regexp)) 697 | eshell-prompt-regexp (symbol-value (cadr func-regexp))) 698 | (setq eshell-git-prompt-current-theme theme) 699 | (eshell-printn "")) 700 | (error 701 | "Theme \"%s\" is not available. 702 | Run this command again without argument to view all available themes. 703 | 704 | usage: use-theme: (&optional theme)" 705 | (symbol-name theme)))) 706 | nil) 707 | 708 | (provide 'eshell-git-prompt) 709 | 710 | ;; Local Variables: 711 | ;; coding: utf-8 712 | ;; End: 713 | ;;; eshell-git-prompt.el ends here 714 | -------------------------------------------------------------------------------- /img/eshell-git-prompt-git-radar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuchunyang/eshell-git-prompt/dfcf9cd93add6763e2c46603b0323274d4c22906/img/eshell-git-prompt-git-radar.png -------------------------------------------------------------------------------- /img/eshell-git-prompt-powerline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuchunyang/eshell-git-prompt/dfcf9cd93add6763e2c46603b0323274d4c22906/img/eshell-git-prompt-powerline.png -------------------------------------------------------------------------------- /img/eshell-git-prompt-robbyrussell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuchunyang/eshell-git-prompt/dfcf9cd93add6763e2c46603b0323274d4c22906/img/eshell-git-prompt-robbyrussell.png -------------------------------------------------------------------------------- /img/eshell-git-prompt-usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuchunyang/eshell-git-prompt/dfcf9cd93add6763e2c46603b0323274d4c22906/img/eshell-git-prompt-usage.png --------------------------------------------------------------------------------