From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: miha--- via "Bug reports for GNU Emacs, the Swiss army knife of text editors" Newsgroups: gmane.emacs.bugs Subject: bug#51940: 29.0.50; [PATCH] Fontification and indentation in M-x shell and ielm Date: Thu, 18 Nov 2021 14:32:52 +0100 Message-ID: <87pmqxeghn.fsf@miha-pc> References: <87sfvteqx2.fsf@miha-pc> <83ee7dyar2.fsf@gnu.org> Reply-To: Mime-Version: 1.0 Content-Type: multipart/signed; boundary="==-=-="; micalg=pgp-sha256; protocol="application/pgp-signature" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="22298"; mail-complaints-to="usenet@ciao.gmane.io" Cc: 51940@debbugs.gnu.org To: Eli Zaretskii Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Thu Nov 18 14:37:15 2021 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1mnhbG-0005ZJ-7c for geb-bug-gnu-emacs@m.gmane-mx.org; Thu, 18 Nov 2021 14:37:14 +0100 Original-Received: from localhost ([::1]:55640 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mnhbF-0000MF-76 for geb-bug-gnu-emacs@m.gmane-mx.org; Thu, 18 Nov 2021 08:37:13 -0500 Original-Received: from eggs.gnu.org ([209.51.188.92]:54866) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mnhTK-0003U4-FK for bug-gnu-emacs@gnu.org; Thu, 18 Nov 2021 08:29:02 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]:51820) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mnhTK-0001me-6x for bug-gnu-emacs@gnu.org; Thu, 18 Nov 2021 08:29:02 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1mnhTJ-0003LG-UF for bug-gnu-emacs@gnu.org; Thu, 18 Nov 2021 08:29:01 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Thu, 18 Nov 2021 13:29:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 51940 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 51940-submit@debbugs.gnu.org id=B51940.163724210612782 (code B ref 51940); Thu, 18 Nov 2021 13:29:01 +0000 Original-Received: (at 51940) by debbugs.gnu.org; 18 Nov 2021 13:28:26 +0000 Original-Received: from localhost ([127.0.0.1]:35133 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1mnhSf-0003Jv-Em for submit@debbugs.gnu.org; Thu, 18 Nov 2021 08:28:25 -0500 Original-Received: from kamnitnik.top ([209.250.245.214]:34990) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1mnhSb-0003Jl-Sb for 51940@debbugs.gnu.org; Thu, 18 Nov 2021 08:28:20 -0500 Original-Received: from localhost (BSN-77-156-43.static.siol.net [193.77.156.43]) by kamnitnik.top (Postfix) with ESMTPSA id A5FE39CFA8; Thu, 18 Nov 2021 13:28:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kamnitnik.top; s=mail; t=1637242096; bh=jrT5foErKA4+fNEHRxDOxJxlsi7byTgCvoy5CT4Y/I8=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=FDoo1mPg1u+Zof17D3CxnbwS7C2cFOHpo+2ZHpFy5TYgd0xiYKGtfjUctj8LAy6xv NbOKQb4EPYRHwTI6b8JXtCP0VboYTdNxBZBj24fJJJgNvfm8iLU5TuVa5zjGYOtL3k PWmBPeeFOVkmM2TeQodg/EZlI3fgKpoKxhlI+sIDPUusSd2ZeigZ6Sy9QptXuiDIRh BmTikaFK8RdNvlBg4GAPCOYTY14pmHb/xtsQVnl+8ePt+nifyq0ginF4EvUSw9fGKl J16EGIaBOA7Lk3ZE28FuhAg8A22mHJxEl9tfamXGcOiTkp8Ke2f0NQqXXwUwic+aLi 8jZyP/uk2QGgA== In-Reply-To: <83ee7dyar2.fsf@gnu.org> X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.io gmane.emacs.bugs:220317 Archived-At: --==-=-= Content-Type: multipart/mixed; boundary="=-=-=" --=-=-= Content-Type: text/plain Content-Transfer-Encoding: quoted-printable Eli Zaretskii writes: >> Date: Thu, 18 Nov 2021 10:47:37 +0100 >> From: miha--- via "Bug reports for GNU Emacs, >> the Swiss army knife of text editors" >>=20 >> Please find the attached patches which enable fontification and >> indentation in M-x shell and ielm. The main fontification and >> indentation machinery is implemented in comint.el and should be general >> enough for use by other comint derived modes. It works by making an >> indirect buffer, putting it in a suitable major mode (sh-mode in >> M-x shell buffers, for example) and fontifying and indenting user input >> that buffer. > > Thanks. > > One general comment about the doc strings: > >> +(defvar-local comint-indirect-setup-function nil >> + "Function to set up an indirect buffer. > > here and elsewhere in the changes, please modify the first sentence of > the doc strings so that they don't sound too general. "Set up an > indirect buffer" doesn't hint on the fact that this is for comint > buffers, not in general about indirect buffers. The same problem > exists with many doc strings in these patches. Okay, attached are revised patches with improved doc strings and a fixed commit message. --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0001-Implement-a-general-input-fontification-mechanism-fo.patch Content-Transfer-Encoding: quoted-printable From=206b5a52475642b707c7f0cc0f871f6540d8a0aa73 Mon Sep 17 00:00:00 2001 From: =3D?UTF-8?q?Miha=3D20Rihtar=3DC5=3DA1i=3DC4=3D8D?=3D Date: Mon, 15 Nov 2021 22:51:51 +0100 Subject: [PATCH 1/5] Implement a general input fontification mechanism for comint modes * lisp/comint.el (comint-indirect-setup-function): New variable for comint derived major modes to customize. (comint-fl-mode): New minor mode that fontifies input text through an indirect buffer. (comint-indirect-setup-hook): (comint--indirect-buffer): (comint--fl-saved-jit-lock-contextually): (comint--fl-on): (comint--fl-off): (comint--fl-ppss-flush-indirect): (comint--fl-fontify-region): (comint--intersect-regions): (comint-indirect-buffer): (comint--indirect-cleanup): New functions and buffer-local variables. =2D-- lisp/comint.el | 223 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/lisp/comint.el b/lisp/comint.el index 544f0b8b82..7709178afd 100644 =2D-- a/lisp/comint.el +++ b/lisp/comint.el @@ -1931,6 +1931,7 @@ comint-send-input (when comint-highlight-input (add-text-properties beg end '( font-lock-face comint-highlight-in= put + comint--fl-inhibit-fontification t front-sticky t ))) (unless comint-use-prompt-regexp ;; Give old user input a field property of `input', to @@ -4042,6 +4043,228 @@ comint-osc-hyperlink-handler (cons (point-marker) (match-string-no-properties 1 text))))) =20 +;;; Input fontification through an indirect buffer +;;=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D +;; +;; Modes derived from `comint-mode' can set up fontification input +;; text with the help of an indirect buffer whose major mode and +;; font-lock settings are set accordingly. + +(defvar-local comint-indirect-setup-function nil + "Function to set up an indirect comint fontification buffer. +This function is called by `comint-indirect-buffer' with zero +arguments after making an indirect buffer. It is usually set to +a major-mode command whose font-locking is desired for input +text. In order to prevent possible mode hooks from running, the +variable `delay-mode-hooks' is set to t prior to calling this +function and `change-major-mode-hook' along with +`after-change-major-mode-hook' are bound to nil.") + +(defcustom comint-indirect-setup-hook nil + "Hook run after setting up an indirect comint fontification buffer. +It is run after the indirect buffer is set up for fontification +of input regions." + :group 'comint + :type 'hook + :version "29.1") + +(defvar-local comint--indirect-buffer nil + "Indirect buffer used for input fontification.") + +(defvar-local comint--fl-saved-jit-lock-contextually nil) + +(define-minor-mode comint-fl-mode + "Enable input fontification. +Input fontification happens through an indirect buffer created +with `comint-indirect-buffer', which see. + +This function signals an error if `comint-use-prompt-regexp' is +non-nil because input fontification isn't compatible with this +setting." + :lighter nil + (if comint-fl-mode + (let ((success nil)) + (unwind-protect + (progn + (comint--fl-on) + (setq success t)) + (unless success + (setq comint-fl-mode nil) + (comint--fl-off)))) + (comint--fl-off))) + +(defun comint--fl-on () + "Enable input fontification for the current buffer." + (comint--fl-off) + + (when comint-use-prompt-regexp + (error + "Input fontification is incompatible with `comint-use-prompt-regexp'"= )) + + (add-function :around (local 'font-lock-fontify-region-function) + #'comint--fl-fontify-region) + ;; `before-change-functions' are only run in the current buffer and + ;; not in its indirect buffers, which means that we must manually + ;; flush ppss cache + (add-hook 'before-change-functions + #'comint--fl-ppss-flush-indirect 99 t) + + ;; Set up contextual fontification + (unless (booleanp jit-lock-contextually) + (setq comint--fl-saved-jit-lock-contextually + jit-lock-contextually) + (setq-local jit-lock-contextually t) + (when jit-lock-mode + (jit-lock-mode t)))) + +(defun comint--fl-off () + "Disable input fontification for the current buffer." + (remove-function (local 'font-lock-fontify-region-function) + #'comint--fl-fontify-region) + (remove-hook 'before-change-functions + #'comint--fl-ppss-flush-indirect t) + + ;; Reset contextual fontification + (when comint--fl-saved-jit-lock-contextually + (setq-local jit-lock-contextually + comint--fl-saved-jit-lock-contextually) + (setq comint--fl-saved-jit-lock-contextually nil) + (when jit-lock-mode + (jit-lock-mode t))) + + (font-lock-flush)) + +(defun comint--fl-ppss-flush-indirect (beg &rest rest) + (when-let ((buf (comint-indirect-buffer t))) + (with-current-buffer buf + (when (memq #'syntax-ppss-flush-cache before-change-functions) + (apply #'syntax-ppss-flush-cache beg rest))))) + +(defun comint--fl-fontify-region (fun beg end verbose) + "Around advice for `font-lock-fontify-region-function'. +Fontify region between BEG and END. First, highlight it using +FUN. Then highlight only the input text in the region with the +help of an indirect buffer. VERBOSE is passed to the +fontify-region functions. Skip fontification of input regions +with non-nil `comint--fl-inhibit-fontification' text property." + (pcase (funcall fun beg end verbose) + (`(jit-lock-bounds ,beg1 . ,end1) + (setq beg beg1 end end1))) + (pcase + (let ((min (point-min)) + (max (point-max))) + (with-current-buffer (comint-indirect-buffer) + (narrow-to-region min max) + (comint--intersect-regions + nil (lambda (beg end) + (unless (get-text-property + beg 'comint--fl-inhibit-fontification) + (font-lock-fontify-region beg end verbose))) + beg end))) + (`((jit-lock-bounds ,beg1 . ,_) . (jit-lock-bounds ,_ . ,end1)) + (setq beg (min beg beg1)) + (setq end (max end end1)))) + + `(jit-lock-bounds ,beg . ,end)) + +(defun comint--intersect-regions (fun-output fun-input beg end) + "Iterate over output and input regions between BEG and END. +Divide the region specified by BEG and END into smaller regions +that cover either process output (its 'field property is 'output) +or input (all remaining text). Interchangeably call FUN-OUTPUT on +each output region, and FUN-INPUT on each input region. + +FUN-OUTPUT and FUN-INPUT are passed two arguments, the beginning +and end of the smaller region. Before calling each function, +narrow the buffer to the surrounding process output or input. You +can also pass nil as either function to skip its corresponding +regions. + +Return a cons cell of return values of the first and last +function called, or nil, if no function was called (if BEG =3D END)." + (let ((beg1 beg) + (end1 (copy-marker nil t)) + (return-beg nil) (return-end nil) + (is-output (eq (get-text-property beg 'field) 'output))) + (setq end (copy-marker end t)) + + (while (< beg1 end) + (set-marker + end1 (or (if is-output + (text-property-not-all beg1 end 'field 'output) + (text-property-any beg1 end 'field 'output)) + end)) + (when-let ((fun (if is-output fun-output fun-input))) + (save-restriction + (let ((beg2 beg1) + (end2 end1)) + (when (=3D beg2 beg) + (setq beg2 (field-beginning beg2))) + (when (=3D end2 end) + (setq end2 (field-end end2))) + ;; Narrow to the whole field surrounding the region + (narrow-to-region beg2 end2)) + (setq return-end (list (funcall fun beg1 + (marker-position end1))))) + (unless return-beg + (setq return-beg return-end))) + (setq beg1 (marker-position end1)) + (setq is-output (not is-output))) + + (set-marker end nil) + (set-marker end1 nil) + (when return-beg + (cons (car return-beg) (car return-end))))) + +(defun comint-indirect-buffer (&optional no-create) + "Return an indirect comint fontification buffer. +If an indirect buffer for the current buffer already exists, +return it, otherwise create it first and set it up by calling +`comint-indirect-setup-function' with zero arguments, turning on +font-lock, and running `comint-indirect-setup-hook'. This setup +happens with `delay-mode-hooks' set to t in order to prevent +possible SETUP-FUN's mode hooks from running. + +If an indirect buffer doesn't exist and NO-CREATE is non-nil, +return nil." + (or + comint--indirect-buffer + (unless no-create + (let ((setup-hook + (if (local-variable-p 'comint-indirect-setup-hook) + (list comint-indirect-setup-hook))) + (setup-fun comint-indirect-setup-function)) + + (add-hook 'change-major-mode-hook #'comint--indirect-cleanup + nil t) + + (with-current-buffer + (setq comint--indirect-buffer + (make-indirect-buffer + (current-buffer) + (generate-new-buffer-name + (concat " " (buffer-name) "-comint-indirect")))) + (setq-local delay-mode-hooks t) + (when setup-fun + (let ((change-major-mode-hook nil) + (after-change-major-mode-hook nil)) + (funcall setup-fun))) + (setq-local font-lock-dont-widen t) + (setq-local font-lock-support-mode nil) + (font-lock-mode) + (when setup-hook + (setq-local comint-indirect-setup-hook + (car setup-hook))) + (run-hooks 'comint-indirect-setup-hook)) + comint--indirect-buffer)))) + +(defun comint--indirect-cleanup () + (when comint--indirect-buffer + (kill-buffer comint--indirect-buffer) + (setq comint--indirect-buffer nil))) + + + ;;; Converting process modes to use comint mode ;;=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D ;; The code in the Emacs 19 distribution has all been modified to use comi= nt =2D-=20 2.34.0 --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0002-Input-fontification-for-M-x-shell.patch Content-Transfer-Encoding: quoted-printable From=20960dffce408027fd2e20f3b9f9c7e4a35c860833 Mon Sep 17 00:00:00 2001 From: =3D?UTF-8?q?Miha=3D20Rihtar=3DC5=3DA1i=3DC4=3D8D?=3D Date: Mon, 15 Nov 2021 23:13:03 +0100 Subject: [PATCH 2/5] Input fontification for M-x shell * lisp/shell.el (shell-comint-fl-enable): New user option to control input fontification. (shell-indirect-setup-hook): New hook. (shell-mode): Set up and enable input fontification. =2D-- lisp/shell.el | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lisp/shell.el b/lisp/shell.el index 370532ea46..a91a59f070 100644 =2D-- a/lisp/shell.el +++ b/lisp/shell.el @@ -306,6 +306,22 @@ shell-input-autoexpand (const :tag "on" t)) :group 'shell) =20 +(defcustom shell-comint-fl-enable t + "Enable highlighting of input in shell buffers. +This variable only has effect when the shell is started. Use the +command `comint-fl-mode' to toggle highlighting of input." + :type 'boolean + :group 'shell + :safe 'booleanp + :version "29.1") + +(defcustom shell-indirect-setup-hook nil + "Hook run after setting up an indirect shell fontification buffer." + :type 'boolean + :group 'shell + :safe 'booleanp + :version "29.1") + (defvar shell-dirstack nil "List of directories saved by pushd in this buffer's shell. Thus, this does not include the shell's current directory.") @@ -516,6 +532,8 @@ shell-completion-vars =20 (put 'shell-mode 'mode-class 'special) =20 +(defvar sh-shell-file) + (define-derived-mode shell-mode comint-mode "Shell" "Major mode for interacting with an inferior shell. \\ @@ -572,6 +590,11 @@ shell-mode control whether input and output cause the window to scroll to the end of = the buffer." :interactive nil + :after-hook + (and (null comint-use-prompt-regexp) + shell-comint-fl-enable + (comint-fl-mode)) + (setq comint-prompt-regexp shell-prompt-pattern) (shell-completion-vars) (setq-local paragraph-separate "\\'") @@ -591,6 +614,19 @@ shell-mode (setq-local ansi-color-apply-face-function #'shell-apply-ansi-color) (shell-reapply-ansi-color) =20 + (add-hook 'comint-indirect-setup-hook + #'shell-indirect-setup-hook 'append t) + (setq comint-indirect-setup-function + (let ((shell shell--start-prog)) + (lambda () + (require 'sh-script) + (cl-letf + (((default-value 'sh-shell-file) + (or shell sh-shell-file)) + (inhibit-message t) + (message-log-max nil)) + (sh-mode))))) + ;; This is not really correct, since the shell buffer does not really ;; edit this directory. But it is useful in the buffer list and menus. (setq list-buffers-directory (expand-file-name default-directory)) @@ -645,6 +681,10 @@ shell-mode ": [[:digit:]]+:[[:digit:]]+;"))) (comint-read-input-ring t))) =20 +(defun shell-indirect-setup-hook () + "Run `shell-indirect-setup-hook'." + (run-hooks 'shell-indirect-setup-hook)) + (defun shell-apply-ansi-color (beg end face) "Apply FACE as the ansi-color face for the text between BEG and END." (when face =2D-=20 2.34.0 --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0003-Input-fontification-for-M-x-ielm.patch Content-Transfer-Encoding: quoted-printable From=202ec0286b5697bfc469afcc8e915e2c090b06f5fb Mon Sep 17 00:00:00 2001 From: =3D?UTF-8?q?Miha=3D20Rihtar=3DC5=3DA1i=3DC4=3D8D?=3D Date: Mon, 15 Nov 2021 23:16:23 +0100 Subject: [PATCH 3/5] Input fontification for M-x ielm * lisp/ielm.el (ielm-comint-fl-enable): New user option to control input fontification. (ielm-indirect-setup-hook): New hook. (inferior-emacs-lisp-mode): Set up and enable input fontification. =2D-- lisp/ielm.el | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lisp/ielm.el b/lisp/ielm.el index 39820a893a..d792bd871d 100644 =2D-- a/lisp/ielm.el +++ b/lisp/ielm.el @@ -472,6 +472,26 @@ ielm-set-pm ;; Set the process mark in the current buffer to POS. (set-marker (process-mark (get-buffer-process (current-buffer))) pos)) =20 +;;; Input fontification + +(defcustom ielm-comint-fl-enable t + "Enable highlighting of input in ielm buffers. +This variable only has effect when the shell is started. Use the +command `comint-fl-mode' to toggle highlighting of input." + :type 'boolean + :safe 'booleanp + :version "29.1") + +(defcustom ielm-indirect-setup-hook nil + "Hook run after setting up an indirect ielm fontification buffer." + :type 'boolean + :safe 'booleanp + :version "29.1") + +(defun ielm-indirect-setup-hook () + "Run `ielm-indirect-setup-hook'." + (run-hooks 'ielm-indirect-setup-hook)) + ;;; Major mode =20 (define-derived-mode inferior-emacs-lisp-mode comint-mode "IELM" @@ -526,6 +546,10 @@ inferior-emacs-lisp-mode Customized bindings may be defined in `ielm-map', which currently contains: \\{ielm-map}" :syntax-table emacs-lisp-mode-syntax-table + :after-hook + (and (null comint-use-prompt-regexp) + ielm-comint-fl-enable + (comint-fl-mode)) =20 (setq comint-prompt-regexp (concat "^" (regexp-quote ielm-prompt))) (setq-local paragraph-separate "\\'") @@ -564,6 +588,10 @@ inferior-emacs-lisp-mode (setq-local font-lock-defaults '(ielm-font-lock-keywords nil nil ((?: . "w") (?- . "w") (?* . "w")= ))) =20 + (add-hook 'comint-indirect-setup-hook + #'ielm-indirect-setup-hook 'append t) + (setq comint-indirect-setup-function #'emacs-lisp-mode) + ;; A dummy process to keep comint happy. It will never get any input (unless (comint-check-proc (current-buffer)) ;; Was cat, but on non-Unix platforms that might not exist, so =2D-=20 2.34.0 --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0004-Highlight-non-existent-commands-in-M-x-shell.patch Content-Transfer-Encoding: quoted-printable From=20a30796d8bafd3cea96f58339a4258ad25890f683 Mon Sep 17 00:00:00 2001 From: =3D?UTF-8?q?Miha=3D20Rihtar=3DC5=3DA1i=3DC4=3D8D?=3D Date: Mon, 15 Nov 2021 23:21:03 +0100 Subject: [PATCH 4/5] Highlight non-existent commands in M-x shell * lisp/shell.el (shell-mode): Enable highlighting of non-existent commands if requested. (shell-highlight-undef-aliases): (shell-highlight-undef-remote-file-name-inhibit-cache): New user options. (shell-highlight-undef-mode): New minor mode. (shell-highlight-undef-defined-face): (shell-highlight-undef-undefined-face): (shell-highlight-undef-alias-face): New faces. (shell-highlight-undef--exec-cache): (shell-highlight-undef--face): (shell-highlight-undef-keywords): (shell-highlight-undef-regexp): (shell-highlight-undef--executable-find): (shell-highlight-undef-matcher): (shell-highlight-undef--indirect): (shell-highlight--setup): (shell-highlight-undef-reset-mode): New functions and buffer local variables. =2D-- lisp/shell.el | 227 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 224 insertions(+), 3 deletions(-) diff --git a/lisp/shell.el b/lisp/shell.el index a91a59f070..274716f68a 100644 =2D-- a/lisp/shell.el +++ b/lisp/shell.el @@ -322,6 +322,16 @@ shell-indirect-setup-hook :safe 'booleanp :version "29.1") =20 +(defcustom shell-highlight-undef-enable nil + "Enable highlighting of undefined commands in shell buffers. +This variable only has effect when the shell is started. Use the +command `shell-highlight-undef-mode' to toggle highlighting of +undefined commands." + :type 'boolean + :group 'shell + :safe 'booleanp + :version "29.1") + (defvar shell-dirstack nil "List of directories saved by pushd in this buffer's shell. Thus, this does not include the shell's current directory.") @@ -591,9 +601,11 @@ shell-mode buffer." :interactive nil :after-hook =2D (and (null comint-use-prompt-regexp) =2D shell-comint-fl-enable =2D (comint-fl-mode)) + (unless comint-use-prompt-regexp + (if shell-comint-fl-enable + (comint-fl-mode)) + (if shell-highlight-undef-enable + (shell-highlight-undef-mode))) =20 (setq comint-prompt-regexp shell-prompt-pattern) (shell-completion-vars) @@ -1471,6 +1483,215 @@ shell-narrow-to-prompt (point-max) (shell--prompt-begin-position)))))) =20 +;;; Highlight undefined commands +;; +;; To highlight non-existent commands, customize +;; `shell-highlight-undef-enable' to t. To highlight some commands as +;; aliases, add them to `shell-highlight-undef-aliases'. + +(defcustom shell-highlight-undef-aliases nil + "List of commands to highlight as a command alias." + :group 'shell + :type '(repeat string) + :version "29.1") + +(defface shell-highlight-undef-defined-face + '((t :inherit 'font-lock-function-name-face)) + "Face used for existent commands." + :group 'shell + :version "29.1") + +(defface shell-highlight-undef-undefined-face + '((t :inherit 'font-lock-warning-face)) + "Face used for non-existent commands." + :group 'shell + :version "29.1") + +(defface shell-highlight-undef-alias-face + '((t :inherit 'font-lock-variable-name-face)) + "Face used for command aliases." + :group 'shell + :version "29.1") + +(defcustom shell-highlight-undef-remote-file-name-inhibit-cache nil + "Whether to use cache for finding remote executables. +See `remote-file-name-inhibit-cache' for description." + :group 'faces + :type '(choice + (const :tag "Do not inhibit file name cache" nil) + (const :tag "Do not use file name cache" t) + (integer :tag "Do not use file name cache" + :format "Do not use file name cache older than %v secon= ds" + :value 10)) + :version "29.1") + +(defvar shell--highlight-undef-exec-cache nil + "Cache of executable files found in `exec-path'. +An alist, whose elements are of the form +\(REMOTE TIME EXECUTABLES), where REMOTE is a string, returned by +`file-remote-p', TIME is the return value of `float-time' end +EXECUTABLES is a hash table with keys being the base-names of +executable files. + +Cache expiry is controlled by the user option +`remote-file-name-inhibit-cache'.") + +(defvar shell--highlight-undef-face 'shell-highlight-undef-defined-face) + +(defvar shell-highlight-undef-keywords + `((,#'shell-highlight-undef-matcher 6 shell--highlight-undef-face))) + +(defvar-local shell-highlight-undef-regexp regexp-unmatchable) + +(defun shell--highlight-undef-executable-find (command) + "Return non-nil if COMMAND is found in `exec-path'. +Similar to `executable-find', but use cache stored in +`shell--highlight-undef-exec-cache'." + (let ((remote (file-remote-p default-directory)) + as ret found-in-cache delta-time) + (if (null remote) + (executable-find command) + + (setq delta-time + shell-highlight-undef-remote-file-name-inhibit-cache) + + (pcase (setq as (assoc remote shell--highlight-undef-exec-cache)) + (`(,_ ,time ,hash) + (when (pcase delta-time + ((pred numberp) (<=3D (float-time) (+ time delta-time))) + ('t nil) + ('nil t)) + (setq ret (gethash command hash)) + (setq found-in-cache t))) + (_ (setq as (list remote 0 (make-hash-table :test #'equal))) + (push as shell--highlight-undef-exec-cache))) + + (if found-in-cache + ret + ;; Build cache + (setcar (cdr as) (float-time)) + (let ((hash (clrhash (caddr as)))) + (dolist (dir (exec-path)) + (pcase-dolist (`(,f . ,attr) + (condition-case nil + (directory-files-and-attributes + (concat remote dir) nil nil 'nosort 'integ= er) + (file-error nil))) + ;; Approximation. Assume every non-directory file in $PATH = is an + ;; executable. Alternatively, we could check + ;; `file-executable-p', but doing so for every file in $PATH= is + ;; slow on remote machines. + (unless (eq t (file-attribute-type attr)) + (puthash f t hash)))) + (gethash command hash)))))) + +(defun shell-highlight-undef-matcher (end) + "Matcher used to highlight commands up to END." + (when (re-search-forward shell-highlight-undef-regexp end t) + (save-match-data + (let ((cmd (match-string 6)) + (beg (match-beginning 6))) + (setq shell--highlight-undef-face + (let* ((buf (buffer-base-buffer)) + (default-directory + (if buf (buffer-local-value 'default-directory buf) + default-directory))) + (cond + ;; Don't highlight command output. Mostly useful if + ;; `comint-fl-mode' is disabled. + ((text-property-any beg (point) 'field 'output) + nil) + ((member cmd shell-highlight-undef-aliases) + 'shell-highlight-undef-alias-face) + ;; Check if it contains a directory separator + ((file-name-directory cmd) + (when (file-name-absolute-p cmd) + (setq cmd (concat + (or (bound-and-true-p comint-file-name-pref= ix) + (file-remote-p default-directory)) + cmd))) + (if (or (file-executable-p cmd) + (file-directory-p cmd)) + 'shell-highlight-undef-defined-face + 'shell-highlight-undef-undefined-face)) + ((shell--highlight-undef-executable-find cmd) + 'shell-highlight-undef-defined-face) + (t 'shell-highlight-undef-undefined-face)))))) + t)) + +(defvar-local shell--highlight-undef-indirect nil + "Whether we are using a `comint-fl-mode' indirect buffer.") + +(declare-function sh-feature "sh-script" (alist &optional function)) +(defvar sh-leading-keywords) +(defvar sh-other-keywords) + +(define-minor-mode shell-highlight-undef-mode + "Highlight undefined shell commands and aliases." + :init-value nil + (if shell--highlight-undef-indirect + (progn + (setq shell--highlight-undef-indirect nil) + (when-let ((buf (comint-indirect-buffer t))) + (with-current-buffer buf + (font-lock-remove-keywords nil shell-highlight-undef-keywords)= ))) + (font-lock-remove-keywords nil shell-highlight-undef-keywords)) + (remove-hook 'comint-fl-mode-hook + #'shell-highlight-undef-reset-mode t) + (remove-hook 'comint-indirect-setup-hook #'shell--highlight-undef-setup = t) + + (when shell-highlight-undef-mode + (when comint-use-prompt-regexp + (setq shell-highlight-undef-mode nil) + (error + "`shell-highlight-undef-mode' is incompatible with `comint-use-prom= pt-regexp'")) + + (require 'sh-script) + + (let ((regexp + ;; Adapted from `sh-font-lock-keywords-1' + (concat + "\\(" + "[;(){}`|&]" + (if comint-fl-mode + ;; `comint-fl-mode' already puts point-min on end of + ;; prompt + "" + (concat "\\|" comint-prompt-regexp)) + "\\|^" + "\\)" + "[ \t]*\\(\\(" + (regexp-opt (sh-feature sh-leading-keywords) t) + "[ \t]+\\)?" + (regexp-opt (append (sh-feature sh-leading-keywords) + (sh-feature sh-other-keywords)) + t) + "[ \t]+\\)?\\_<\\(\\(?:\\s_\\|\\sw\\|/\\)+\\)\\_>"))) + (cond (comint-fl-mode + (setq shell--highlight-undef-indirect t) + (if-let ((buf (comint-indirect-buffer t))) + (with-current-buffer buf + (shell--highlight-undef-setup regexp)) + (add-hook 'comint-indirect-setup-hook + (lambda () + (shell--highlight-undef-setup regexp)) + nil t))) + (t (shell--highlight-undef-setup regexp)))) + + (add-hook 'comint-fl-mode-hook + #'shell-highlight-undef-reset-mode nil t)) + + (font-lock-flush)) + +(defun shell--highlight-undef-setup (regexp) + (font-lock-add-keywords nil shell-highlight-undef-keywords t) + (setq shell-highlight-undef-regexp regexp)) + +(defun shell-highlight-undef-reset-mode () + "If `shell-highlight-undef-mode' is on, turn it off and on." + (when shell-highlight-undef-mode + (shell-highlight-undef-mode 1))) + (provide 'shell) =20 ;;; shell.el ends here =2D-=20 2.34.0 --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0005-Input-indentation-for-M-x-shell.patch Content-Transfer-Encoding: quoted-printable From=207fb17ea46239f514b43ac179b17f76a31bee6650 Mon Sep 17 00:00:00 2001 From: =3D?UTF-8?q?Miha=3D20Rihtar=3DC5=3DA1i=3DC4=3D8D?=3D Date: Mon, 15 Nov 2021 23:37:36 +0100 Subject: [PATCH 5/5] Input indentation for M-x shell * lisp/comint.el (comint-indent-input-line): (comint-indent-input-line-default): (comint-indent-input-region): (comint-indent-input-region-default): New functions that implement a general mechanism for input indentation through an indirect buffer in comint derived major modes. * lisp/shell.el (shell-mode): Set up input indentation according to sh-mode. =2D-- lisp/comint.el | 93 +++++++++++++++++++++++++++++++++++++++++++++----- lisp/shell.el | 4 +++ 2 files changed, 89 insertions(+), 8 deletions(-) diff --git a/lisp/comint.el b/lisp/comint.el index 7709178afd..0d115e7c70 100644 =2D-- a/lisp/comint.el +++ b/lisp/comint.el @@ -4043,21 +4043,21 @@ comint-osc-hyperlink-handler (cons (point-marker) (match-string-no-properties 1 text))))) =20 =2D;;; Input fontification through an indirect buffer +;;; Input fontification and indentation through an indirect buffer ;;=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D ;; =2D;; Modes derived from `comint-mode' can set up fontification input =2D;; text with the help of an indirect buffer whose major mode and =2D;; font-lock settings are set accordingly. +;; Modes derived from `comint-mode' can set up fontification and +;; indentation of input text with the help of an indirect buffer whose +;; major mode and font-lock settings are set accordingly. =20 (defvar-local comint-indirect-setup-function nil "Function to set up an indirect comint fontification buffer. This function is called by `comint-indirect-buffer' with zero arguments after making an indirect buffer. It is usually set to =2Da major-mode command whose font-locking is desired for input =2Dtext. In order to prevent possible mode hooks from running, the =2Dvariable `delay-mode-hooks' is set to t prior to calling this =2Dfunction and `change-major-mode-hook' along with +a major-mode command whose font-locking and indentation are +desired for input text. In order to prevent possible mode hooks +from running, the variable `delay-mode-hooks' is set to t prior +to calling this function and `change-major-mode-hook' along with `after-change-major-mode-hook' are bound to nil.") =20 (defcustom comint-indirect-setup-hook nil @@ -4216,6 +4216,83 @@ comint--intersect-regions (when return-beg (cons (car return-beg) (car return-end))))) =20 +(defun comint-indent-input-line (fun) + "Indent current process output or input line. +If point is on output, call FUN, otherwise indent the current +line in the indirect buffer created by `comint-indirect-buffer', +which see." + (if (or comint-use-prompt-regexp + (eq (get-text-property (point) 'field) 'output)) + (funcall fun) + (let ((point (point)) + (min (point-min)) + (max (point-max))) + (unwind-protect + (with-current-buffer (comint-indirect-buffer) + (narrow-to-region min max) + (goto-char point) + (narrow-to-region (field-beginning) (field-end)) + (unwind-protect (funcall indent-line-function) + (setq point (point)))) + (goto-char point))))) + +(defun comint-indent-input-region (fun start end) + "Indent process output and input regions between START and END. +Output text between START and END is indented with FUN and input +text is indented in the indirect buffer created by +`comint-indirect-buffer', which see." + (if comint-use-prompt-regexp + (funcall fun start end) + (let ((opoint (copy-marker (point))) + final-point) + (unwind-protect + (comint--intersect-regions + (lambda (start end) + (goto-char opoint) + (if (=3D opoint (point)) + (unwind-protect (funcall fun start end) + (setq final-point (copy-marker (point)))) + (funcall fun start end))) + (lambda (start end) + (let ((min (point-min)) + (max (point-max)) + (final-point1 nil)) + (unwind-protect + (with-current-buffer (comint-indirect-buffer) + (narrow-to-region min max) + (goto-char opoint) + (if (=3D opoint (point)) + (unwind-protect + (funcall indent-region-function start end) + (setq final-point1 (point))) + (funcall indent-region-function start end))) + (when final-point1 + (setq final-point (copy-marker final-point1)))))) + start end) + (if final-point + (progn + (goto-char final-point) + (set-marker final-point nil)) + (goto-char opoint)) + (set-marker opoint nil))))) + +(defun comint-indent-input-line-default () + "Indent current process output or input line. +If point is on output, indent the current line according to the +default value of `indent-line-function', otherwise indent the +current line in the indirect buffer created by +`comint-indirect-buffer', which see." + (comint-indent-input-line (default-value 'indent-line-function))) + +(defun comint-indent-input-region-default (start end) + "Indent process output and input regions between START and END. +Output text between START and END is indented according to the +default value of `indent-region-function' and input text is +indented in the indirect buffer created by +`comint-indirect-buffer', which see." + (comint-indent-input-region (default-value 'indent-line-function) + start end)) + (defun comint-indirect-buffer (&optional no-create) "Return an indirect comint fontification buffer. If an indirect buffer for the current buffer already exists, diff --git a/lisp/shell.el b/lisp/shell.el index 274716f68a..0662b77c55 100644 =2D-- a/lisp/shell.el +++ b/lisp/shell.el @@ -639,6 +639,10 @@ shell-mode (message-log-max nil)) (sh-mode))))) =20 + (setq-local indent-line-function #'comint-indent-input-line-default) + (setq-local indent-region-function + #'comint-indent-input-region-default) + ;; This is not really correct, since the shell buffer does not really ;; edit this directory. But it is useful in the buffer list and menus. (setq list-buffers-directory (expand-file-name default-directory)) =2D-=20 2.34.0 --=-=-=-- --==-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQJHBAEBCAAxFiEEmxVnesoT5rQXvVXnswkaGpIVmT8FAmGWVgQTHG1paGFAa2Ft bml0bmlrLnRvcAAKCRCzCRoakhWZP4dHD/0bMHNv92LBciGx4jtGl5Awv575eKy0 RD38UsV48bzI9KK32krUlw0Otl2tpxbXvdY2E1ce/HtL4m/MNjYMLtoLYlatBW5H 2oGhrFdXvJNHjgZu8yvRc5fNAcp8PWHnHPAf3GQyccAmJgs84W62fRikX405OOlM vCT2WWadwPErIItf94fpxKp3TTvClJ2gdW5PTcUZvBboDMO0PMibBkDHDrKyr6TU 1yO4yP+zHjU/3E5oou0BrHPGY92xALlIH/3fzH8KsVVbU+8yGuOdhHFslKPhbcgL zI8uW0lcqGqCVyqi080OLCILrAEUJpiZypBuuWSRnxlykAjoppvUsIr4oxLlOGKz l0xg0V2CZfEqRcbWSp7eprcvbjefR1Oib0zR7/rECySiVztIwlCwJ/l6Ry96/lEd Jmxs3U5UGAFNixc7F3QQ7l/SNtLH8wkCNhIQUZ4kJEK0KMLVh8XHpRIYNDaMlv/H OuD4RaXeVI678CikZJ/rhE+HHzao0g1AXBzMOXxb5zFCP58R9dZuB0oudZ+aIAV8 Rk9c+NSAtvycQlh2QsAhgzSSOeC/+7flFqpF3+a3QOmnO09PI1lYl3xvahJBr4XF kaQ25jKJqnlEOm1CUW5Hm3U4TNSGJoWK8u09MBBMimi2ghk+vzdv3T6SyoP0AZ5S ha6D3KG2McPUCA== =AXCI -----END PGP SIGNATURE----- --==-=-=--