2017-12-03 09:19:05 +01:00
|
|
|
|
;;; db-eshell --- Configuration for eshell -*- lexical-binding: t -*-
|
2017-08-06 14:08:25 +02:00
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
2018-01-24 19:29:57 +01:00
|
|
|
|
;; Parts inspired by:
|
|
|
|
|
;; - https://www.masteringemacs.org/article/complete-guide-mastering-eshell
|
|
|
|
|
;; - https://github.com/howardabrams/dot-files/blob/master/emacs-eshell.org
|
2017-08-06 14:08:25 +02:00
|
|
|
|
|
2018-01-24 19:29:57 +01:00
|
|
|
|
;;; Code:
|
2017-08-06 14:08:25 +02:00
|
|
|
|
|
2022-08-31 18:43:35 +02:00
|
|
|
|
(require 'db-customize)
|
2022-08-29 18:46:07 +02:00
|
|
|
|
(require 'dash)
|
2020-06-26 23:08:00 +02:00
|
|
|
|
(require 'subr-x)
|
2020-06-26 22:59:46 +02:00
|
|
|
|
(require 'seq)
|
|
|
|
|
(require 'eshell)
|
|
|
|
|
(require 'em-basic)
|
|
|
|
|
(require 'em-dirs)
|
|
|
|
|
(require 'em-hist)
|
|
|
|
|
(autoload 'magit-status "magit")
|
|
|
|
|
|
2017-08-06 14:08:25 +02:00
|
|
|
|
|
2018-11-03 12:01:34 +01:00
|
|
|
|
;; Various
|
2017-08-06 14:08:25 +02:00
|
|
|
|
|
2020-06-26 23:08:00 +02:00
|
|
|
|
(defun db/run-or-hide-eshell (arg)
|
2023-02-13 13:22:51 +01:00
|
|
|
|
"Opens an eshell buffer if not already in one.
|
2023-04-16 19:01:01 +02:00
|
|
|
|
|
|
|
|
|
Otherwise moves the cursor to the window where we have been before.
|
|
|
|
|
|
|
|
|
|
The buffer's name has to start with “*eshell*” to be recognized
|
|
|
|
|
by this function. Otherwise the current buffer is not treated as
|
|
|
|
|
an eshell buffer.
|
|
|
|
|
|
2023-10-12 16:39:29 +02:00
|
|
|
|
When ARG is given, change to `default-directory' after switching
|
|
|
|
|
to the eshell buffer."
|
2020-06-26 23:08:00 +02:00
|
|
|
|
(interactive "P")
|
2023-10-12 16:39:29 +02:00
|
|
|
|
(let ((current-dir (expand-file-name default-directory)))
|
|
|
|
|
(cl-flet ((in-eshell-buffer-p ()
|
|
|
|
|
(and (derived-mode-p 'eshell-mode)
|
|
|
|
|
(string-match-p "^\\*eshell\\*" (buffer-name)))))
|
|
|
|
|
|
|
|
|
|
(if (and (not arg)
|
|
|
|
|
(in-eshell-buffer-p))
|
|
|
|
|
(bury-buffer)
|
|
|
|
|
|
|
|
|
|
(unless (in-eshell-buffer-p)
|
|
|
|
|
(if-let ((eshell-window (cl-find-if (lambda (window)
|
|
|
|
|
(with-current-buffer (window-buffer window)
|
|
|
|
|
(in-eshell-buffer-p)))
|
|
|
|
|
(window-list-1))))
|
|
|
|
|
(select-window eshell-window)
|
|
|
|
|
;; No running eshell found, open new one.
|
|
|
|
|
(--if-let (display-buffer (eshell 1))
|
|
|
|
|
(select-window it)
|
|
|
|
|
(error "Could not start eshell (`display-buffer' returned nil)"))))
|
|
|
|
|
|
2020-06-26 23:08:00 +02:00
|
|
|
|
(when arg
|
|
|
|
|
(end-of-line)
|
|
|
|
|
(eshell-kill-input)
|
|
|
|
|
(insert (format "cd '%s'" current-dir))
|
2023-02-13 13:22:51 +01:00
|
|
|
|
(eshell-send-input))))))
|
2020-06-26 23:08:00 +02:00
|
|
|
|
|
2017-08-06 14:08:25 +02:00
|
|
|
|
(defun eshell-clear-buffer ()
|
|
|
|
|
"Clear terminal."
|
|
|
|
|
(interactive)
|
|
|
|
|
(let ((inhibit-read-only t))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(eshell-send-input)))
|
|
|
|
|
|
2022-08-31 18:43:35 +02:00
|
|
|
|
(defcustom db/eshell-prompt-include-git-state (if (member system-type '(windows-nt cygwin)) nil t)
|
|
|
|
|
"Whether to include git state information in the eshell prompt.
|
|
|
|
|
|
|
|
|
|
State information includes whether the worktree is dirty, whether
|
|
|
|
|
the index contains uncommitted changes, whether a merge is in
|
|
|
|
|
progress and whether the stash stack is non-empty.
|
|
|
|
|
|
|
|
|
|
Including this in the prompt might be slow on certain
|
|
|
|
|
systems (looking at you, Windows) and may thus not be desirable.
|
|
|
|
|
Set to nil to disable."
|
|
|
|
|
:group 'personal-settings
|
|
|
|
|
:type '(choice (const nil) (const t)))
|
|
|
|
|
|
2022-08-27 15:57:59 +02:00
|
|
|
|
(defun db/eshell-git-branch-string ()
|
|
|
|
|
;; Inspired by https://github.com/howardabrams/dot-files/blob/master/emacs-eshell.org#special-prompt
|
|
|
|
|
"Return name of git branch of current directory, as a string.
|
2022-08-27 16:17:14 +02:00
|
|
|
|
|
2022-08-29 18:46:07 +02:00
|
|
|
|
The format will be BASE-NAME@BASE-DIR[STATE], where BASE-DIR is
|
|
|
|
|
the directory containing the .git directory or link file of the
|
2022-08-27 16:17:14 +02:00
|
|
|
|
current git repository, and BRANCH-NAME is the name of the
|
2022-08-29 18:46:07 +02:00
|
|
|
|
current branch. STATE will display information about whether the
|
|
|
|
|
worktree is dirty or whether the repository needs pushing. When
|
|
|
|
|
no extra state information is available, STATE will be empty and
|
|
|
|
|
the brackets will be ommitted.
|
2022-08-27 16:17:14 +02:00
|
|
|
|
|
2022-08-27 15:57:59 +02:00
|
|
|
|
Return the empty string if the current directory is not part of a
|
|
|
|
|
git repository."
|
|
|
|
|
(interactive)
|
2022-08-27 16:17:14 +02:00
|
|
|
|
(let ((pwd (eshell/pwd))
|
|
|
|
|
repo-dir)
|
|
|
|
|
(when (and (not (file-remote-p pwd))
|
|
|
|
|
(eshell-search-path "git")
|
2023-03-23 19:50:35 +01:00
|
|
|
|
(setq repo-dir (locate-dominating-file (file-truename pwd) ".git")))
|
|
|
|
|
|
2022-08-30 19:06:32 +02:00
|
|
|
|
(if (string-prefix-p (file-truename (file-name-concat repo-dir ".git"))
|
|
|
|
|
(file-truename pwd))
|
|
|
|
|
"GIT_DIR"
|
|
|
|
|
|
2023-03-23 19:50:35 +01:00
|
|
|
|
(save-match-data
|
|
|
|
|
(let* ((has-HEAD nil)
|
|
|
|
|
(git-branch (with-temp-buffer
|
|
|
|
|
(if (zerop (call-process "git" nil t nil
|
|
|
|
|
"rev-parse" "--abbrev-ref" "HEAD"))
|
|
|
|
|
(progn
|
|
|
|
|
(setq has-HEAD t)
|
|
|
|
|
(string-trim (buffer-string)))
|
|
|
|
|
|
|
|
|
|
;; When resolving HEAD fails, we do not have any commits yet.
|
|
|
|
|
"UNINITIALIZED")))
|
|
|
|
|
state-list)
|
|
|
|
|
|
|
|
|
|
(when (and db/eshell-prompt-include-git-state
|
|
|
|
|
has-HEAD)
|
|
|
|
|
|
2023-03-23 20:02:40 +01:00
|
|
|
|
(when (zerop (call-process "git" nil nil nil "rev-parse" "MERGE_HEAD"))
|
|
|
|
|
;; MERGE_HEAD exists, so we are in a merge
|
2023-03-23 19:50:35 +01:00
|
|
|
|
(push "merge" state-list))
|
|
|
|
|
|
|
|
|
|
(when (= 1 (call-process "git" nil nil nil
|
|
|
|
|
"diff" "--no-ext-diff" "--quiet" "--exit-code"))
|
|
|
|
|
(push "dirty" state-list))
|
|
|
|
|
|
|
|
|
|
(when (= 1 (call-process "git" nil nil nil
|
|
|
|
|
"diff" "--no-ext-diff" "--quiet" "--exit-code" "--cached"))
|
|
|
|
|
(push "uncommitted" state-list))
|
|
|
|
|
|
|
|
|
|
(when (with-temp-buffer
|
|
|
|
|
(and (= 0 (call-process "git" nil t nil
|
|
|
|
|
"stash" "list"))
|
|
|
|
|
(not (= 0 (buffer-size)))))
|
|
|
|
|
(push "stash" state-list)))
|
|
|
|
|
|
|
|
|
|
(let ((base-dir (file-name-nondirectory (directory-file-name repo-dir))))
|
2022-08-30 19:06:32 +02:00
|
|
|
|
(if state-list
|
|
|
|
|
(format "%s@%s[%s]" git-branch base-dir (apply #'concat (-interpose "|" state-list)))
|
2023-03-23 19:50:35 +01:00
|
|
|
|
(format "%s@%s" git-branch base-dir)))))))))
|
2022-08-27 15:57:59 +02:00
|
|
|
|
|
2018-07-19 13:49:26 +02:00
|
|
|
|
(defun eshell/default-prompt-function ()
|
|
|
|
|
"A prompt for eshell of the form
|
|
|
|
|
|
2022-08-27 16:38:55 +02:00
|
|
|
|
┌─$USER@$HOST $PWD (current-git-branch)
|
2018-07-19 14:30:01 +02:00
|
|
|
|
└─
|
2018-07-19 13:49:26 +02:00
|
|
|
|
|
2022-08-27 16:17:14 +02:00
|
|
|
|
Information about the current git branch will be empty when the
|
|
|
|
|
current directory is not part of a git repository. See
|
|
|
|
|
`db/eshell-git-branch-string' for more details about the
|
|
|
|
|
formatting."
|
2018-07-19 13:49:26 +02:00
|
|
|
|
(let ((head-face '(:foreground "#859900")))
|
2019-10-05 17:57:32 +02:00
|
|
|
|
(concat (propertize "┌─" 'face head-face)
|
2023-05-09 17:53:36 +02:00
|
|
|
|
(downcase (user-login-name))
|
2019-10-05 17:57:32 +02:00
|
|
|
|
"@"
|
2018-07-19 13:49:26 +02:00
|
|
|
|
(system-name)
|
2019-10-05 17:57:32 +02:00
|
|
|
|
" "
|
2018-07-19 13:49:26 +02:00
|
|
|
|
(propertize (abbreviate-file-name (eshell/pwd))
|
2019-10-05 17:57:32 +02:00
|
|
|
|
'face '(:foreground "#dc322f"))
|
2022-08-27 15:57:59 +02:00
|
|
|
|
(when-let ((git-branch (db/eshell-git-branch-string)))
|
2022-08-27 16:38:55 +02:00
|
|
|
|
(format " (git:%s)" git-branch))
|
2019-10-05 17:57:32 +02:00
|
|
|
|
"\n"
|
|
|
|
|
(propertize "└─" 'face head-face)
|
|
|
|
|
(if (zerop (user-uid)) "#" "$")
|
|
|
|
|
(propertize " " 'face '(:weight bold)))))
|
2018-07-19 13:49:26 +02:00
|
|
|
|
|
2018-07-19 14:02:44 +02:00
|
|
|
|
(defun eshell-insert-history ()
|
|
|
|
|
"Displays the eshell history to select and insert back into your eshell."
|
|
|
|
|
;; directly taken from Howard Abrams
|
|
|
|
|
(interactive)
|
|
|
|
|
(insert (completing-read "Eshell history: "
|
2018-11-03 12:09:54 +01:00
|
|
|
|
(seq-uniq (ring-elements eshell-history-ring)))))
|
2018-07-19 14:02:44 +02:00
|
|
|
|
|
2017-08-06 14:08:25 +02:00
|
|
|
|
|
2018-10-20 14:53:47 +02:00
|
|
|
|
;; Git Completion
|
|
|
|
|
;; https://tsdh.wordpress.com/2013/05/31/eshell-completion-for-git-bzr-and-hg/
|
|
|
|
|
|
2017-08-06 14:08:25 +02:00
|
|
|
|
(defun pcmpl-git-commands ()
|
|
|
|
|
"Return the most common git commands by parsing the git output."
|
|
|
|
|
(with-temp-buffer
|
2018-01-24 19:29:42 +01:00
|
|
|
|
(if (not (zerop (call-process "git" nil (current-buffer) nil "help" "--all")))
|
|
|
|
|
(warn "Cannot call `git’ to obtain list of available commands; completion won’t be available.")
|
|
|
|
|
(goto-char 0)
|
|
|
|
|
(let (commands)
|
|
|
|
|
(while (re-search-forward
|
2018-12-09 14:55:52 +01:00
|
|
|
|
"^[[:blank:]]\\{3\\}\\([[:word:]-.]+\\)[[:blank:]]+"
|
2018-01-24 19:29:42 +01:00
|
|
|
|
nil t)
|
2018-12-09 14:55:52 +01:00
|
|
|
|
(push (match-string 1) commands))
|
2018-01-24 19:29:42 +01:00
|
|
|
|
(sort commands #'string<)))))
|
2017-08-06 14:08:25 +02:00
|
|
|
|
|
|
|
|
|
(defconst pcmpl-git-commands (pcmpl-git-commands)
|
|
|
|
|
"List of `git' commands.")
|
|
|
|
|
|
|
|
|
|
(defvar pcmpl-git-ref-list-cmd "git for-each-ref refs/ --format='%(refname)'"
|
|
|
|
|
"The `git' command to run to get a list of refs.")
|
|
|
|
|
|
|
|
|
|
(defun pcmpl-git-get-refs (type)
|
|
|
|
|
"Return a list of `git' refs filtered by TYPE."
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(insert (shell-command-to-string pcmpl-git-ref-list-cmd))
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(let (refs)
|
|
|
|
|
(while (re-search-forward (concat "^refs/" type "/\\(.+\\)$") nil t)
|
|
|
|
|
(push (match-string 1) refs))
|
|
|
|
|
(nreverse refs))))
|
|
|
|
|
|
|
|
|
|
(defun pcmpl-git-remotes ()
|
|
|
|
|
"Return a list of remote repositories."
|
|
|
|
|
(split-string (shell-command-to-string "git remote")))
|
|
|
|
|
|
|
|
|
|
(defun pcomplete/git ()
|
|
|
|
|
"Completion for `git'."
|
|
|
|
|
;; Completion for the command argument.
|
|
|
|
|
(pcomplete-here* pcmpl-git-commands)
|
|
|
|
|
(cond
|
|
|
|
|
((pcomplete-match "help" 1)
|
|
|
|
|
(pcomplete-here* pcmpl-git-commands))
|
|
|
|
|
((pcomplete-match (regexp-opt '("pull" "push")) 1)
|
|
|
|
|
(pcomplete-here (pcmpl-git-remotes)))
|
|
|
|
|
;; provide branch completion for the command `checkout'.
|
|
|
|
|
((pcomplete-match (regexp-opt '("checkout" "co")) 1)
|
|
|
|
|
(pcomplete-here* (append (pcmpl-git-get-refs "heads")
|
|
|
|
|
(pcmpl-git-get-refs "tags"))))
|
|
|
|
|
(t
|
|
|
|
|
(while (pcomplete-here (pcomplete-entries))))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;; End
|
|
|
|
|
|
|
|
|
|
(provide 'db-eshell)
|
2018-11-03 12:01:34 +01:00
|
|
|
|
|
2017-08-06 14:08:25 +02:00
|
|
|
|
;;; db-eshell ends here
|