;;; em-elecslash.el --- electric forward slashes -*- lexical-binding:t -*- ;; Copyright (C) 2022 Free Software Foundation, Inc. ;; Author: Sean Whitton ;; This file is part of GNU Emacs. ;; GNU Emacs 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. ;; GNU Emacs 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 GNU Emacs. If not, see . ;;; Commentary: ;; Electric forward slash in remote Eshells. ;;; Code: (require 'tramp) (require 'thingatpt) (require 'esh-cmd) (require 'esh-ext) (require 'esh-mode) ;; This makes us an option when customizing `eshell-modules-list'. ;;;###autoload (progn (defgroup eshell-elecslash nil "When `default-directory' is remote thanks to Eshell's TRAMP integration, and you are supplying absolute paths to commands, it is easy to accidentally refer to a local path when you meant a remote path. This happens because absolute paths passed to Lisp functions must be prefixed with /method:host: but absolute paths passed to external commands must not have this prefix; it is easy to invoke a built-in command without the prefix by mistake. This module tries to resolve this difficulty by filling in the /method:host: electrically just when it's needed." :tag "Electric forward slash" :group 'eshell-module)) ;;; Functions: (defun eshell-elecslash-initialize () ;Called from `eshell-mode' via intern-soft! "Initialize electric forward slash support." (add-hook 'post-self-insert-hook #'eshell-electric-forward-slash nil t)) (defun eshell-electric-forward-slash () "Electric insertion of TRAMP part of `default-directory' in remote Eshells. Added to `post-self-insert-hook' when the Eshell elecslash module is initialized. Typing a forward slash in a remote Eshell performs the completion. Typing a second forward slash undoes it." (when (eq ?/ (char-before)) (delete-char -1) (let ((tilde-before (eq ?~ (char-before))) (command (save-excursion (eshell-bol) (skip-syntax-forward " ") (thing-at-point 'sexp)))) (if (and (file-remote-p default-directory) ;; We can't formally parse the input. But if there is ;; one of these operators behind us, then looking at ;; the first command would not be sensible. So be ;; conservative: don't insert the Tramp prefix if there ;; are any of these operators behind us. (not (looking-back (regexp-opt '("&&" "|" ";")) eshell-last-output-end)) (or (= (point) eshell-last-output-end) (and tilde-before (= (1- (point)) eshell-last-output-end)) (and (or tilde-before (eq ?\s (char-syntax (char-before)))) (or (eshell-find-alias-function command) (and (fboundp (intern-soft command)) (or eshell-prefer-lisp-functions (not (eshell-search-path command)))))))) (let ((map (make-sparse-keymap)) (start (if tilde-before (1- (point)) (point))) (localname (tramp-file-name-localname (tramp-dissect-file-name default-directory)))) (when tilde-before (delete-char -1)) (insert (substring default-directory 0 (string-search localname default-directory))) (unless tilde-before (insert "/")) ;; Typing a second slash undoes the insertion, for when ;; you really do want to type a local absolute path. (define-key map "/" (lambda () (interactive) (delete-region start (point)) (insert (if tilde-before "~/" "/")))) (set-transient-map map)) (insert "/"))))) (provide 'em-elecslash) ;; Local Variables: ;; generated-autoload-file: "esh-groups.el" ;; End: ;;; esh-elecslash.el ends here