branch: externals/substitute
commit 2f2e8739ff48445da4804aa4e485c9c7eb40f78b
Author: Protesilaos Stavrou <[email protected]>
Commit: Protesilaos Stavrou <[email protected]>

    Define three new commands for outline, page, defun+below
---
 substitute.el | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 90 insertions(+)

diff --git a/substitute.el b/substitute.el
index fe7731db96..d2432bf16f 100644
--- a/substitute.el
+++ b/substitute.el
@@ -101,6 +101,9 @@ Possible meaningful values for SCOPE are:
     ('below "from point to the END of the buffer")
     ('above "from point to the BEGINNING of the buffer")
     ('defun "in the current DEFUN")
+    ('defun-and-below "from point to the END of the current DEFUN")
+    ('outline "in the current OUTLINE level")
+    ('page "in the current PAGE")
     (_ "across the BUFFER")))
 
 (defun substitute--prettify-target-description (target)
@@ -179,17 +182,82 @@ Pass to it the TARGET and SCOPE arguments."
   (narrow-to-defun)
   (goto-char (point-min)))
 
+(defun substitute--scope-current-defun-and-below (target)
+  "Position point to match current TARGET and below only in this defun."
+  (narrow-to-defun)
+  (if-let* ((_ (region-active-p))
+            (bounds (region-bounds)))
+      (goto-char (caar bounds))
+    (thing-at-point-looking-at target)
+    (goto-char (match-beginning 0))))
+
 (defun substitute--scope-top-of-buffer ()
   "Position point to the top of the buffer."
   (widen)
   (goto-char (point-min)))
 
+(defun substitute--get-bounds (regexp position add-line-prefix)
+  "Get bounds of REGEXP from POSITION.
+With ADD-LINE-PREFIX, prepend ^ to the REGEXP, else take it as-is."
+  (let ((original-position (point))
+        (beg nil)
+        (end nil)
+        (rx (if add-line-prefix
+                (format "^\\(?:%s\\)" regexp)
+              regexp)))
+    (goto-char position)
+    (when (re-search-backward rx nil t)
+      (setq beg (match-beginning 0)))
+    (goto-char (line-end-position))
+    (when (re-search-forward rx nil t)
+      (setq end (match-beginning 0)))
+    (goto-char original-position)
+    (cond
+     ((and beg end)
+      (cons beg end))
+     (beg
+      (cons beg (point-max)))
+     (end
+      (cons (point-min) end)))))
+
+(defun substitute-narrow-to-regexp (thing position add-line-prefix)
+  "Narrow to the THING closest to POSITION.
+THING is the symbol of a variable whose value is a regular expression,
+such as `outline-regexp'.
+
+ADD-LINE-PREFIX prepends ^ to the value of THING, else it takes the
+value as-is."
+  (if-let* ((_ (boundp thing))
+            (regexp (symbol-value thing))
+            (bounds (substitute--get-bounds outline-regexp position 
add-line-prefix))
+            (beg (car bounds))
+            (end (cdr bounds)))
+      (narrow-to-region beg end)
+    (user-error "There is no match for REGEXP here: `%s'" regexp)))
+
+(defun substitute--scope-current-outline ()
+  "Position point to the top of the current outline per `outline-regexp'.
+In programming modes this might be practically the same as
+`narrow-to-defun'.  In modes like Org or Markdown, it will be the
+current heading and the text below it without subheadings and their
+text."
+  (substitute-narrow-to-regexp 'outline-regexp (point) :add-line-prefix)
+  (goto-char (point-min)))
+
+(defun substitute--scope-current-page ()
+  "Position point to the top of the current page per `page-delimiter'."
+  (substitute-narrow-to-regexp 'outline-regexp (point) :add-line-prefix)
+  (goto-char (point-min)))
+
 (defun substitute--setup-scope (target scope)
   "Derive SCOPE for TARGET."
   (pcase scope
     ('below (substitute--scope-current-and-below target))
     ('above (substitute--scope-current-and-above target))
     ('defun (substitute--scope-current-defun))
+    ('defun-and-below (substitute--scope-current-defun-and-below target))
+    ('outline (substitute--scope-current-outline))
+    ('page (substitute--scope-current-page))
     (_ (substitute--scope-top-of-buffer))))
 
 (defvar-local substitute--last-matches nil
@@ -313,6 +381,24 @@ same as always calling this command with FIXED-CASE." doc)
  "in the defun (per `narrow-to-defun')"
  'defun)
 
+;;;###autoload (autoload 'substitute-target-in-defun-and-below "substitute")
+(substitute-define-substitute-command
+ substitute-target-in-defun-and-below
+ "in the defun (per `narrow-to-defun') and going down"
+ 'defun-and-below)
+
+;;;###autoload (autoload 'substitute-target-in-outline "substitute")
+(substitute-define-substitute-command
+ substitute-target-in-outline
+ "in the current outline level"
+ 'outline)
+
+;;;###autoload (autoload 'substitute-target-in-page "substitute")
+(substitute-define-substitute-command
+ substitute-target-in-page
+ "in the current page"
+ 'page)
+
 ;;;###autoload (autoload 'substitute-target-below-point "substitute")
 (substitute-define-substitute-command
  substitute-target-below-point
@@ -351,6 +437,10 @@ Meant to be assigned to a prefix key, like this:
 
 (define-key substitute-prefix-map (kbd "b") #'substitute-target-in-buffer)
 (define-key substitute-prefix-map (kbd "d") #'substitute-target-in-defun)
+(define-key substitute-prefix-map (kbd "D") 
#'substitute-target-in-defun-and-below)
+(define-key substitute-prefix-map (kbd "o") #'substitute-target-in-outline)
+;; TODO 2025-11-29: I will also add a `paragraph' scope, which should be bound 
to p.
+(define-key substitute-prefix-map (kbd "P") #'substitute-target-in-page)
 (define-key substitute-prefix-map (kbd "r") #'substitute-target-above-point)
 (define-key substitute-prefix-map (kbd "s") #'substitute-target-below-point)
 

Reply via email to