From c915c51cd32b6354b951f10fb4d2d3666b3480d2 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sat, 16 Apr 2022 08:23:14 -0700 Subject: [PATCH] New electric forward slash Eshell module * lisp/eshell/em-elecslash.el: New module. * etc/NEWS: * doc/misc/eshell.texi (Electric forward slash): Document the module. --- doc/misc/eshell.texi | 59 +++++++++++++++++++ etc/NEWS | 8 +++ lisp/eshell/em-elecslash.el | 113 ++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 lisp/eshell/em-elecslash.el diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index 372e4c3ffb..61db05c4e3 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -1241,6 +1241,7 @@ Extension modules * Key rebinding:: * Smart scrolling:: * Terminal emulation:: +* Electric forward slash:: @end menu @node Writing a module @@ -1273,6 +1274,64 @@ Terminal emulation This section is not yet written. +@node Electric forward slash +@section Electric forward slash + +This optional module tries to help with passing absolute paths to +commands when @code{default-directory} is remote. When using Eshell's +Tramp support, it is easy to accidentally refer to a local path when +you meant a remote path. The problem arises because absolute paths +passed to Lisp functions must have Tramp's @code{/method:host:} +prefix, but absolute paths passed to external commands must not have +this prefix. Typically, one does not think about whether a command is +a Lisp function or an external command while inputting command line +arguments. For example, suppose you execute + +@example + cd /ssh:root@@example.com: + find /etc -name "*gnu*" +@end example + +@noindent and in reviewing the output of the command, you identify a +file @code{/etc/gnugnu} that should be moved somewhere else. So you +type + +@example + mv /etc/gnugnu /tmp +@end example + +@noindent But since @code{mv} refers to the local Lisp function +@code{eshell/mv}, not a remote shell command, to say this is to +request that the local file @code{/etc/gnugnu} be moved into the local +@code{/tmp} directory. What you should have typed is + +@example + mv /ssh:root@@example.com:/etc/gnugnu /ssh:root@@example.com:/tmp +@end example + +@noindent but it is easy to forget to do that, and it takes much +longer to type. + +If the @code{eshell-elecslash} module has been added to +@code{eshell-modules-list}, and @code{default-directory} is remote, +then when you type the first forward slash of an argument to a Lisp +function, the Tramp prefix will be filled in for you. A second +forward slash can be used to undo the insertion, for when you really +do want to pass a local absolute path, such as when you want to copy a +remote file to the local machine. And when typing arguments to +external commands, the Tramp prefix is not filled in. The result is +that you don't have to think about inserting the Tramp prefix and can +just type absolute paths in the same way for both types of command. +The Tramp prefix is additionally filled in when you type @code{~/}. + +The code that determines whether or not the Tramp prefix should be +inserted uses simple heuristics. A limitation of the current +implementation is that only the status as Lisp function or external +program of the command at the very beginning of input can be +considered. Thus when chaining commands with the operators @code{&&}, +@code{||}, @code{|} and @code{;}, the electric forward slash is active +only within the first command. + @node Bugs and ideas @chapter Bugs and ideas @cindex reporting bugs and ideas diff --git a/etc/NEWS b/etc/NEWS index 79c27da549..696c3ff844 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1165,6 +1165,14 @@ support for pipelines which will move a lot of data. See section "Running Shell Pipelines Natively" in the Eshell manual, node "(eshell) Input/Output". ++++ +*** New optional Eshell module to help avoid mistakes when supplying +absolute paths to commands in remote Eshells. 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 instead of a remote path. See +"Electric forward slash" in the Eshell manual. + ** Miscellaneous +++ diff --git a/lisp/eshell/em-elecslash.el b/lisp/eshell/em-elecslash.el new file mode 100644 index 0000000000..e2585fe2a5 --- /dev/null +++ b/lisp/eshell/em-elecslash.el @@ -0,0 +1,113 @@ +;;; 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 -- 2.30.2