diff options
author | Aryadev Chavali <aryadev@aryadevchavali.com> | 2024-06-13 00:55:25 +0100 |
---|---|---|
committer | Aryadev Chavali <aryadev@aryadevchavali.com> | 2024-06-13 00:56:08 +0100 |
commit | f418d17001a72ee0cce8ac84dc5c56c919b0f98d (patch) | |
tree | b704b960c0f268a71a2d6e15f470eb5e91260e2c /Emacs | |
parent | 6fa811691e751bf240427655911973552fd392dc (diff) | |
download | dotfiles-f418d17001a72ee0cce8ac84dc5c56c919b0f98d.tar.gz dotfiles-f418d17001a72ee0cce8ac84dc5c56c919b0f98d.tar.bz2 dotfiles-f418d17001a72ee0cce8ac84dc5c56c919b0f98d.zip |
(Emacs/config|elisp)~rework Eshell
Now I have separate modules for the additional new functions I
introduced for eshell and for the prompt function I made. Cleans up
the configuration a bit and makes it easier to examine those files on
their own, which I expect to grow.
Diffstat (limited to 'Emacs')
-rw-r--r-- | Emacs/.config/emacs/.config/eshell/aliases | 2 | ||||
-rw-r--r-- | Emacs/.config/emacs/config.org | 169 | ||||
-rw-r--r-- | Emacs/.config/emacs/elisp/eshell-additions.el | 57 | ||||
-rw-r--r-- | Emacs/.config/emacs/elisp/eshell-prompt.el | 112 |
4 files changed, 216 insertions, 124 deletions
diff --git a/Emacs/.config/emacs/.config/eshell/aliases b/Emacs/.config/emacs/.config/eshell/aliases index 8686c44..c7effa4 100644 --- a/Emacs/.config/emacs/.config/eshell/aliases +++ b/Emacs/.config/emacs/.config/eshell/aliases @@ -5,4 +5,4 @@ alias gs magit-status alias clear clear-scrollback alias d dired-other-window $1 alias gt goto -alias p~ project-root +alias pr project-root diff --git a/Emacs/.config/emacs/config.org b/Emacs/.config/emacs/config.org index 84c25e1..3411eea 100644 --- a/Emacs/.config/emacs/config.org +++ b/Emacs/.config/emacs/config.org @@ -1686,16 +1686,27 @@ expression it tries to evaluate it by testing against these conditions: - it's an external command (bash evaluator) Essentially, you get the best of both Emacs and external shell programs *ALL WITHIN* Emacs for free. -*** Eshell functionality -Bind some evil-like movements for easy shell usage, and a toggle -function to pull up the eshell quickly. +*** Eshell keymaps, display and variables +Bind some evil-like movements for easy shell usage, a display record +so when you call eshell it kinda looks like VSCode's terminal popup. + +NOTE: This mode doesn't allow you to set maps the normal way; you need +to set keybindings on eshell-mode-hook, otherwise it'll just overwrite +them. #+begin_src emacs-lisp (use-package eshell :defer t :general (shell-leader "t" #'eshell) + :display + ("\\*e?shell\\*" + (display-buffer-at-bottom) + (window-height . 0.33)) :init + (setq eshell-cmpl-ignore-case t + eshell-cd-on-directory t + eshell-highlight-prompt nil) (add-hook 'eshell-mode-hook (proc @@ -1703,6 +1714,7 @@ function to pull up the eshell quickly. (general-def :states '(normal insert) :keymaps 'eshell-mode-map + "0" #'eshell-bol "M-j" #'eshell-next-matching-input-from-input "M-k" #'eshell-previous-matching-input-from-input) (local-leader @@ -1711,134 +1723,45 @@ function to pull up the eshell quickly. (recenter)) "k" #'eshell-kill-process)))) #+end_src -*** Eshell pretty symbols and display -Pretty symbols and a display record. -#+begin_src emacs-lisp -(use-package eshell - :defer t - :display - ("\\*e?shell\\*" ; for general shells as well - (display-buffer-at-bottom) - (window-height . 0.33))) -#+end_src -*** Eshell variables and aliases -Set some sane defaults, a banner and a prompt. The prompt checks for -a git repo in the current directory and provides some extra -information in that case (in particular, branch name and if there any -changes that haven't been committed). +*** Eshell prompt +Here I use my external library +[[file:elisp/eshell-prompt.el][eshell-prompt]], which provides a more +dynamic prompt for Eshell. Current features include: ++ Git (with difference from remote and number of modified files) ++ Current date and time ++ A coloured prompt which changes colour based on the exit status of + the previous command +NOTE: I don't defer this package because it doesn't use any eshell +internals, just standard old Emacs packages. #+begin_src emacs-lisp -(use-package eshell - :defer t +(use-package eshell-prompt + :load-path "elisp/" :config - (defun +eshell/--git-get-remote-status () - (let* ((branch-status (split-string - (shell-command-to-string "git status | grep 'Your branch is'"))) - (status (nth 3 branch-status)) - (diff (cl-position "by" branch-status :test #'string=))) - (if (null diff) - (propertize "=" 'font-lock-face '(:foreground "green")) - (let ((n (nth (+ 1 diff) branch-status))) - (concat - (cond - ((string= status "ahead") - (propertize "→ " 'font-lock-face '(:foreground "dodger blue"))) - ((string= status "behind") - (propertize "← " 'font-lock-face '(:foreground "orange red")))) - n))))) - - (defun +eshell/--git-get-change-status () - (let ((changed-files (- (length (split-string (shell-command-to-string "git status -s" ) "\n")) 1))) - (if (= changed-files 0) - (propertize "✓" 'font-lock-face '(:foreground "green")) - (propertize (number-to-string changed-files) 'font-lock-face '(:foreground "red"))))) - - (defun +eshell/get-git-properties () - (let ((git-branch (shell-command-to-string "git branch"))) - (if (or (string= git-branch "") - (not (string= "*" (substring git-branch 0 1)))) - "" - (format - "(%s<%s>[%s])" - (nth 2 (split-string git-branch "\n\\|\\*\\| ")) - (+eshell/--git-get-change-status) - (+eshell/--git-get-remote-status))))) - - (defun +eshell/prompt-function () - (let ((git (+eshell/get-git-properties))) - (mapconcat - (lambda (item) - (if (listp item) - (propertize (car item) - 'read-only t - 'font-lock-face (cdr item) - 'front-sticky '(font-lock-face read-only) - 'rear-nonsticky '(font-lock-face read-only)) - item)) - (list - '("[") - `(,(abbreviate-file-name (eshell/pwd)) :foreground "LimeGreen") - '("]") - (if (string= git "") - "" - (concat "-" git "")) - "\n" - `(,(format-time-string "[%H:%M:%S]") :foreground "purple") - "\n" - '("𝜆> " :foreground "DeepSkyBlue"))))) - (defun +eshell/banner-message () (concat (shell-command-to-string "~/.local/scripts/cowfortune") "\n")) - - (setq eshell-cmpl-ignore-case t - eshell-cd-on-directory t - eshell-banner-message '(+eshell/banner-message) - eshell-highlight-prompt nil - eshell-prompt-function #'+eshell/prompt-function - eshell-prompt-regexp "^𝜆> ")) -#+end_src -*** Eshell change directory quickly -Add ~eshell/goto~, which is actually a command accessible from within -eshell (this is because ~eshell/*~ creates an accessible function -within eshell with name ~*~). ~eshell/goto~ makes it easier to change -directories by using Emacs' find-file interface (which is much faster -than ~cd ..; ls -l~). - -~eshell/goto~ is a better ~cd~ for eshell. However it is really just -a plaster over a bigger issue for my workflow; many times I want -eshell to be present in the current directory of the buffer I am -using. So here's also a command for opening eshell with the current -directory. -#+begin_src emacs-lisp -(use-package eshell - :defer t + (setq eshell-prompt-regexp (format "^%s" +eshell-prompt/user-prompt) + eshell-prompt-function #'+eshell-prompt/make-prompt + eshell-banner-message '(+eshell/banner-message))) +#+end_src +*** Eshell additions +Using my external library +[[file:elisp/eshell-additions.el][eshell-additions]], I get a few new +eshell internal commands and a surface command to open eshell at the +current working directory. + +NOTE: I don't defer this package because it autoloads any eshell +internals that it uses so I'm only loading what I need to. Any +~eshell/*~ functions need to be known by eshell before launching, so +if I loaded this ~:after~ eshell then the first instance has no +knowledge of the new additions. +#+begin_src emacs-lisp +(use-package eshell-additions + :load-path "elisp/" :general (leader - "T" #'+eshell/current-buffer) - :config - (defun eshell/goto (&rest args) - "Use `read-directory-name' to change directories." - (eshell/cd (list (read-directory-name "Directory?: ")))) - - (defun eshell/project-root (&rest args) - "Change to directory `project-root'" - (if (project-current) - (eshell/cd (list (project-root (project-current)))) - (eshell/echo (format "[%s]: No project in current directory" - (propertize "Error" 'font-lock-face '(:foreground "red")))))) - - (defun +eshell/current-buffer () - (interactive) - (let ((dir (if buffer-file-name - (file-name-directory buffer-file-name) - default-directory)) - (buf (eshell))) - (if dir - (with-current-buffer buf - (eshell/cd dir) - (eshell-send-input)) - (message "Could not switch eshell: buffer is not real file"))))) + "T" #'+eshell/at-cwd)) #+end_src ** WAIT Elfeed :PROPERTIES: diff --git a/Emacs/.config/emacs/elisp/eshell-additions.el b/Emacs/.config/emacs/elisp/eshell-additions.el new file mode 100644 index 0000000..94c948f --- /dev/null +++ b/Emacs/.config/emacs/elisp/eshell-additions.el @@ -0,0 +1,57 @@ +;;; eshell-additions.el --- Some aliases for Eshell -*- lexical-binding: t; -*- + +;; Copyright (C) 2024 Aryadev Chavali + +;; Author: Aryadev Chavali <aryadev@aryadevchavali.com> +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation version 2 of the License + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; + +;;; Code: + +(autoload #'eshell/cd "eshell") +(autoload #'eshell/echo "eshell") +(autoload #'eshell/send-input "eshell") + +;; Aliases +(defun eshell/goto (&rest args) + "Use `read-directory-name' to change directories" + (eshell/cd (list (read-directory-name "Directory?: ")))) + +(defun eshell/project-root (&rest args) + "Change to directory `project-root'" + (if (project-current) + (eshell/cd (list (project-root (project-current)))) + (let ((error-msg (propertize "Error" 'font-lock-face + '(:foreground "red")))) + (eshell/echo + (format "[%s]: No project in current directory" error-msg))))) + +;; Additional functions +(defun +eshell/at-cwd () + "Open an instance of eshell at the current working directory." + (interactive) + (let ((dir (if buffer-file-name + (file-name-directory buffer-file-name) + default-directory)) + (buf (eshell))) + (with-current-buffer buf + (eshell/cd dir) + (eshell-send-input)))) + +(provide 'eshell-additions) +;;; eshell-additions.el ends here diff --git a/Emacs/.config/emacs/elisp/eshell-prompt.el b/Emacs/.config/emacs/elisp/eshell-prompt.el new file mode 100644 index 0000000..7e78c7c --- /dev/null +++ b/Emacs/.config/emacs/elisp/eshell-prompt.el @@ -0,0 +1,112 @@ +;;; eshell-prompt.el --- Generating a good prompt for Eshell -*- lexical-binding: t; -*- + +;; Copyright (C) 2024 Aryadev Chavali + +;; Author: Aryadev Chavali <aryadev@aryadevchavali.com> +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation version 2 of the License. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; We provide a function +eshell-prompt which generates a prompt on +;; demand. + +;;; Code: + +(defvar +eshell-prompt/user-prompt "𝜆> " + "Prompt for user to input.") + +(defun +eshell-prompt/--colour-on-last-command () + "Returns an Emacs colour based on ESHELL-LAST-COMMAND-STATUS." + (if (zerop eshell-last-command-status) + "forestgreen" + "darkred")) + +(defun +eshell-prompt/--git-remote-status () + "Returns a propertized string for the status of a repository +in comparison to its remote. 3 differing strings are returned +dependent on: + +- Is it equivalent to the remote? +- Is it ahead of the remote? +- Is it behind the remote? + +The latter 2 also have a number for exactly how many commits +behind or ahead the local repository is." + (let* ((git-cmd "git status | grep 'Your branch is'") + (branch-status (split-string (shell-command-to-string git-cmd))) + (status (nth 3 branch-status)) + (diff (cl-position "by" branch-status :test #'string=))) + (if (null diff) + (propertize "=" 'font-lock-face '(:foreground "green")) + (let ((n (nth (+ 1 diff) branch-status))) + (concat + (cond + ((string= status "ahead") + (propertize "→ " 'font-lock-face '(:foreground "dodger blue"))) + ((string= status "behind") + (propertize "← " 'font-lock-face '(:foreground "orange red")))) + n))))) + +(defun +eshell-prompt/--git-change-status () + "Returns a propertized string for the condition of the worktree in +a repository. If there are no changes i.e. the worktree is clean +then a green tick is returned, but if there are changes then the +number of files affected are returned in red." + (let* ((git-cmd "git status -s") + (command-output (split-string git-cmd)) + (changed-files (- (length command-output) 1))) + (if (= changed-files 0) + (propertize "✓" 'font-lock-face '(:foreground "green")) + (propertize (number-to-string changed-files) 'font-lock-face '(:foreground "red"))))) + +(defun +eshell-prompt/--git-status () + "Returns a completely formatted string of +form (BRANCH-NAME<CHANGES>[REMOTE-STATUS])." + (let ((git-branch (shell-command-to-string "git brnach"))) + (if (or (string= git-branch "") + (not (string= "*" (substring git-branch 0 1)))) + "" + (format + "(%s<%s>[%s])" + (nth 2 (split-string git-branch "\n\\|\\*\\| ")) + (+eshell-prompt/--git-change-status) + (+eshell-prompt/--git-remote-status))))) + +(defun +eshell-prompt/make-prompt () + (let ((git (+eshell-prompt/--git-status))) + (mapconcat + (lambda (item) + (if (listp item) + (propertize (car item) + 'read-only t + 'font-lock-face (cdr item) + 'front-sticky '(font-lock-face read-only) + 'rear-nonsticky '(font-lock-face read-only)) + item)) + (list + "[" + `(,(abbreviate-file-name (eshell/pwd)) :foreground "LimeGreen") + "]" + (if (string= git "") + "" + (concat "-" git "")) + "\n" + `(,(format-time-string "[%H:%M:%S]") :foreground "purple") + "\n" + (list "𝜆> " ':foreground (+eshell-prompt/--colour-on-last-command)))))) + + +(provide 'eshell-prompt) +;;; eshell-prompt.el ends here |