Add eshell configuration
This commit is contained in:
		
							
								
								
									
										282
									
								
								init.el
									
									
									
									
									
								
							
							
						
						
									
										282
									
								
								init.el
									
									
									
									
									
								
							@ -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))
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user