]> git.eshelyaron.com Git - emacs.git/commitdiff
Add 'compile' builtin command for Eshell
authorJim Porter <jporterbugs@gmail.com>
Sun, 13 Aug 2023 19:07:39 +0000 (12:07 -0700)
committerJim Porter <jporterbugs@gmail.com>
Wed, 16 Aug 2023 17:01:49 +0000 (10:01 -0700)
* lisp/eshell/em-unix.el (eshell-compile, eshell/compile): New
functions.
(eshell/make, eshell-grep): Use 'eshell-compile'.
(eshell/glimpse): It's no longer necessary to let-bind 'null-device';
'eshell-grep' no longer calls 'grep' (the Lisp function), which needed
'null-device' to be nil for this case.

* test/lisp/eshell/em-unix-tests.el: New file.

* doc/misc/eshell.texi (Built-ins): Document the 'compile' builtin.

* etc/NEWS: Announce this change (bug#65273).

doc/misc/eshell.texi
etc/NEWS
lisp/eshell/em-unix.el
test/lisp/eshell/em-unix-tests.el [new file with mode: 0644]

index ca31cb2589db7b3e17abbff7bd4edf78baab5eb6..211b13c995ce6df663708a8ba310b9837d615d91 100644 (file)
@@ -523,6 +523,17 @@ Clear the scrollback contents of the Eshell window.  Unlike the
 command @command{clear}, this command deletes content in the Eshell
 buffer.
 
+@item compile
+@cmindex compile
+Run an external command, sending its output to a compilation buffer if
+the command would output to the screen and is not part of a pipeline
+or subcommand.  This is particularly useful when defining aliases, so
+that interactively, the output shows up in a compilation buffer, but
+you can still pipe the output elsewhere if desired.  For example, if
+you have a grep-like command on your system, you might define an alias
+for it like so: @samp{alias mygrep 'compile --mode=grep-mode -- mygrep
+$*'}.
+
 @item cp
 @cmindex cp
 Copy a file to a new location or copy multiple files to the same
index 57f04609679bcc065412cc17f1d5ac8100966f65..010d59401ffbdb3aaf85bf00eae3b6d8d8db1dbb 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -295,6 +295,16 @@ using this new option.  (Or set 'display-buffer-alist' directly.)
 
 ** Eshell
 
++++
+*** New builtin Eshell command 'compile'.
+
+This command runs another command, sending its output to a compilation
+buffer when the command would output interatively.  This can be useful
+when defining aliases so that they produce a compilation buffer when
+appropriate, but still allow piping the output elsewhere if desired.
+For more information, see the "(eshell) Built-ins" node in the Eshell
+manual.
+
 +++
 *** New splice operator for Eshell dollar expansions.
 Dollar expansions in Eshell now let you splice the elements of the
index a8c86b925bcd854fc911d14f3f0c703dd5866ecf..509b2d3181969223692b844d8323b8a84a199e50 100644 (file)
@@ -692,19 +692,56 @@ Concatenate FILE(s), or standard input, to standard output.")
 
 ;; special front-end functions for compilation-mode buffers
 
+(defun eshell-compile (command args &optional method mode)
+  "Run an external COMMAND with ARGS using a compilation buffer when possible.
+COMMAND should be a list of command-line arguments.  By default,
+if the command is outputting to the screen and is not part of a
+pipeline or subcommand, open an compilation buffer to hold the
+results; otherwise, write the output on stdout.
+
+If METHOD is `interactive', always open a compilation buffer.  If
+METHOD is `plain', always write to stdout.
+
+MODE, if specified, is the major mode to set in the compilation
+buffer (see `compilation-start')."
+  (if (and (not (eq method 'interactive))
+           (or (eq method 'plain)
+               eshell-in-pipeline-p
+               eshell-in-subcommand-p
+               (not (eshell-interactive-output-p))))
+      (throw 'eshell-replace-command
+              (eshell-parse-command (concat "*" command) args))
+    (compile
+     (mapconcat #'shell-quote-argument
+                (eshell-stringify-list (flatten-tree (cons command args)))
+                " ")
+     mode)))
+
+(defun eshell/compile (&rest args)
+  "Run an external COMMAND using a compilation buffer when possible.
+See `eshell-compile'."
+  (eshell-eval-using-options
+   "compile" args
+   '((?m "mode" t mode "the mode to set in the compilation buffer")
+     (?i "interactive" 'interactive method "always open a compilation buffer")
+     (?p "plain" 'plain method "always write to stdout")
+     :usage "[-p | -i] [-m MODE] COMMAND...
+Run COMMAND in a compilation buffer when outputting to the screen and
+not part of a pipeline or subcommand."
+     :parse-leading-options-only)
+   (when (stringp mode)
+     (setq mode (intern mode)))
+   (eshell-compile (car args) (cdr args) method mode)))
+
+(put 'eshell/compile 'eshell-no-numeric-conversions t)
+
 (defun eshell/make (&rest args)
   "Use `compile' to do background makes.
 Fallback to standard make when called synchronously."
-  (if (and eshell-current-subjob-p
-          (eshell-interactive-output-p))
-      (let ((compilation-process-setup-function
-            (list 'lambda nil
-                  (list 'setq 'process-environment
-                        (list 'quote (eshell-copy-environment))))))
-       (compile (concat "make " (eshell-flatten-and-stringify args))))
-    (throw 'eshell-replace-command
-          (eshell-parse-command "*make" (eshell-stringify-list
-                                         (flatten-tree args))))))
+  (eshell-compile "make" args
+                  ;; Use plain output unless we're executing in the
+                  ;; background.
+                  (not eshell-current-subjob-p)))
 
 (put 'eshell/make 'eshell-no-numeric-conversions t)
 
@@ -777,22 +814,10 @@ and if it's not part of a command pipeline.  Otherwise, it calls the
 external command."
   (if (and maybe-use-occur eshell-no-grep-available)
       (eshell-poor-mans-grep args)
-    (if (or eshell-plain-grep-behavior
-           (not (and (eshell-interactive-output-p)
-                     (not eshell-in-pipeline-p)
-                     (not eshell-in-subcommand-p))))
-       (throw 'eshell-replace-command
-              (eshell-parse-command (concat "*" command)
-                                    (eshell-stringify-list
-                                     (flatten-tree args))))
-      (let* ((args (mapconcat 'identity
-                             (mapcar 'shell-quote-argument
-                                     (eshell-stringify-list
-                                      (flatten-tree args)))
-                             " "))
-            (cmd (format "%s -n %s" command args))
-            compilation-scroll-output)
-       (grep cmd)))))
+    (eshell-compile command (cons "-n" args)
+                    (and eshell-plain-grep-behavior
+                         'interactive)
+                     #'grep-mode)))
 
 (defun eshell/grep (&rest args)
   "Use Emacs grep facility instead of calling external grep."
@@ -816,8 +841,7 @@ external command."
 
 (defun eshell/glimpse (&rest args)
   "Use Emacs grep facility instead of calling external glimpse."
-  (let (null-device)
-    (eshell-grep "glimpse" (append '("-z" "-y") args))))
+  (eshell-grep "glimpse" (append '("-z" "-y") args)))
 
 ;; completions rules for some common UNIX commands
 
diff --git a/test/lisp/eshell/em-unix-tests.el b/test/lisp/eshell/em-unix-tests.el
new file mode 100644 (file)
index 0000000..d7b6c55
--- /dev/null
@@ -0,0 +1,68 @@
+;;; em-unix-tests.el --- em-unix test suite  -*- lexical-binding:t -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; 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:
+
+;; Tests for Eshell's implementation of various UNIX commands.
+
+;;; Code:
+
+(require 'ert)
+(require 'em-unix)
+
+(require 'eshell-tests-helpers
+         (expand-file-name "eshell-tests-helpers"
+                           (file-name-directory (or load-file-name
+                                                    default-directory))))
+
+;;; Tests:
+
+(ert-deftest em-unix-test/compile/interactive ()
+  "Check that `eshell/compile' opens a compilation buffer interactively."
+  (skip-unless (executable-find "echo"))
+  (with-temp-eshell
+   (eshell-match-command-output "compile echo hello"
+                                "#<buffer \\*compilation\\*>")
+   (with-current-buffer "*compilation*"
+     (forward-line 3)
+     (should (looking-at "echo hello")))))
+
+(ert-deftest em-unix-test/compile/noninteractive ()
+  "Check that `eshell/compile' writes to stdout noninteractively."
+  (skip-unless (executable-find "echo"))
+  (eshell-command-result-equal "compile echo hello"
+                               "hello\n"))
+
+(ert-deftest em-unix-test/compile/pipeline ()
+  "Check that `eshell/compile' writes to stdout from a pipeline."
+  (skip-unless (and (executable-find "echo")
+                    (executable-find "cat")))
+  (with-temp-eshell
+   (eshell-match-command-output "compile echo hello | *cat"
+                                "\\`hello\n")))
+
+(ert-deftest em-unix-test/compile/subcommand ()
+  "Check that `eshell/compile' writes to stdout from a subcommand."
+  (skip-unless (and (executable-find "echo")
+                    (executable-find "cat")))
+  (with-temp-eshell
+   (eshell-match-command-output "echo ${compile echo hello}"
+                                "\\`hello\n")))
+
+;; em-unix-tests.el ends here