New electric forward slash Eshell module

* lisp/eshell/em-elecslash.el: New file.
* etc/NEWS:
* doc/misc/eshell.texi (Electric forward slash): Document the module.
(Make / electric): Retitle to "Make / more electric", update, add
"@noindent", and standardize terminology.
This commit is contained in:
Sean Whitton 2022-04-16 08:23:14 -07:00
parent ad89ec84ee
commit f8aa771af3
3 changed files with 191 additions and 4 deletions

View file

@ -1560,6 +1560,7 @@ Eshell module.} You also need to load the following as shown:
* Key rebinding::
* Smart scrolling::
* Terminal emulation::
* Electric forward slash::
@end menu
@node Writing a module
@ -1592,6 +1593,61 @@ This section is not yet written.
This section is not yet written.
@node Electric forward slash
@section Electric forward slash
To help with supplying absolute file name arguments to remote
commands, you can add the @code{eshell-elecslash} module to
@code{eshell-modules-list}. Then, typing @kbd{/} as the first
character of a command line argument will automatically insert the
Tramp prefix @file{/method:host:}. If this is not what you want
(e.g.@: because you want to refer to a local file), you can type
another @kbd{/} to undo the automatic insertion. Typing @kbd{~/} also
inserts the Tramp prefix. The automatic insertion applies only when
@code{default-directory} is remote and the command is a Lisp function.
In particular, typing arguments to external commands doesn't insert
the prefix.
The result is that in most cases of supplying absolute file name
arguments to commands you should see the Tramp prefix inserted
automatically only when that's what you'd reasonably expect. This
frees you from having to keep track of whether commands are Lisp
functions or external when typing 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 @file{/etc/gnugnu} that should be moved somewhere else. So you
type
@example
mv /etc/gnugnu /tmp
@end example
@noindent But since @command{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 @file{/etc/gnugnu} be moved into the local
@file{/tmp} directory. After you add @code{eshell-elecslash} to
@code{eshell-modules-list}, then when you type the above @command{mv}
invocation you will get the following input, which is what you
intended:
@example
mv /ssh:root@@example.com:/etc/gnugnu /ssh:root@@example.com:/tmp
@end example
The code that determines whether or not the Tramp prefix should be
inserted uses simple heuristics. A limitation of the current
implementation is that it inspects whether only the command at the
very beginning of input is a Lisp function or external program. 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
@ -1995,11 +2051,12 @@ only. That way, it could be listed as a login shell.
@item The first keypress after @kbd{M-x watson} triggers
@code{eshell-send-input}
@item Make @kbd{/} electric
@item Make @kbd{/} more electric
So that it automatically expands and corrects pathnames. Or make
pathname completion for Pcomplete auto-expand @samp{/u/i/std@key{TAB}} to
@samp{/usr/include/std@key{TAB}}.
@noindent so that it automatically expands and corrects file names,
beyond what the @code{em-elecslash} module is able to do. Or make
file name completion for Pcomplete auto-expand
@samp{/u/i/std@key{TAB}} to @samp{/usr/include/std@key{TAB}}.
@item Write the @command{pushd} stack to disk along with @code{last-dir-ring}

View file

@ -1251,6 +1251,16 @@ 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 module to help supplying absolute file names to remote commands.
After enabling the new 'eshell-elecslash' module, typing a forward
slash as the first character of a command line argument will
automatically insert the Tramp prefix. The automatic insertion
applies only when 'default-directory' is remote and the command is a
Lisp function. This frees you from having to keep track of whether
commands are Lisp function or external when supplying absolute file
name arguments. See "Electric forward slash" in the Eshell manual.
** Miscellaneous
+++

120
lisp/eshell/em-elecslash.el Normal file
View file

@ -0,0 +1,120 @@
;;; em-elecslash.el --- electric forward slashes -*- lexical-binding:t -*-
;; Copyright (C) 2022 Free Software Foundation, Inc.
;; Author: Sean Whitton <spwhitton@spwhitton.name>
;; 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 <https://www.gnu.org/licenses/>.
;;; 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
"Electric forward slash in remote Eshells.
This module helps with supplying absolute file name arguments to
remote commands. After enabling it, typing a forward slash as
the first character of a command line argument will automatically
insert the Tramp prefix, /method:host:. The automatic insertion
applies only when `default-directory' is remote and the command
is a Lisp function.
The result is that in most cases of supplying absolute file name
arguments to commands you should see the Tramp prefix inserted
automatically only when that's what you'd reasonably expect.
This frees you from having to keep track of whether commands are
Lisp functions or external when typing command line arguments."
:tag "Electric forward slash"
:group 'eshell-module))
;;; Functions:
(defun eshell-elecslash-initialize () ;Called from `eshell-mode' via intern-soft!
"Initialize remote Eshell electric forward slash support."
(add-hook 'post-self-insert-hook
#'eshell-electric-forward-slash nil t))
(defun eshell-electric-forward-slash ()
"Implementation of electric forward slash in remote Eshells.
Initializing the `eshell-elecslash' module adds this function to
`post-self-insert-hook'. Typing / or ~/ as the first character
of a command line argument automatically inserts the Tramp prefix
in the case that `default-directory' is remote and the command is
a Lisp function. Typing a second forward slash undoes the
insertion."
(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 file name.
(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