From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Matthias Meulien Newsgroups: gmane.emacs.bugs Subject: bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers Date: Fri, 14 Jan 2022 17:41:16 +0100 Message-ID: <87pmouw9qr.fsf@gmail.com> References: <87lf1sw6ji.fsf@gmail.com> <86h7cgdk4v.fsf@mail.linkov.net> <87ee7kvshn.fsf@gmail.com> <87a6i7x5iq.fsf@gmail.com> <86k0hbam7r.fsf@mail.linkov.net> <878rxrmy7q.fsf@gmail.com> <86y256uc0v.fsf@mail.linkov.net> <87r1ajknsr.fsf@gmail.com> <86ilvu6w6k.fsf@mail.linkov.net> <871r2hc51a.fsf@gmail.com> <87r19zs662.fsf@gmail.com> <871r1zm7ji.fsf@gmail.com> <87wnjrkrwd.fsf@gmail.com> <86ilv8pnf3.fsf@mail.linkov.net> <87k0fouzxs.fsf@gmail.com> <86sftumahu.fsf@mail.linkov.net> 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="26722"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/29.0.50 (gnu/linux) Cc: 51809@debbugs.gnu.org To: Juri Linkov Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Fri Jan 14 17:44:49 2022 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 1n8Ph3-0006je-6H for geb-bug-gnu-emacs@m.gmane-mx.org; Fri, 14 Jan 2022 17:44:49 +0100 Original-Received: from localhost ([::1]:36156 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1n8Ph2-00019F-0I for geb-bug-gnu-emacs@m.gmane-mx.org; Fri, 14 Jan 2022 11:44:48 -0500 Original-Received: from eggs.gnu.org ([209.51.188.92]:58322) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1n8PeO-0007Me-Ly for bug-gnu-emacs@gnu.org; Fri, 14 Jan 2022 11:42:04 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]:45051) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1n8PeL-0003le-P6 for bug-gnu-emacs@gnu.org; Fri, 14 Jan 2022 11:42:04 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1n8PeL-000693-ON for bug-gnu-emacs@gnu.org; Fri, 14 Jan 2022 11:42:01 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Matthias Meulien Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Fri, 14 Jan 2022 16:42:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 51809 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 51809-submit@debbugs.gnu.org id=B51809.164217848823556 (code B ref 51809); Fri, 14 Jan 2022 16:42:01 +0000 Original-Received: (at 51809) by debbugs.gnu.org; 14 Jan 2022 16:41:28 +0000 Original-Received: from localhost ([127.0.0.1]:37951 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1n8Pdn-00067s-LE for submit@debbugs.gnu.org; Fri, 14 Jan 2022 11:41:28 -0500 Original-Received: from mail-wr1-f46.google.com ([209.85.221.46]:35837) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1n8Pdk-00067a-Jo for 51809@debbugs.gnu.org; Fri, 14 Jan 2022 11:41:26 -0500 Original-Received: by mail-wr1-f46.google.com with SMTP id e9so16602768wra.2 for <51809@debbugs.gnu.org>; Fri, 14 Jan 2022 08:41:24 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=wTzyuGS3/Y69mPp8YSLoxSLfwwfrMNtOZbR87Y05FcA=; b=LcZjGXJiCDFSQXGtT3cTJiuDjsk54EVvGCTtO2jNbAIz8GxOPpHQkniC1W0ZoDWIkQ 2VhxqrPXdhMS2eA/QEu/I78H/VLbsQVxvWAuSU9siOXFX5wD+Nk4iU80rZMzgH8iYSza ZV2uDtB1buQjElqlFFq5b7exCCr55NYRoLARBczWlAAyQQRojLHivEnesvmsd2NKzHKm IPOvt2LQj61lWbJ77cKKsSAbYFM1qk8NlLDVGjCbzQi5XERL0RgUHfDhY7f6Ah3IuPeU y6ijLHVEXknAevp9hZ1prFNc4rnP86LIMVB0ERzRgyV6U2oMlJssAbSq5cmG92v5Ckcm mrZg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to :message-id:user-agent:mime-version; bh=wTzyuGS3/Y69mPp8YSLoxSLfwwfrMNtOZbR87Y05FcA=; b=5rgk2Pm+jb7dF9szPRLPR/cA4xTwxhc6J1PV+3oxDAGq7VrFL5y7NsCCy72784AXgD U7x8A6IdJ+PbNz+u8ybVwDZKxPcffSPMaVnGAVUzS7Q+4upl5y/x7iQ/nLTE9jx2Qo4v V5MuryqeHdSVgiqmNhfDsHOoB7pHbSZxkh6Y6jh1K9UJmWmmSQbWNBiOqyHhAXOCf702 gn1v6evLw6JYkyyFqbxf/lc6n+zLFR+4j9EBDhB6nPFgWbwM38lDjvQUdBvkSkwGghuK aPRKRDnx8xcgk3HdqPvrZWyL8zY9+SUhG3d00MsiAG/1ePMYXuHYELgOdemr66RczN0N RfCQ== X-Gm-Message-State: AOAM532xIvH5uhYecIgt427FDOcQYeyN0Tu82/4QQa/5F3Kh1xqi+wHT 4igjd4p91k4mQlIDCf3Jk8IiJs8Gjtw= X-Google-Smtp-Source: ABdhPJyhBAmKUdggnOJ9ziEMX+bZ1Shso9lXoV5/3BZkJkvMzbwfuKzy5IVQCjjnbIaH+Cq6itNlHA== X-Received: by 2002:a05:6000:12cc:: with SMTP id l12mr9049058wrx.290.1642178478565; Fri, 14 Jan 2022 08:41:18 -0800 (PST) Original-Received: from carbon.localdomain ([2a01:e0a:245:c850:98f5:429a:aa8e:95bb]) by smtp.gmail.com with ESMTPSA id bk2sm5518565wrb.65.2022.01.14.08.41.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 14 Jan 2022 08:41:17 -0800 (PST) In-Reply-To: <86sftumahu.fsf@mail.linkov.net> (Juri Linkov's message of "Tue, 11 Jan 2022 19:46:05 +0200") 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:224222 Archived-At: --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Hi Juri, Juri Linkov writes: >>> (...) Also xref works nicely, although I don't know why it requires >>> `outline-apply-default-state' after enabling `outline-minor-mode': >> >> It's a mistake to rely on `hack-local-variables-hook' to call >> `outline-apply-default-state' when Outline minor mode is enabled since >> `hack-local-variables-hook' is run after processing a file's local >> variable specs. > > There is also such a case possible where the file begins with a prop-line: > > # -*- mode: outline-minor; -*- > > and ends with the Local Variables section, e.g.: > > # outline-default-state: 2 > > `hack-local-variables-hook' can handle such case in `outline-minor-mode'. > This usage of "mode:" looks quite exotic to me. Isn't outine-minor a minor mode? According to the documentation: The special variable/value pair =E2=80=98mode: MODENAME;=E2=80=99, if pr= esent, specifies a major mode (without the =E2=80=9C-mode=E2=80=9D suffix). I prefer not support this if possible. >> I'll send an updated patch after some time using this. > > Now you can take in use a new hook added in bug#52855 that allows > applying the default state after vc-diff finishes: > > (add-hook 'vc-diff-finish-functions 'outline-apply-default-state) Thanks, I updated my configuration to the following: (add-hook 'diff-mode-hook #'(lambda () (setq outline-default-state 1 outline-default-rules '(subtree-is-long subtree-has-long-lines (match-regexp . "NEWS\\|test\\|package-lock\\.json\\|poetry\\.loc= k"))))) (add-hook 'vc-diff-finish-functions #'(lambda () (when outline-minor-mode (outline-apply-default-state)))) (add-hook 'xref-after-update-hook #'(lambda () (setq outline-regexp (if (eq xref-file-name-display 'abs) "/" "[^ 0-9]") outline-default-state 1 outline-default-rules '((match-regexp . "ChangeLog\\|te= st/manual/etags"))) (outline-minor-mode))) Here is an updated patch (just rebased my local branch, some minor conflicts appeared in outline.el): --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0001-Extend-Outline-mode-with-default-visibility-state.patch >From 46934ba08d597a4d0d6e9d6c3918e5eabc1ec613 Mon Sep 17 00:00:00 2001 From: Matthias Meulien Date: Wed, 8 Dec 2021 22:35:42 +0100 Subject: [PATCH] Extend Outline mode with default visibility state * etc/NEWS: Announce support for default visibility state. * lisp/outline.el (outline-mode, outline-minor-mode): Ensure default visibility state is applied. (outline-hide-sublevels): Add optional argument for function to call on each heading. (outline-default-state): Define the default visibility state. (outline-apply-default-state): Apply default visibility state. --- etc/NEWS | 10 +++ lisp/outline.el | 180 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 187 insertions(+), 3 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index ea9ba49892..49b5db9b1a 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -243,6 +243,16 @@ These will take you (respectively) to the next and previous "page". --- *** 'describe-char' now also outputs the name of emoji combinations. +** Outline Mode + +*** Support for a default visibility state. +Customize the option 'outline-default-state' to define what headings +are visible when the mode is set. When equal to a number, the option +'outline-default-state-subtree-visibility' determines the visibility +of the subtree starting at the corresponding level. Values are +provided to show a heading subtree unless the heading match a regexp, +or its subtree has long lines or is long. + ** Outline Minor Mode +++ diff --git a/lisp/outline.el b/lisp/outline.el index 4027142c94..bca0f3bef8 100644 --- a/lisp/outline.el +++ b/lisp/outline.el @@ -351,7 +351,8 @@ outline-mode '(outline-font-lock-keywords t nil nil backward-paragraph)) (setq-local imenu-generic-expression (list (list nil (concat "^\\(?:" outline-regexp "\\).*$") 0))) - (add-hook 'change-major-mode-hook #'outline-show-all nil t)) + (add-hook 'change-major-mode-hook #'outline-show-all nil t) + (add-hook 'hack-local-variables-hook #'outline-apply-default-state nil t)) (defvar outline-minor-mode-map) @@ -434,7 +435,8 @@ outline-minor-mode nil t) (setq-local line-move-ignore-invisible t) ;; Cause use of ellipses for invisible text. - (add-to-invisibility-spec '(outline . t))) + (add-to-invisibility-spec '(outline . t)) + (outline-apply-default-state)) (when outline-minor-mode-highlight (if font-lock-fontified (font-lock-remove-keywords nil outline-font-lock-keywords)) @@ -1089,7 +1091,7 @@ outline-hide-sublevels (outline-map-region (lambda () (if (<= (funcall outline-level) levels) - (outline-show-heading))) + (outline-show-heading))) beg end) ;; Finally unhide any trailing newline. (goto-char (point-max)) @@ -1303,6 +1305,178 @@ outline-headers-as-kill (insert "\n\n")))))) (kill-new (buffer-string))))))) +(defcustom outline-default-state nil + "If non-nil, some headings are initially outlined. + +Note that the default state is applied when the major mode is set +or when the command `outline-apply-default-state' is called +interactively. + +When nil, headings visibility is left unchanged. + +If equal to `outline-show-all', all text of buffer is shown. + +If equal to `outline-show-only-headings', only headings are shown. + +If equal to a number, show only headings up to and including the +corresponding level. See `outline-default-rules' to customize +visibility of the subtree at the choosen level. + +If equal to a lambda function or function name, this function is +expected to toggle headings visibility, and will be called after +the mode is enabled." + :version "29.1" + :type '(choice (const :tag "Disabled" nil) + (const :tag "Show all" outline-show-all) + (const :tag "Only headings" outline-show-only-headings) + (natnum :tag "Show headings up to level" :value 1) + (function :tag "Custom function"))) + +(defcustom outline-default-rules nil + "Determines visibility of subtree starting at `outline-default-state' level. + +When nil, the subtree is hidden unconditionally. + +When equal to a list, each element should be one of the following: + +- A cons cell with CAR `match-regexp' and CDR a regexp, the + subtree will be hidden when the outline heading match the + regexp. + +- `subtree-has-long-lines' to only show the heading branches when + long lines are detected in its subtree (see + `outline-default-long-line' for the definition of long lines). + +- `subtree-is-long' to only show the heading branches when its + subtree contains more than `outline-default-line-count' lines. + +- A lambda function or function name which will be evaluated with + point at the beginning of the heading and the match data set + appropriately, the function being expected to toggle the + heading visibility." + :version "29.1" + :type '(choice (const :tag "Hide subtree" nil) + (set :tag "Show subtree unless" + (cons :tag "Heading match regexp" + (const match-regexp) string) + (const :tag "Subtree has long lines" + subtree-has-long-lines) + (const :tag "Subtree is long" + subtree-is-long) + (cons :tag "Custom function" + (const custom-function) function)))) + +(defcustom outline-default-long-line 1000 + "Minimal number of characters in a line for a heading to be outlined." + :version "29.1" + :type '(natnum :tag "Number of characters")) + +(defcustom outline-default-line-count 50 + "Minimal number of lines for a heading to be outlined." + :version "29.1" + :type '(natnum :tag "Number of lines")) + +(defun outline-apply-default-state () + "Apply the outline state defined by `outline-default-state'." + (interactive) + (cond + ((integerp outline-default-state) + (outline--show-headings-up-to-level outline-default-state)) + ((when (functionp outline-default-state) + (funcall outline-default-state))))) + +(defun outline-show-only-headings () + "Show only headings." + (interactive) + (outline-show-all) + (outline-hide-region-body (point-min) (point-max))) + +(eval-when-compile (require 'so-long)) +(autoload 'so-long-detected-long-line-p "so-long") +(defvar so-long-skip-leading-comments) +(defvar so-long-threshold) +(defvar so-long-max-lines) + +(defun outline--show-headings-up-to-level (level) + "Show only headings up to a LEVEL level. + +Like `outline-hide-sublevels' but, for each heading at level +LEVEL, decides of subtree visibility according to +`outline-default-rules'." + (if (not outline-default-rules) + (outline-hide-sublevels level) + (if (< level 1) + (error "Must keep at least one level of headers")) + (save-excursion + (let* (outline-view-change-hook + (beg (progn + (goto-char (point-min)) + ;; Skip the prelude, if any. + (unless (outline-on-heading-p t) (outline-next-heading)) + (point))) + (end (progn + (goto-char (point-max)) + ;; Keep empty last line, if available. + (if (bolp) (1- (point)) (point)))) + (heading-regexp + (cdr-safe + (assoc 'match-regexp outline-default-rules))) + (check-line-count + (memq 'subtree-is-long outline-default-rules)) + (check-long-lines + (memq 'subtree-has-long-lines outline-default-rules)) + (custom-function + (cdr-safe + (assoc 'custom-function outline-default-rules)))) + (if (< end beg) + (setq beg (prog1 end (setq end beg)))) + ;; First hide everything. + (outline-hide-sublevels level) + ;; Then unhide the top level headers. + (outline-map-region + (lambda () + (let ((current-level (funcall outline-level))) + (when (< current-level level) + (outline-show-heading) + (outline-show-entry)) + (when (= current-level level) + (cond + ((and heading-regexp + (let ((beg (point)) + (end (progn (outline-end-of-heading) (point)))) + (string-match-p heading-regexp (buffer-substring beg end)))) + ;; hide entry when heading match regexp + (outline-hide-entry)) + ((and check-line-count + (save-excursion + (let ((beg (point)) + (end (progn (outline-end-of-subtree) (point)))) + (<= outline-default-line-count (count-lines beg end))))) + ;; show only branches when line count of subtree > + ;; threshold + (outline-show-branches)) + ((and check-long-lines + (save-excursion + (let ((beg (point)) + (end (progn (outline-end-of-subtree) (point)))) + (save-restriction + (narrow-to-region beg end) + (let ((so-long-skip-leading-comments nil) + (so-long-threshold outline-default-long-line) + (so-long-max-lines nil)) + (so-long-detected-long-line-p)))))) + ;; show only branches when long lines are detected + ;; in subtree + (outline-show-branches)) + (custom-function + ;; call custom function if defined + (funcall custom-function)) + (t + ;; if no previous clause succeeds, show subtree + (outline-show-subtree)))))) + beg end))) + (run-hooks 'outline-view-change-hook))) + (defun outline--cycle-state () "Return the cycle state of current heading. Return either 'hide-all, 'headings-only, or 'show-all." -- 2.30.2 --=-=-= Content-Type: text/plain What would be the next step? Should we stop here or introduce some customization in Diff mode to help people discover how the "outline default state feature" can be used there (not convinced myself it'd be usefull)? -- Matthias --=-=-=--