unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
@ 2021-11-13 13:04 Matthias Meulien
  2021-11-13 17:45 ` Juri Linkov
  2021-12-28  8:09 ` Matthias Meulien
  0 siblings, 2 replies; 35+ messages in thread
From: Matthias Meulien @ 2021-11-13 13:04 UTC (permalink / raw)
  To: 51809

[-- Attachment #1: Type: text/plain, Size: 478 bytes --]


Hi,

Attached is a patch that adds support for an outline default state in
Diff buffers.

One state makes only files and hunks headings visibles. Another one
outlines files with long hunks. A third value is proposed for users who
want to implement their own state.

My point is that, when I first review a changeset, I am trying to get an
overview of the changes; And files cumulating long hunks often don't
help for that matter.

Tell me if it's worth including in Emacs 29.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Support-for-outline-default-state-in-Diff-buffers.patch --]
[-- Type: text/x-diff, Size: 5428 bytes --]

From f698c22bffd8cd5dd5c98a86f0e143c4e184b4dc Mon Sep 17 00:00:00 2001
From: Matthias Meulien <orontee@gmail.com>
Date: Sat, 13 Nov 2021 12:08:58 +0100
Subject: [PATCH] Support for outline default state in Diff buffers

* lisp/vc/diff-mode.el (diff-outline-default-state): Add custom
variable that defines an outline state and apply that state in Diff
buffers.
---
 lisp/vc/diff-mode.el | 92 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 90 insertions(+), 2 deletions(-)

diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index e68aa2257d..01ea1c3994 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -50,7 +50,11 @@
 ;;
 ;; - in diff-apply-hunk, strip context in replace-match to better
 ;;   preserve markers and spacing.
+;;
 ;; - Handle `diff -b' output in context->unified.
+;;
+;; - Support outlining files by name (eg to skip automatically
+;;   generated files like package-lock.json in Javascript projects).
 
 ;;; Code:
 (eval-when-compile (require 'cl-lib))
@@ -147,6 +151,27 @@ diff-font-lock-syntax
                  (const :tag "Highlight syntax" t)
                  (const :tag "Allow hunk-based fallback" hunk-also)))
 
+(defcustom diff-outline-default-state nil
+  "If non-nil, some files or hunk are outlined.
+Outlining is performed by Outline minor mode.
+
+If `hide-body', only file and hunk headings are visible.
+
+If `size-threshold', files whose hunks cover more than
+`diff-file-outline-threshold' lines are outlined."
+  :version "29.1"
+  :type '(choice (const :tag "Don't outline " nil)
+                 (const :tag "Outline hunks" hide-body)
+                 (const :tag "Outline files with long hunks" size-threshold)
+                 (function :tag "Custom function")))
+
+(defcustom diff-file-outline-threshold 50
+  "Number of lines of hunks for a file to be outlined.
+
+Used by `diff-outline-file-according-to-size'."
+  :version "29.1"
+  :type '(natnum :tag "Number of lines"))
+
 (defvar diff-vc-backend nil
   "The VC backend that created the current Diff buffer, if any.")
 
@@ -1578,7 +1603,8 @@ diff-setup-whitespace
 
 (defun diff-setup-buffer-type ()
   "Try to guess the `diff-buffer-type' from content of current Diff mode buffer.
-`outline-regexp' is updated accordingly."
+`outline-regexp' is updated accordingly and outline default state
+applied."
   (save-excursion
     (goto-char (point-min))
     (setq-local diff-buffer-type
@@ -1589,7 +1615,8 @@ diff-setup-buffer-type
     (setq diff-outline-regexp
           (concat "\\(^diff --git.*\n\\|" diff-hunk-header-re "\\)"))
     (setq-local outline-level #'diff--outline-level))
-  (setq-local outline-regexp diff-outline-regexp))
+  (setq-local outline-regexp diff-outline-regexp)
+  (diff-outline-apply-default-state))
 
 (defun diff-delete-if-empty ()
   ;; An empty diff file means there's no more diffs to integrate, so we
@@ -2143,6 +2170,67 @@ diff-refresh-hunk
       (delete-file file1)
       (delete-file file2))))
 
+(defun diff-outline-apply-default-state ()
+  "Apply the outline state defined by `diff-outline-default-state'.
+
+When `diff-outline-default-state' is non-nil, Outline minor mode
+is enabled."
+  (when diff-outline-default-state
+    (when (not outline-minor-mode)
+      (outline-minor-mode))
+    (cond
+     ((eq diff-outline-default-state 'size-threshold)
+      (diff-outline-file-according-to-size))
+     ((eq diff-outline-default-state 'hide-body)
+      (outline-hide-body))
+     ((when (functionp diff-outline-default-state)
+        (funcall diff-outline-default-state))))))
+
+(defun diff-outline-file-according-to-size ()
+  "Outline file with long hunks.
+
+A file is outlined when its hunks cover more than
+`diff-file-outline-threshold' lines. Does nothing when Outline
+minor mode is not enabled or `diff-file-outline-threshold'.
+
+Inspired by `outline-hide-sublevels'."
+  (interactive)
+  (when (and outline-minor-mode diff-file-outline-threshold)
+    (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)))))
+	(if (< end beg)
+            (setq beg (prog1 end (setq end beg))))
+	;; First hide sublevels
+	(outline-hide-sublevels 1)
+	;; Then unhide short subtrees
+	(outline-map-region
+	 (lambda ()
+           (when (= (funcall outline-level) 1)
+             (goto-char (match-end 0))
+             (let ((overlays (overlays-at (point))))
+               (while overlays
+		 (let ((overlay (car overlays)))
+		   (progn
+                     (when (eq (overlay-get overlay 'invisible) 'outline)
+                       (let ((size (count-lines
+                                    (overlay-end overlay)
+                                    (overlay-start overlay))))
+                         (goto-char (match-beginning 0))
+			 (if (< size diff-file-outline-threshold)
+			     (outline-show-subtree)
+                           (outline-show-branches))))
+                     (setq overlays (cdr overlays))))))))
+	 beg end)))))
+
 ;;; Fine change highlighting.
 
 (defface diff-refine-changed
-- 
2.30.2


[-- Attachment #3: Type: text/plain, Size: 6264 bytes --]




In GNU Emacs 29.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.24, cairo version 1.16.0)
 of 2021-11-13 built on carbon
Repository revision: f698c22bffd8cd5dd5c98a86f0e143c4e184b4dc
Repository branch: dev/mm
Windowing system distributor 'The X.Org Foundation', version 11.0.12011000
System Description: Debian GNU/Linux 11 (bullseye)

Configured using:
 'configure --with-native-compilation'

Configured features:
ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GPM GSETTINGS HARFBUZZ JPEG
JSON LCMS2 LIBOTF LIBSELINUX LIBXML2 M17N_FLT MODULES NATIVE_COMP NOTIFY
INOTIFY PDUMPER PNG RSVG SECCOMP SOUND THREADS TIFF TOOLKIT_SCROLL_BARS
X11 XDBE XIM XPM GTK3 ZLIB

Important settings:
  value of $LANG: fr_FR.UTF-8
  value of $XMODIFIERS: @im=ibus
  locale-coding-system: utf-8-unix

Major mode: VC dir

Minor modes in effect:
  highlight-changes-visible-mode: t
  shell-dirtrack-mode: t
  minions-mode: t
  desktop-save-mode: t
  save-place-mode: t
  electric-pair-mode: t
  icomplete-mode: t
  global-so-long-mode: t
  global-auto-revert-mode: t
  auto-insert-mode: t
  text-scale-mode: t
  tooltip-mode: t
  global-eldoc-mode: t
  show-paren-mode: t
  electric-layout-mode: t
  electric-indent-mode: t
  mouse-wheel-mode: t
  tab-bar-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  blink-cursor-mode: t
  window-divider-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t
  buffer-read-only: t
  line-number-mode: t
  indent-tabs-mode: t
  transient-mark-mode: t

Load-path shadows:
/home/matthias/.config/emacs/elpa/transient-20211029.1405/transient hides /usr/local/share/emacs/29.0.50/lisp/transient
/home/matthias/.config/emacs/elpa/dictionary-20201001.1727/dictionary hides /usr/local/share/emacs/29.0.50/lisp/net/dictionary

Features:
(shadow flow-fill nndoc gnus-dup url-cache crm debbugs-gnu debbugs
soap-client url-http url-auth url-gw rng-xsd xsd-regexp misearch
multi-isearch emacsbug sendmail mm-archive qp gnus-fun sort smiley
gnus-cite mail-extr gnus-async gnus-bcklg follow gnus-ml disp-table
novice gnus-topic nndraft nnmh nnfolder utf-7 reftex-dcr reftex
reftex-loaddefs reftex-vars tex-mode tramp-archive tramp-gvfs
tramp-cache zeroconf tramp tramp-loaddefs trampver tramp-integration
files-x tramp-compat ls-lisp epa-file gnutls network-stream nsm
gnus-agent gnus-srvr gnus-score score-mode nnvirtual gnus-msg gnus-cache
hl-line add-log smerge-mode diff flyspell ox-odt rng-loc rng-uri
rng-parse rng-match rng-dt rng-util rng-pttrn nxml-parse nxml-ns
nxml-enc xmltok nxml-util ox-latex ox-icalendar org-agenda ox-html table
ox-ascii ox-publish ox goto-addr org-element avl-tree generator ol-eww
eww xdg url-queue mm-url ol-rmail ol-mhe ol-irc ol-info ol-gnus nnselect
gnus-search eieio-opt speedbar ezimage dframe gnus-art mm-uu mml2015
mm-view mml-smime smime dig gnus-sum shr kinsoku svg dom ol-docview
doc-view image-mode exif ol-bibtex ol-bbdb ol-w3m ol-doi org-link-doi
mule-util jka-compr dired-aux bug-reference display-line-numbers
hilit-chg vc-dir whitespace vc-mtn vc-hg vc-bzr vc-src vc-sccs vc-svn
vc-cvs vc-rcs vc bash-completion shell eglot array jsonrpc ert ewoc
debug backtrace xref flymake-proc flymake compile pcase project imenu
avoid minions carbon-custom cus-edit cus-load gnus-demon nntp gnus-group
gnus-undo gnus-start gnus-dbus dbus xml gnus-cloud nnimap nnmail
mail-source utf7 netrc parse-time gnus-spec gnus-win nnoo gnus-int
gnus-range message yank-media rmc puny rfc822 mml mml-sec epa derived
epg rfc6068 epg-config mm-decode mm-bodies mm-encode mail-parse rfc2231
mailabbrev gmm-utils mailheader gnus nnheader gnus-util rmail
rmail-loaddefs rfc2047 rfc2045 ietf-drums mail-utils mm-util mail-prsvr
wid-edit gnus-dired dired-x dired dired-loaddefs org-capture org-refile
org ob ob-tangle ob-ref ob-lob ob-table ob-exp org-macro org-footnote
org-src ob-comint org-pcomplete pcomplete comint ansi-color ring
org-list org-faces org-entities org-version ob-emacs-lisp ob-core
ob-eval org-table oc-basic bibtex iso8601 time-date ol org-keys oc
org-compat org-macs org-loaddefs format-spec find-func cal-menu calendar
cal-loaddefs dictionary link connection advice markdown-mode
edit-indirect color thingatpt noutline outline skeleton find-file vc-git
diff-mode easy-mmode vc-dispatcher ispell desktop frameset server
bookmark text-property-search pp saveplace elec-pair icomplete so-long
autorevert filenotify autoinsert cc-mode cc-fonts cc-guess cc-menus
cc-cmds cc-styles cc-align cc-engine cc-vars cc-defs generic-x
face-remap proof-site proof-autoloads info package browse-url url
url-proxy url-privacy url-expand url-methods url-history url-cookie
url-domsuf url-util mailcap url-handlers url-parse auth-source eieio
eieio-core eieio-loaddefs password-cache json map url-vars comp
comp-cstr warnings rx cl-seq cl-macs cl-extra help-mode seq gv subr-x
byte-opt bytecomp byte-compile cconv cl-loaddefs cl-lib iso-transl
tooltip eldoc paren electric uniquify ediff-hook vc-hooks
lisp-float-type elisp-mode mwheel term/x-win x-win term/common-win x-dnd
tool-bar dnd fontset image regexp-opt fringe tabulated-list replace
newcomment text-mode lisp-mode prog-mode register page tab-bar menu-bar
rfn-eshadow isearch easymenu timer select scroll-bar mouse jit-lock
font-lock syntax font-core term/tty-colors frame minibuffer cl-generic
cham georgian utf-8-lang misc-lang vietnamese tibetan thai tai-viet lao
korean japanese eucjp-ms cp51932 hebrew greek romanian slovak czech
european ethiopic indian cyrillic chinese composite emoji-zwj charscript
charprop case-table epa-hook jka-cmpr-hook help simple abbrev obarray
cl-preloaded nadvice button loaddefs faces cus-face macroexp files
window text-properties overlay sha1 md5 base64 format env code-pages
mule custom widget hashtable-print-readable backquote threads dbusbind
inotify lcms2 dynamic-setting system-font-setting font-render-setting
cairo move-toolbar gtk x-toolkit x multi-tty make-network-process
native-compile emacs)

Memory information:
((conses 16 1000739 201421)
 (symbols 48 39721 27)
 (strings 32 228421 52284)
 (string-bytes 1 7452737)
 (vectors 16 85442)
 (vector-slots 8 1542974 101097)
 (floats 8 3167 1088)
 (intervals 56 22491 1885)
 (buffers 992 61))

-- 
Matthias

^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-13 13:04 bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers Matthias Meulien
@ 2021-11-13 17:45 ` Juri Linkov
  2021-11-13 18:08   ` Matthias Meulien
  2021-12-28  8:09 ` Matthias Meulien
  1 sibling, 1 reply; 35+ messages in thread
From: Juri Linkov @ 2021-11-13 17:45 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

> Attached is a patch that adds support for an outline default state in
> Diff buffers.

Thanks.

> One state makes only files and hunks headings visibles. Another one
> outlines files with long hunks. A third value is proposed for users who
> want to implement their own state.
>
> My point is that, when I first review a changeset, I am trying to get an
> overview of the changes; And files cumulating long hunks often don't
> help for that matter.
>
> Tell me if it's worth including in Emacs 29.

I'm using outline-minor-mode in diff buffers all the time
with (add-hook 'diff-mode-hook 'outline-minor-mode)
and would like to understand how your patch improves this.

Could the above hook be replaced with customization of
diff-outline-default-state?

> +;; - Support outlining files by name (eg to skip automatically
> +;;   generated files like package-lock.json in Javascript projects).
>…
> +(defcustom diff-file-outline-threshold 50
> +  "Number of lines of hunks for a file to be outlined.

Often the files that need to be hidden contain just one very long line
without newlines such as in compiled assets, etc. and eventually make
Emacs unresponsive.  This is a big problem.  Would it be possible
in your patch to check the size of the hunk counting characters
instead of lines?





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-13 17:45 ` Juri Linkov
@ 2021-11-13 18:08   ` Matthias Meulien
  2021-11-13 18:27     ` Juri Linkov
                       ` (2 more replies)
  0 siblings, 3 replies; 35+ messages in thread
From: Matthias Meulien @ 2021-11-13 18:08 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

Juri Linkov <juri@linkov.net> writes:

> (...) I'm using outline-minor-mode in diff buffers all the time with
> (add-hook 'diff-mode-hook 'outline-minor-mode) and would like to
> understand how your patch improves this.
>
> Could the above hook be replaced with customization of
> diff-outline-default-state?

It was supposed to. But I am also using outline-minor-mode in diff
buffers all the time with the same hook as you, and I must confess I've
not tested until you asked: `diff-outline-apply-default-state' is
supposed to automatically turn on `outline-minor-mode' but I forgot to
autoload the later resulting in "Symbol’s value as variable is void:
outline-minor-mode". I'll fix this.

>
>> +;; - Support outlining files by name (eg to skip automatically
>> +;;   generated files like package-lock.json in Javascript projects).
>>…
>> +(defcustom diff-file-outline-threshold 50
>> +  "Number of lines of hunks for a file to be outlined.
>
> Often the files that need to be hidden contain just one very long line
> without newlines such as in compiled assets, etc. and eventually make
> Emacs unresponsive.  This is a big problem.  Would it be possible
> in your patch to check the size of the hunk counting characters
> instead of lines?

Good point. I guess counting characters can be achieved by:
  (- (overlay-end overlay) (overlay-start overlay))
thus it looks feasible.

Wouldn't it better to add a new choice "Outline hunks with long lines"
and allow multiple choices in `diff-outline-default-state'
customization?

It would allow to implement one more usefull choice (from my pov),
"Outline files by name" to hide automatically generated files (like
package-lock.json in Javascript projects using NPM)...

Does it looks reasonnable to you?
-- 
Matthias





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-13 18:08   ` Matthias Meulien
@ 2021-11-13 18:27     ` Juri Linkov
  2021-11-13 18:41     ` Matthias Meulien
  2021-11-14 18:25     ` Juri Linkov
  2 siblings, 0 replies; 35+ messages in thread
From: Juri Linkov @ 2021-11-13 18:27 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

>>> +;; - Support outlining files by name (eg to skip automatically
>>> +;;   generated files like package-lock.json in Javascript projects).
>>>…
>>> +(defcustom diff-file-outline-threshold 50
>>> +  "Number of lines of hunks for a file to be outlined.
>>
>> Often the files that need to be hidden contain just one very long line
>> without newlines such as in compiled assets, etc. and eventually make
>> Emacs unresponsive.  This is a big problem.  Would it be possible
>> in your patch to check the size of the hunk counting characters
>> instead of lines?
>
> Good point. I guess counting characters can be achieved by:
>   (- (overlay-end overlay) (overlay-start overlay))
> thus it looks feasible.

It seems this should work.

> Wouldn't it better to add a new choice "Outline hunks with long lines"
> and allow multiple choices in `diff-outline-default-state'
> customization?

Many customizable variables that check the size such as
large-file-warning-threshold etc. count bytes/characters.
But if you think that someone might want to restrict
only the number of lines whereas wanting to see long lines,
then a new option could be added too.

> It would allow to implement one more usefull choice (from my pov),
> "Outline files by name" to hide automatically generated files (like
> package-lock.json in Javascript projects using NPM)...
>
> Does it looks reasonnable to you?

An option to hide by file names (I assume a regexp?) looks useful too.

This is how I configured the xref output buffer to
initially hide only ChangeLog and test files:

#+begin_src emacs-lisp
(add-hook 'xref-after-update-hook
          (lambda ()
            (setq-local outline-regexp (if (eq xref-file-name-display 'abs)
                                           "/" "[^ 0-9]"))
            (outline-minor-mode +1)
            (save-excursion
              (goto-char (point-min))
              (while (and (re-search-forward "ChangeLog\\|test/manual/etags" nil t)
                          (get-text-property (point) 'xref-group))
                (outline-cycle)))))
#+end_src

Something like this could be customizable in diff-mode.





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-13 18:08   ` Matthias Meulien
  2021-11-13 18:27     ` Juri Linkov
@ 2021-11-13 18:41     ` Matthias Meulien
  2021-11-13 19:29       ` Juri Linkov
  2021-11-14 18:25     ` Juri Linkov
  2 siblings, 1 reply; 35+ messages in thread
From: Matthias Meulien @ 2021-11-13 18:41 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

[-- Attachment #1: Type: text/plain, Size: 440 bytes --]

Matthias Meulien <orontee@gmail.com> writes:

>> (...)  Could the above hook be replaced with customization of
>> diff-outline-default-state?

With the attached patch, Outline minor mode is automatically enabled
when `diff-outline-default-state' is customized. Tested within "emacs
-q". That said, I am not a proficient elisper and my use of the
autoload, declare-function, eval-when-compile machinery may not be
standard. Sorry for this.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Support-for-outline-default-state-in-Diff-buffers.patch --]
[-- Type: text/x-diff, Size: 5640 bytes --]

From 967df41ffc0bcaf74d4b2c8da4be9a354169498b Mon Sep 17 00:00:00 2001
From: Matthias Meulien <orontee@gmail.com>
Date: Sat, 13 Nov 2021 12:08:58 +0100
Subject: [PATCH] Support for outline default state in Diff buffers

* lisp/vc/diff-mode.el (diff-outline-default-state): Add custom
variable that defines an outline state and apply that state in Diff
buffers.
---
 lisp/vc/diff-mode.el | 95 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 93 insertions(+), 2 deletions(-)

diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index e68aa2257d..8751a9905c 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -50,7 +50,11 @@
 ;;
 ;; - in diff-apply-hunk, strip context in replace-match to better
 ;;   preserve markers and spacing.
+;;
 ;; - Handle `diff -b' output in context->unified.
+;;
+;; - Support outlining files by name (eg to skip automatically
+;;   generated files like package-lock.json in Javascript projects).
 
 ;;; Code:
 (eval-when-compile (require 'cl-lib))
@@ -62,6 +66,9 @@
 (defvar vc-find-revision-no-save)
 (defvar add-log-buffer-file-name-function)
 
+(eval-when-compile (require 'outline))
+(autoload 'outline-minor-mode "outline")
+(defvar outline-minor-mode)
 
 (defgroup diff-mode ()
   "Major mode for viewing/editing diffs."
@@ -147,6 +154,27 @@ diff-font-lock-syntax
                  (const :tag "Highlight syntax" t)
                  (const :tag "Allow hunk-based fallback" hunk-also)))
 
+(defcustom diff-outline-default-state nil
+  "If non-nil, some files or hunk are outlined.
+Outlining is performed by Outline minor mode.
+
+If `hide-body', only file and hunk headings are visible.
+
+If `size-threshold', files whose hunks cover more than
+`diff-file-outline-threshold' lines are outlined."
+  :version "29.1"
+  :type '(choice (const :tag "Don't outline " nil)
+                 (const :tag "Outline hunks" hide-body)
+                 (const :tag "Outline files with long hunks" size-threshold)
+                 (function :tag "Custom function")))
+
+(defcustom diff-file-outline-threshold 50
+  "Number of lines of hunks for a file to be outlined.
+
+Used by `diff-outline-file-according-to-size'."
+  :version "29.1"
+  :type '(natnum :tag "Number of lines"))
+
 (defvar diff-vc-backend nil
   "The VC backend that created the current Diff buffer, if any.")
 
@@ -1578,7 +1606,8 @@ diff-setup-whitespace
 
 (defun diff-setup-buffer-type ()
   "Try to guess the `diff-buffer-type' from content of current Diff mode buffer.
-`outline-regexp' is updated accordingly."
+`outline-regexp' is updated accordingly and outline default state
+applied."
   (save-excursion
     (goto-char (point-min))
     (setq-local diff-buffer-type
@@ -1589,7 +1618,8 @@ diff-setup-buffer-type
     (setq diff-outline-regexp
           (concat "\\(^diff --git.*\n\\|" diff-hunk-header-re "\\)"))
     (setq-local outline-level #'diff--outline-level))
-  (setq-local outline-regexp diff-outline-regexp))
+  (setq-local outline-regexp diff-outline-regexp)
+  (diff-outline-apply-default-state))
 
 (defun diff-delete-if-empty ()
   ;; An empty diff file means there's no more diffs to integrate, so we
@@ -2143,6 +2173,67 @@ diff-refresh-hunk
       (delete-file file1)
       (delete-file file2))))
 
+(defun diff-outline-apply-default-state ()
+  "Apply the outline state defined by `diff-outline-default-state'.
+
+When `diff-outline-default-state' is non-nil, Outline minor mode
+is enabled."
+  (when diff-outline-default-state
+    (when (not outline-minor-mode)
+      (outline-minor-mode))
+    (cond
+     ((eq diff-outline-default-state 'size-threshold)
+      (diff-outline-file-according-to-size))
+     ((eq diff-outline-default-state 'hide-body)
+      (outline-hide-body))
+     ((when (functionp diff-outline-default-state)
+        (funcall diff-outline-default-state))))))
+
+(defun diff-outline-file-according-to-size ()
+  "Outline file with long hunks.
+
+A file is outlined when its hunks cover more than
+`diff-file-outline-threshold' lines. Does nothing when Outline
+minor mode is not enabled.
+
+Inspired by `outline-hide-sublevels'."
+  (interactive)
+  (when outline-minor-mode
+    (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)))))
+	(if (< end beg)
+            (setq beg (prog1 end (setq end beg))))
+	;; First hide sublevels
+	(outline-hide-sublevels 1)
+	;; Then unhide short subtrees
+	(outline-map-region
+	 (lambda ()
+           (when (= (funcall outline-level) 1)
+             (goto-char (match-end 0))
+             (let ((overlays (overlays-at (point))))
+               (while overlays
+		 (let ((overlay (car overlays)))
+		   (progn
+                     (when (eq (overlay-get overlay 'invisible) 'outline)
+                       (let ((size (count-lines
+                                    (overlay-end overlay)
+                                    (overlay-start overlay))))
+                         (goto-char (match-beginning 0))
+			 (if (< size diff-file-outline-threshold)
+			     (outline-show-subtree)
+                           (outline-show-branches))))
+                     (setq overlays (cdr overlays))))))))
+	 beg end)))))
+
 ;;; Fine change highlighting.
 
 (defface diff-refine-changed
-- 
2.30.2


[-- Attachment #3: Type: text/plain, Size: 14 bytes --]


-- 
Matthias

^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-13 18:41     ` Matthias Meulien
@ 2021-11-13 19:29       ` Juri Linkov
  2021-11-13 21:27         ` Matthias Meulien
  2021-11-13 23:29         ` Matthias Meulien
  0 siblings, 2 replies; 35+ messages in thread
From: Juri Linkov @ 2021-11-13 19:29 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

>>> (...)  Could the above hook be replaced with customization of
>>> diff-outline-default-state?
>
> With the attached patch, Outline minor mode is automatically enabled
> when `diff-outline-default-state' is customized. Tested within "emacs
> -q". That said, I am not a proficient elisper and my use of the
> autoload, declare-function, eval-when-compile machinery may not be
> standard. Sorry for this.

> +(autoload 'outline-minor-mode "outline")

Actually, outline-minor-mode is already autoloaded, so no special
handling is needed for it.

But the problem is that too many outline functions are used in
diff-mode.el in your patch.  This is a clear indication that
some part should be moved to outline.el.

Note that the following function has no diff-specific code, so it could
be refactored and some generalized function added to outline.el.  Such
function could be like `outline-map-region' to accept arguments `beg', `end'
and a predicate function that defines whether to hide or show the body.

> +  (when outline-minor-mode
> +    (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)))))
> +	(if (< end beg)
> +            (setq beg (prog1 end (setq end beg))))
> +	;; First hide sublevels
> +	(outline-hide-sublevels 1)
> +	;; Then unhide short subtrees
> +	(outline-map-region
> +	 (lambda ()
> +           (when (= (funcall outline-level) 1)
> +             (goto-char (match-end 0))
> +             (let ((overlays (overlays-at (point))))
> +               (while overlays
> +		 (let ((overlay (car overlays)))
> +		   (progn
> +                     (when (eq (overlay-get overlay 'invisible) 'outline)
> +                       (let ((size (count-lines
> +                                    (overlay-end overlay)
> +                                    (overlay-start overlay))))
> +                         (goto-char (match-beginning 0))
> +			 (if (< size diff-file-outline-threshold)
> +			     (outline-show-subtree)
> +                           (outline-show-branches))))
> +                     (setq overlays (cdr overlays))))))))
> +	 beg end)))))





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-13 19:29       ` Juri Linkov
@ 2021-11-13 21:27         ` Matthias Meulien
  2021-11-13 23:29         ` Matthias Meulien
  1 sibling, 0 replies; 35+ messages in thread
From: Matthias Meulien @ 2021-11-13 21:27 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

[-- Attachment #1: Type: text/plain, Size: 999 bytes --]

Juri Linkov <juri@linkov.net> writes:

Thanks for your comments!

>> +(autoload 'outline-minor-mode "outline")
>
> Actually, outline-minor-mode is already autoloaded, so no special
> handling is needed for it.

Ok, I'll improve this.

> But the problem is that too many outline functions are used in
> diff-mode.el in your patch.  This is a clear indication that
> some part should be moved to outline.el.

You're right. 

> Note that the following function has no diff-specific code, so it could
> be refactored and some generalized function added to outline.el.  Such
> function could be like `outline-map-region' to accept arguments `beg', `end'
> and a predicate function that defines whether to hide or show the
> body.

Ok. I'll make a try.

In the meantime, here is a new version that implements both:

- Outline based on presence of long lines (using `so-long-detected-long-line-p'
predicate)

- Outline based on file names matching a regexp (well, truly file
  heading matching a regexp).


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Support-for-outline-default-state-in-Diff-buffers.patch --]
[-- Type: text/x-diff, Size: 8868 bytes --]

From a571efeb5f90e42d1c86e0bef8fe0ebba819914c Mon Sep 17 00:00:00 2001
From: Matthias Meulien <orontee@gmail.com>
Date: Sat, 13 Nov 2021 12:08:58 +0100
Subject: [PATCH] Support for outline default state in Diff buffers

* lisp/vc/diff-mode.el (diff-outline-default-state): Add custom
variable that defines an outline state and apply that state in Diff
buffers.
---
 lisp/vc/diff-mode.el | 166 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 164 insertions(+), 2 deletions(-)

diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index e68aa2257d..578e80b4e2 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -50,7 +50,11 @@
 ;;
 ;; - in diff-apply-hunk, strip context in replace-match to better
 ;;   preserve markers and spacing.
+;;
 ;; - Handle `diff -b' output in context->unified.
+;;
+;; - Support outlining files by name (eg to skip automatically
+;;   generated files like package-lock.json in Javascript projects).
 
 ;;; Code:
 (eval-when-compile (require 'cl-lib))
@@ -62,6 +66,15 @@
 (defvar vc-find-revision-no-save)
 (defvar add-log-buffer-file-name-function)
 
+(eval-when-compile (require 'outline))
+(autoload 'outline-minor-mode "outline")
+(defvar outline-minor-mode)
+
+(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)
 
 (defgroup diff-mode ()
   "Major mode for viewing/editing diffs."
@@ -147,6 +160,61 @@ diff-font-lock-syntax
                  (const :tag "Highlight syntax" t)
                  (const :tag "Allow hunk-based fallback" hunk-also)))
 
+(defcustom diff-outline-default-state nil
+  "If non-nil, some files or hunk are outlined.
+Outlining is performed by Outline minor mode.
+
+If equal to `outline-hunks', only file and hunk headings are
+visibles.
+
+If equal to a lambda function or function name, this function is
+expected to toggle file or hunks visibility, and will be called
+after the mode is enabled.
+
+If equal to a list of symbols, hunk headings will be outlined
+depending on the conditions defined for each symbol:
+
+- If `line-count-threshold', when hunks under a given file
+  heading cover more than `diff-outline-line-count-threshold'
+  lines, they are outlined;
+
+- If `file-heading-regexp', file headings which match the regexp
+  `diff-outline-file-heading-regexp' are outlined;
+
+- If `long-line-threshold', when a hunk under a given file
+  heading have a line with more than
+  `diff-outline-long-line-threshold' characters, all hunks for
+  that file heading are outlined."
+  :version "29.1"
+  :type '(choice (const :tag "Don't outline " nil)
+                 (const :tag "Outline hunks" outline-hunks)
+                 (set :tag "Outline some files"
+                      (const
+                       :tag "Outline files with long hunks"
+                       line-count-threshold)
+                      (const
+                       :tag "Outline files by name"
+                       file-heading-regexp)
+                      (const
+                       :tag "Outline files whose hunks involve long lines"
+                       long-line-threshold))
+                 (function :tag "Custom function")))
+
+(defcustom diff-outline-line-count-threshold 50
+  "Minimal number of lines of hunks for a file to be outlined."
+  :version "29.1"
+  :type '(natnum :tag "Number of lines"))
+
+(defcustom diff-outline-file-heading-regexp "ChangeLog\\|package-lock\\.json"
+  "Regexp to match file headings to be outlined."
+  :version "29.1"
+  :type '(regexp :tag "Files to outline"))
+
+(defcustom diff-outline-long-line-threshold 1000
+  "Minimal number of characters in a line for a file to be outlined."
+  :version "29.1"
+  :type '(natnum :tag "Number of lines"))
+
 (defvar diff-vc-backend nil
   "The VC backend that created the current Diff buffer, if any.")
 
@@ -1578,7 +1646,8 @@ diff-setup-whitespace
 
 (defun diff-setup-buffer-type ()
   "Try to guess the `diff-buffer-type' from content of current Diff mode buffer.
-`outline-regexp' is updated accordingly."
+`outline-regexp' is updated accordingly and outline default state
+applied."
   (save-excursion
     (goto-char (point-min))
     (setq-local diff-buffer-type
@@ -1589,7 +1658,8 @@ diff-setup-buffer-type
     (setq diff-outline-regexp
           (concat "\\(^diff --git.*\n\\|" diff-hunk-header-re "\\)"))
     (setq-local outline-level #'diff--outline-level))
-  (setq-local outline-regexp diff-outline-regexp))
+  (setq-local outline-regexp diff-outline-regexp)
+  (diff-outline-apply-default-state))
 
 (defun diff-delete-if-empty ()
   ;; An empty diff file means there's no more diffs to integrate, so we
@@ -2143,6 +2213,98 @@ diff-refresh-hunk
       (delete-file file1)
       (delete-file file2))))
 
+(defun diff-outline-apply-default-state ()
+  "Apply the outline state defined by `diff-outline-default-state'.
+
+When `diff-outline-default-state' is non-nil, Outline minor mode
+is enabled."
+  (when diff-outline-default-state
+    (when (not outline-minor-mode)
+      (outline-minor-mode))
+    (cond
+     ((eq diff-outline-default-state 'outline-hunks)
+      (outline-hide-body))
+     ((listp diff-outline-default-state)
+      (diff--outline-apply-default-state))
+     ((when (functionp diff-outline-default-state)
+        (funcall diff-outline-default-state))))))
+
+(defun diff--outline-apply-default-state ()
+  "Outline file or hunks according to `diff-outline-default-state'.
+
+See `diff-outline-default-state' for details.
+
+Inspired by `outline-hide-sublevels'."
+  (interactive)
+  (when outline-minor-mode
+    (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)))))
+	(if (< end beg)
+            (setq beg (prog1 end (setq end beg))))
+	;; First hide sublevels
+	(outline-hide-sublevels 1)
+	;; Then unhide short subtrees
+	(outline-map-region
+	 (lambda ()
+           (when (= (funcall outline-level) 1)
+             (goto-char (match-end 0))
+             (let ((overlays (overlays-at (point))))
+               (while overlays
+		 (let ((overlay (car overlays)))
+		   (progn
+                     (when (eq (overlay-get overlay 'invisible) 'outline)
+                       (goto-char (match-beginning 0))
+                       (cond
+                        ((and
+                          (memq 'file-heading-regexp
+                                diff-outline-default-state)
+                          ;; hide entry when file heading match
+                          ;; `diff-outline-file-heading-regexp'
+                          (message (concat "trying regexp on " (match-string 0)))
+                          (string-match-p
+                           diff-outline-file-heading-regexp
+                           (match-string 0)))
+                         (outline-hide-entry))
+
+                        ((and
+                          (memq 'line-count-threshold
+                                diff-outline-default-state)
+                          ;; show only branches when line count >
+                          ;; threshold
+                          (let ((line-count (count-lines
+                                             (overlay-end overlay)
+                                             (overlay-start overlay))))
+			    (< diff-outline-line-count-threshold line-count)))
+                         (outline-show-branches))
+
+                        ((and
+                          (memq 'long-line-threshold
+                                diff-outline-default-state)
+                          ;; show only branches when a long line is
+                          ;; detected
+                          (save-restriction
+                            (narrow-to-region (overlay-start overlay)
+                                              (overlay-end overlay))
+                            (let ((so-long-skip-leading-comments nil)
+                                  (so-long-threshold
+                                   diff-outline-long-line-threshold)
+                                  (so-long-max-lines nil))
+                              (so-long-detected-long-line-p))))
+                         (outline-show-branches))
+
+                        (t (outline-show-subtree))))
+                     (setq overlays (cdr overlays))))))))
+	 beg end)))))
+
 ;;; Fine change highlighting.
 
 (defface diff-refine-changed
-- 
2.30.2


[-- Attachment #3: Type: text/plain, Size: 14 bytes --]


-- 
Matthias

^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-13 19:29       ` Juri Linkov
  2021-11-13 21:27         ` Matthias Meulien
@ 2021-11-13 23:29         ` Matthias Meulien
  2021-11-29 17:06           ` Juri Linkov
  1 sibling, 1 reply; 35+ messages in thread
From: Matthias Meulien @ 2021-11-13 23:29 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

[-- Attachment #1: Type: text/plain, Size: 54 bytes --]

Updated patch that takes Juri comments into account.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Support-for-outline-default-state-in-Diff-buffers.patch --]
[-- Type: text/x-diff, Size: 9068 bytes --]

From 4a9f53e73dcbcd77df339bca012ee72aec343f18 Mon Sep 17 00:00:00 2001
From: Matthias Meulien <orontee@gmail.com>
Date: Sat, 13 Nov 2021 12:08:58 +0100
Subject: [PATCH] Support for outline default state in Diff buffers

* lisp/outline.el (outline-map-sublevel-overlay):
* lisp/vc/diff-mode.el (diff-outline-default-state): Variable that
defines an outline state.
(diff-outline-apply-default-state)
(diff--outline-set-file-heading-visibility): Apply outline state
defined in `diff-outline-default-state'.
---
 lisp/outline.el      |  40 ++++++++++++++
 lisp/vc/diff-mode.el | 128 ++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 166 insertions(+), 2 deletions(-)

diff --git a/lisp/outline.el b/lisp/outline.el
index cefb811703..77de31f785 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -1100,6 +1100,46 @@ outline-hide-sublevels
 
 (define-obsolete-function-alias 'hide-sublevels #'outline-hide-sublevels "25.1")
 
+(defun outline-map-sublevel-overlay (level fun)
+  "Hide everything and call FUN on the LEVEL sublevels of headers .
+
+When FUN is called, point is at the beginning of the heading, the
+match data is set appropriately, and FUN receives one argument,
+the outline overlay for the heading entry.
+
+This also unhides the top heading-less body, if any."
+  (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)))))
+      (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 ()
+	 (when (= (funcall outline-level) level)
+           (goto-char (match-end 0))
+           (let ((overlays (overlays-at (point))))
+             (while overlays
+	       (let ((overlay (car overlays)))
+                 (when (eq (overlay-get overlay 'invisible) 'outline)
+                   (goto-char (match-beginning 0))
+                   (funcall fun overlay))
+                 (setq overlays (cdr overlays)))))))
+       beg end)))
+  (run-hooks 'outline-view-change-hook))
+
 (defun outline-hide-other ()
   "Hide everything except current body and parent and top-level headings.
 This also unhides the top heading-less body, if any."
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index e68aa2257d..9617e6ceee 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -50,6 +50,7 @@
 ;;
 ;; - in diff-apply-hunk, strip context in replace-match to better
 ;;   preserve markers and spacing.
+;;
 ;; - Handle `diff -b' output in context->unified.
 
 ;;; Code:
@@ -62,6 +63,18 @@
 (defvar vc-find-revision-no-save)
 (defvar add-log-buffer-file-name-function)
 
+(eval-when-compile (require 'outline))
+(defvar outline-minor-mode)
+(declare-function outline-minor-mode "outline" (&optional args))
+(declare-function outline-hide-entry "outline")
+(declare-function outline-show-branches "outline")
+(declare-function outline-show-subtree "outline" (&optional event))
+
+(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)
 
 (defgroup diff-mode ()
   "Major mode for viewing/editing diffs."
@@ -147,6 +160,61 @@ diff-font-lock-syntax
                  (const :tag "Highlight syntax" t)
                  (const :tag "Allow hunk-based fallback" hunk-also)))
 
+(defcustom diff-outline-default-state nil
+  "If non-nil, some files or hunk are outlined.
+Outlining is performed by Outline minor mode.
+
+If equal to `outline-hunks', only file and hunk headings are
+visibles.
+
+If equal to a lambda function or function name, this function is
+expected to toggle file or hunks visibility, and will be called
+after the mode is enabled.
+
+If equal to a list of symbols, hunk headings will be outlined
+depending on the conditions defined for each symbol:
+
+- If `line-count-threshold', when hunks under a given file
+  heading cover more than `diff-outline-line-count-threshold'
+  lines, they are outlined;
+
+- If `file-heading-regexp', file headings which match the regexp
+  `diff-outline-file-heading-regexp' are outlined;
+
+- If `long-line-threshold', when a hunk under a given file
+  heading have a line with more than
+  `diff-outline-long-line-threshold' characters, all hunks for
+  that file heading are outlined."
+  :version "29.1"
+  :type '(choice (const :tag "Don't outline " nil)
+                 (const :tag "Outline hunks" outline-hunks)
+                 (set :tag "Outline some files"
+                      (const
+                       :tag "Outline files with long hunks"
+                       line-count-threshold)
+                      (const
+                       :tag "Outline files by name"
+                       file-heading-regexp)
+                      (const
+                       :tag "Outline files whose hunks involve long lines"
+                       long-line-threshold))
+                 (function :tag "Custom function")))
+
+(defcustom diff-outline-line-count-threshold 50
+  "Minimal number of lines of hunks for a file to be outlined."
+  :version "29.1"
+  :type '(natnum :tag "Number of lines"))
+
+(defcustom diff-outline-file-heading-regexp "ChangeLog\\|package-lock\\.json"
+  "Regexp to match file headings to be outlined."
+  :version "29.1"
+  :type '(regexp :tag "Files to outline"))
+
+(defcustom diff-outline-long-line-threshold 1000
+  "Minimal number of characters in a line for a file to be outlined."
+  :version "29.1"
+  :type '(natnum :tag "Number of lines"))
+
 (defvar diff-vc-backend nil
   "The VC backend that created the current Diff buffer, if any.")
 
@@ -1578,7 +1646,8 @@ diff-setup-whitespace
 
 (defun diff-setup-buffer-type ()
   "Try to guess the `diff-buffer-type' from content of current Diff mode buffer.
-`outline-regexp' is updated accordingly."
+`outline-regexp' is updated accordingly and outline default state
+applied."
   (save-excursion
     (goto-char (point-min))
     (setq-local diff-buffer-type
@@ -1589,7 +1658,8 @@ diff-setup-buffer-type
     (setq diff-outline-regexp
           (concat "\\(^diff --git.*\n\\|" diff-hunk-header-re "\\)"))
     (setq-local outline-level #'diff--outline-level))
-  (setq-local outline-regexp diff-outline-regexp))
+  (setq-local outline-regexp diff-outline-regexp)
+  (diff-outline-apply-default-state))
 
 (defun diff-delete-if-empty ()
   ;; An empty diff file means there's no more diffs to integrate, so we
@@ -2143,6 +2213,60 @@ diff-refresh-hunk
       (delete-file file1)
       (delete-file file2))))
 
+(defun diff-outline-apply-default-state ()
+  "Apply the outline state defined by `diff-outline-default-state'.
+
+When `diff-outline-default-state' is non-nil, Outline minor mode
+is enabled."
+  (interactive)
+  (when diff-outline-default-state
+    (when (not outline-minor-mode)
+      (outline-minor-mode))
+    (cond
+     ((eq diff-outline-default-state 'outline-hunks)
+      (outline-hide-body))
+     ((listp diff-outline-default-state)
+      (outline-map-sublevel-overlay
+       1 #'diff--outline-set-file-heading-visibility))
+     ((when (functionp diff-outline-default-state)
+        (funcall diff-outline-default-state))))))
+
+(defun diff--outline-set-file-heading-visibility (overlay)
+  (cond
+   ;; hide entry when file heading match
+   ;; `diff-outline-file-heading-regexp'
+   ((and
+     (memq 'file-heading-regexp
+           diff-outline-default-state)
+     (string-match-p
+      diff-outline-file-heading-regexp
+      (match-string 0)))
+    (outline-hide-entry))
+   ;; show only branches when line count > threshold
+   ((and
+     (memq 'line-count-threshold
+           diff-outline-default-state)
+     (let ((line-count (count-lines
+                        (overlay-end overlay)
+                        (overlay-start overlay))))
+       (< diff-outline-line-count-threshold line-count)))
+    (outline-show-branches))
+   ;; show only branches when a long line is detected
+   ((and
+     (memq 'long-line-threshold
+           diff-outline-default-state)
+     (save-restriction
+       (narrow-to-region (overlay-start overlay)
+                         (overlay-end overlay))
+       (let ((so-long-skip-leading-comments nil)
+             (so-long-threshold
+              diff-outline-long-line-threshold)
+             (so-long-max-lines nil))
+         (so-long-detected-long-line-p))))
+    (outline-show-branches))
+   ;; otherwise show subtree
+   (t (outline-show-subtree))))
+
 ;;; Fine change highlighting.
 
 (defface diff-refine-changed
-- 
2.30.2


[-- Attachment #3: Type: text/plain, Size: 14 bytes --]


-- 
Matthias

^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-13 18:08   ` Matthias Meulien
  2021-11-13 18:27     ` Juri Linkov
  2021-11-13 18:41     ` Matthias Meulien
@ 2021-11-14 18:25     ` Juri Linkov
  2021-11-14 19:35       ` Matthias Meulien
  2021-11-14 19:54       ` Matthias Meulien
  2 siblings, 2 replies; 35+ messages in thread
From: Juri Linkov @ 2021-11-14 18:25 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

>> (...) I'm using outline-minor-mode in diff buffers all the time with
>> (add-hook 'diff-mode-hook 'outline-minor-mode) and would like to
>> understand how your patch improves this.
>>
>> Could the above hook be replaced with customization of
>> diff-outline-default-state?
>
> It was supposed to. But I am also using outline-minor-mode in diff
> buffers all the time with the same hook as you, and I must confess I've
> not tested until you asked: `diff-outline-apply-default-state' is
> supposed to automatically turn on `outline-minor-mode' but I forgot to
> autoload the later resulting in "Symbol’s value as variable is void:
> outline-minor-mode". I'll fix this.

Something strange happened recently with diff-mode and outline-minor-mode.
I can't use this combination anymore.  While reading your patches, I'm trying
to type TAB on the hunk header to hide already viewed hunks of your patch.
But TAB does nothing now.  Do you see the same?  TAB is used to hide/show body
when outline-minor-mode-cycle is t.





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-14 18:25     ` Juri Linkov
@ 2021-11-14 19:35       ` Matthias Meulien
  2021-11-14 19:46         ` Juri Linkov
  2021-11-14 19:54       ` Matthias Meulien
  1 sibling, 1 reply; 35+ messages in thread
From: Matthias Meulien @ 2021-11-14 19:35 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

Juri Linkov <juri@linkov.net> writes:

> Something strange happened recently with diff-mode and outline-minor-mode.
> I can't use this combination anymore.  While reading your patches, I'm trying
> to type TAB on the hunk header to hide already viewed hunks of your patch.
> But TAB does nothing now.  Do you see the same?  TAB is used to hide/show body
> when outline-minor-mode-cycle is t.

I've observed too that `outline-minor-mode-cycle' didn't worked well in
`diff-mode' buffers but I never saw it working!

Since I am used to hitting TAB to go to next button or for completion,
and I am used to the horrible `outline-minor-mode-prefix' bindings, my
feeling is that TAB was a bad choice for this, and I didn't try to
debug: I just turned off `outline-minor-mode-cycle'...

Do you have `diff-font-lock-prettify' enabled? I remember the situation
was better without this, not sure...
-- 
Matthias





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-14 19:35       ` Matthias Meulien
@ 2021-11-14 19:46         ` Juri Linkov
  0 siblings, 0 replies; 35+ messages in thread
From: Juri Linkov @ 2021-11-14 19:46 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

>> Something strange happened recently with diff-mode and outline-minor-mode.
>> I can't use this combination anymore.  While reading your patches, I'm trying
>> to type TAB on the hunk header to hide already viewed hunks of your patch.
>> But TAB does nothing now.  Do you see the same?  TAB is used to hide/show body
>> when outline-minor-mode-cycle is t.
>
> I've observed too that `outline-minor-mode-cycle' didn't worked well in
> `diff-mode' buffers but I never saw it working!
>
> Since I am used to hitting TAB to go to next button or for completion,
> and I am used to the horrible `outline-minor-mode-prefix' bindings, my
> feeling is that TAB was a bad choice for this, and I didn't try to
> debug: I just turned off `outline-minor-mode-cycle'...

TAB is not a problem when `outline-minor-mode-cycle-filter'
is customized to allow outline's TAB only on some parts
of the outline heading, e.g. only at the beginning,
or everywhere except the beginning of the diff hunk line.
Then typing TAB everywhere else will go to the next button.

> Do you have `diff-font-lock-prettify' enabled? I remember the situation
> was better without this, not sure...

Nope, `diff-font-lock-prettify' is disabled.





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-14 18:25     ` Juri Linkov
  2021-11-14 19:35       ` Matthias Meulien
@ 2021-11-14 19:54       ` Matthias Meulien
  2021-11-14 20:31         ` Juri Linkov
  1 sibling, 1 reply; 35+ messages in thread
From: Matthias Meulien @ 2021-11-14 19:54 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

Juri Linkov <juri@linkov.net> writes:

> Something strange happened recently with diff-mode and outline-minor-mode.
> I can't use this combination anymore.  While reading your patches, I'm trying
> to type TAB on the hunk header to hide already viewed hunks of your patch.
> But TAB does nothing now.  Do you see the same?  TAB is used to hide/show body
> when outline-minor-mode-cycle is t.

I just tested it: On hunk heading, TAB calls `diff-hunk-next'. On file
headings TAB cycle visibility. It's what I always observed. Looks crazy
but not buggy to my eyes.

-- 
Matthias





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-14 19:54       ` Matthias Meulien
@ 2021-11-14 20:31         ` Juri Linkov
  0 siblings, 0 replies; 35+ messages in thread
From: Juri Linkov @ 2021-11-14 20:31 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

>> Something strange happened recently with diff-mode and outline-minor-mode.
>> I can't use this combination anymore.  While reading your patches, I'm trying
>> to type TAB on the hunk header to hide already viewed hunks of your patch.
>> But TAB does nothing now.  Do you see the same?  TAB is used to hide/show body
>> when outline-minor-mode-cycle is t.
>
> I just tested it: On hunk heading, TAB calls `diff-hunk-next'. On file
> headings TAB cycle visibility. It's what I always observed. Looks crazy
> but not buggy to my eyes.

Before the recent changes, TAB cycled visibility not only on file headings,
but also on hunk headings.  It was very useful to use TAB to cycle visibility
on hunk headings on a long patch where navigating to the file heading
to be able to use TAB on it will take too much time.  It's much easier
to navigate to the nearest hunk heading to cycle its visibility.





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-13 23:29         ` Matthias Meulien
@ 2021-11-29 17:06           ` Juri Linkov
  2021-11-30 19:33             ` Matthias Meulien
  2021-12-11 18:18             ` Matthias Meulien
  0 siblings, 2 replies; 35+ messages in thread
From: Juri Linkov @ 2021-11-29 17:06 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

Hi Matthias,

> Updated patch that takes Juri comments into account.

I didn't forget about your patch.  Actually, I have been using it all the time,
and it saved me in many cases when long lines in diffs make Emacs unresponsive.
With your patch, there are only slight delays for 2-3 seconds while scrolling
a lot of hidden outlines, but this is not a problem, thank you very much.

I have the same problem of too long outline-body lines in xref buffers,
so need to use a similar feature with:

#+begin_src emacs-lisp
(add-hook 'xref-after-update-hook
          (lambda ()
            (setq-local outline-regexp (if (eq xref-file-name-display 'abs)
                                           "/" "[^ 0-9]"))
            (outline-minor-mode +1)
            (outline-map-region
             (lambda ()
               (when (string-match-p "ChangeLog\\|test/manual/etags"
                                     (buffer-substring
                                      (line-beginning-position)
                                      (line-end-position)))
                 (outline-hide-entry)))
             (point-min) (point-max))))
#+end_src

It would be nice to move this feature completely to outline.el,
so it could be used by other modes, not only in diff-mode.
Please see bug#49731 that will be closed after this feature
will be supported generally by outline-minor-mode.

Then there are two variants: to add customizable variables
to outline-minor-mode like outline-default-state or
outline-hide-initial, or allow hiding initial heading
with a hook like

  (add-hook 'outline-minor-mode-hook 'outline-hide-file-headings)

that could use a regexp from e.g. outline-hide-heading-regexp
or some better name.

> +(defun outline-map-sublevel-overlay (level fun)

Instead of adding a new function, you can use outline-map-region
after adding a new argument, e.g.

  (defun outline-map-region (fun beg end &optional next-heading-fun)

By default, outline-map-region uses outline-next-heading to move to the
next heading, but a new argument could allow to use
outline-next-visible-heading or outline-forward-same-level, etc.
with (or (and next-heading-fun (funcall next-heading-fun)) (outline-next-heading))

> +(defcustom diff-outline-file-heading-regexp "ChangeLog\\|package-lock\\.json"

There is no need to add arbitrary default values.
The users know better what values are needed.
For example, I customized it to "public/packs",
so it hides the outline headings for compiled assets like:
"public/packs-pro/js/application-fa9d8202220130e40f46.js"

> +(defun diff-outline-apply-default-state ()
> +  (when diff-outline-default-state
> +    (when (not outline-minor-mode)
> +      (outline-minor-mode))

Actually, the above lines are not needed because the same can be achieved by:

  (add-hook 'diff-mode-hook 'outline-minor-mode)

> +    (cond
> +     ((eq diff-outline-default-state 'outline-hunks)
> +      (outline-hide-body))

These lines are not needed too, because the same can be achieved by:

  (add-hook 'outline-minor-mode-hook 'outline-hide-body)

> +     ((when (functionp diff-outline-default-state)
> +        (funcall diff-outline-default-state))))))

And this can be achieved by:

  (add-hook 'outline-minor-mode-hook 'custom-function)

> +(defun diff--outline-set-file-heading-visibility (overlay)
> +  (cond
> +   ((and
> +     (memq 'file-heading-regexp
> +           diff-outline-default-state)
> +     (string-match-p
> +      diff-outline-file-heading-regexp
> +      (match-string 0)))

Here (match-string 0) is unusable in most values of outline-regexp
that don't contain the whole heading line.  A better way
to get the whole heading line usable in modes other than diff-mode
would be:

  (buffer-substring (line-beginning-position) (line-end-position))





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-29 17:06           ` Juri Linkov
@ 2021-11-30 19:33             ` Matthias Meulien
  2021-12-11 18:18             ` Matthias Meulien
  1 sibling, 0 replies; 35+ messages in thread
From: Matthias Meulien @ 2021-11-30 19:33 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

Hi Juri,

> (...) Actually, I have been using it all the time, and it saved me in
> many cases when long lines in diffs make Emacs unresponsive.

Good news!

> I have the same problem of too long outline-body lines in xref
> buffers, (...)  It would be nice to move this feature completely to
> outline.el,

I fully agree.

> so it could be used by other modes, not only in diff-mode.
> Please see bug#49731 that will be closed after this feature
> will be supported generally by outline-minor-mode.
>
> Then there are two variants:

Yes. I'll think of it. If I remember correctly, org-mode handles an
initial state too, I've no idea of how it's implemented there. It may
help to choose between the two variants in order to merge all
implementations in one place at some point.

And many thanks for your comments on the patch. I'll modify it after I
have had a look at your sugestion to move the feature to outline.el. But
since I've not much spare time, please be patient!
-- 
Matthias





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-29 17:06           ` Juri Linkov
  2021-11-30 19:33             ` Matthias Meulien
@ 2021-12-11 18:18             ` Matthias Meulien
  2021-12-12  8:43               ` Juri Linkov
  1 sibling, 1 reply; 35+ messages in thread
From: Matthias Meulien @ 2021-12-11 18:18 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

[-- Attachment #1: Type: text/plain, Size: 232 bytes --]

Hi Juri,

Juri Linkov <juri@linkov.net> writes:

> (...) It would be nice to move this feature completely to outline.el,
> so it could be used by other modes, not only in diff-mode.

I've this simple patch for the outline.el part.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Extend-Outline-mode-with-default-visibility-state.patch --]
[-- Type: text/x-diff, Size: 4263 bytes --]

From d8fee4c307178fc2e0e7c206b8b8a42b2acda719 Mon Sep 17 00:00:00 2001
From: Matthias Meulien <orontee@gmail.com>
Date: Wed, 8 Dec 2021 22:35:42 +0100
Subject: [PATCH] Extend Outline mode with 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
---
 lisp/outline.el | 50 ++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 45 insertions(+), 5 deletions(-)

diff --git a/lisp/outline.el b/lisp/outline.el
index 2ede4e23ea..a3e5da4f5b 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -353,7 +353,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)
+  (outline-apply-default-state))
 
 (defvar outline-minor-mode-map)
 
@@ -436,7 +437,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 (or outline-minor-mode-cycle outline-minor-mode-highlight)
       (if font-lock-fontified
           (font-lock-remove-keywords nil outline-font-lock-keywords))
@@ -1058,13 +1060,16 @@ outline-show-heading
 		       (progn (outline-end-of-heading) (point))
 		       nil))
 
-(defun outline-hide-sublevels (levels)
+(defun outline-hide-sublevels (levels &optional fun)
   "Hide everything but the top LEVELS levels of headers, in whole buffer.
 This also unhides the top heading-less body, if any.
 
 Interactively, the prefix argument supplies the value of LEVELS.
 When invoked without a prefix argument, LEVELS defaults to the level
-of the current heading, or to 1 if the current line is not a heading."
+of the current heading, or to 1 if the current line is not a heading.
+
+When FUN is defined, sublevels aren't hidden but FUN is called
+for each of them."
   (interactive (list
 		(cond
 		 (current-prefix-arg (prefix-numeric-value current-prefix-arg))
@@ -1093,7 +1098,9 @@ outline-hide-sublevels
       (outline-map-region
        (lambda ()
 	 (if (<= (funcall outline-level) levels)
-	     (outline-show-heading)))
+	     (if fun
+                 (funcall fun)
+               (outline-show-heading))))
        beg end)
       ;; Finally unhide any trailing newline.
       (goto-char (point-max))
@@ -1307,6 +1314,39 @@ outline-headers-as-kill
                     (insert "\n\n"))))))
           (kill-new (buffer-string)))))))
 
+(defcustom outline-default-state nil
+  "If non-nil, some headings are initially outlined.
+
+If equal to `only-headings', only heading are shown.
+
+If equal to a number, hide everything but the headings at that
+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 "Show all" nil)
+                 (const :tag "Only headings" only-headings)
+                 (natnum :tag "Outline level")
+                 (function :tag "Custom function"))
+  :local t
+  :safe t)
+;; TODO fix variable being set through file local variable
+
+(defun outline-apply-default-state ()
+  "Apply the outline state defined by `outline-default-state'."
+  (interactive)
+  (cond
+   ((not outline-default-state) (outline-show-all))
+   ((eq outline-default-state 'only-headings)
+    (outline-show-all)
+    (outline-hide-region-body (point-min) (point-max)))
+   ((integerp outline-default-state)
+    (outline-hide-sublevels outline-default-state))
+   ((when (functionp outline-default-state)
+      (funcall outline-default-state)))))
+
 (defun outline--cycle-state ()
   "Return the cycle state of current heading.
 Return either 'hide-all, 'headings-only, or 'show-all."
-- 
2.30.2


[-- Attachment #3: Type: text/plain, Size: 299 bytes --]


One thing that bothers me is that I am not able to store the wanted
default visibility state as a local variable... Any suggestion welcome!

Also, I've not started to rewrite the diff-mode part on top of this
patch, so comments are most welcome in case I am going in wrong
direction.
-- 
Matthias


^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-11 18:18             ` Matthias Meulien
@ 2021-12-12  8:43               ` Juri Linkov
  2021-12-13  7:55                 ` Matthias Meulien
  0 siblings, 1 reply; 35+ messages in thread
From: Juri Linkov @ 2021-12-12  8:43 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

> I've this simple patch for the outline.el part.

Thanks, this is a good starting point to add just the basic functionality
like org-mode initial visibility supported by ‘org-startup-folded’ and
per-file settings:

   #+STARTUP: fold              (or ‘overview’, this is equivalent)
   #+STARTUP: nofold            (or ‘showall’, this is equivalent)
   #+STARTUP: content
   #+STARTUP: show<n>levels (<n> = 2..5)
   #+STARTUP: showeverything

> @@ -1058,13 +1060,16 @@ outline-show-heading
> -(defun outline-hide-sublevels (levels)
> +(defun outline-hide-sublevels (levels &optional fun)

It seems you don't use this argument in this patch?

> +  :local t
> +  :safe t)
> +;; TODO fix variable being set through file local variable

> One thing that bothers me is that I am not able to store the wanted
> default visibility state as a local variable... Any suggestion welcome!

For example, `outline-minor-mode-cycle' and `outline-minor-mode-highlight'
have no `:local t', but when visiting a file that sets these file local variables,
then automatically become local.

> +(defun outline-apply-default-state ()
> +  "Apply the outline state defined by `outline-default-state'."
> +  (interactive)
> +  (cond
> +   ((not outline-default-state) (outline-show-all))

It seems this change doesn't keep the current default behavior.
Maybe the result will look like it currently works, maybe not.
Who knows what effect will have calling `outline-show-all'
by default in some user configurations.

> +   ((eq outline-default-state 'only-headings)
> +    (outline-show-all)
> +    (outline-hide-region-body (point-min) (point-max)))
> +   ((integerp outline-default-state)
> +    (outline-hide-sublevels outline-default-state))
> +   ((when (functionp outline-default-state)
> +      (funcall outline-default-state)))))

Maybe some other values from org-mode could be supported too?

> Also, I've not started to rewrite the diff-mode part on top of this
> patch, so comments are most welcome in case I am going in wrong
> direction.

I think the direction is right: first outline could support
the initial visibility feature, then later various modes
could use it: in diff-mode, xref, etc.





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-12  8:43               ` Juri Linkov
@ 2021-12-13  7:55                 ` Matthias Meulien
  2021-12-13  8:58                   ` Juri Linkov
  2021-12-26 16:05                   ` Matthias Meulien
  0 siblings, 2 replies; 35+ messages in thread
From: Matthias Meulien @ 2021-12-13  7:55 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

Juri Linkov <juri@linkov.net> writes:

>> I've this simple patch for the outline.el part.
>
> Thanks, this is a good starting point to add just the basic functionality
> like org-mode initial visibility supported by ‘org-startup-folded’ and
> per-file settings:
>
>    #+STARTUP: fold              (or ‘overview’, this is equivalent)
>    #+STARTUP: nofold            (or ‘showall’, this is equivalent)
>
>    #+STARTUP: content
>    #+STARTUP: show<n>levels (<n> = 2..5)
>
>    #+STARTUP: showeverything
>
>> @@ -1058,13 +1060,16 @@ outline-show-heading
>> -(defun outline-hide-sublevels (levels)
>> +(defun outline-hide-sublevels (levels &optional fun)
>
> It seems you don't use this argument in this patch?

It will be necessary for `diff-mode' to provide its own default state
functions that decide of the visibility of each heading
individually. You're right, it's not needed right now so I'll remove it
and reintroduce it when working on `diff-mode'.

>> +  :local t
>> +  :safe t)
>> +;; TODO fix variable being set through file local variable
>
>> One thing that bothers me is that I am not able to store the wanted
>> default visibility state as a local variable... Any suggestion welcome!
>
> For example, `outline-minor-mode-cycle' and `outline-minor-mode-highlight'
> have no `:local t', but when visiting a file that sets these file local variables,
> then automatically become local.
>
>> +(defun outline-apply-default-state ()
>> +  "Apply the outline state defined by `outline-default-state'."
>> +  (interactive)
>> +  (cond
>> +   ((not outline-default-state) (outline-show-all))
>
> It seems this change doesn't keep the current default behavior.
> Maybe the result will look like it currently works, maybe not.
> Who knows what effect will have calling `outline-show-all'
> by default in some user configurations.

Good point. I can remove that line but one must be aware that calling
`outline-apply-default-state' interactively after changing some headings
visibility won't restore the default state. Not very intuitive...

>
>> +   ((eq outline-default-state 'only-headings)
>> +    (outline-show-all)
>> +    (outline-hide-region-body (point-min) (point-max)))
>> +   ((integerp outline-default-state)
>> +    (outline-hide-sublevels outline-default-state))
>> +   ((when (functionp outline-default-state)
>> +      (funcall outline-default-state)))))
>
> Maybe some other values from org-mode could be supported too?

Sure.

Let me first fix handling of local values. I saw that `outline-mode' is
set *before* local variables when I visit a file whose major mode
default to `outline-mode'. 

> I think the direction is right: first outline could support
> the initial visibility feature, then later various modes
> could use it: in diff-mode, xref, etc.

That's my plan.

For xref mode I am wondering whether "regexp" based outline mode is the
right thing to use. Should there be an outline mode based on
text-properties? The buffer content is built internally and we know
where "headings" are inserted... But it's another topic.

Thanks for reading the patch! I'll send another one, hopefully taking
your remarks into account, when support of local variables is fixed.
-- 
Matthias





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-13  7:55                 ` Matthias Meulien
@ 2021-12-13  8:58                   ` Juri Linkov
  2021-12-26 16:05                   ` Matthias Meulien
  1 sibling, 0 replies; 35+ messages in thread
From: Juri Linkov @ 2021-12-13  8:58 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

>> Thanks, this is a good starting point to add just the basic functionality
>> like org-mode initial visibility supported by ‘org-startup-folded’ and
>> per-file settings:
>>
>>    #+STARTUP: fold              (or ‘overview’, this is equivalent)
>>    #+STARTUP: nofold            (or ‘showall’, this is equivalent)
>>    #+STARTUP: content
>>    #+STARTUP: show<n>levels (<n> = 2..5)
>>    #+STARTUP: showeverything
>>
>>> @@ -1058,13 +1060,16 @@ outline-show-heading
>>> -(defun outline-hide-sublevels (levels)
>>> +(defun outline-hide-sublevels (levels &optional fun)
>>
>> It seems you don't use this argument in this patch?
>
> It will be necessary for `diff-mode' to provide its own default state
> functions that decide of the visibility of each heading
> individually. You're right, it's not needed right now so I'll remove it
> and reintroduce it when working on `diff-mode'.

When we will have enough outline functions to be used as values
of `outline-default-state', then `diff-mode' doesn't need to provide
its own default state functions - `diff-mode' could just set
a suitable buffer-local value of `outline-default-state'
from the list of options provided by `outline-default-state'.

Also `outline-default-state' could support a string value
applied as a regexp on outline heading lines to decide
whether to hide or show that outline.

>>> +  (cond
>>> +   ((not outline-default-state) (outline-show-all))
>>
>> It seems this change doesn't keep the current default behavior.
>> Maybe the result will look like it currently works, maybe not.
>> Who knows what effect will have calling `outline-show-all'
>> by default in some user configurations.
>
> Good point. I can remove that line but one must be aware that calling
> `outline-apply-default-state' interactively after changing some headings
> visibility won't restore the default state. Not very intuitive...

`outline-default-state' could provide an explicit value
that will show all outlines.  Maybe even the default value
of `outline-default-state' could be changed to this explicit value.
Then calling `outline-apply-default-state' interactively
will show all outlines.  This way it will be more clear
to users what is going on.

>>> +   ((eq outline-default-state 'only-headings)
>>> +    (outline-show-all)
>>> +    (outline-hide-region-body (point-min) (point-max)))
>>> +   ((integerp outline-default-state)
>>> +    (outline-hide-sublevels outline-default-state))
>>> +   ((when (functionp outline-default-state)
>>> +      (funcall outline-default-state)))))
>>
>> Maybe some other values from org-mode could be supported too?
>
> Sure.

Ideally, there should be an outline-mode function for most values,
so there should be no need for special handling, e.g. instead of
special handling of `only-headings', an outline command should
handle this by funcall (maybe there is an existing function that does this?)

Only integer and string values should have special handling here.

> Let me first fix handling of local values. I saw that `outline-mode' is
> set *before* local variables when I visit a file whose major mode
> default to `outline-mode'.

I don't know where is the problem.  For example, in etc/compilation.txt
these lines set local variables correctly:

  ;;; outline-minor-mode-cycle: t
  ;;; outline-minor-mode-highlight: t
  ;;; mode: outline-minor

> For xref mode I am wondering whether "regexp" based outline mode is the
> right thing to use. Should there be an outline mode based on
> text-properties? The buffer content is built internally and we know
> where "headings" are inserted... But it's another topic.

Good idea.





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-13  7:55                 ` Matthias Meulien
  2021-12-13  8:58                   ` Juri Linkov
@ 2021-12-26 16:05                   ` Matthias Meulien
  2021-12-26 16:21                     ` Eli Zaretskii
                                       ` (2 more replies)
  1 sibling, 3 replies; 35+ messages in thread
From: Matthias Meulien @ 2021-12-26 16:05 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

[-- Attachment #1: Type: text/plain, Size: 294 bytes --]

Matthias Meulien <orontee@gmail.com> writes:

> (...) Thanks for reading the patch! I'll send another one, hopefully
> taking your remarks into account, when support of local variables is
> fixed.

Here is an updated patch implementing a default state for Outline mode
and Outline minor mode:


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Extend-Outline-mode-with-default-visibility-state.patch --]
[-- Type: text/x-diff, Size: 9692 bytes --]

From db0cf942950c7e997d2701742ce16c8385f452e0 Mon Sep 17 00:00:00 2001
From: Matthias Meulien <orontee@gmail.com>
Date: Wed, 8 Dec 2021 22:35:42 +0100
Subject: [PATCH] Extend Outline mode with 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
---
 lisp/outline.el | 183 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 180 insertions(+), 3 deletions(-)

diff --git a/lisp/outline.el b/lisp/outline.el
index 2ede4e23ea..c52b9cd4e7 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -353,7 +353,9 @@ 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))
 
 (defvar outline-minor-mode-map)
 
@@ -436,7 +438,9 @@ 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))
+        (add-hook 'hack-local-variables-hook
+	          #'outline-apply-default-state))
     (when (or outline-minor-mode-cycle outline-minor-mode-highlight)
       (if font-lock-fontified
           (font-lock-remove-keywords nil outline-font-lock-keywords))
@@ -1093,7 +1097,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))
@@ -1307,6 +1311,179 @@ outline-headers-as-kill
                     (insert "\n\n"))))))
           (kill-new (buffer-string)))))))
 
+(defcustom outline-default-state nil
+  "If non-nil, some headings are initially outlined.
+
+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 the corresponding
+level. See `outline-default-state-subtree-visibility' 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-state-subtree-visibility nil
+  "Defines visibility of subtree starting at level defined by `outline-default-state'.
+
+When nil, the subtree is hidden unconditionally.
+
+When equal to a list, each element is expected to equal one of:
+
+- 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-long-line-threshold' for the definition of long
+   lines).
+
+- `subtree-is-long' to only show the heading branches when its
+  subtree contains more than `outline-line-count-threshold'
+  lines.
+
+- A lambda function or function name which will be evaluated with
+  point at the beginningg 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 "Body has long lines"
+                             subtree-has-long-lines)
+                      (const :tag "Body is long"
+                             subtree-is-long)
+                      (cons :tag "Custom function"
+                            (const custom-function) function))))
+
+(defcustom outline-long-line-threshold 1000
+  "Minimal number of characters in a line for a heading to be outlined."
+  :version "29.1"
+  :type '(natnum :tag "Number of lines"))
+
+(defcustom outline-line-count-threshold 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 and call FUN on the leaves.
+
+Like `outline-hide-sublevels' but but call
+`outline-default-state-subtree-visibility' for each heading at
+level equal to LEVEL."
+  (if (not outline-default-state-subtree-visibility)
+      (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-state-subtree-visibility)))
+             (check-line-count
+              (memq 'subtree-is-long
+                    outline-default-state-subtree-visibility))
+             (check-long-lines
+              (memq 'subtree-has-long-lines
+                    outline-default-state-subtree-visibility))
+             (custom-function
+              (cdr-safe
+               (assoc 'custom-function
+                      outline-default-state-subtree-visibility))))
+        (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 (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)))
+                                 (line-count (count-lines beg end)))
+                            (< outline-line-count-threshold line-count))))
+                   ;; 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-long-line-threshold)
+                                    (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


[-- Attachment #3: Type: text/plain, Size: 44 bytes --]


Here is a file used to test this feature:


[-- Attachment #4: test.outline --]
[-- Type: text/plain, Size: 2596 bytes --]

# -*- mode: outline; -*-

Help to test implementation of outline default state.

* Heading 1

Preambule

** Heading with long lines 1.1

With looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong line

** Heading 1.2

Some text

** Heading 1.3

A first paragraph followed by a second paragraph but with less
interesting text.

To be discussed.

** Heading with not so long line 1.4

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa	

* Heading 2

Preamble to a heading with many lines.

** Heading with many lines 2.1

Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines
Many lines

** Heading 2.2
Many lines
* Heading 3

Preamble

** Heading matching regex 3.1 TOHIDE

Hidden body

*** Heading 3.1.1

Body of hidden parent

**** Heading 3.1.1.1

*** Heading 3.1.2

Still in a hidden parent

* Heading 4

Last body
and nothing
else but those three lines

# Local Variables:
# outline-default-state: 2
# outline-default-state-subtree-visibility: ((match-regexp . "TOHIDE") subtree-has-long-lines subtree-is-long)
# outline-long-line-threshold: 200
# outline-line-count-threshold: 100
# End:

[-- Attachment #5: Type: text/plain, Size: 174 bytes --]


There's a bug when used with diff-mode (where `outline-level' returns
unexpected values), the starting point of that thread! I'll try to study
this in the forthcoming days.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-26 16:05                   ` Matthias Meulien
@ 2021-12-26 16:21                     ` Eli Zaretskii
  2021-12-26 19:19                       ` Matthias Meulien
  2021-12-26 20:32                     ` Matthias Meulien
  2021-12-28 18:32                     ` Juri Linkov
  2 siblings, 1 reply; 35+ messages in thread
From: Eli Zaretskii @ 2021-12-26 16:21 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809, juri

> From: Matthias Meulien <orontee@gmail.com>
> Date: Sun, 26 Dec 2021 17:05:25 +0100
> Cc: 51809@debbugs.gnu.org
> 
> Here is an updated patch implementing a default state for Outline mode
> and Outline minor mode:

Thanks, a few comments to the documentation parts:

> +If equal to a number, show only headings up to the corresponding
> +level.

"Up to and including" or "up to and excluding"?

Also, please make sure you leave 2 spaces between sentences in all the
doc strings and comments, per our conventions.

> +  :type '(choice (const :tag "Disabled" nil)

It isn't clear what exactly is "disabled" under this value.  The doc
string itself is also not clear about what happens when the value is
nil: "some headings are outlined" leaves me wondering what that means.
> +(defcustom outline-default-state-subtree-visibility nil
> +  "Defines visibility of subtree starting at level defined by `outline-default-state'.

Not "Defines", "Determines".

Also, the first line is too long.

> +When equal to a list, each element is expected to equal one of:
                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
"...should be one of the following:"

(Using "equal" here is not appropriate in any case, since you don't
always describe actual values.)

> +- A lambda function or function name which will be evaluated with
> +  point at the beginningg of the heading and the match data set
                  ^^^^^^^^^^
A typo.

And finally, I think this should have a NEWS entry.





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-26 16:21                     ` Eli Zaretskii
@ 2021-12-26 19:19                       ` Matthias Meulien
  0 siblings, 0 replies; 35+ messages in thread
From: Matthias Meulien @ 2021-12-26 19:19 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 51809, juri

[-- Attachment #1: Type: text/plain, Size: 150 bytes --]

Eli Zaretskii <eliz@gnu.org> writes:

> (...) a few comments to the documentation parts:

Here is an updated patch taking your remarks into account:


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Extend-Outline-mode-with-default-visibility-state.patch --]
[-- Type: text/x-diff, Size: 10749 bytes --]

From ecf57d0fb33ba3d569ca8fb2933993e139bbf94e Mon Sep 17 00:00:00 2001
From: Matthias Meulien <orontee@gmail.com>
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 | 190 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 197 insertions(+), 3 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index cfea513cca..9a49ff8379 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -215,6 +215,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 5e3d4e0e00..ad45e38946 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -354,7 +354,9 @@ 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))
 
 (defvar outline-minor-mode-map)
 
@@ -437,7 +439,9 @@ 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))
+        (add-hook 'hack-local-variables-hook
+	          #'outline-apply-default-state))
     (when (or outline-minor-mode-cycle outline-minor-mode-highlight)
       (if font-lock-fontified
           (font-lock-remove-keywords nil outline-font-lock-keywords))
@@ -1094,7 +1098,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))
@@ -1308,6 +1312,186 @@ 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-state-subtree-visibility' 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-state-subtree-visibility 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-long-line-threshold' for the definition of long
+   lines).
+
+- `subtree-is-long' to only show the heading branches when its
+  subtree contains more than `outline-line-count-threshold'
+  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-long-line-threshold 1000
+  "Minimal number of characters in a line for a heading to be outlined."
+  :version "29.1"
+  :type '(natnum :tag "Number of lines"))
+
+(defcustom outline-line-count-threshold 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 and call FUN on the leaves.
+
+Like `outline-hide-sublevels' but but call
+`outline-default-state-subtree-visibility' for each heading at
+level equal to LEVEL."
+  (if (not outline-default-state-subtree-visibility)
+      (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-state-subtree-visibility)))
+             (check-line-count
+              (memq 'subtree-is-long
+                    outline-default-state-subtree-visibility))
+             (check-long-lines
+              (memq 'subtree-has-long-lines
+                    outline-default-state-subtree-visibility))
+             (custom-function
+              (cdr-safe
+               (assoc 'custom-function
+                      outline-default-state-subtree-visibility))))
+        (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 (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)))
+                                 (line-count (count-lines beg end)))
+                            (< outline-line-count-threshold line-count))))
+                   ;; 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-long-line-threshold)
+                                    (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


[-- Attachment #3: Type: text/plain, Size: 15 bytes --]



-- 
Matthias

^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-26 16:05                   ` Matthias Meulien
  2021-12-26 16:21                     ` Eli Zaretskii
@ 2021-12-26 20:32                     ` Matthias Meulien
  2021-12-26 20:55                       ` Matthias Meulien
  2021-12-28 18:32                     ` Juri Linkov
  2 siblings, 1 reply; 35+ messages in thread
From: Matthias Meulien @ 2021-12-26 20:32 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

Matthias Meulien <orontee@gmail.com> writes:

> There's a bug when used with diff-mode (where `outline-level' returns
> unexpected values), the starting point of that thread! I'll try to
> study this in the forthcoming days.

Fixed with (applied to the patch sent in reply to Eli's suggestions on
documentation):

diff --git a/lisp/outline.el b/lisp/outline.el
index ad45e38946..2dc9805b85 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -1449,7 +1449,7 @@ outline--show-headings-up-to-level
         ;; Then unhide the top level headers.
         (outline-map-region
          (lambda ()
-             (let ((current-level (outline-level)))
+             (let ((current-level (funcall outline-level)))
 	       (when (< current-level level)
                  (outline-show-heading)
                  (outline-show-entry))


I am now back to the origin of this bug report (toggling visibility of
some diff hunks depending on long lines, chunks size, or file names) and
I see there's a major drawback with the outline mode default visibility
state approach: When outline minor mode is set through diff-mode-hook,
the buffer is emtpy and applying the default state visibility is a
no-op; It's a side-effect of the buffer content being generated
asynchronously...





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-26 20:32                     ` Matthias Meulien
@ 2021-12-26 20:55                       ` Matthias Meulien
  2021-12-27 19:52                         ` Juri Linkov
  2021-12-28 18:37                         ` Juri Linkov
  0 siblings, 2 replies; 35+ messages in thread
From: Matthias Meulien @ 2021-12-26 20:55 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

Matthias Meulien <orontee@gmail.com> writes:

> I am now back to the origin of this bug report (toggling visibility of
> some diff hunks depending on long lines, chunks size, or file names) and
> I see there's a major drawback with the outline mode default visibility
> state approach: When outline minor mode is set through diff-mode-hook,
> the buffer is emtpy and applying the default state visibility is a
> no-op; It's a side-effect of the buffer content being generated
> asynchronously...

I fixed it using an advice to `vc-diff-finish', but wondering whether
there's a clean way to achieve the same...

For posterity, here is a sample configuration for *vc-diff* buffers to
hide hunks with long lines, long hunks or matching some regexp.

(add-hook 'diff-mode-hook #'(lambda ()
			      (setq diff-font-lock-prettify t
				    outline-default-state 1
				    outline-default-state-subtree-visibility
				    '(subtree-is-long
				      subtree-has-long-lines
				      (match-regexp . "NEWS\\|test")))
			      (outline-minor-mode)))

(defadvice vc-diff-finish (after ensure-outline-apply-default-state activate)
  (when outline-minor-mode
    (outline-apply-default-state)))





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-26 20:55                       ` Matthias Meulien
@ 2021-12-27 19:52                         ` Juri Linkov
  2021-12-28 18:37                         ` Juri Linkov
  1 sibling, 0 replies; 35+ messages in thread
From: Juri Linkov @ 2021-12-27 19:52 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

>> I am now back to the origin of this bug report (toggling visibility of
>> some diff hunks depending on long lines, chunks size, or file names) and
>> I see there's a major drawback with the outline mode default visibility
>> state approach: When outline minor mode is set through diff-mode-hook,
>> the buffer is emtpy and applying the default state visibility is a
>> no-op; It's a side-effect of the buffer content being generated
>> asynchronously...
>
> I fixed it using an advice to `vc-diff-finish',

This problem is not specific to diff-mode.  Like I demonstrated
earlier, xref requires using xref-after-update-hook,
so now it could look like this:

#+begin_src emacs-lisp
(add-hook 'xref-after-update-hook
          (lambda ()
            (setq-local outline-regexp (if (eq xref-file-name-display 'abs)
                                           "/" "[^ 0-9]")
                        outline-default-state 1)
            (outline-minor-mode)
            (outline-apply-default-state)))
#+end_src

> but wondering whether there's a clean way to achieve the same...

Some time ago in another bug report I proposed to add a hook
at the end of 'vc-diff-finish', so it will solve this problem.

PS: Please give me more time to test your patch thoroughly
in various modes.





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-11-13 13:04 bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers Matthias Meulien
  2021-11-13 17:45 ` Juri Linkov
@ 2021-12-28  8:09 ` Matthias Meulien
  1 sibling, 0 replies; 35+ messages in thread
From: Matthias Meulien @ 2021-12-28  8:09 UTC (permalink / raw)
  To: 51809

[-- Attachment #1: Type: text/plain, Size: 35 bytes --]

Updated patch with docstring fix.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Extend-Outline-mode-with-default-visibility-state.patch --]
[-- Type: text/x-diff, Size: 10757 bytes --]

From 167e7f9d8d26b075e01c3a8c8618da3a2138d145 Mon Sep 17 00:00:00 2001
From: Matthias Meulien <orontee@gmail.com>
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 | 190 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 197 insertions(+), 3 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index cfea513cca..9a49ff8379 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -215,6 +215,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 5e3d4e0e00..abb0d93adf 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -354,7 +354,9 @@ 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))
 
 (defvar outline-minor-mode-map)
 
@@ -437,7 +439,9 @@ 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))
+        (add-hook 'hack-local-variables-hook
+	          #'outline-apply-default-state))
     (when (or outline-minor-mode-cycle outline-minor-mode-highlight)
       (if font-lock-fontified
           (font-lock-remove-keywords nil outline-font-lock-keywords))
@@ -1094,7 +1098,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))
@@ -1308,6 +1312,186 @@ 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-state-subtree-visibility' 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-state-subtree-visibility 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-long-line-threshold' for the definition of long
+   lines).
+
+- `subtree-is-long' to only show the heading branches when its
+  subtree contains more than `outline-line-count-threshold'
+  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-long-line-threshold 1000
+  "Minimal number of characters in a line for a heading to be outlined."
+  :version "29.1"
+  :type '(natnum :tag "Number of lines"))
+
+(defcustom outline-line-count-threshold 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-state-subtree-visibility'."
+  (if (not outline-default-state-subtree-visibility)
+      (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-state-subtree-visibility)))
+             (check-line-count
+              (memq 'subtree-is-long
+                    outline-default-state-subtree-visibility))
+             (check-long-lines
+              (memq 'subtree-has-long-lines
+                    outline-default-state-subtree-visibility))
+             (custom-function
+              (cdr-safe
+               (assoc 'custom-function
+                      outline-default-state-subtree-visibility))))
+        (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)))
+                                 (line-count (count-lines beg end)))
+                            (< outline-line-count-threshold line-count))))
+                   ;; 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-long-line-threshold)
+                                    (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


^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-26 16:05                   ` Matthias Meulien
  2021-12-26 16:21                     ` Eli Zaretskii
  2021-12-26 20:32                     ` Matthias Meulien
@ 2021-12-28 18:32                     ` Juri Linkov
  2021-12-28 21:45                       ` Matthias Meulien
  2 siblings, 1 reply; 35+ messages in thread
From: Juri Linkov @ 2021-12-28 18:32 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

> @@ -353,7 +353,9 @@ outline-mode
> +  (add-hook 'change-major-mode-hook #'outline-show-all nil t)
> +  (add-hook 'hack-local-variables-hook
> +	    #'outline-apply-default-state))
>
> @@ -436,7 +438,9 @@ outline-minor-mode
> +        (add-hook 'hack-local-variables-hook
> +	          #'outline-apply-default-state))

Are you sure about modifying the global value of hack-local-variables-hook
instead of the buffer-local hook with `nil t' at the end?

> Here is a file used to test this feature:
>
> # -*- mode: outline; -*-
> [...]
> # Local Variables:
> # outline-default-state: 2
> # outline-default-state-subtree-visibility: ((match-regexp . "TOHIDE") subtree-has-long-lines subtree-is-long)
> # outline-long-line-threshold: 200
> # outline-line-count-threshold: 100
> # End:

When this feature will be used a lot, even variable names will affect
usability - with longer names usability deteriorates, and 40 characters of
'outline-default-state-subtree-visibility' takes half of the standard window width.
Would it be possible to find a shorter name?

Since it defines the rules, how about 'outline-default-rules'?
It has the same length as 'outline-default-state', so these names
will align nicely:

# Local Variables:
# outline-default-state: 1
# outline-default-rules: ((match-regexp . "ChangeLog"))
# mode: outline-minor
# End:

Also outline-long-line-threshold and outline-line-count-threshold could share
the same prefix, maybe:

# outline-default-long-line: 200
# outline-default-line-count: 100





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-26 20:55                       ` Matthias Meulien
  2021-12-27 19:52                         ` Juri Linkov
@ 2021-12-28 18:37                         ` Juri Linkov
  2021-12-28 21:46                           ` Matthias Meulien
  2021-12-28 22:28                           ` Matthias Meulien
  1 sibling, 2 replies; 35+ messages in thread
From: Juri Linkov @ 2021-12-28 18:37 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

>> I am now back to the origin of this bug report (toggling visibility of
>> some diff hunks depending on long lines, chunks size, or file names) and
>> I see there's a major drawback with the outline mode default visibility
>> state approach: When outline minor mode is set through diff-mode-hook,
>> the buffer is emtpy and applying the default state visibility is a
>> no-op; It's a side-effect of the buffer content being generated
>> asynchronously...
>
> I fixed it using an advice to `vc-diff-finish', but wondering whether
> there's a clean way to achieve the same...
> [...]
> (defadvice vc-diff-finish (after ensure-outline-apply-default-state activate)
>   (when outline-minor-mode
>     (outline-apply-default-state)))

After bug#52855 will be closed, it should be possible to do this by:

  (add-hook 'vc-diff-finish-functions 'outline-apply-default-state)

Also xref works nicely, although I don't know why it requires
`outline-apply-default-state' after enabling `outline-minor-mode':

#+begin_src emacs-lisp
(add-hook 'xref-after-update-hook
          (lambda ()
            (setq-local outline-regexp (if (eq xref-file-name-display 'abs)
                                           "/" "[^ 0-9]")
                        outline-default-state 1
                        outline-default-state-subtree-visibility '((match-regexp . "ChangeLog\\|test/manual/etags")))
            (outline-minor-mode +1)
            (outline-apply-default-state)))
#+end_src





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-28 18:32                     ` Juri Linkov
@ 2021-12-28 21:45                       ` Matthias Meulien
  0 siblings, 0 replies; 35+ messages in thread
From: Matthias Meulien @ 2021-12-28 21:45 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

[-- Attachment #1: Type: text/plain, Size: 1110 bytes --]

Juri Linkov <juri@linkov.net> writes:

>> @@ -353,7 +353,9 @@ outline-mode
>> +  (add-hook 'change-major-mode-hook #'outline-show-all nil t)
>> +  (add-hook 'hack-local-variables-hook
>> +	    #'outline-apply-default-state))
>>
>> @@ -436,7 +438,9 @@ outline-minor-mode
>> +        (add-hook 'hack-local-variables-hook
>> +	          #'outline-apply-default-state))
>
> Are you sure about modifying the global value of hack-local-variables-hook
> instead of the buffer-local hook with `nil t' at the end?

Ah, ah, my bad. Many thanks!

> (...) When this feature will be used a lot, even variable names will
> affect usability - with longer names usability deteriorates, and 40
> characters of 'outline-default-state-subtree-visibility' takes half of
> the standard window width.  Would it be possible to find a shorter
> name?

Good point.

> Since it defines the rules, how about 'outline-default-rules'?

LGTM.

> (...) Also outline-long-line-threshold and
> outline-line-count-threshold could share the same prefix, maybe:
>
> # outline-default-long-line: 200
> # outline-default-line-count: 100

Adopted!


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Extend-Outline-mode-with-default-visibility-state.patch --]
[-- Type: text/x-diff, Size: 10456 bytes --]

From 6f8fb8b142c913405d9e00732ffbdecb8331ddb7 Mon Sep 17 00:00:00 2001
From: Matthias Meulien <orontee@gmail.com>
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 | 181 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 188 insertions(+), 3 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index cfea513cca..9a49ff8379 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -215,6 +215,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 5e3d4e0e00..1a878dee04 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -354,7 +354,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)
 
@@ -437,7 +438,9 @@ 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))
+        (add-hook 'hack-local-variables-hook
+                  #'outline-apply-default-state nil t))
     (when (or outline-minor-mode-cycle outline-minor-mode-highlight)
       (if font-lock-fontified
           (font-lock-remove-keywords nil outline-font-lock-keywords))
@@ -1094,7 +1097,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))
@@ -1308,6 +1311,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


[-- Attachment #3: Type: text/plain, Size: 14 bytes --]


-- 
Matthias

^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-28 18:37                         ` Juri Linkov
@ 2021-12-28 21:46                           ` Matthias Meulien
  2021-12-28 22:28                           ` Matthias Meulien
  1 sibling, 0 replies; 35+ messages in thread
From: Matthias Meulien @ 2021-12-28 21:46 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

Juri Linkov <juri@linkov.net> writes:

> (...) Also xref works nicely, although I don't know why it requires
> `outline-apply-default-state' after enabling `outline-minor-mode':

Same here. I'll have a look.
-- 
Matthias





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-28 18:37                         ` Juri Linkov
  2021-12-28 21:46                           ` Matthias Meulien
@ 2021-12-28 22:28                           ` Matthias Meulien
  2022-01-11 17:46                             ` Juri Linkov
  1 sibling, 1 reply; 35+ messages in thread
From: Matthias Meulien @ 2021-12-28 22:28 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

Juri Linkov <juri@linkov.net> 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.

An explicit call to `outline-apply-default-state' in the implementation
of outline-minor-mode fix the problem:

diff --git a/lisp/outline.el b/lisp/outline.el
index 1a878dee04..65956b9dae 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -439,8 +439,7 @@ outline-minor-mode
         (setq-local line-move-ignore-invisible t)
 	;; Cause use of ellipses for invisible text.
 	(add-to-invisibility-spec '(outline . t))
-        (add-hook 'hack-local-variables-hook
-                  #'outline-apply-default-state nil t))
+        (outline-apply-default-state))
     (when (or outline-minor-mode-cycle outline-minor-mode-highlight)
       (if font-lock-fontified
           (font-lock-remove-keywords nil outline-font-lock-keywords))

I'll send an updated patch after some time using this.
-- 
Matthias





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2021-12-28 22:28                           ` Matthias Meulien
@ 2022-01-11 17:46                             ` Juri Linkov
  2022-01-14 16:41                               ` Matthias Meulien
  0 siblings, 1 reply; 35+ messages in thread
From: Juri Linkov @ 2022-01-11 17:46 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

>> (...)  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'.

> 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)





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2022-01-11 17:46                             ` Juri Linkov
@ 2022-01-14 16:41                               ` Matthias Meulien
  2022-01-16 18:14                                 ` Juri Linkov
  2022-01-17 21:10                                 ` Matthias Meulien
  0 siblings, 2 replies; 35+ messages in thread
From: Matthias Meulien @ 2022-01-14 16:41 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

[-- Attachment #1: Type: text/plain, Size: 2197 bytes --]

Hi Juri,

Juri Linkov <juri@linkov.net> 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 ‘mode: MODENAME;’, if present,
   specifies a major mode (without the “-mode” 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\\.lock")))))

(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\\|test/manual/etags")))
              (outline-minor-mode)))

Here is an updated patch (just rebased my local branch, some minor
conflicts appeared in outline.el):


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Extend-Outline-mode-with-default-visibility-state.patch --]
[-- Type: text/x-diff, Size: 10363 bytes --]

From 46934ba08d597a4d0d6e9d6c3918e5eabc1ec613 Mon Sep 17 00:00:00 2001
From: Matthias Meulien <orontee@gmail.com>
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


[-- Attachment #3: Type: text/plain, Size: 230 bytes --]


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

^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2022-01-14 16:41                               ` Matthias Meulien
@ 2022-01-16 18:14                                 ` Juri Linkov
  2022-01-17 21:10                                 ` Matthias Meulien
  1 sibling, 0 replies; 35+ messages in thread
From: Juri Linkov @ 2022-01-16 18:14 UTC (permalink / raw)
  To: Matthias Meulien; +Cc: 51809

tags 51809 + patch fixed
thanks

Hi Matthias,

>>   # -*- mode: outline-minor; -*-
>
> 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 ‘mode: MODENAME;’, if present,
>    specifies a major mode (without the “-mode” suffix).
>
> I prefer not support this if possible.

You are right, it rather should be more like:

  -*- mode: text; mode: outline-minor; -*-

that is supported.

> Here is an updated patch (just rebased my local branch, some minor
> conflicts appeared in outline.el):

Thanks for working on this patch!  I've tested it thoroughly,
and everything works nicely, so I pushed it to master.

> 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)?

Maybe more documentation about using this in diff-mode
could be added to the docstrings and to the manual?





^ permalink raw reply	[flat|nested] 35+ messages in thread

* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers
  2022-01-14 16:41                               ` Matthias Meulien
  2022-01-16 18:14                                 ` Juri Linkov
@ 2022-01-17 21:10                                 ` Matthias Meulien
  1 sibling, 0 replies; 35+ messages in thread
From: Matthias Meulien @ 2022-01-17 21:10 UTC (permalink / raw)
  To: Juri Linkov; +Cc: 51809

Matthias Meulien <orontee@gmail.com> writes:

> (...)  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)?

I see one problem: Once one has some default state configured for Diff
mode, it applies to patch embedded in Gnus Article buffers, but there
the outline-minor-mode keymap isn't active and there's no way to toggle
headings visibility... No idea how to improve this situation...





^ permalink raw reply	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2022-01-17 21:10 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-11-13 13:04 bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers Matthias Meulien
2021-11-13 17:45 ` Juri Linkov
2021-11-13 18:08   ` Matthias Meulien
2021-11-13 18:27     ` Juri Linkov
2021-11-13 18:41     ` Matthias Meulien
2021-11-13 19:29       ` Juri Linkov
2021-11-13 21:27         ` Matthias Meulien
2021-11-13 23:29         ` Matthias Meulien
2021-11-29 17:06           ` Juri Linkov
2021-11-30 19:33             ` Matthias Meulien
2021-12-11 18:18             ` Matthias Meulien
2021-12-12  8:43               ` Juri Linkov
2021-12-13  7:55                 ` Matthias Meulien
2021-12-13  8:58                   ` Juri Linkov
2021-12-26 16:05                   ` Matthias Meulien
2021-12-26 16:21                     ` Eli Zaretskii
2021-12-26 19:19                       ` Matthias Meulien
2021-12-26 20:32                     ` Matthias Meulien
2021-12-26 20:55                       ` Matthias Meulien
2021-12-27 19:52                         ` Juri Linkov
2021-12-28 18:37                         ` Juri Linkov
2021-12-28 21:46                           ` Matthias Meulien
2021-12-28 22:28                           ` Matthias Meulien
2022-01-11 17:46                             ` Juri Linkov
2022-01-14 16:41                               ` Matthias Meulien
2022-01-16 18:14                                 ` Juri Linkov
2022-01-17 21:10                                 ` Matthias Meulien
2021-12-28 18:32                     ` Juri Linkov
2021-12-28 21:45                       ` Matthias Meulien
2021-11-14 18:25     ` Juri Linkov
2021-11-14 19:35       ` Matthias Meulien
2021-11-14 19:46         ` Juri Linkov
2021-11-14 19:54       ` Matthias Meulien
2021-11-14 20:31         ` Juri Linkov
2021-12-28  8:09 ` Matthias Meulien

Code repositories for project(s) associated with this inbox:

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).