Add eshell configuration

This commit is contained in:
Alexander Rosenberg 2023-11-07 03:19:30 -08:00
parent 7dae839ad6
commit c5c137b645
Signed by: Zander671
GPG Key ID: 5FD0394ADBD72730

282
init.el
View File

@ -541,33 +541,25 @@ visual states."
;; project.el
(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
("s" . my/project-vterm)
("s" . my/project-eshell)
("u" . my/project-run))
: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))
(defvar eshell-buffer-name)
(defun my/project-eshell (prompt &optional arg)
"Switch to or create an eshell buffer in the current projects root."
(interactive (list t current-prefix-arg))
(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))))
(default-directory (project-root proj))
(eshell-buffer-name
(concat "*eshell for project " default-directory "*")))
(eshell)))
(defun my/project-eshell-or-default (&optional arg)
"Open an eshell for the current project, otherwise, open a normal eshell."
(interactive "P")
(unless (my/project-eshell nil arg)
(eshell arg)))
(defvar my/project-run-command nil
"Command to run with `my/project-run'.")
(put 'my/project-run-command 'safe-local-variable (lambda (val)
@ -676,7 +668,30 @@ COMMAND and COMINT are like `compile'."
;; 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)
(use-package eat)
@ -685,53 +700,180 @@ COMMAND and COMINT are like `compile'."
(use-package eshell
:ensure nil
:defer nil
:hook ((eshell-mode . my/-eshell-local-init-alias-hook)
(eshell-mode . my/eshell-update-aliases)
(eshell-load . eat-eshell-visual-command-mode)
(eshell-load . eat-eshell-mode))
:hook ((eshell-load . eat-eshell-visual-command-mode)
(eshell-load . eat-eshell-mode)
(eshell-mode . my/-eshell-mode-setup))
:init
(defun my/-eshell-mode-setup ()
"Setup function run from `eshell-mode-hook'"
(setq-local corfu-auto nil))
(setq-default eshell-command-aliases-list
'(("clear" "clear t" 0 7 (escaped t))))
(defvar my/eshell-aliases
'(("ls" . "eza --git -F")
("la" . "ls -a"))
"Aliases for eshell that work better than the default.")
(defun my/-eshell-resolve-alias (name)
"Recursively resolve an alias, NAME, from `my/eshell-aliases'."
(if-let ((entry (assoc name my/eshell-aliases))
(def (split-string-and-unquote (cdr entry))))
(if-let (sub-def (my/-eshell-resolve-alias (car def)))
(append sub-def (cdr def))
def)))
(defun my/eshell-update-aliases ()
"Update aliases in `my/eshell-aliases'."
(interactive)
(dolist (entry my/eshell-aliases)
(when-let ((sym (intern (concat "eshell/" (car entry))))
(def (my/-eshell-resolve-alias (car entry))))
(defalias sym
#'(lambda (&rest args)
(eshell-flush -1)
(throw 'eshell-external
(eshell-external-command (car def)
(append (cdr def) args)))
(eshell-flush))
(concat "Eshell alias for \"" (s-join " " def) "\"")))))
(defun my/-eshell-local-init-alias-hook ()
"Run from `eshell-mode-hook' to initialize aliases."
(dolist (entry my/eshell-aliases)
(add-to-list 'eshell-complex-commands (car entry))))
(defun my/-eshell-lookup-my-alias-first (oldfun name)
"Advice around `eshell-lookup-alias' to also lookup aliases from `my/eshell-aliases'."
(if-let (def (my/-eshell-resolve-alias name))
(list name (s-join " " def))
(funcall oldfun name)))
(advice-add 'eshell-lookup-alias :around #'my/-eshell-lookup-my-alias-first)
(defun my/-eshell-ignore-alias-file (oldfun)
"Ignore aliases in `eshell-aliases-file'"
(let ((eshell-command-aliases-list nil))
(funcall oldfun)))
(advice-add 'eshell-alias-initialize :around #'my/-eshell-ignore-alias-file))
'(("clear" "clear t")
("e" "find-file $1")
("n" "find-file $1")
("emacs" "find-file $1")
("nvim" "find-file $1")
("ls" "eza --git -F $*")
("la" "ls -a $*")
("l" "ls -l $*")
("ll" "la -l $*")
("gt" "git status $*")
("gp" "git push $*")
("gu" "git pull $*")
("gf" "git fetch $*")
("ga" "git add $*")
("gcm" "git commit -m ${string-join $* \" \"}")))
(defun eshell/bm (&optional name)
"Change to directory of bookmark NAME.
If no name is given, list all bookmarks instead."
(if name
(eshell/cd (bookmark-get-filename name))
(eshell-print (string-join (bookmark-all-names) " "))))
(defun my/-replace-home-with-tilda (path)
(let ((home (getenv "HOME")))
(if (equal home path)
"~"
(setq home (file-name-as-directory home))
(if (string-prefix-p home path)
(concat "~/" (seq-subseq path (length home)))
path))))
(defun my/-eshell-prompt-cut-path (num path)
"Cut PATH down to NUM components."
(let ((parts (string-split path "/" t nil)))
(concat
(when (and (file-name-absolute-p path)
(not (equal "~" (car parts)))
(<= (length parts) num))
"/")
(string-join (last parts num) "/"))))
(defun my/-eshell-prompt-get-dir ()
"Get dir for `my/-eshell-prompt-function'"
(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
:hook (eshell-mode . my/-setup-eshell-help-func)
:init
@ -739,8 +881,6 @@ COMMAND and COMINT are like `compile'."
(eldoc-mode 1)
(setq-local evil-lookup-func #'esh-help-run-help))
(setup-esh-help-eldoc))
(use-package esh-autosuggest
:hook (eshell-mode . esh-autosuggest-mode))
(use-package eshell-syntax-highlighting
:init
(eshell-syntax-highlighting-global-mode 1))