* 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; 45+ 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 related [flat|nested] 45+ 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; 45+ 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] 45+ 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; 45+ 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] 45+ 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; 45+ 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] 45+ 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; 45+ 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 related [flat|nested] 45+ 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; 45+ 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] 45+ 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; 45+ 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 related [flat|nested] 45+ 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; 45+ 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 related [flat|nested] 45+ 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; 45+ 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] 45+ 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; 45+ 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] 45+ 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; 45+ 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 related [flat|nested] 45+ 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; 45+ 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] 45+ 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; 45+ 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] 45+ 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; 45+ 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] 45+ 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; 45+ 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 related [flat|nested] 45+ 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; 45+ 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] 45+ 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; 45+ 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 related [flat|nested] 45+ 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; 45+ 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 related [flat|nested] 45+ 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; 45+ 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] 45+ 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; 45+ 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] 45+ 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; 45+ 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] 45+ 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; 45+ 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] 45+ 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; 45+ 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 related [flat|nested] 45+ 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; 45+ 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] 45+ 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; 45+ 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 related [flat|nested] 45+ 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; 45+ 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] 45+ 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 2022-01-29 19:12 ` Juri Linkov 2022-02-05 18:45 ` Juri Linkov 1 sibling, 2 replies; 45+ 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] 45+ messages in thread
* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers 2022-01-17 21:10 ` Matthias Meulien @ 2022-01-29 19:12 ` Juri Linkov 2022-02-05 18:45 ` Juri Linkov 1 sibling, 0 replies; 45+ messages in thread From: Juri Linkov @ 2022-01-29 19:12 UTC (permalink / raw) To: Matthias Meulien; +Cc: 51809 > 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... A hackish way would be something like this: diff --git a/lisp/gnus/mm-view.el b/lisp/gnus/mm-view.el index c40c38a95f..49cac01e2f 100644 --- a/lisp/gnus/mm-view.el +++ b/lisp/gnus/mm-view.el @@ -532,7 +532,8 @@ mm-display-inline-fontify (funcall mode)) (let ((auto-mode-alist (delq (rassq 'doc-view-mode-maybe auto-mode-alist) - (copy-sequence auto-mode-alist)))) + (copy-sequence auto-mode-alist))) + outline-default-state) ;; Don't run hooks that might assume buffer-file-name ;; really associates buffer with a file (bug#39190). (delay-mode-hooks (set-auto-mode)) But OTOH, when long lines is a problem, then it's natural to expect that the user would want to hide long lines in Gnus Article buffers as well. And it's still possible to use outline cycling commands after saving an attachment to a file. ^ permalink raw reply related [flat|nested] 45+ messages in thread
* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers 2022-01-17 21:10 ` Matthias Meulien 2022-01-29 19:12 ` Juri Linkov @ 2022-02-05 18:45 ` Juri Linkov 2022-02-05 22:00 ` Lars Ingebrigtsen 1 sibling, 1 reply; 45+ messages in thread From: Juri Linkov @ 2022-02-05 18:45 UTC (permalink / raw) To: Matthias Meulien; +Cc: 51809 [-- Attachment #1: Type: text/plain, Size: 787 bytes --] > 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... Oh, I see now it's a real problem. Some patches are not displayed because their hunks are hidden. Fro example, a patch from bug#53770 attached below, becomes hidden with these settings: (add-hook 'diff-mode-hook (lambda () (setq-local outline-default-state 2) (outline-minor-mode 1))) For some unknown reason, outline--show-headings-up-to-level first hides all sublevels, but never shows them again, because or some level problem - it uses the level 21. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: woman.patch --] [-- Type: text/x-diff, Size: 632 bytes --] --- woman.el.orig 2022-02-04 14:08:54.188622150 +1100 +++ woman.el 2022-02-04 14:08:51.254677185 +1100 @@ -2299,9 +2299,9 @@ (replace-match woman-unpadded-space-string t t)) ;; Discard optional hyphen \%; concealed newlines \<newline>; - ;; point-size change function \sN,\s+N, \s-N: + ;; kerning \/, \,; point-size change function \sN,\s+N, \s-N: (goto-char from) - (while (re-search-forward "\\\\\\([%\n]\\|s[-+]?[0-9]+\\)" nil t) + (while (re-search-forward "\\\\\\([%\n/,]\\|s[-+]?[0-9]+\\)" nil t) (woman-delete-match 0)) ;; BEWARE: THIS SHOULD PROBABLY ALL BE DONE MUCH LATER!!!!! ^ permalink raw reply [flat|nested] 45+ messages in thread
* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers 2022-02-05 18:45 ` Juri Linkov @ 2022-02-05 22:00 ` Lars Ingebrigtsen 2022-02-12 17:09 ` Juri Linkov 0 siblings, 1 reply; 45+ messages in thread From: Lars Ingebrigtsen @ 2022-02-05 22:00 UTC (permalink / raw) To: Juri Linkov; +Cc: Matthias Meulien, 51809 Juri Linkov <juri@linkov.net> writes: >> 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... > > Oh, I see now it's a real problem. Some patches are not displayed > because their hunks are hidden. Fro example, a patch from bug#53770 > attached below, becomes hidden with these settings: > > (add-hook 'diff-mode-hook > (lambda () > (setq-local outline-default-state 2) > (outline-minor-mode 1))) Hm, that's not good... Perhaps we could just disable outline on the Gnus level when displaying parts? -- (domestic pets only, the antidote for overdose, milk.) bloggy blog: http://lars.ingebrigtsen.no ^ permalink raw reply [flat|nested] 45+ messages in thread
* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers 2022-02-05 22:00 ` Lars Ingebrigtsen @ 2022-02-12 17:09 ` Juri Linkov 2022-02-12 17:26 ` Matthias Meulien 2022-02-14 21:07 ` Matthias Meulien 0 siblings, 2 replies; 45+ messages in thread From: Juri Linkov @ 2022-02-12 17:09 UTC (permalink / raw) To: Lars Ingebrigtsen; +Cc: Matthias Meulien, 51809 >>> 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... >> >> Oh, I see now it's a real problem. Some patches are not displayed >> because their hunks are hidden. Fro example, a patch from bug#53770 >> attached below, becomes hidden with these settings: >> >> (add-hook 'diff-mode-hook >> (lambda () >> (setq-local outline-default-state 2) >> (outline-minor-mode 1))) > > Hm, that's not good... Perhaps we could just disable outline on the > Gnus level when displaying parts? I'm still not sure about disabling outlines in Gnus because when the user configured to hide outlines e.g. with long lines, then it makes sense to hide long lines in Gnus too. But the problem with the reported attached file is different: in that file outlines are never displayed even outside of Gnus. This is because the diff format is slightly different, but `outline--show-headings-up-to-level' first hides all outlines, but then fails to unhide them back. Matthias, could you please check what is wrong with the reported diff file and why vasibility of its outlines can't be changed? ^ permalink raw reply [flat|nested] 45+ messages in thread
* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers 2022-02-12 17:09 ` Juri Linkov @ 2022-02-12 17:26 ` Matthias Meulien 2022-02-14 21:07 ` Matthias Meulien 1 sibling, 0 replies; 45+ messages in thread From: Matthias Meulien @ 2022-02-12 17:26 UTC (permalink / raw) To: Juri Linkov; +Cc: Lars Ingebrigtsen, 51809 [-- Attachment #1: Type: text/plain, Size: 220 bytes --] > > Matthias, could you please check what is wrong with the reported diff file > and why vasibility of its outlines can't be changed? > Sure. In the forthcoming days, I am away from home right now, without computer. > [-- Attachment #2: Type: text/html, Size: 733 bytes --] ^ permalink raw reply [flat|nested] 45+ messages in thread
* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers 2022-02-12 17:09 ` Juri Linkov 2022-02-12 17:26 ` Matthias Meulien @ 2022-02-14 21:07 ` Matthias Meulien 2022-02-14 21:13 ` Matthias Meulien 2022-02-14 21:33 ` Matthias Meulien 1 sibling, 2 replies; 45+ messages in thread From: Matthias Meulien @ 2022-02-14 21:07 UTC (permalink / raw) To: Juri Linkov; +Cc: Lars Ingebrigtsen, 51809 Juri Linkov <juri@linkov.net> writes: > But the problem with the reported attached file is different: > in that file outlines are never displayed even outside of Gnus. > This is because the diff format is slightly different, but > `outline--show-headings-up-to-level' first hides all outlines, > but then fails to unhide them back. > > Matthias, could you please check what is wrong with the reported diff file > and why vasibility of its outlines can't be changed? First note that with the reported attached file `diff-buffer-type' isn't equal to 'git: There's no "diff --git" header. If you replace the file first three lines with the following four lines then there's no outline problem: diff --git a/woman.el.orig b/woman.el index 44328a2b28..214f7435d9 100644 --- a/woman.el.orig +++ b/woman.el When `diff-buffer-type' is nil, `outline-regexp' is set to `diff-outline-regex', which default to "\\([*+][*+][*+] [^0-9]\\|@@ ...\\|\\*\\*\\* [0-9].\\|--- [0-9]..\\)", and `outline-level' use the default function which returns the number of characters matched by ‘outline-regexp’ Since the regexp match the first 5 characters of the first line of the diff ("+++ w") and the first 6 characters of the second line ("@@ -22"), the file has two headings at level 5 and 6! If you have set `outline-default-state' to be equal to 1 and kept `outline-default-rules' to its default nil (meaning the subtree starting at level 1 must be hidden), then the file outlines are logically hidden by `outline-apply-default-state'. I suspect that it's what you see. From my pov, the problem doesn't come from the "default outline state machinery" but from wrong defaults for `outline-level' when `diff-buffer-type' is nil. My first impression is that setting `outline-level' to `diff--outline-level' inconditionnaly should work but since I don't understand why `diff-outline-regex' hasn't been defined in terms of `diff-file-header-re' and `diff-hunk-header-re' I guess I am missing some subtleties. (Note that there's another minor bug when `diff-buffer-type` isn't equal to 'git: `diff--font-lock-prettify` should be a no-op since the regexp written there match only Git generated diffs, confirmed by the FIXME string "This has only been tested with Git's diff output." written in the implementation). -- Matthias ^ permalink raw reply [flat|nested] 45+ messages in thread
* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers 2022-02-14 21:07 ` Matthias Meulien @ 2022-02-14 21:13 ` Matthias Meulien 2022-02-14 21:33 ` Matthias Meulien 1 sibling, 0 replies; 45+ messages in thread From: Matthias Meulien @ 2022-02-14 21:13 UTC (permalink / raw) To: Juri Linkov; +Cc: Lars Ingebrigtsen, 51809 Matthias Meulien <orontee@gmail.com> writes: Oh and don't forget to set `outline-default-state' to nil before reading my previous mail 😃. -- Matthias ^ permalink raw reply [flat|nested] 45+ messages in thread
* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers 2022-02-14 21:07 ` Matthias Meulien 2022-02-14 21:13 ` Matthias Meulien @ 2022-02-14 21:33 ` Matthias Meulien 2022-02-14 21:39 ` Matthias Meulien 2022-02-16 19:20 ` Juri Linkov 1 sibling, 2 replies; 45+ messages in thread From: Matthias Meulien @ 2022-02-14 21:33 UTC (permalink / raw) To: Juri Linkov; +Cc: Lars Ingebrigtsen, 51809 [-- Attachment #1: Type: text/plain, Size: 683 bytes --] Matthias Meulien <orontee@gmail.com> writes: > My first impression is that setting `outline-level' to > `diff--outline-level' inconditionnaly should work but since I don't > understand why `diff-outline-regex' hasn't been defined in terms of > `diff-file-header-re' and `diff-hunk-header-re' I guess I am missing > some subtleties. > > (Note that there's another minor bug when `diff-buffer-type` isn't equal > to 'git: `diff--font-lock-prettify` should be a no-op since the regexp > written there match only Git generated diffs, confirmed by the FIXME > string "This has only been tested with Git's diff output." written in > the implementation). Here's the corresponding patch: [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-Fix-computation-of-outline-heading-level-for-non-git.patch --] [-- Type: text/x-diff, Size: 1471 bytes --] From 4cf31f61caa50ba97e67847b175d598387fabba4 Mon Sep 17 00:00:00 2001 From: Matthias Meulien <orontee@gmail.com> Date: Mon, 14 Feb 2022 22:29:49 +0100 Subject: [PATCH] Fix computation of outline heading level for non-git diff * lisp/vc/diff-mode.el (diff-setup-buffer-type): Compute outline heading level using diff-hunk-header-re. (diff--font-lock-prettify): Disable prettify in non-git diff. --- lisp/vc/diff-mode.el | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index 0bf7899246..d0c05d3204 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -1596,8 +1596,8 @@ diff-setup-buffer-type nil))) (when (eq diff-buffer-type 'git) (setq diff-outline-regexp - (concat "\\(^diff --git.*\n\\|" diff-hunk-header-re "\\)")) - (setq-local outline-level #'diff--outline-level)) + (concat "\\(^diff --git.*\n\\|" diff-hunk-header-re "\\)"))) + (setq-local outline-level #'diff--outline-level) (setq-local outline-regexp diff-outline-regexp)) (defun diff-delete-if-empty () @@ -2599,7 +2599,8 @@ 'diff-fringe-nul nil nil 'center) (defun diff--font-lock-prettify (limit) - (when diff-font-lock-prettify + (when (and diff-font-lock-prettify + (eq diff-buffer-type 'git)) (save-excursion ;; FIXME: Include the first space for context-style hunks! (while (re-search-forward "^[-+! ]" limit t) -- 2.30.2 [-- Attachment #3: Type: text/plain, Size: 85 bytes --] Without this patch when I visit the reported woman.patch file I see the following: [-- Attachment #4: without_patch.png --] [-- Type: image/png, Size: 4681 bytes --] [-- Attachment #5: Type: text/plain, Size: 28 bytes --] but with the patch I see: [-- Attachment #6: with_patch.png --] [-- Type: image/png, Size: 67278 bytes --] [-- Attachment #7: Type: text/plain, Size: 14 bytes --] -- Matthias ^ permalink raw reply related [flat|nested] 45+ messages in thread
* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers 2022-02-14 21:33 ` Matthias Meulien @ 2022-02-14 21:39 ` Matthias Meulien 2022-02-16 19:20 ` Juri Linkov 1 sibling, 0 replies; 45+ messages in thread From: Matthias Meulien @ 2022-02-14 21:39 UTC (permalink / raw) To: Juri Linkov; +Cc: Lars Ingebrigtsen, 51809 [-- Attachment #1: Type: text/plain, Size: 107 bytes --] Sorry in the second screenshot, `outline-default-state' was nil. I wanted to show you when it's set to 1: [-- Attachment #2: with_patch_level_1.png --] [-- Type: image/png, Size: 12593 bytes --] [-- Attachment #3: Type: text/plain, Size: 9 bytes --] and 2: [-- Attachment #4: with_patch_level_2.png --] [-- Type: image/png, Size: 14627 bytes --] [-- Attachment #5: Type: text/plain, Size: 14 bytes --] -- Matthias ^ permalink raw reply [flat|nested] 45+ messages in thread
* bug#51809: 29.0.50; [PATCH] Support for outline default state in Diff buffers 2022-02-14 21:33 ` Matthias Meulien 2022-02-14 21:39 ` Matthias Meulien @ 2022-02-16 19:20 ` Juri Linkov 1 sibling, 0 replies; 45+ messages in thread From: Juri Linkov @ 2022-02-16 19:20 UTC (permalink / raw) To: Matthias Meulien; +Cc: Lars Ingebrigtsen, 51809 close 51809 29.0.50 thanks >> My first impression is that setting `outline-level' to >> `diff--outline-level' inconditionnaly should work but since I don't >> understand why `diff-outline-regex' hasn't been defined in terms of >> `diff-file-header-re' and `diff-hunk-header-re' I guess I am missing >> some subtleties. >> >> (Note that there's another minor bug when `diff-buffer-type` isn't equal >> to 'git: `diff--font-lock-prettify` should be a no-op since the regexp >> written there match only Git generated diffs, confirmed by the FIXME >> string "This has only been tested with Git's diff output." written in >> the implementation). > > Here's the corresponding patch: Thanks for the quick fix! I confirm that it works even when `diff-buffer-type' is nil. So now your patch is pushed to master, and this request is closed. ^ permalink raw reply [flat|nested] 45+ 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; 45+ 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] 45+ 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; 45+ 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 related [flat|nested] 45+ 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; 45+ 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] 45+ 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; 45+ 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] 45+ 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; 45+ 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] 45+ 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; 45+ 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] 45+ 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; 45+ 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] 45+ 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; 45+ 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 related [flat|nested] 45+ messages in thread
end of thread, other threads:[~2022-02-16 19:20 UTC | newest] Thread overview: 45+ 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 2022-01-29 19:12 ` Juri Linkov 2022-02-05 18:45 ` Juri Linkov 2022-02-05 22:00 ` Lars Ingebrigtsen 2022-02-12 17:09 ` Juri Linkov 2022-02-12 17:26 ` Matthias Meulien 2022-02-14 21:07 ` Matthias Meulien 2022-02-14 21:13 ` Matthias Meulien 2022-02-14 21:33 ` Matthias Meulien 2022-02-14 21:39 ` Matthias Meulien 2022-02-16 19:20 ` Juri Linkov 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 public 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).