From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Stefan Monnier via "Bug reports for GNU Emacs, the Swiss army knife of text editors" Newsgroups: gmane.emacs.bugs Subject: bug#63861: [PATCH] pp.el: New "pretty printing" code Date: Fri, 16 Jun 2023 14:26:54 -0400 Message-ID: References: <83fs799jmi.fsf@gnu.org> <83r0qs74qs.fsf@gnu.org> <83pm5zws19.fsf@gnu.org> Reply-To: Stefan Monnier Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="10438"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Cc: 63861@debbugs.gnu.org To: Eli Zaretskii Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Fri Jun 16 20:28:19 2023 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 1qAEBG-0002Tv-5B for geb-bug-gnu-emacs@m.gmane-mx.org; Fri, 16 Jun 2023 20:28:18 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qAEB4-0005Qs-9D; Fri, 16 Jun 2023 14:28:06 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1qAEB0-0005QM-Ai for bug-gnu-emacs@gnu.org; Fri, 16 Jun 2023 14:28:02 -0400 Original-Received: from debbugs.gnu.org ([209.51.188.43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1qAEB0-0007mX-28 for bug-gnu-emacs@gnu.org; Fri, 16 Jun 2023 14:28:02 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1qAEAz-0000pa-TR for bug-gnu-emacs@gnu.org; Fri, 16 Jun 2023 14:28:01 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Stefan Monnier Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Fri, 16 Jun 2023 18:28:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 63861 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 63861-submit@debbugs.gnu.org id=B63861.16869400313133 (code B ref 63861); Fri, 16 Jun 2023 18:28:01 +0000 Original-Received: (at 63861) by debbugs.gnu.org; 16 Jun 2023 18:27:11 +0000 Original-Received: from localhost ([127.0.0.1]:50343 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qAEA9-0000oS-U7 for submit@debbugs.gnu.org; Fri, 16 Jun 2023 14:27:11 -0400 Original-Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:8354) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1qAEA7-0000oE-Ni for 63861@debbugs.gnu.org; Fri, 16 Jun 2023 14:27:09 -0400 Original-Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 295671004AE; Fri, 16 Jun 2023 14:27:02 -0400 (EDT) Original-Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 612BF100048; Fri, 16 Jun 2023 14:26:55 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1686940015; bh=esjnikzyDPL9R3c2hai8U0PX15APhvyuyaKEyhkrvjc=; h=From:To:Cc:Subject:In-Reply-To:References:Date:From; b=bfMjOizC/qVhhslqght1beBbqPe+CLEkOLAX945mft3k4Wmjg/V0+pHLZZJkWZiR1 kXRVHO/FVBKt0u2k+L3IdM4VliMBrRIGKOj6OumvL1zOg6Bxx3FUh1aRk59WXj4AvL xM98LLQpCKMnPD7dsccOfwSAtYMvtsNUIURapsBaC2eH9ecYPK9fc5DckksUi4OIg/ l8JEcpazGCzdE134f/L2e88oKH05l5DwIhe33V9KzkHB6Tp7+GEtnvZ2e6O7jF41Fs hqMuqN7/jTAFmVxZgd/tp2grpJmrQc4HzuuOeFHYQnfTWKwijpDH0FQEuUtux6NFZu Kv1GxvkVXQ54A== Original-Received: from alfajor (unknown [45.44.229.252]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id 18BD6120803; Fri, 16 Jun 2023 14:26:55 -0400 (EDT) In-Reply-To: <83pm5zws19.fsf@gnu.org> (Eli Zaretskii's message of "Tue, 13 Jun 2023 13:55:14 +0300") 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-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.bugs:263484 Archived-At: --=-=-= Content-Type: text/plain > Otherwise LGTM, thanks. OK, I think I have it almost ready see the patches below. I just hit one snag when trying to fix the tests. We have for example the following test: (ert-deftest pp-print-quote () (should (string= (pp-to-string 'quote) "quote")) (should (string= (pp-to-string ''quote) "'quote")) (should (string= (pp-to-string '('a 'b)) "('a 'b)\n")) (should (string= (pp-to-string '(''quote 'quote)) "(''quote 'quote)\n")) This is how the old code behaved, i.e. the output sometimes ends with \n and sometimes not, depending on whether the object printed is a list or not. Currently, my new code behaves the same when using `pp-28` or `pp-29` but when using the new default (i.e. `pp-fill`) the output never ends in \n. This change was not intentional, but I think it makes sense because it's more consistent. I'm not completely sure how we should fix this. I think the old behavior of sometimes adding \n and sometimes not is not desirable, so I think we should change it (a backward incompatible change). We have two remaining choices: A) never add \n B) always add \n AFAICT, in practice the old behavior resulted in a \n added in most cases, so (B) should lead to less breakage, but OTOH I think (A) would be cleaner since it's easier for callers to add a \n when needed than for them to remove a \n. WDYT? A or B? Stefan --=-=-= Content-Type: text/x-diff; charset=iso-8859-1 Content-Disposition: inline; filename=0001-lisp-emacs-lisp-lisp-mode.el-lisp-ppss-Fix-performan.patch Content-Transfer-Encoding: quoted-printable >From 31bc44c81386f8db2aecfe1529d051fed1367df9 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Fri, 16 Jun 2023 13:14:27 -0400 Subject: [PATCH 1/5] * lisp/emacs-lisp/lisp-mode.el (lisp-ppss): Fix performance bug MIME-Version: 1.0 Content-Type: text/plain; charset=3DUTF-8 Content-Transfer-Encoding: 8bit (nth 2 ppss) can be absent but not incorrect, so don't recompute ppss for (nth 2 ppss) when (nth 2 ppss) is already provided. When calling `lisp-indent-line` on all the lines in a region, this sometimes introduced a gratuitous O(N=B2) complexity. --- lisp/emacs-lisp/lisp-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el index d44c9d6e23d..9914ededb85 100644 --- a/lisp/emacs-lisp/lisp-mode.el +++ b/lisp/emacs-lisp/lisp-mode.el @@ -876,7 +876,7 @@ lisp-ppss 2 (counting from 0). This is important for Lisp indentation." (unless pos (setq pos (point))) (let ((pss (syntax-ppss pos))) - (if (nth 9 pss) + (if (and (not (nth 2 pss)) (nth 9 pss)) (let ((sexp-start (car (last (nth 9 pss))))) (parse-partial-sexp sexp-start pos nil nil (syntax-ppss sexp-sta= rt))) pss))) --=20 2.39.2 --=-=-= Content-Type: text/x-diff; charset=utf-8 Content-Disposition: inline; filename=0002-pp.el-pp-default-function-New-custom-var.patch Content-Transfer-Encoding: quoted-printable >From 16c8fa5e209e5d13f86e87a84a678608de0d5341 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Fri, 16 Jun 2023 13:31:13 -0400 Subject: [PATCH 2/5] pp.el (pp-default-function): New custom var * lisp/emacs-lisp/pp.el (pp-use-max-width): Make obsolete. (pp-default-function): New custom var. (pp--object, pp--region): New helper functions. (pp-29): New function, extracted from `pp-to-string`. (pp-to-string): Add `pp-function` arg and obey `pp-default-function`. (pp-28): New function, extracted from `pp-buffer`. (pp-buffer): Rewrite, using `pp` so it obeys `pp-default-function`. (pp): Add new calling convention to apply it to a region, and obey `pp-default-function`. (pp-emacs-lisp-code): Add new calling convention to apply it to a region. --- lisp/emacs-lisp/pp.el | 198 ++++++++++++++++++++++++++++++------------ 1 file changed, 144 insertions(+), 54 deletions(-) diff --git a/lisp/emacs-lisp/pp.el b/lisp/emacs-lisp/pp.el index e6e3cd6c6f4..0798e46f735 100644 --- a/lisp/emacs-lisp/pp.el +++ b/lisp/emacs-lisp/pp.el @@ -52,53 +52,132 @@ pp-use-max-width large lists." :type 'boolean :version "29.1") +(make-obsolete-variable 'pp-use-max-width 'pp-default-function "30.1") + +(defcustom pp-default-function #'pp-29 + ;; FIXME: The best pretty printer to use depends on the use-case + ;; so maybe we should allow callers to specify what they want (maybe with + ;; options like `fast', `compact', `code', `data', ...) and these + ;; can then be mapped to actual pretty-printing algorithms. + ;; Then again, callers can just directly call the corresponding function. + "Function that `pp' should dispatch to for pretty printing. +That function can be called in one of two ways: +- with a single argument, which it should insert and pretty-print at point. +- with two arguments which delimit a region containing Lisp sexps + which should be pretty-printed. +In both cases, the function can presume that the buffer is setup for +Lisp syntax." + :type '(choice + (const :tag "Emacs=E2=89=A428 algorithm, fast and good enough" p= p-28) + (const :tag "Work hard for code (slow on large inputs)" + pp-emacs-lisp-code) + (const :tag "`pp-emacs-lisp-code' if `pp-use-max-width' else `pp= -28'" + pp-29) + function) + :version "30.1") =20 (defvar pp--inhibit-function-formatting nil) =20 +;; There are basically two APIs for a pretty-printing function: +;; +;; - either the function takes an object (and prints it in addition to +;; prettifying it). +;; - or the function takes a region containing an already printed object +;; and prettifies its content. +;; +;; `pp--object' and `pp--region' are helper functions to convert one +;; API to the other. + + +(defun pp--object (object region-function) + "Pretty-print OBJECT at point. +The prettifying is done by REGION-FUNCTION which is +called with two positions as arguments and should fold lines +within that region. Returns the result as a string." + (let ((print-escape-newlines pp-escape-newlines) + (print-quoted t) + (beg (point))) + ;; FIXME: In many cases it would be preferable to use `cl-prin1' here. + (prin1 object (current-buffer)) + (funcall region-function beg (point)))) + +(defun pp--region (beg end object-function) + "Pretty-print the object(s) contained within BEG..END. +OBJECT-FUNCTION is called with a single object as argument +and should pretty print it at point into the current buffer." + (save-excursion + (with-restriction beg end + (goto-char (point-min)) + (while + (progn + ;; We'll throw away all the comments within objects, but let's + ;; try at least to preserve the comments between objects. + (forward-comment (point-max)) + (let ((beg (point)) + (object (ignore-error end-of-buffer + (list (read (current-buffer)))))) + (when (consp object) + (delete-region beg (point)) + (funcall object-function (car object)) + t))))))) + +(defun pp-29 (beg-or-sexp &optional end) ;FIXME: Better name? + "Prettify the current region with printed representation of a Lisp objec= t. +Uses the pretty-printing algorithm that was standard in Emacs-29, +which, depending on `pp-use-max-width', will either use `pp-28' +or `pp-emacs-lisp-code'." + (if pp-use-max-width + (let ((pp--inhibit-function-formatting t)) ;FIXME: Why? + (pp-emacs-lisp-code beg-or-sexp end)) + (pp-28 beg-or-sexp end))) + ;;;###autoload -(defun pp-to-string (object) +(defun pp-to-string (object &optional pp-function) "Return a string containing the pretty-printed representation of OBJECT. OBJECT can be any Lisp object. Quoting characters are used as needed -to make output that `read' can handle, whenever this is possible." - (if pp-use-max-width - (let ((pp--inhibit-function-formatting t)) - (with-temp-buffer - (pp-emacs-lisp-code object) - (buffer-string))) - (with-temp-buffer - (lisp-mode-variables nil) - (set-syntax-table emacs-lisp-mode-syntax-table) - (let ((print-escape-newlines pp-escape-newlines) - (print-quoted t)) - (prin1 object (current-buffer))) - (pp-buffer) - (buffer-string)))) +to make output that `read' can handle, whenever this is possible. +Optional argument PP-FUNCTION overrides `pp-default-function'." + (with-temp-buffer + (lisp-mode-variables nil) + (set-syntax-table emacs-lisp-mode-syntax-table) + (funcall (or pp-function pp-default-function) object) + (buffer-string))) =20 ;;;###autoload (defun pp-buffer () "Prettify the current buffer with printed representation of a Lisp objec= t." (interactive) - (goto-char (point-min)) - (while (not (eobp)) - (cond - ((ignore-errors (down-list 1) t) - (save-excursion - (backward-char 1) - (skip-chars-backward "'`#^") - (when (and (not (bobp)) (memq (char-before) '(?\s ?\t ?\n))) + (pp (point-min) (point-max))) + +(defun pp-28 (beg &optional end) ;FIXME: Better name? + "Prettify the current region with printed representation of a Lisp objec= t. +Uses the pretty-printing algorithm that was standard in Emacs=E2=89=A429. +Non-interactively can also be called with a single argument, in which +case that argument will be inserted pretty-printed at point." + (interactive "r") + (if (null end) (pp--object beg #'pp-29) + (save-restriction beg end + (goto-char (point-min)) + (while (not (eobp)) + (cond + ((ignore-errors (down-list 1) t) + (save-excursion + (backward-char 1) + (skip-chars-backward "'`#^") + (when (and (not (bobp)) (memq (char-before) '(?\s ?\t ?\n))) + (delete-region + (point) + (progn (skip-chars-backward " \t\n") (point))) + (insert "\n")))) + ((ignore-errors (up-list 1) t) + (skip-syntax-forward ")") (delete-region (point) - (progn (skip-chars-backward " \t\n") (point))) - (insert "\n")))) - ((ignore-errors (up-list 1) t) - (skip-syntax-forward ")") - (delete-region - (point) - (progn (skip-chars-forward " \t\n") (point))) - (insert ?\n)) - (t (goto-char (point-max))))) - (goto-char (point-min)) - (indent-sexp)) + (progn (skip-chars-forward " \t\n") (point))) + (insert ?\n)) + (t (goto-char (point-max))))) + (goto-char (point-min)) + (indent-sexp)))) =20 ;;;###autoload (defun pp (object &optional stream) @@ -106,14 +185,22 @@ pp Quoting characters are printed as needed to make output that `read' can handle, whenever this is possible. =20 -This function does not apply special formatting rules for Emacs -Lisp code. See `pp-emacs-lisp-code' instead. - -By default, this function won't limit the line length of lists -and vectors. Bind `pp-use-max-width' to a non-nil value to do so. - -Output stream is STREAM, or value of `standard-output' (which see)." - (princ (pp-to-string object) (or stream standard-output))) +Uses the pretty-printing code specified in `pp-default-function'. + +Output stream is STREAM, or value of `standard-output' (which see). +An alternative calling convention is to pass it two buffer positions, +in which case it will prettify that region's content." + (cond + ((and (integerp object) (integerp stream)) + (funcall pp-default-function object stream)) + ((and (eq (or stream standard-output) (current-buffer)) + ;; Make sure the current buffer is setup sanely. + (eq (syntax-table) emacs-lisp-mode-syntax-table) + (eq indent-line-function #'lisp-indent-line)) + ;; Skip the buffer->string->buffer middle man. + (funcall pp-default-function object)) + (t + (princ (pp-to-string object) (or stream standard-output))))) =20 ;;;###autoload (defun pp-display-expression (expression out-buffer-name &optional lisp) @@ -220,21 +307,24 @@ pp-macroexpand-last-sexp (pp-macroexpand-expression (pp-last-sexp)))) =20 ;;;###autoload -(defun pp-emacs-lisp-code (sexp) +(defun pp-emacs-lisp-code (sexp &optional end) "Insert SEXP into the current buffer, formatted as Emacs Lisp code. Use the `pp-max-width' variable to control the desired line length. -Note that this could be slow for large SEXPs." +Note that this could be slow for large SEXPs. +Can also be called with two arguments, in which case they're taken to be +the bounds of a region containing Lisp code to pretty-print." (require 'edebug) - (let ((obuf (current-buffer))) - (with-temp-buffer - (emacs-lisp-mode) - (pp--insert-lisp sexp) - (insert "\n") - (goto-char (point-min)) - (indent-sexp) - (while (re-search-forward " +$" nil t) - (replace-match "")) - (insert-into-buffer obuf)))) + (if end (pp--region sexp end #'pp-emacs-lisp-code) + (let ((obuf (current-buffer))) + (with-temp-buffer + (emacs-lisp-mode) + (pp--insert-lisp sexp) + (insert "\n") + (goto-char (point-min)) + (indent-sexp) + (while (re-search-forward " +$" nil t) + (replace-match "")) + (insert-into-buffer obuf))))) =20 (defun pp--insert-lisp (sexp) (cl-case (type-of sexp) --=20 2.39.2 --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0003-pp.el-pp-buffer-Mark-as-obsolete.patch >From 2e10c9ef0b697fe55d6a5162312a217bc22133a1 Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Fri, 16 Jun 2023 13:21:15 -0400 Subject: [PATCH 3/5] pp.el (pp-buffer): Mark as obsolete * lisp/emacs-lisp/pp.el (pp-buffer): Mark as obsolete * lisp/org/org-table.el (org-table-fedit-lisp-indent): * lisp/emacs-lisp/lisp-mode.el (indent-pp-sexp): * lisp/emacs-lisp/backtrace.el (backtrace--multi-line): * lisp/ielm.el (ielm-eval-input): * lisp/help-fns.el (describe-variable): Use the new `pp` calling convention instead of `pp-buffer`. (help-fns-edit-variable): Use `pp` instead of `prin1` + `pp-buffer`. Use the new `pp` --- lisp/emacs-lisp/backtrace.el | 2 +- lisp/emacs-lisp/lisp-mode.el | 2 +- lisp/emacs-lisp/pp.el | 1 + lisp/help-fns.el | 9 ++++----- lisp/ielm.el | 2 +- lisp/org/org-table.el | 5 ++++- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lisp/emacs-lisp/backtrace.el b/lisp/emacs-lisp/backtrace.el index 57912c854b0..81cfafa5738 100644 --- a/lisp/emacs-lisp/backtrace.el +++ b/lisp/emacs-lisp/backtrace.el @@ -556,7 +556,7 @@ backtrace-multi-line (defun backtrace--multi-line () "Pretty print the current buffer, then remove the trailing newline." (set-syntax-table emacs-lisp-mode-syntax-table) - (pp-buffer) + (pp (point-min) (point-max)) (goto-char (1- (point-max))) (delete-char 1)) diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el index 9914ededb85..83b374551fc 100644 --- a/lisp/emacs-lisp/lisp-mode.el +++ b/lisp/emacs-lisp/lisp-mode.el @@ -1418,7 +1418,7 @@ indent-pp-sexp (save-excursion (save-restriction (narrow-to-region (point) (progn (forward-sexp 1) (point))) - (pp-buffer) + (pp (point-min) (point-max)) (goto-char (point-max)) (if (eq (char-before) ?\n) (delete-char -1))))) diff --git a/lisp/emacs-lisp/pp.el b/lisp/emacs-lisp/pp.el index 0798e46f735..65325dea6f1 100644 --- a/lisp/emacs-lisp/pp.el +++ b/lisp/emacs-lisp/pp.el @@ -146,6 +146,7 @@ pp-to-string ;;;###autoload (defun pp-buffer () "Prettify the current buffer with printed representation of a Lisp object." + (declare (obsolete pp "30")) (interactive) (pp (point-min) (point-max))) diff --git a/lisp/help-fns.el b/lisp/help-fns.el index b9388b45397..79a2b9a495b 100644 --- a/lisp/help-fns.el +++ b/lisp/help-fns.el @@ -1341,7 +1341,7 @@ describe-variable (lisp-data-mode) (set-syntax-table emacs-lisp-mode-syntax-table) (insert print-rep) - (pp-buffer) + (pp (point-min) (point-max)) (font-lock-ensure) (let ((pp-buffer (current-buffer))) (with-current-buffer buf @@ -1368,7 +1368,7 @@ describe-variable (cl-prin1 origval)) (save-restriction (narrow-to-region from (point)) - (save-excursion (pp-buffer))) + (save-excursion (pp (point-min) (point-max)))) (help-fns--editable-variable from (point) variable origval buffer) (if (< (point) (+ from 20)) @@ -1399,7 +1399,7 @@ describe-variable (cl-prin1 global-val) (save-restriction (narrow-to-region from (point)) - (save-excursion (pp-buffer))) + (save-excursion (pp (point-min) (point-max)))) ;; See previous comment for this function. ;; (help-xref-on-pp from (point)) (if (< (point) (+ from 20)) @@ -1479,8 +1479,7 @@ help-fns-edit-variable (unless var (error "No variable under point")) (pop-to-buffer-same-window (format "*edit %s*" (nth 0 var))) - (prin1 (nth 1 var) (current-buffer)) - (pp-buffer) + (pp (nth 1 var) (current-buffer)) (goto-char (point-min)) (help-fns--edit-value-mode) (insert (format ";; Edit the `%s' variable.\n" (nth 0 var)) diff --git a/lisp/ielm.el b/lisp/ielm.el index 5c370733c05..f7984ea162c 100644 --- a/lisp/ielm.el +++ b/lisp/ielm.el @@ -436,7 +436,7 @@ ielm-eval-input ;; right buffer! (with-current-buffer ielmbuf (cl-prin1 result tmpbuf)) - (pp-buffer) + (pp (point-min) (point-max)) (concat (buffer-string) aux)))))) (error (setq error-type "IELM Error") diff --git a/lisp/org/org-table.el b/lisp/org/org-table.el index 42f234790c5..ecd17c76ec2 100644 --- a/lisp/org/org-table.el +++ b/lisp/org/org-table.el @@ -3717,7 +3717,10 @@ org-table-fedit-lisp-indent (setq this-command nil) (while (re-search-forward "[ \t]*\n[ \t]*" nil t) (replace-match " "))) - (pp-buffer) + (if (fboundp 'pp-buffer) ;Obsolete since Emacs-30 + (with-suppressed-warnings ((obsolete pp-buffer)) + (pp-buffer)) + (pp (point-min) (point-max))) (untabify (point-min) (point-max)) (goto-char (1+ (point-min))) (while (re-search-forward "^." nil t) -- 2.39.2 --=-=-= Content-Type: text/x-diff; charset=utf-8 Content-Disposition: inline; filename=0004-pp.el-pp-fill-New-default-pp-function.patch Content-Transfer-Encoding: quoted-printable >From cee9fb91a200afbaa9d3e7e52d8cd1533e150acc Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Fri, 16 Jun 2023 13:35:06 -0400 Subject: [PATCH 4/5] pp.el (pp-fill): New default pp function * lisp/emacs-lisp/pp.el (pp-default-function): Change default. (pp--within-fill-column-p): New helper function. (pp-fill): New function. --- lisp/emacs-lisp/pp.el | 91 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/lisp/emacs-lisp/pp.el b/lisp/emacs-lisp/pp.el index 65325dea6f1..2dc8f7cb65d 100644 --- a/lisp/emacs-lisp/pp.el +++ b/lisp/emacs-lisp/pp.el @@ -54,7 +54,7 @@ pp-use-max-width :version "29.1") (make-obsolete-variable 'pp-use-max-width 'pp-default-function "30.1") =20 -(defcustom pp-default-function #'pp-29 +(defcustom pp-default-function #'pp-fill ;; FIXME: The best pretty printer to use depends on the use-case ;; so maybe we should allow callers to specify what they want (maybe with ;; options like `fast', `compact', `code', `data', ...) and these @@ -68,6 +68,7 @@ pp-default-function In both cases, the function can presume that the buffer is setup for Lisp syntax." :type '(choice + (const :tag "Fit within `fill-column'" pp-fill) (const :tag "Emacs=E2=89=A428 algorithm, fast and good enough" p= p-28) (const :tag "Work hard for code (slow on large inputs)" pp-emacs-lisp-code) @@ -143,6 +144,94 @@ pp-to-string (funcall (or pp-function pp-default-function) object) (buffer-string))) =20 +(defun pp--within-fill-column-p () + "Return non-nil if point is within `fill-column'." + ;; Try and make it O(fill-column) rather than O(current-column), + ;; so as to avoid major slowdowns on long lines. + ;; FIXME: This doesn't account for invisible text or `display' propertie= s :-( + (and (save-excursion + (re-search-backward + "^\\|\n" (max (point-min) (- (point) fill-column)) t)) + (<=3D (current-column) fill-column))) + +(defun pp-fill (beg &optional end) + "Break lines in Lisp code between BEG and END so it fits within `fill-co= lumn'. +Presumes the current buffer has syntax and indentation properly +configured for that. +Designed under the assumption that the region occupies a single line, +tho it should also work if that's not the case. +Can also be called with a single argument, in which case +it inserts and pretty-prints that arg at point." + (interactive "r") + (if (null end) (pp--object beg #'pp-fill) + (goto-char beg) + (let ((end (copy-marker end t)) + (newline (lambda () + (skip-chars-forward ")]}") + (unless (save-excursion (skip-chars-forward " \t") (e= olp)) + (insert "\n") + (indent-according-to-mode))))) + (while (progn (forward-comment (point-max)) + (< (point) end)) + (let ((beg (point)) + ;; Whether we're in front of an element with paired delimite= rs. + ;; Can be something funky like #'(lambda ..) or ,'#s(...). + (paired (when (looking-at "['`,#]*[[:alpha:]]*\\([({[\"]\\)") + (match-beginning 1)))) + ;; Go to the end of the sexp. + (goto-char (or (scan-sexps (or paired (point)) 1) end)) + (unless + (and + ;; The sexp is all on a single line. + (save-excursion (not (search-backward "\n" beg t))) + ;; And its end is within `fill-column'. + (or (pp--within-fill-column-p) + ;; If the end of the sexp is beyond `fill-column', + ;; try to move the sexp to its own line. + (and + (save-excursion + (goto-char beg) + (if (save-excursion (skip-chars-backward " \t({[',") + (bolp)) + ;; The sexp was already on its own line. + nil + (skip-chars-backward " \t") + (setq beg (copy-marker beg t)) + (if paired (setq paired (copy-marker paired t))) + ;; We could try to undo this insertion if it + ;; doesn't reduce the indentation depth, but I'm + ;; not sure it's worth the trouble. + (insert "\n") (indent-according-to-mode) + t)) + ;; Check again if we moved the whole exp to a new line. + (pp--within-fill-column-p)))) + ;; The sexp is spread over several lines, and/or its end is + ;; (still) beyond `fill-column'. + (when (and paired (not (eq ?\" (char-after paired)))) + ;; The sexp has sub-parts, so let's try and spread + ;; them over several lines. + (save-excursion + (goto-char beg) + (when (looking-at "(\\([^][()\" \t\n;']+\\)") + ;; Inside an expression of the form (SYM ARG1 + ;; ARG2 ... ARGn) where SYM has a `lisp-indent-function' + ;; property that's a number, insert a newline after + ;; the corresponding ARGi, because it tends to lead to + ;; more natural and less indented code. + (let* ((sym (intern-soft (match-string 1))) + (lif (and sym (get sym 'lisp-indent-function)))) + (if (eq lif 'defun) (setq lif 2)) + (when (natnump lif) + (goto-char (match-end 0)) + (forward-sexp lif) + (funcall newline))))) + (save-excursion + (pp-fill (1+ paired) (1- (point))))) + ;; Now the sexp either ends beyond `fill-column' or is + ;; spread over several lines (or both). Either way, the + ;; rest of the line should be moved to its own line. + (funcall newline))))))) + ;;;###autoload (defun pp-buffer () "Prettify the current buffer with printed representation of a Lisp objec= t." --=20 2.39.2 --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0005-lispref-streams.texi-Document-new-PP-functionality.patch >From f55500ef4033c5919783f391c079b9e5ec61fc0c Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Fri, 16 Jun 2023 13:35:36 -0400 Subject: [PATCH 5/5] lispref/streams.texi: Document new PP functionality * doc/lispref/streams.texi (Output Functions): Document new `pp` calling convention. (Output Variables): Document `pp-default-function`. --- doc/lispref/streams.texi | 24 ++++++++++++++++++++---- etc/NEWS | 10 ++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/doc/lispref/streams.texi b/doc/lispref/streams.texi index 89046a68249..2eb71b83f9c 100644 --- a/doc/lispref/streams.texi +++ b/doc/lispref/streams.texi @@ -755,10 +755,17 @@ Output Functions @end defmac @cindex pretty-printer -@defun pp object &optional stream -This function outputs @var{object} to @var{stream}, just like -@code{prin1}, but does it in a prettier way. That is, it'll -indent and fill the object to make it more readable for humans. +@defun pp object-or-beg &optional stream-or-end +This function indents and fills the printed representation of an +object (typically representing ELisp code) to make it more readable +for humans. + +It accepts two calling conventions: if called with two integers +@var{beg} and @var{end}, it indents and fills the corresponding +region, presumably containing the printed representation of one or +more objects, otherwise it takes a @var{object} and an optional +@var{stream}, and prints @var{object} like @code{prin1}, but does it +in a prettier way. @end defun If you need to use binary I/O in batch mode, e.g., use the functions @@ -981,6 +988,15 @@ Output Variables having their own escape syntax such as newline. @end defvar +@defopt pp-default-function +This user variable specifies the function used by @code{pp} to prettify +its output. By default it uses @code{pp-fill} which attempts to +strike a good balance between speed and generating natural looking output +that fits within @code{fill-column}. The previous default was +@code{pp-28}, which tends to be faster but generate output that looks +less natural and is less compact. +@end defopt + @node Output Overrides @section Overriding Output Variables @cindex overrides, in output functions diff --git a/etc/NEWS b/etc/NEWS index 61e6e161665..7aa387b3a5c 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -396,6 +396,16 @@ name as a string. The new function 'dictionary-completing-read-dictionary' can be used to prompt with completion based on dictionaries that the server supports. +** Pp +*** New 'pp-default-function' custom variable replaces 'pp-use-max-width'. + +*** New default pretty printing function, which tries to obey 'fill-column'. + +*** 'pp' can be applied to a region rather than an object. +As a consequence, 'pp-buffer' is now declared obsolete. + +*** 'pp-to-string' takes an additional 'pp-function' argument. +This arg specifies the prettifying algorithm to use. * New Modes and Packages in Emacs 30.1 -- 2.39.2 --=-=-=--