diff options
Diffstat (limited to 'eshell-conf.el')
-rw-r--r-- | eshell-conf.el | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/eshell-conf.el b/eshell-conf.el new file mode 100644 index 0000000..8d2951e --- /dev/null +++ b/eshell-conf.el @@ -0,0 +1,271 @@ +;;; eshell-conf.el --- My configurations for Eshell -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 李俊緯 + +;; Author: 李俊緯 <mmemmew@gmail.com> +;; Keywords: terminals, tools, convenience + +;; 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, either version 3 of the License, or +;; (at your option) any later version. + +;; 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: + +;; This is my configuration file for Eshell, the shell emulation +;; written in Emacs Lisp. + +;;; Code: + +(eval-when-compile + (require 'cl-lib) + (require 'esh-mode) + (require 'eshell)) +(require 'esh-util) +(require 'ring) + +;; Eshell sets the keymap in the major mode function... + +;;;###autoload +(add-hook 'eshell-mode-hook #'durand-set-eshell-keys) + +;;;###autoload +(defun durand-set-eshell-keys () + "Set my key-bindings." + (define-key eshell-mode-map (vector #xf) ; C-o + (lambda () + (interactive) + (delete-region eshell-last-output-end (point)) + (insert "clear t") + (eshell-send-input)))) + +;;;###autoload +(defun eshell/j (&rest args) + "Implementation of `j'. +See `eshell-j' for the actual functionality." + (eshell-eval-using-options + "j" args + '((?r "recent" 'exclusive use-recent-p "Find in recent directories instead of symbolic links.") + (?a "all" nil use-recent-p "Find both in recent directories and in symbolic links.") + (?h "help" nil nil "Print this help message.") + :usage "[-hra] [short-cut]") + (eshell-j args use-recent-p))) + +;;;###autoload +(defun durand-eshell-delete-dups (sequence &rest args) + "Delete duplicate elements in SEQUENCE. +If the keyword argument TEST is non-nil, it should be a function +with two arguments which tests for equality of elements in the +sequence. The default is the function `equal'. + +If the keyword argument KEY is non-nil, it should be a function +with one argument which returns the key of the element in the +sequence to be compared by the test function. The default is the +function `identity'. + +Note that this function is not supposed to change global state, +including match data, so the functions in TEST and KEY are +supposed to leave the global state alone as well. + +\(fn SEQUENCE &key TEST KEY)" + (declare (pure t) (side-effect-free t)) + (let* ((len (length sequence)) + (temp-obarray (obarray-make len)) + (valid-key-num (+ (cond ((plist-member args :key) 1) (0)) + (cond ((plist-member args :test) 1) (0)))) + (key (cond ((cadr (plist-member args :key))) + (#'identity))) + (test-fn (cond ((cadr (plist-member args :test))) + (#'equal))) + found-table result) + (cond ((or (= (mod (length args) 2) 1) + (> (length args) (* 2 valid-key-num))) + (user-error "Invalid keyword arguments. Only :key and :test are allowed, but got %S" + args))) + ;; Note: This just puts a property to the symbol. + (define-hash-table-test 'durand-delete-dups-test + test-fn (function (lambda (obj) (intern (format "%S" obj) temp-obarray)))) + (setq found-table (make-hash-table :test 'durand-delete-dups-test :size len)) + (mapc + (function + (lambda (element) + (cond ((gethash (funcall key element) found-table)) + ;; Abuse the fact that `puthash' always returns VALUE. + ((puthash (funcall key element) t found-table) + (setq result (cons element result)))))) + sequence) + (nreverse result))) + +;;;###autoload +(defun eshell-j (&optional short-cut use-recent-p) + "Jump to SHORT-CUT. +Where this jumps to is determined by the symbolic links in the +directory '~/.marks'. If USE-RECENT-P is non-nil, then also +include recent directories in the list of candidates. Moreover, +if USE-RECENT-P is 'exclusive, then only list the recent +directories as candidates, unless there are no recent +directories, in which case it falls back to use the marks as the +candidates." + (let* ((mark-directory (expand-file-name "~/.marks")) + (short-cut (eshell-flatten-and-stringify short-cut)) + (links (delq nil + (mapcar + (function + (lambda (name) + (cond + ((or (string= "." (file-name-nondirectory name)) + (string= ".." (file-name-nondirectory name))) + nil) + (name)))) + (directory-files mark-directory t short-cut t)))) + (candidates (cond + ((and use-recent-p + (not (eq use-recent-p 'exclusive)) + (ring-p eshell-last-dir-ring) + (not (ring-empty-p eshell-last-dir-ring))) + (append (mapcar (function + (lambda (file) + (cons (file-name-nondirectory file) + file))) + links) + (cond + ((string-match-p short-cut mark-directory) + (list (cons mark-directory mark-directory)))) + (mapcar (function (lambda (file) (cons file file))) + (ring-elements eshell-last-dir-ring)))) + ((and use-recent-p + (eq use-recent-p 'exclusive) + (ring-p eshell-last-dir-ring) + (not (ring-empty-p eshell-last-dir-ring))) + (mapcar (function (lambda (file) (cons file file))) + (ring-elements eshell-last-dir-ring))) + ((append + (mapcar (function + (lambda (file) + (cons (file-name-nondirectory file) + file))) + links) + (cond + ((string-match-p short-cut mark-directory) + (list (cons mark-directory mark-directory)))))))) + ;; Delete duplicate items + (candidates + (durand-eshell-delete-dups + candidates + :test #'string= + ;; In Haskell this woule be a simple function composition. + :key (function (lambda (ls) (file-truename (cdr ls))))))) + (cond + ((null candidates) + (user-error "No candidates matching %s found" short-cut)) + ((null (cdr candidates)) + ;; Only one candidate + (eshell/cd (file-truename (cdar candidates)))) + ((eshell/cd (file-truename + (cdr + (assoc + (let ((completion-regexp-list + (cons short-cut completion-regexp-list))) + (completing-read "Choose a link: " candidates nil t)) + candidates #'string=)))))))) + +;;;###autoload +(defun eshell/l (&rest args) + "Equivalent with ls -ahl ARGS. +If called without ARGS, then use ./ instead." + (eshell/ls "-hal" (or args "./"))) + +;;;###autoload +(defun eshell/r (&rest args) + "Replace the last command by the specifications in ARGS." + (eshell-eval-using-options + "r" args + '((?h "help" nil nil "Print this help message") + (?n "number" t last-number "Which last command to replace") + :usage "[-n number] [replacement specifications...] +REPLACEMENT SPECIFICATIONS are pairs of the form MATCH=REPLACE. +This command will find the last command, or the last N-th command +if given the option -n, and replace any match of MATCH by +REPLACE." + :preserve-args + :parse-leading-options-only + :show-usage) + (eshell-r args last-number))) + +;;;###autoload +(defun eshell-r (args &optional last-number) + "Replace the LAST-NUMBER th command by ARGS. +ARGS are pairs of the form MATCH=REPLACE. This command will find +the last command, or the LAST-NUMBER-th command if LAST-NUMBER is +non-nil, and replace the first match of MATCH by REPLACE. + +LAST-NUMBER is passed to `prefix-numeric-value': If it is nil, +then it means 1; if it is a minus sign, then it means -1; if it +is a cons cell, and its `car' is an integer, then it means its +`car'; if it is an integer, then it means that integer; and any +other value means 1." + (let* ((last-number (prefix-numeric-value last-number)) + (args (mapcar (lambda (pair) + (split-string pair "=")) + args)) + (nth-last-command (ring-ref eshell-history-ring last-number)) + temp) + ;; Transform ARGS to the required format. + + ;; NOTE; Using `mapc' is recommended by the manual. + (mapc + (function + (lambda (arg) + (cond + ((and (consp arg) + (= (length arg) 2) + (stringp (car arg)) + (stringp (cadr arg))) + (setq temp (cons arg temp))) + ((and (consp arg) + (= (length arg) 1) + (stringp (car arg))) + (setq temp (cons + (list (caar temp) + (concat (cadar temp) (cons 32 nil) (car arg))) + (cdr temp)))) + ((user-error "Wrong specification: MATCH=REPLACE required, but got %S" arg))))) + args) + (setq args (nreverse temp)) + ;; Replace the command + (save-match-data + (while (consp args) + (setq temp (car args)) + (cond + ;; Cannot use `string-match-p' here. + ((string-match (car temp) nth-last-command) + (setq nth-last-command (replace-match + (or (cadr temp) "") + t nil nth-last-command)))) + (setq args (cdr args)))) + ;; Let the user know what the result of substitution is. + (eshell-printn nth-last-command) + ;; Check we don't do another r command. + (cond + ((and (/= (length nth-last-command) 0) + (= (aref nth-last-command 0) ?r) + (or (= (length nth-last-command) 1) + (= (aref nth-last-command 1) 32))) + (user-error "Repeating a repeating command. This is probably not what you want"))) + (eshell-command-result nth-last-command))) + +(add-to-list 'eshell-expand-input-functions + #'eshell-expand-history-references) + +(setq eshell-list-files-after-cd t) + +(provide 'eshell-conf) +;;; eshell-conf.el ends here |