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: Sun, 28 Nov 2021 13:20:27 +0100 Message-ID: <871r30jwtw.fsf@miha-pc> References: <87sfvteqx2.fsf@miha-pc> <83ee7dyar2.fsf@gnu.org> <87pmqxeghn.fsf@miha-pc> <8335nk7bz1.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="6702"; 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 Sun Nov 28 13:16:20 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 1mrJ6S-0001XL-Ba for geb-bug-gnu-emacs@m.gmane-mx.org; Sun, 28 Nov 2021 13:16:20 +0100 Original-Received: from localhost ([::1]:58710 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mrJ6R-0002C1-4K for geb-bug-gnu-emacs@m.gmane-mx.org; Sun, 28 Nov 2021 07:16:19 -0500 Original-Received: from eggs.gnu.org ([209.51.188.92]:53410) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mrJ6B-00028u-BH for bug-gnu-emacs@gnu.org; Sun, 28 Nov 2021 07:16:03 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]:51754) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mrJ6B-0006T9-2i for bug-gnu-emacs@gnu.org; Sun, 28 Nov 2021 07:16:03 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1mrJ6A-00079x-VX for bug-gnu-emacs@gnu.org; Sun, 28 Nov 2021 07:16:02 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sun, 28 Nov 2021 12:16:02 +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.163810174127485 (code B ref 51940); Sun, 28 Nov 2021 12:16:02 +0000 Original-Received: (at 51940) by debbugs.gnu.org; 28 Nov 2021 12:15:41 +0000 Original-Received: from localhost ([127.0.0.1]:35066 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1mrJ5n-00079D-JZ for submit@debbugs.gnu.org; Sun, 28 Nov 2021 07:15:41 -0500 Original-Received: from kamnitnik.top ([209.250.245.214]:54918) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1mrJ5k-000794-LK for 51940@debbugs.gnu.org; Sun, 28 Nov 2021 07:15:38 -0500 Original-Received: from localhost (BSN-77-156-43.static.siol.net [193.77.156.43]) by kamnitnik.top (Postfix) with ESMTPSA id 75C659C717; Sun, 28 Nov 2021 12:15:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kamnitnik.top; s=mail; t=1638101735; bh=rwbjzIstmLQiWMibAhkkYqK0TcBChQdQ023mkyIY240=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=UfzMtvr9aFsnGbDhnR70K+3znvjUm7A1Trt3gxvsLVP7aSWOcOULSfTF7Zs/+AsSk ooCqyJSHoOPi/X4PTPEDKzDp6jtj72vo4c6e3zrIqf6WxAEKHIUsCf393ghvzmwbWu dKLxc5QVEyKOm+A14OWPUUAAClxXBdWtrjUyaSomuLi9rN1yaxmNRCXYhEOIKdnFU2 FrAeHxocxRGResHmlOXzWPTloUc/1+6B6hD3mdyY7WaQIWPTE0rl7wLZl02T7iZNT8 /tqk799ys5e6G1GH9BnmhEC1MUeixm7uZmq9rANZoWfrQH7oWcye0ALJvKgDJf0321 vmtWtyScWOTVw== In-Reply-To: <8335nk7bz1.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:220964 Archived-At: --==-=-= Content-Type: multipart/mixed; boundary="=-=-=" --=-=-= Content-Type: text/plain Eli Zaretskii writes: > The doc strings are still not specific enough, sorry for being unclear > in what I meant. Let's take an example: > >> +(define-minor-mode comint-fl-mode >> + "Enable input fontification. > > The first line should say something like > > Enable input fontification in comint buffers. > >> +(defun comint--fl-off () >> + "Disable input fontification for the current buffer." > > Similarly here: > > Disable input fontification in the current comint buffer. > >> +(defun comint--fl-fontify-region (fun beg end verbose) >> + "Around advice for `font-lock-fontify-region-function'. > > Likewise here: mention comint in the first line. > >> +(defcustom shell-highlight-undef-aliases nil >> + "List of commands to highlight as a command alias." > > Here, the doc string should mention shell command aliases, otherwise > it's too general ("command alias" doesn't necessarily imply a shell). > > Please go over the doc strings with this criterion in mind, and see > what else needs to be fixed in the same way. Thanks for feedback, revised patches attached. I hope I've clarified the doc strings this time. I've also made a minor code simplification in the fourth patch. Best regards. --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0001-Implement-a-general-input-fontification-mechanism-fo.patch Content-Transfer-Encoding: quoted-printable From=20ec5c05b0b32ce3ed52d95307f5902b7589e7aefd 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 | 229 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) diff --git a/lisp/comint.el b/lisp/comint.el index 544f0b8b82..dbbd687e18 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,234 @@ 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 in the current comint buffer. +This minor mode is useful if the current major mode derives from +`comint-mode' and if `comint-indirect-setup-function' is set. +Comint modes that support input fontification usually set this +variable buffer-locally to a major-mode command whose +font-locking is desired for input text. + +Input text is fontified through an indirect buffer created with +`comint-indirect-buffer', which see. + +This function signals an error if `comint-use-prompt-regexp' is +non-nil. 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 in the current comint 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 in the current comint 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) + "Fontify process output and user input in the current comint buffer. +First, highlight the region between BEG and END 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 comint 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=20c4eac600dffc7cfd04e260059cb40757f46fa8a0 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=20948c02d0a03c18bbfbd098f9cbe668e5dd30b6f7 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 | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/lisp/ielm.el b/lisp/ielm.el index 39820a893a..853868fa70 100644 =2D-- a/lisp/ielm.el +++ b/lisp/ielm.el @@ -472,6 +472,27 @@ 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 creating an ielm buffer. Use +the command `comint-fl-mode' to toggle highlighting of input in +an already existing ielm buffer." + :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 +547,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 +589,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=200f3ec0f20a407f68aff6cafd6694f492ca3f8d44 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 | 230 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 3 deletions(-) diff --git a/lisp/shell.el b/lisp/shell.el index a91a59f070..71a37c37ca 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,218 @@ shell-narrow-to-prompt (point-max) (shell--prompt-begin-position)))))) =20 +;;; Highlight undefined commands +;; +;; To highlight non-existent shell commands, customize +;; `shell-highlight-undef-enable' to t. To highlight some shell +;; commands as aliases, add them to `shell-highlight-undef-aliases'. + +(defcustom shell-highlight-undef-aliases nil + "List of shell 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 shell commands." + :group 'shell + :version "29.1") + +(defface shell-highlight-undef-undefined-face + '((t :inherit 'font-lock-warning-face)) + "Face used for non-existent shell commands." + :group 'shell + :version "29.1") + +(defface shell-highlight-undef-alias-face + '((t :inherit 'font-lock-variable-name-face)) + "Face used for shell command aliases." + :group 'shell + :version "29.1") + +(defcustom shell-highlight-undef-remote-file-name-inhibit-cache nil + "Whether to use cache to determine fontification a shell command. +When fontification of non-existent commands is enabled on a +remote shell buffer, use cache to speed up searching for +executable files on the remote machine. This options is used to +control expiry of this cache. 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 shell 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 + "t if shell commands are fontified in `comint-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. +This minor mode is mostly useful in `shell-mode' buffers and +works better if `comint-fl-mode' is enabled." + :init-value nil + (if shell--highlight-undef-indirect + (progn + (remove-hook 'comint-indirect-setup-hook shell--highlight-undef-in= direct t) + (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) + + (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\\|/\\)+\\)\\_>")) + (setup + (lambda () + (setq shell-highlight-undef-regexp regexp) + (font-lock-add-keywords nil shell-highlight-undef-keywords t= )))) + (cond (comint-fl-mode + (setq shell--highlight-undef-indirect setup) + (if-let ((buf (comint-indirect-buffer t))) + (with-current-buffer buf + (funcall setup)) + (add-hook 'comint-indirect-setup-hook setup nil t))) + (t (funcall setup)))) + + (add-hook 'comint-fl-mode-hook + #'shell-highlight-undef-reset-mode nil t)) + + (font-lock-flush)) + +(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=2068c23c6e154e9386cb95e1e4737dc4ea980663c4 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 dbbd687e18..23ec9cbabb 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 @@ -4222,6 +4222,83 @@ comint--intersect-regions (when return-beg (cons (car return-beg) (car return-end))))) =20 +(defun comint-indent-input-line (fun) + "Indent current line from comint process output or input. +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 comint process output and input 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 line from comint process output or input. +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 comint process output and input 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 71a37c37ca..3b1244fa8d 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----- iQJHBAEBCAAxFiEEmxVnesoT5rQXvVXnswkaGpIVmT8FAmGjdAsTHG1paGFAa2Ft bml0bmlrLnRvcAAKCRCzCRoakhWZP3YoD/4tNFN/7IrtjRxEWs8EMi0Blj2g57Dl lnHV24ev3guk0rA1oJCJpUVpnlyY3pscQ4kwlfPSkqy9IfdbE6jWjlmURxAkxXUr SqpKFYVox+JZjVd71T/3NGYbNlsbrRM75g3vXE+q6MFXVcVVsduAHQ1BgW4EEbQg 47/JDeBUU8OlLt170F0TcsAnjm+uq35Yp5zYOyki5ykHaqNFzAK+A2KO99758u5V 6MjmLRFZ9IiFAJpqicGZ5j2F6PCthg4YXfZXYlWcw0JBcAzfzBrQgbDUa7u+2Khg AEDnTSd9MesGeIre+IAuRzemc1DEY8/QlftJeWgkLEA9w0rUhB6tPo+fTfnxgRaq QXpv9/TIhnRys9YiETS0Do0Nqc0XACgVcmKebBV9zfip1UsxsOBjCb2UxfNKkJLw rAISqcLF+Kmlvy1eos3Hib3ENpJreAGew/3573kmfo1h85Z5Pf4qo/vBNUK8lYaI Bpe1vXQRLYk86x1X+dKw5UcAaMDKKBldtOTfxaQLGqa/uUwHFWdtbrX5hGSCLYJj Gk6tR+h4jfGIPM67sQ1PKl+jUXvwpbKqtnxvK4XdH1raQZygY6ki6A/JoFKw8V1n iNBmmWf3EIBy2wMB2fymsFBPnXlzlrYgLccB4saO1opA5NNDLgYO76hT2S7BxluH 26VYBeC6GqiV/w== =exM+ -----END PGP SIGNATURE----- --==-=-=--