Add eshell configuration
This commit is contained in:
parent
7dae839ad6
commit
c5c137b645
282
init.el
282
init.el
@ -541,33 +541,25 @@ visual states."
|
|||||||
|
|
||||||
;; project.el
|
;; project.el
|
||||||
(use-package project
|
(use-package project
|
||||||
:bind (("C-c v" . my/project-vterm-or-default)
|
:bind (("C-c v" . my/project-eshell-or-default)
|
||||||
:map project-prefix-map
|
:map project-prefix-map
|
||||||
("s" . my/project-vterm)
|
("s" . my/project-eshell)
|
||||||
("u" . my/project-run))
|
("u" . my/project-run))
|
||||||
:init
|
:init
|
||||||
(defvar my/project-vterm-hash-table (make-hash-table :test 'equal)
|
(defvar eshell-buffer-name)
|
||||||
"Hash table that maps project root dirs to vterm buffers.")
|
(defun my/project-eshell (prompt &optional arg)
|
||||||
(defun my/project-vterm (prompt)
|
"Switch to or create an eshell buffer in the current projects root."
|
||||||
"Switch to or create a vterm buffer in the current projects root."
|
(interactive (list t current-prefix-arg))
|
||||||
(interactive (list t))
|
|
||||||
(if-let ((proj (project-current prompt))
|
(if-let ((proj (project-current prompt))
|
||||||
(default-directory (project-root proj)))
|
(default-directory (project-root proj))
|
||||||
(if-let ((vterm-buff (gethash default-directory
|
(eshell-buffer-name
|
||||||
my/project-vterm-hash-table))
|
(concat "*eshell for project " default-directory "*")))
|
||||||
((buffer-live-p vterm-buff)))
|
(eshell)))
|
||||||
(switch-to-buffer vterm-buff)
|
(defun my/project-eshell-or-default (&optional arg)
|
||||||
(puthash default-directory
|
"Open an eshell for the current project, otherwise, open a normal eshell."
|
||||||
(vterm (concat "*vterm for project " default-directory "*"))
|
(interactive "P")
|
||||||
my/project-vterm-hash-table))))
|
(unless (my/project-eshell nil arg)
|
||||||
(defun my/project-vterm-or-default ()
|
(eshell arg)))
|
||||||
"Open a vterm for the current project, otherwise, open a normal vterm."
|
|
||||||
(interactive)
|
|
||||||
(unless (my/project-vterm nil)
|
|
||||||
(if-let ((vterm-buff (gethash nil my/project-vterm-hash-table))
|
|
||||||
((buffer-live-p vterm-buff)))
|
|
||||||
(switch-to-buffer vterm-buff)
|
|
||||||
(puthash nil (vterm vterm-buffer-name) my/project-vterm-hash-table))))
|
|
||||||
(defvar my/project-run-command nil
|
(defvar my/project-run-command nil
|
||||||
"Command to run with `my/project-run'.")
|
"Command to run with `my/project-run'.")
|
||||||
(put 'my/project-run-command 'safe-local-variable (lambda (val)
|
(put 'my/project-run-command 'safe-local-variable (lambda (val)
|
||||||
@ -676,7 +668,30 @@ COMMAND and COMINT are like `compile'."
|
|||||||
|
|
||||||
;; vterm
|
;; vterm
|
||||||
(use-package vterm
|
(use-package vterm
|
||||||
:hook (vterm-mode . with-editor-export-editor))
|
:hook (vterm-mode . with-editor-export-editor)
|
||||||
|
:init
|
||||||
|
(defvar my/project-vterm-hash-table (make-hash-table :test 'equal)
|
||||||
|
"Hash table that maps project root dirs to vterm buffers.")
|
||||||
|
(defun my/project-vterm (prompt)
|
||||||
|
"Switch to or create a vterm buffer in the current projects root."
|
||||||
|
(interactive (list t))
|
||||||
|
(if-let ((proj (project-current prompt))
|
||||||
|
(default-directory (project-root proj)))
|
||||||
|
(if-let ((vterm-buff (gethash default-directory
|
||||||
|
my/project-vterm-hash-table))
|
||||||
|
((buffer-live-p vterm-buff)))
|
||||||
|
(switch-to-buffer vterm-buff)
|
||||||
|
(puthash default-directory
|
||||||
|
(vterm (concat "*vterm for project " default-directory "*"))
|
||||||
|
my/project-vterm-hash-table))))
|
||||||
|
(defun my/project-vterm-or-default ()
|
||||||
|
"Open a vterm for the current project, otherwise, open a normal vterm."
|
||||||
|
(interactive)
|
||||||
|
(unless (my/project-vterm nil)
|
||||||
|
(if-let ((vterm-buff (gethash nil my/project-vterm-hash-table))
|
||||||
|
((buffer-live-p vterm-buff)))
|
||||||
|
(switch-to-buffer vterm-buff)
|
||||||
|
(puthash nil (vterm vterm-buffer-name) my/project-vterm-hash-table)))))
|
||||||
|
|
||||||
;; eat (mostly for eshell purposes)
|
;; eat (mostly for eshell purposes)
|
||||||
(use-package eat)
|
(use-package eat)
|
||||||
@ -685,53 +700,180 @@ COMMAND and COMINT are like `compile'."
|
|||||||
(use-package eshell
|
(use-package eshell
|
||||||
:ensure nil
|
:ensure nil
|
||||||
:defer nil
|
:defer nil
|
||||||
:hook ((eshell-mode . my/-eshell-local-init-alias-hook)
|
:hook ((eshell-load . eat-eshell-visual-command-mode)
|
||||||
(eshell-mode . my/eshell-update-aliases)
|
(eshell-load . eat-eshell-mode)
|
||||||
(eshell-load . eat-eshell-visual-command-mode)
|
(eshell-mode . my/-eshell-mode-setup))
|
||||||
(eshell-load . eat-eshell-mode))
|
|
||||||
:init
|
:init
|
||||||
|
(defun my/-eshell-mode-setup ()
|
||||||
|
"Setup function run from `eshell-mode-hook'"
|
||||||
|
(setq-local corfu-auto nil))
|
||||||
(setq-default eshell-command-aliases-list
|
(setq-default eshell-command-aliases-list
|
||||||
'(("clear" "clear t" 0 7 (escaped t))))
|
'(("clear" "clear t")
|
||||||
(defvar my/eshell-aliases
|
("e" "find-file $1")
|
||||||
'(("ls" . "eza --git -F")
|
("n" "find-file $1")
|
||||||
("la" . "ls -a"))
|
("emacs" "find-file $1")
|
||||||
"Aliases for eshell that work better than the default.")
|
("nvim" "find-file $1")
|
||||||
(defun my/-eshell-resolve-alias (name)
|
("ls" "eza --git -F $*")
|
||||||
"Recursively resolve an alias, NAME, from `my/eshell-aliases'."
|
("la" "ls -a $*")
|
||||||
(if-let ((entry (assoc name my/eshell-aliases))
|
("l" "ls -l $*")
|
||||||
(def (split-string-and-unquote (cdr entry))))
|
("ll" "la -l $*")
|
||||||
(if-let (sub-def (my/-eshell-resolve-alias (car def)))
|
("gt" "git status $*")
|
||||||
(append sub-def (cdr def))
|
("gp" "git push $*")
|
||||||
def)))
|
("gu" "git pull $*")
|
||||||
(defun my/eshell-update-aliases ()
|
("gf" "git fetch $*")
|
||||||
"Update aliases in `my/eshell-aliases'."
|
("ga" "git add $*")
|
||||||
(interactive)
|
("gcm" "git commit -m ${string-join $* \" \"}")))
|
||||||
(dolist (entry my/eshell-aliases)
|
(defun eshell/bm (&optional name)
|
||||||
(when-let ((sym (intern (concat "eshell/" (car entry))))
|
"Change to directory of bookmark NAME.
|
||||||
(def (my/-eshell-resolve-alias (car entry))))
|
If no name is given, list all bookmarks instead."
|
||||||
(defalias sym
|
(if name
|
||||||
#'(lambda (&rest args)
|
(eshell/cd (bookmark-get-filename name))
|
||||||
(eshell-flush -1)
|
(eshell-print (string-join (bookmark-all-names) " "))))
|
||||||
(throw 'eshell-external
|
(defun my/-replace-home-with-tilda (path)
|
||||||
(eshell-external-command (car def)
|
(let ((home (getenv "HOME")))
|
||||||
(append (cdr def) args)))
|
(if (equal home path)
|
||||||
(eshell-flush))
|
"~"
|
||||||
(concat "Eshell alias for \"" (s-join " " def) "\"")))))
|
(setq home (file-name-as-directory home))
|
||||||
(defun my/-eshell-local-init-alias-hook ()
|
(if (string-prefix-p home path)
|
||||||
"Run from `eshell-mode-hook' to initialize aliases."
|
(concat "~/" (seq-subseq path (length home)))
|
||||||
(dolist (entry my/eshell-aliases)
|
path))))
|
||||||
(add-to-list 'eshell-complex-commands (car entry))))
|
(defun my/-eshell-prompt-cut-path (num path)
|
||||||
(defun my/-eshell-lookup-my-alias-first (oldfun name)
|
"Cut PATH down to NUM components."
|
||||||
"Advice around `eshell-lookup-alias' to also lookup aliases from `my/eshell-aliases'."
|
(let ((parts (string-split path "/" t nil)))
|
||||||
(if-let (def (my/-eshell-resolve-alias name))
|
(concat
|
||||||
(list name (s-join " " def))
|
(when (and (file-name-absolute-p path)
|
||||||
(funcall oldfun name)))
|
(not (equal "~" (car parts)))
|
||||||
(advice-add 'eshell-lookup-alias :around #'my/-eshell-lookup-my-alias-first)
|
(<= (length parts) num))
|
||||||
(defun my/-eshell-ignore-alias-file (oldfun)
|
"/")
|
||||||
"Ignore aliases in `eshell-aliases-file'"
|
(string-join (last parts num) "/"))))
|
||||||
(let ((eshell-command-aliases-list nil))
|
(defun my/-eshell-prompt-get-dir ()
|
||||||
(funcall oldfun)))
|
"Get dir for `my/-eshell-prompt-function'"
|
||||||
(advice-add 'eshell-alias-initialize :around #'my/-eshell-ignore-alias-file))
|
(my/-eshell-prompt-cut-path
|
||||||
|
3 (if-let ((worktree (car-safe (car-safe (magit-list-worktrees))))
|
||||||
|
(parent (file-name-parent-directory worktree)))
|
||||||
|
(file-relative-name default-directory parent)
|
||||||
|
(my/-replace-home-with-tilda default-directory))))
|
||||||
|
(defun my/-eshell-prompt-status-char-for-branch (branch remote)
|
||||||
|
"Get the status char representing the relation between BRANCH and REMOTE."
|
||||||
|
(let ((lines (process-lines vc-git-program
|
||||||
|
"rev-list"
|
||||||
|
"--left-right"
|
||||||
|
(concat branch "..." remote)))
|
||||||
|
(to-remote nil)
|
||||||
|
(to-local nil))
|
||||||
|
(dolist (line lines)
|
||||||
|
(if-let (((not (string-empty-p line)))
|
||||||
|
(dir-char (aref line 0)))
|
||||||
|
(if (= dir-char ?<)
|
||||||
|
(setq to-local t)
|
||||||
|
(setq to-remote t))))
|
||||||
|
(cond
|
||||||
|
((and to-remote to-local) ?)
|
||||||
|
(to-remote ?)
|
||||||
|
(to-local ?))))
|
||||||
|
(defun my/-eshell-prompt-current-branch-status ()
|
||||||
|
"Get the status char for the current branch and its remote."
|
||||||
|
(let ((refs (process-lines vc-git-program
|
||||||
|
"for-each-ref"
|
||||||
|
"--format=%(HEAD)%00%(refname:short)%00%(upstream:short)"
|
||||||
|
"refs/heads")))
|
||||||
|
(catch 'break
|
||||||
|
(dolist (ref refs)
|
||||||
|
(if-let ((split-ref (split-string ref "\0" nil nil))
|
||||||
|
((equal (car split-ref) "*")))
|
||||||
|
(throw 'break (my/-eshell-prompt-status-char-for-branch
|
||||||
|
(cadr split-ref)
|
||||||
|
(caddr split-ref))))))))
|
||||||
|
(defun my/-eshell-prompt-git-state-chars ()
|
||||||
|
"Get chars, like + and ✘ for `my/-eshell-prompt-function'."
|
||||||
|
(let ((lines (process-lines vc-git-program "status" "--porcelain=v1"))
|
||||||
|
(branch-status (my/-eshell-prompt-current-branch-status))
|
||||||
|
(status-arr))
|
||||||
|
(dolist (line lines)
|
||||||
|
(cl-loop with fields = (string-split line " " t " *")
|
||||||
|
with status-str = (car-safe fields)
|
||||||
|
for status-char across status-str
|
||||||
|
do
|
||||||
|
(cond ((or (= status-char ?M) (= status-char ?T))
|
||||||
|
(add-to-list 'status-arr ?!))
|
||||||
|
((= status-char ??)
|
||||||
|
(add-to-list 'status-arr ??))
|
||||||
|
((or (= status-char ?A) (= status-char ?C))
|
||||||
|
(add-to-list 'status-arr ?+))
|
||||||
|
((= status-char ?D)
|
||||||
|
(add-to-list 'status-arr ?))
|
||||||
|
((= status-char ?R)
|
||||||
|
(add-to-list 'status-arr ?»)))))
|
||||||
|
(sort status-arr #'<)
|
||||||
|
(when branch-status
|
||||||
|
(push branch-status status-arr))
|
||||||
|
(apply 'string status-arr)))
|
||||||
|
(defun my/-eshell-prompt-git-status ()
|
||||||
|
"Get git status for `my/-eshell-prompt-function'"
|
||||||
|
(let ((branch (car (vc-git-branches)))
|
||||||
|
(state (my/-eshell-prompt-git-state-chars)))
|
||||||
|
(concat
|
||||||
|
(propertize (concat " " branch) 'face '(:foreground "medium purple"))
|
||||||
|
(unless (string-empty-p state)
|
||||||
|
(propertize (concat " [" state "]") 'face '(:foreground "red"))))))
|
||||||
|
(defun my/-eshell-prompt-vc-status ()
|
||||||
|
"Get vc status for `my/-eshell-prompt-function'."
|
||||||
|
(if-let (backend (vc-responsible-backend default-directory t))
|
||||||
|
(if (eq backend 'Git)
|
||||||
|
(my/-eshell-prompt-git-status)
|
||||||
|
(my/-eshell-prompt-set-face-color
|
||||||
|
(concat " " (downcase (symbol-name backend)))
|
||||||
|
"purple"))))
|
||||||
|
(defvar-local my/-eshell-prompt-last-start-time nil
|
||||||
|
"Start time of last eshell command.")
|
||||||
|
(defun my/-eshell-prompt-timer-pre-cmd ()
|
||||||
|
"Command run before each eshell program to record the time."
|
||||||
|
(setq my/-eshell-prompt-last-start-time (current-time)))
|
||||||
|
(add-hook 'eshell-pre-command-hook #'my/-eshell-prompt-timer-pre-cmd)
|
||||||
|
(defun my/-eshell-prompt-format-span (span)
|
||||||
|
"Format SPAN as \"XhXms.\""
|
||||||
|
(let* ((hours (/ span 3600))
|
||||||
|
(mins (% (/ span 60) 60))
|
||||||
|
(secs (% span 60)))
|
||||||
|
(concat (unless (= hours 0)
|
||||||
|
(format "%dh" hours))
|
||||||
|
(unless (= mins 0)
|
||||||
|
(format "%dm" mins))
|
||||||
|
(format "%ds" secs))))
|
||||||
|
(defun my/-eshell-prompt-last-command-time (end-time)
|
||||||
|
"Return the prompt component for the time of the last command."
|
||||||
|
(if-let ((my/-eshell-prompt-last-start-time)
|
||||||
|
(len (time-subtract end-time
|
||||||
|
my/-eshell-prompt-last-start-time))
|
||||||
|
(float-len (float-time len))
|
||||||
|
((< 3 float-len))
|
||||||
|
(int-len (round float-len)))
|
||||||
|
(concat " time "
|
||||||
|
(propertize (my/-eshell-prompt-format-span int-len)
|
||||||
|
'face '(:foreground "gold1")))))
|
||||||
|
(defun my/-eshell-prompt-function ()
|
||||||
|
"Function for `eshell-prompt-function'"
|
||||||
|
(let* ((end-time (current-time))
|
||||||
|
(dir (my/-eshell-prompt-get-dir))
|
||||||
|
(prompt (concat
|
||||||
|
"\n"
|
||||||
|
(propertize dir 'face '(:foreground "dark turquoise"))
|
||||||
|
(unless (file-writable-p dir)
|
||||||
|
" ")
|
||||||
|
(my/-eshell-prompt-vc-status)
|
||||||
|
(my/-eshell-prompt-last-command-time end-time)
|
||||||
|
(propertize "\n" 'read-only t 'rear-nonsticky t)
|
||||||
|
(propertize
|
||||||
|
"❯ " 'face `(:foreground
|
||||||
|
,(if (= eshell-last-command-status 0)
|
||||||
|
"lime green"
|
||||||
|
"red"))
|
||||||
|
'rear-nonsticky t))))
|
||||||
|
(setq my/-eshell-prompt-last-start-time nil)
|
||||||
|
prompt))
|
||||||
|
(setq eshell-prompt-function #'my/-eshell-prompt-function
|
||||||
|
eshell-prompt-regexp "^❯ "
|
||||||
|
eshell-highlight-prompt nil))
|
||||||
(use-package esh-help
|
(use-package esh-help
|
||||||
:hook (eshell-mode . my/-setup-eshell-help-func)
|
:hook (eshell-mode . my/-setup-eshell-help-func)
|
||||||
:init
|
:init
|
||||||
@ -739,8 +881,6 @@ COMMAND and COMINT are like `compile'."
|
|||||||
(eldoc-mode 1)
|
(eldoc-mode 1)
|
||||||
(setq-local evil-lookup-func #'esh-help-run-help))
|
(setq-local evil-lookup-func #'esh-help-run-help))
|
||||||
(setup-esh-help-eldoc))
|
(setup-esh-help-eldoc))
|
||||||
(use-package esh-autosuggest
|
|
||||||
:hook (eshell-mode . esh-autosuggest-mode))
|
|
||||||
(use-package eshell-syntax-highlighting
|
(use-package eshell-syntax-highlighting
|
||||||
:init
|
:init
|
||||||
(eshell-syntax-highlighting-global-mode 1))
|
(eshell-syntax-highlighting-global-mode 1))
|
||||||
|
Loading…
Reference in New Issue
Block a user