branch: externals/org-transclusion
commit 301937daf0256a85edd40e692b3351bccc2663b1
Author: gggion <[email protected]>
Commit: gggion <[email protected]>

    refactor(indent-mode): preserve source fringes through org-indent refreshes
    
    Convert org-transclusion-indent-mode from hooks-only to full minor mode
    with automatic source buffer fringe preservation.
    
    The core issue: org-indent-mode continuously regenerates line-prefix
    and wrap-prefix text properties through multiple asynchronous pathways
    (initialization agent, after-change hooks, heading level changes,
    font-lock, timers), obliterating appended fringe indicators in text
    properties.
    
    Timing mechanism: now we synchronize with org-indent via
    org-indent-agentized-buffers and regenerate fringes based on the overlay
    properties that signal an element is a transclusion source.
    
    Key changes:
    - Use of idle timers and change tracking
    - Auto-enable in source buffers when overlays detected
    - Auto-disable when last transclusion removed
    - Integration with `org-indent-post-buffer-init-functions`
    
    * org-transclusion-indent-mode.el: Complete rewrite as minor mode with
    source buffer fringe preservation.
    
    * org-transclusion.el (org-transclusion-source-overlay-modified):
    Delegate fringe re-application to indent-mode when active.
---
 org-transclusion-indent-mode.el | 223 +++++++++++++++++++++++++++++++++++++---
 org-transclusion.el             |   7 +-
 2 files changed, 214 insertions(+), 16 deletions(-)

diff --git a/org-transclusion-indent-mode.el b/org-transclusion-indent-mode.el
index f0f4c445f7..9037981951 100644
--- a/org-transclusion-indent-mode.el
+++ b/org-transclusion-indent-mode.el
@@ -22,37 +22,232 @@
 ;;; Commentary:
 ;;  This file is part of Org-transclusion
 ;;  URL: https://github.com/nobiot/org-transclusion
-;;  
+;;
 ;;  This extension ensures org-indent-mode properties are correctly
 ;;  applied to transcluded content and refreshed after transclusion
-;;  removal.
+;;  removal. It also preserves fringe indicators in source buffers
+;;  when org-indent-mode regenerates line-prefix properties.
+;;
+;;  The timing mechanism for synchronizing with org-indent's asynchronous
+;;  initialization is copied from org-modern-indent.
 
 ;;; Code:
 
 (require 'org-indent)
 
+;;;; Variables
+
+(defvar-local org-transclusion-indent--timer nil
+  "Timer for debounced fringe re-application.")
+
+(defvar-local org-transclusion-indent--last-change-tick nil
+  "Buffer modification tick at last fringe application.")
+
+(defvar-local org-transclusion-indent--has-overlays nil
+  "Non-nil if buffer has ever had source overlays.
+Used to prevent premature mode deactivation during buffer refresh.")
+
+(defvar-local org-transclusion-indent--init nil
+  "Initialization state for waiting on org-indent.
+Either nil, t (initialized), or (TIMER ATTEMPT-COUNT).")
+
+;;;; Helper Functions
+
+(defun org-transclusion-indent--find-source-overlays ()
+  "Return list of all transclusion source overlays in current buffer."
+  (seq-filter
+   (lambda (ov) (overlay-get ov 'org-transclusion-by))
+   (overlays-in (point-min) (point-max))))
+
+(defun org-transclusion-indent--reapply-all-fringes ()
+  "Re-apply fringe indicators to all transcluded regions in buffer.
+This function is called after any change that might have removed
+`line-prefix' or `wrap-prefix' properties."
+  (when (buffer-live-p (current-buffer))
+    (let ((current-tick (buffer-modified-tick))
+          (overlays (org-transclusion-indent--find-source-overlays)))
+      ;; Track if we have overlays
+      (when overlays
+        (setq org-transclusion-indent--has-overlays t))
+
+      ;; Only re-apply if buffer actually changed since last application
+      (unless (eq current-tick org-transclusion-indent--last-change-tick)
+        (setq org-transclusion-indent--last-change-tick current-tick)
+        (dolist (ov overlays)
+          (let ((ov-beg (overlay-start ov))
+                (ov-end (overlay-end ov)))
+            (when (and ov-beg ov-end)
+              ;; Check if fringes are missing by examining first line
+              (save-excursion
+                (goto-char ov-beg)
+                (let* ((line-beg (line-beginning-position))
+                       (line-end (min (1+ line-beg) ov-end))
+                       (line-prefix (get-text-property line-beg 'line-prefix)))
+                  ;; If line-prefix exists but has no fringe, re-apply
+                  (when (and line-prefix
+                             (not (org-transclusion-prefix-has-fringe-p 
line-prefix)))
+                    (org-transclusion-add-fringe-to-region
+                     (current-buffer) ov-beg ov-end
+                     'org-transclusion-source-fringe)))))))))))
+
+(defun org-transclusion-indent--schedule-reapply ()
+  "Schedule fringe re-application after a short delay.
+This debounces rapid changes to avoid excessive processing."
+  (when org-transclusion-indent--timer
+    (cancel-timer org-transclusion-indent--timer))
+  (setq org-transclusion-indent--timer
+        (run-with-idle-timer 0.1 nil
+                             (lambda (buf)
+                               (when (buffer-live-p buf)
+                                 (with-current-buffer buf
+                                   
(org-transclusion-indent--reapply-all-fringes))))
+                             (current-buffer))))
+
+(defun org-transclusion-indent--after-change (_beg _end _len)
+  "Schedule fringe re-application after buffer change.
+Added to `after-change-functions' in source buffers."
+  (org-transclusion-indent--schedule-reapply))
+
+(defun org-transclusion-indent--check-and-disable ()
+  "Disable mode if no source overlays remain in buffer.
+Only disables if overlays have been checked and confirmed absent,
+not during temporary states like buffer refresh."
+  (when (and org-transclusion-indent--has-overlays
+             (not (org-transclusion-indent--find-source-overlays)))
+    ;; Wait a bit to ensure this isn't just a temporary state
+    (run-with-idle-timer
+     0.2 nil
+     (lambda (buf)
+       (when (buffer-live-p buf)
+         (with-current-buffer buf
+           (unless (org-transclusion-indent--find-source-overlays)
+             (org-transclusion-indent-mode -1)))))
+     (current-buffer))))
+
+(defun org-transclusion-indent--wait-and-init (buf)
+  "Wait for org-indent to finish initializing BUF, then apply fringes.
+Copied from org-modern-indent's timing mechanism."
+  (if (or (not (bound-and-true-p org-indent-agentized-buffers))
+          (memq buf org-indent-agentized-buffers))
+      ;; org-indent is ready
+      (org-transclusion-indent--init buf)
+    ;; Still waiting
+    (when (buffer-live-p buf)
+      (with-current-buffer buf
+        (if org-transclusion-indent--init
+            (let ((cnt (cl-incf (cadr org-transclusion-indent--init))))
+              (if (> cnt 5)
+                  (progn
+                    (message "org-transclusion-indent-mode: Gave up waiting 
for %s to initialize" buf)
+                    (setq org-transclusion-indent--init t))
+                (timer-activate
+                 (timer-set-time (car org-transclusion-indent--init)
+                                 (time-add (current-time) 0.2)))))
+          (setq org-transclusion-indent--init
+                (list (run-at-time 0.1 nil 
#'org-transclusion-indent--wait-and-init buf)
+                      1)))))))
+
+(defun org-transclusion-indent--init (buf)
+  "Initialize indent mode in BUF after org-indent completes.
+To be added to `org-indent-post-buffer-init-functions'."
+  (when (buffer-live-p buf)
+    (with-current-buffer buf
+      (setq org-transclusion-indent--init t)
+      (org-transclusion-indent--reapply-all-fringes))))
+
+(defun org-transclusion-indent--auto-enable-maybe ()
+  "Auto-enable indent mode if source overlays are detected.
+Added to `post-command-hook' in `org-mode' buffers with `org-indent-mode'."
+  (when (and (not org-transclusion-indent-mode)
+             (org-transclusion-indent--find-source-overlays))
+    (org-transclusion-indent-mode +1)))
+
+;;;; Destination Buffer Support
+
 (defun org-transclusion-indent--add-properties (beg end)
   "Ensure org-indent properties exist in transcluded region.
-BEG and END are the transcluded region bounds.
-
-The main package adds uniform fringe indicators to transcluded content
-via text properties. This function ensures org-indent-mode's indentation
-properties are applied if org-indent-mode is active, but does not modify
-the fringe indicators."
+BEG and END are the transcluded region bounds."
   (when org-indent-mode
-    ;; Ensure org-indent properties exist
     (org-indent-add-properties beg end)))
 
 (defun org-transclusion-indent--refresh-source-region (src-buf src-beg src-end)
   "Refresh org-indent properties in source region after transclusion removal.
-SRC-BUF is the source buffer, SRC-BEG and SRC-END are the region bounds.
-This ensures visual indentation updates immediately when org-indent-mode
-is active."
+SRC-BUF is the source buffer, SRC-BEG and SRC-END are the region bounds."
   (when (buffer-local-value 'org-indent-mode src-buf)
     (with-current-buffer src-buf
-      (org-indent-add-properties src-beg src-end))))
+      (org-indent-add-properties src-beg src-end)
+      ;; Check if mode should be disabled
+      (when (and (boundp 'org-transclusion-indent-mode)
+                 org-transclusion-indent-mode)
+        (org-transclusion-indent--check-and-disable)))))
+
+;;;; Minor Mode Definition
+
+;;;###autoload
+(define-minor-mode org-transclusion-indent-mode
+  "Minor mode for org-indent-mode support in org-transclusion.
+
+This mode serves two purposes:
+
+1. In destination buffers: ensures org-indent properties are applied
+   to transcluded content.
+
+2. In source buffers: preserves fringe indicators when org-indent-mode
+   regenerates `line-prefix' properties.
+
+The mode auto-activates in source buffers when transclusion source
+overlays are detected, and auto-deactivates when all transclusions
+are removed."
+  :init-value nil
+  :lighter " OT-Indent"
+  :group 'org-transclusion
+  (if org-transclusion-indent-mode
+      (progn
+        ;; Install hooks for source buffer fringe preservation
+        (add-hook 'after-change-functions
+                  #'org-transclusion-indent--after-change nil t)
+
+        ;; Register with org-indent or wait for it
+        (cond
+         ;; Already initialized before, just toggle
+         ((or (called-interactively-p 'any) org-transclusion-indent--init)
+          (org-transclusion-indent--init (current-buffer)))
+         ;; Register with buffer init hook if available
+         ((boundp 'org-indent-post-buffer-init-functions)
+          (add-hook 'org-indent-post-buffer-init-functions
+                    #'org-transclusion-indent--init nil t))
+         ;; Fallback: wait for org-indent
+         (t (org-transclusion-indent--wait-and-init (current-buffer)))))
+
+    ;; Cleanup
+    (remove-hook 'after-change-functions
+                 #'org-transclusion-indent--after-change t)
+    (when (boundp 'org-indent-post-buffer-init-functions)
+      (remove-hook 'org-indent-post-buffer-init-functions
+                   #'org-transclusion-indent--init t))
+    (when org-transclusion-indent--timer
+      (cancel-timer org-transclusion-indent--timer)
+      (setq org-transclusion-indent--timer nil))
+    (when (and (listp org-transclusion-indent--init)
+               (timerp (car org-transclusion-indent--init)))
+      (cancel-timer (car org-transclusion-indent--init)))
+    (setq org-transclusion-indent--has-overlays nil
+          org-transclusion-indent--init nil)))
+
+;;;###autoload
+(defun org-transclusion-indent-mode-setup ()
+  "Set up auto-activation of `indent mode' in `org-mode' buffers.
+Adds `post-command-hook' to detect when source overlays appear."
+  (when (and (derived-mode-p 'org-mode)
+             (bound-and-true-p org-indent-mode))
+    (add-hook 'post-command-hook
+              #'org-transclusion-indent--auto-enable-maybe nil t)))
+
+;; Auto-setup in org-mode buffers - add late to hook like org-modern-indent
+(add-hook 'org-mode-hook #'org-transclusion-indent-mode-setup 90)
+
+;;;; Hook Registration
 
-;; Register hooks when extension loads
 (add-hook 'org-transclusion-after-add-functions
           #'org-transclusion-indent--add-properties)
 (add-hook 'org-transclusion-after-remove-functions
diff --git a/org-transclusion.el b/org-transclusion.el
index 43300ab4d7..9fafe4830a 100644
--- a/org-transclusion.el
+++ b/org-transclusion.el
@@ -1510,8 +1510,11 @@ dynamic updates."
   (when (and after-p (overlay-buffer ov))
     (let ((ov-beg (overlay-start ov))
           (ov-end (overlay-end ov)))
-      (org-transclusion-add-fringe-to-region
-       (overlay-buffer ov) ov-beg ov-end 'org-transclusion-source-fringe))))
+      ;; Only re-apply fringes if org-transclusion-indent-mode is NOT active
+      ;; When indent-mode is active, its after-change-function handles this
+      (unless (buffer-local-value 'org-transclusion-indent-mode 
(overlay-buffer ov))
+        (org-transclusion-add-fringe-to-region
+         (overlay-buffer ov) ov-beg ov-end 'org-transclusion-source-fringe)))))
 
 ;;;; Utility Functions
 (defun org-transclusion-find-source-marker (beg end)

Reply via email to