From c4915678f3881765caac5036b1f20b02175469e3 Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Sun, 13 Aug 2023 12:07:39 -0700 Subject: [PATCH] Add 'compile' builtin command for Eshell * 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 | 11 +++++ etc/NEWS | 10 ++++ lisp/eshell/em-unix.el | 80 ++++++++++++++++++++----------- test/lisp/eshell/em-unix-tests.el | 68 ++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 28 deletions(-) create mode 100644 test/lisp/eshell/em-unix-tests.el diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index ca31cb2589d..211b13c995c 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -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 diff --git a/etc/NEWS b/etc/NEWS index 57f04609679..010d59401ff 100644 --- 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 diff --git a/lisp/eshell/em-unix.el b/lisp/eshell/em-unix.el index a8c86b925bc..509b2d31819 100644 --- a/lisp/eshell/em-unix.el +++ b/lisp/eshell/em-unix.el @@ -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 index 00000000000..d7b6c55fe45 --- /dev/null +++ b/test/lisp/eshell/em-unix-tests.el @@ -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 . + +;;; 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" + "#") + (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 -- 2.39.2