summaryrefslogtreecommitdiff
path: root/eshell-conf.el
diff options
context:
space:
mode:
Diffstat (limited to 'eshell-conf.el')
-rw-r--r--eshell-conf.el271
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