branch: externals/hyperbole
commit 8af4e68a25623dbe5134053cdadb0c5b06beec9f
Merge: 645e8341ec 447b83c2ca
Author: Robert Weiner <[email protected]>
Commit: GitHub <[email protected]>

    Merge pull request #870 from rswgnu/rsw
    
    hywiki.el - Add insertion of HyWikiWord#section reference links
    
    hui.el - Eliminate recursive require with 'hywiki
    
    hywiki-word-is-p - Revert to using all 'string-match' calls
    
    ibtypes::action - Fix to check for angle bracket delimiters.
---
 ChangeLog          |  51 +++++++++++++
 README.md          |  24 ++++++
 README.toc.md      |  31 +++++++-
 hasht.el           |  22 +++---
 hbut.el            |   7 +-
 hibtypes.el        | 218 +++++++++++++++++++++++++++--------------------------
 hsys-consult.el    |   4 +-
 hui-mini.el        |   6 +-
 hui.el             |   9 ++-
 hywiki.el          | 215 +++++++++++++++++++++++++++++++++++++++-------------
 kotl/kotl-mode.el  |   8 +-
 man/hyperbole.texi |   9 ++-
 12 files changed, 419 insertions(+), 185 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7f4d1e62ee..4d4c3198ea 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,54 @@
+2026-02-19  Bob Weiner  <[email protected]>
+
+* hui.el (hui:link-possible-types): Fix to include link-to-wikiword only if
+    an existing referent is found.
+
+* hibtypes.el (action): Fix to check for angle bracket delimiters.
+
+* hywiki.el (hywiki-read-page-reference): Add and use in 'interactive' calls of
+    several functions.
+            (hywiki-word-is-p): Revert to using all 'string-match' calls
+    to prevent any regressions.
+            (hywiki-page-read): Add to read only existing HyWiki page names.
+    Not all existing WikiWords have page names, as there are other referent 
types.
+            (hywiki-consult-file-and-line): Rename to 
`hywiki-consult-page-and-line'.
+           (hywiki-page-exists-p): Add.
+           (hywiki-format-reference): Fix to accept only existing page 
references.
+           (hywiki-read-reference): Delete, use 'hywiki-word-read' instead.
+           (hywiki-add-page): Fix a number of issues where non-page WikiWords 
were
+    allowed.
+            (hywiki-page-read-new): Prevent selection of non-page HyWikiWords.
+
+* hui.el (require 'hywiki):
+         (hui:link-possible-types): Add 'link-to-wikiword' clause.
+* hbut.el (ibut:insert-text): Add 'actypes::link-to-wikiword' clause.
+* man/hyperbole.texi (By Dragging): Add HyWikiWord Reference to referent
+    context table.
+
+* hywiki.el (hywiki-consult-file-and-line): Add.
+           (hywiki-format-reference): Add and use with above function.
+            (hywiki-insert-reference): Add.
+           (hywiki-word-is-p): Use 'string-match-p' instead of 'string-match'.
+           (link-to-wikiword): Add this actype.
+            (hywiki-insert-link): Update to search for section links to insert
+    when 'consult' is available.
+            (require 'hui): Change to an 'eval-when-compile' so does not
+    trigger recursive requirements during normal loading.
+  hui-mini.el (hui:menu-hywiki): Unify with "hui-menu.el".  Replace use of
+    'hywiki-add-path-link' with 'hywiki-insert-link'.
+
+2026-02-17  Bob Weiner  <[email protected]>
+
+* hasht.el (hash-make): Fix to trigger an error if key is not given as a 
string.
+
+2026-02-16  Bob Weiner  <[email protected]>
+
+* hibtypes.el (markdown-follow-inline-link-p): Fix to handle URL arguments sent
+    when point is on the link title and not an included URL (so check if on
+    a URL fails).
+
+* README.md: Add Hyperbole Articles section.
+
 2026-02-18  Mats Lidell  <[email protected]>
 
 * hsys-org.el: require 'find-func for find-library--from-load-history.
diff --git a/README.md b/README.md
index 50d1024877..2124a69922 100644
--- a/README.md
+++ b/README.md
@@ -55,6 +55,30 @@ otherwise, skip to the next section.
 
    - [Linking personal info with implicit 
buttons](https://youtu.be/TQ_fG7b1iHI)
 
+## Articles
+
+   - [HyWiki: My Favorite Part of 
Hyperbole](https://kirankp.com/blog/gnu-hyperbole/)
+
+   - [Hyperbole VisionQuest Part 
1](https://github.com/termitereform/JunkPile/blob/master/HyperboleNotes.org)
+
+   - [Hyperbole VisionQuest Part 
2](https://github.com/termitereform/JunkPile/blob/master/HyperboleNotes2.org)
+
+   - [A Taste of 
Hyperbole](https://www.reddit.com/r/emacs/comments/1kty4mb/a_taste_of_hyperbole_automatically_linking_to_org/)
+
+   - [My Understanding of GNU 
Hyperbole](https://www.reddit.com/r/emacs/comments/nirwpk/my_understanding_of_gnu_hyperbole/)
+
+   - [What does GNU Hyperbole 
do?](https://tilde.town/~ramin_hal9001/articles/intro-to-hyperbole.html)
+
+   - [John Wiegley - The Philosophy Behind 
Hyperbole](https://mail.gnu.org/archive/html/hyperbole-users/2019-01/msg00037.html)
+
+   - [Daily ways GNU Hyperbole helps me stay in flow and reduces cognitive 
load](https://www.reddit.com/r/emacs/comments/jk3cn0/daily_ways_gnu_hyperbole_helps_me_stay_in_flow/)
+
+   - [Doing a Research Project and using GNU Hyperbole's Integrated 
Features](https://www.reddit.com/r/emacs/comments/1g2184d/doing_a_research_project_and_using_gnu_hyperboles/)
+
+   - [AI-generated Hyperbole Architectural 
Documentation](https://deepwiki.com/rswgnu/hyperbole)
+
+   - [Hypermedia and 
Hyperbole](https://www.mgmarlow.com/words/2023-10-26-hyperbole/)
+
 ## Summary
 
 `GNU Hyperbole` (pronounced Ga-new Hi-per-bo-lee), or just `Hyperbole`, is
diff --git a/README.toc.md b/README.toc.md
index 18cb88f5d8..0231bc28e0 100644
--- a/README.toc.md
+++ b/README.toc.md
@@ -7,11 +7,10 @@
  send us a thank you or a testimonial describing your usage if you like
  Hyperbole to [[email protected]](mailto:[email protected])].
 
-
-
 - [GNU Hyperbole 9.0.2pre - The Everyday Hypertextual Information 
Manager](#gnu-hyperbole-902pre---the-everyday-hypertextual-information-manager)
   - [Reference Manual](#reference-manual)
   - [Videos](#videos)
+  - [Articles](#articles)
   - [Summary](#summary)
   - [Installation](#installation)
   - [Invocation](#invocation)
@@ -26,8 +25,6 @@
   - [User Quotes](#user-quotes)
   - [Why was Hyperbole developed?](#why-was-hyperbole-developed)
 
-
-
 ![Hyperbole screenshot of the Koutliner, DEMO file and 
HyRolo](man/im/hyperbole-cv.png)
 
 ## Reference Manual
@@ -74,6 +71,30 @@ otherwise, skip to the next section.
 
    - [Linking personal info with implicit 
buttons](https://youtu.be/TQ_fG7b1iHI)
 
+## Articles
+
+   - [HyWiki: My Favorite Part of 
Hyperbole](https://kirankp.com/blog/gnu-hyperbole/)
+
+   - [Hyperbole VisionQuest Part 
1](https://github.com/termitereform/JunkPile/blob/master/HyperboleNotes.org)
+
+   - [Hyperbole VisionQuest Part 
2](https://github.com/termitereform/JunkPile/blob/master/HyperboleNotes2.org)
+
+   - [A Taste of 
Hyperbole](https://www.reddit.com/r/emacs/comments/1kty4mb/a_taste_of_hyperbole_automatically_linking_to_org/)
+
+   - [My Understanding of GNU 
Hyperbole](https://www.reddit.com/r/emacs/comments/nirwpk/my_understanding_of_gnu_hyperbole/)
+
+   - [What does GNU Hyperbole 
do?](https://tilde.town/~ramin_hal9001/articles/intro-to-hyperbole.html)
+
+   - [John Wiegley - The Philosophy Behind 
Hyperbole](https://mail.gnu.org/archive/html/hyperbole-users/2019-01/msg00037.html)
+
+   - [Daily ways GNU Hyperbole helps me stay in flow and reduces cognitive 
load](https://www.reddit.com/r/emacs/comments/jk3cn0/daily_ways_gnu_hyperbole_helps_me_stay_in_flow/)
+
+   - [Doing a Research Project and using GNU Hyperbole's Integrated 
Features](https://www.reddit.com/r/emacs/comments/1g2184d/doing_a_research_project_and_using_gnu_hyperboles/)
+
+   - [AI-generated Hyperbole Architectural 
Documentation](https://deepwiki.com/rswgnu/hyperbole)
+
+   - [Hypermedia and 
Hyperbole](https://www.mgmarlow.com/words/2023-10-26-hyperbole/)
+
 ## Summary
 
 `GNU Hyperbole` (pronounced Ga-new Hi-per-bo-lee), or just `Hyperbole`, is
@@ -255,6 +276,8 @@ window control menu if it was not already bound prior to 
Hyperbole's
 initialization.  A long video demonstrating many of HyControl's
 features is available at https://youtu.be/M3-aMh1ccJk.
 
+## Hyperbole Manual
+
 The above are the best interactive ways to learn about Hyperbole.
 Hyperbole also includes the Hyperbole Manual, a full reference manual,
 not a simple introduction.  It is included in the "man/" subdirectory
diff --git a/hasht.el b/hasht.el
index b20b0a05a2..4ad4af7109 100644
--- a/hasht.el
+++ b/hasht.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    16-Mar-90 at 03:38:48
-;; Last-Mod:     30-Dec-25 at 14:42:14 by Mats Lidell
+;; Last-Mod:     17-Feb-26 at 22:42:00 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -159,16 +159,20 @@ merge all the values for a given <key> instead."
                  key value sym)
             (if reverse
                 (mapc (lambda (cns)
-                        (when (consp cns)
-                          (setq key (car cns) value (cdr cns)))
-                        (when (setq sym (intern key))
-                          (puthash sym value hash-table)))
+                        (if (consp cns)
+                            (setq key (car cns) value (cdr cns))
+                          (setq key nil value nil))
+                        (if (and (stringp key) (setq sym (intern key)))
+                            (puthash sym value hash-table)
+                           (error "(hash-make): 'key' must be a string, not 
%S" key)))
                       initializer)
               (mapc (lambda (cns)
-                      (when (consp cns)
-                        (setq key (cdr cns) value (car cns)))
-                      (when (setq sym (intern key))
-                        (puthash sym value hash-table)))
+                      (if (consp cns)
+                          (setq key (cdr cns) value (car cns))
+                         (setq key nil value nil))
+                      (if (and (stringp key) (setq sym (intern key)))
+                          (puthash sym value hash-table)
+                         (error "(hash-make): 'key' must be a string, not %S" 
key)))
                     initializer))
             hash-table))))
 
diff --git a/hbut.el b/hbut.el
index 96c53c23fc..850fe36b08 100644
--- a/hbut.el
+++ b/hbut.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    18-Sep-91 at 02:57:09
-;; Last-Mod:     30-Dec-25 at 23:55:08 by Mats Lidell
+;; Last-Mod:     18-Feb-26 at 23:49:33 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -2722,6 +2722,11 @@ Summary of operations based on inputs (name arg from 
\\='hbut:current attrs):
           (insert "<" arg1 ">"))))
       ('actypes::link-to-org-id (insert (format "\"id:%s\"" arg1)))
       ('actypes::link-to-rfc (insert (format "rfc%d" arg1)))
+      ('actypes::link-to-wikiword (insert (if (and (stringp arg1)
+                                                   (string-match-p "\\s-" 
arg1))
+                                              ;; Double-quote when has a space
+                                              (format "\"%s\"" arg1)
+                                            arg1)))
       ('man (insert arg1))
       ('actypes::man-show (insert arg1))
       ('actypes::link-to-file-line (insert (format "\"%s:%d\""
diff --git a/hibtypes.el b/hibtypes.el
index 3c465fa4b5..1786e178b3 100644
--- a/hibtypes.el
+++ b/hibtypes.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    19-Sep-91 at 20:45:31
-;; Last-Mod:      2-Feb-26 at 18:22:19 by Bob Weiner
+;; Last-Mod:     19-Feb-26 at 21:16:16 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -527,15 +527,17 @@ Return t if jump and nil otherwise."
   "If on an inline link, jump to its referent if it is absolute and return 
non-nil.
 Absolute means not relative within the file.  Otherwise, if an
 internal link, move back to OPOINT and return nil."
-  ;; Caller already checked not on a URL (handled elsewhere).
   (let ((path (markdown-link-url)))
     (goto-char opoint)
     (when (markdown-link-p)
       (ibut:label-set (match-string-no-properties 0) (match-beginning 0) 
(match-end 0))
-      (if path
-         (hact 'link-to-file path)
-        (hpath:display-buffer (current-buffer))
-        (hact 'markdown-follow-link-at-point)))))
+      (cond ((hpath:url-p path)
+            (hact 'www-url path))
+            (path
+            (hact 'link-to-file path))
+            (t
+             (hpath:display-buffer (current-buffer))
+             (hact 'markdown-follow-link-at-point))))))
 
 (defib markdown-internal-link ()
   "Display any in-file Markdown link referent at point.
@@ -1606,110 +1608,112 @@ first identifier in the expression must be an Elisp 
variable,
 action type, function symbol to call or test to execute, i.e.
 '<'actype-or-elisp-symbol arg1 ... argN '>'.  For example,
 <mail nil \"[email protected]\">."
-  (let ((hbut:max-len 0)
-       (lbl-key (hattr:get 'hbut:current 'lbl-key))
-       (name (hattr:get 'hbut:current 'name))
+  (let ((lbl-key (hattr:get 'hbut:current 'lbl-key))
        (start-pos (hattr:get 'hbut:current 'lbl-start))
-       (end-pos  (hattr:get 'hbut:current 'lbl-end))
-       (testing-flag (when (bound-and-true-p ert--running-tests) t))
-        actype actype-sym action args lbl var-flag)
-
-    ;; Continue only if there if there is a button label and one of:
-    ;;  1. `ert--running-tests' is non-nil
-    ;;  2. character after start-delim is not a whitespace character
+       (end-pos  (hattr:get 'hbut:current 'lbl-end)))
     (when (and lbl-key
-              (or testing-flag
-                  (not (memq (if (char-after (1+ start-pos))
-                                      (char-syntax (char-after (1+ start-pos)))
-                                    0)
-                                  '(?\  ?\>)))))
-      (setq lbl (ibut:key-to-label lbl-key))
-      ;; Handle $ preceding var name in cases where same name is
-      ;; bound as a function symbol
-      (when (string-match "\\`\\$" lbl)
-        (setq var-flag t
-             lbl (substring lbl 1)))
-      (setq actype (if (string-match-p " " lbl) (car (split-string lbl)) lbl)
-            actype-sym (or (actype:elisp-symbol actype) (intern-soft actype))
-           ;; Must ignore that (boundp nil) would be t here.
-            actype (and actype-sym
-                       (or (fboundp actype-sym) (boundp actype-sym)
-                           (special-form-p actype-sym)
-                           (ert-test-boundp actype-sym))
-                       actype-sym))
-      (when actype
-       ;; For <hynote> buttons, need to double quote each argument so
-       ;; 'read' does not change the idstamp 02 to 2.
-       (when (and (memq actype '(hy hynote))
-                  (string-match-p " " lbl))
-         (setq lbl (replace-regexp-in-string "\"\\(.*\\)\\'" "\\1\""
-                                             (combine-and-quote-strings
-                                              (split-string lbl) "\" \""))))
-        (setq action (read (concat "(" lbl ")"))
-             args (cdr action))
-       ;; Ensure action uses an fboundp symbol if executing a
-       ;; Hyperbole actype.
-       (when (and (car action) (symbolp (car action)))
-         (setcar action (or (symtable:hyperbole-actype-p (car action))
-                            (car action))))
-       (unless assist-flag
-          (cond ((and (symbolp actype) (fboundp actype)
-                     (string-match "-p\\'" (symbol-name actype)))
-                ;; Is a function with a boolean result
-                (setq actype #'display-boolean
-                      args `(',action)))
-               ((and (null args) (symbolp actype) (boundp actype)
-                     (or var-flag (not (fboundp actype))))
-                ;; Is a variable, display its value as the action
-                (setq args `(,actype)
-                      actype #'display-variable))
-               ((and (null args) (symbolp actype) (ert-test-boundp actype))
-                ;; Is an ert-deftest, display the value from executing it
-                (setq actype #'display-value
-                      args `((hypb-ert-run-test ,lbl))))
-               (t
-                ;; All other expressions, display the action result in the 
minibuffer
-                (if (string-match "\\b\\(delete\\|kill\\)-region\\'"
-                                  (symbol-name actype-sym))
-                    ;; With `delete-region' and `kill-region'
-                    ;; actions, if no args, either use any active
-                    ;; region or when none, use the region of the
-                    ;; action button itself, removing it from the
-                    ;; buffer.  The latter action is largely used
-                    ;; only in internal HyWiki tests.
-                    (progn (setq actype #'display-value)
-                           (if (= 1 (length action)) ;; No args
-                               (if (use-region-p)
-                                   ;; Apply function to the active region
-                                   (setq args `((,actype-sym 
(region-beginning) (region-end))))
-                                 ;; Apply function to region of the action 
button itself,
-                                 ;; including delimiters
-                                 (setq args `((,actype-sym ,start-pos
-                                                           ,end-pos))))
-                             (setq args `(',action))))
-                  (if testing-flag
-                      ;; Delete action button after activation when
-                      ;; running an ert test or in a string (so can
-                      ;; test this behavior interactively),
-                      (setq actype #'display-value-and-remove-region
-                            args `(,action ,start-pos ,end-pos))
+               (eq (char-after start-pos) ?\<)
+               (eq (char-before end-pos) ?\>))
+      (let ((hbut:max-len 0)
+           (name (hattr:get 'hbut:current 'name))
+           (testing-flag (when (bound-and-true-p ert--running-tests) t))
+            actype actype-sym action args lbl var-flag)
+
+        ;; Continue only if there if there is one of:
+        ;;  1. `ert--running-tests' is non-nil
+        ;;  2. character after start-delim is not a whitespace character
+        (when (and (or testing-flag
+                      (not (memq (if (char-after (1+ start-pos))
+                                     (char-syntax (char-after (1+ start-pos)))
+                                   0)
+                                 '(?\  ?\>)))))
+          (setq lbl (ibut:key-to-label lbl-key))
+          ;; Handle $ preceding var name in cases where same name is
+          ;; bound as a function symbol
+          (when (string-match "\\`\\$" lbl)
+            (setq var-flag t
+                 lbl (substring lbl 1)))
+          (setq actype (if (string-match-p " " lbl) (car (split-string lbl)) 
lbl)
+                actype-sym (or (actype:elisp-symbol actype) (intern-soft 
actype))
+               ;; Must ignore that (boundp nil) would be t here.
+                actype (and actype-sym
+                           (or (fboundp actype-sym) (boundp actype-sym)
+                               (special-form-p actype-sym)
+                               (ert-test-boundp actype-sym))
+                           actype-sym))
+          (when actype
+           ;; For <hynote> buttons, need to double quote each argument so
+           ;; 'read' does not change the idstamp 02 to 2.
+           (when (and (memq actype '(hy hynote))
+                      (string-match-p " " lbl))
+             (setq lbl (replace-regexp-in-string "\"\\(.*\\)\\'" "\\1\""
+                                                 (combine-and-quote-strings
+                                                  (split-string lbl) "\" 
\""))))
+            (setq action (read (concat "(" lbl ")"))
+                 args (cdr action))
+           ;; Ensure action uses an fboundp symbol if executing a
+           ;; Hyperbole actype.
+           (when (and (car action) (symbolp (car action)))
+             (setcar action (or (symtable:hyperbole-actype-p (car action))
+                                (car action))))
+           (unless assist-flag
+              (cond ((and (symbolp actype) (fboundp actype)
+                         (string-match "-p\\'" (symbol-name actype)))
+                    ;; Is a function with a boolean result
+                    (setq actype #'display-boolean
+                          args `(',action)))
+                   ((and (null args) (symbolp actype) (boundp actype)
+                         (or var-flag (not (fboundp actype))))
+                    ;; Is a variable, display its value as the action
+                    (setq args `(,actype)
+                          actype #'display-variable))
+                   ((and (null args) (symbolp actype) (ert-test-boundp actype))
+                    ;; Is an ert-deftest, display the value from executing it
                     (setq actype #'display-value
-                          args `(,action)))))))
-
-       ;; Create implicit button object and store in symbol hbut:current.
-       (ibut:label-set lbl)
-       (ibut:create :name name :lbl-key lbl-key :lbl-start start-pos
-                    :lbl-end end-pos :categ 'ibtypes::action :actype actype
-                    :args args)
-
-        ;; Necessary so can return a null value, which actype:act cannot.
-        (let ((hrule:action
-              (if (eq hrule:action #'actype:identity)
-                   #'actype:identity
-                 #'actype:eval)))
-          (if (eq hrule:action #'actype:identity)
-             `(hact ',actype ,@args)
-            `(hact ',actype ,@(mapcar #'eval args))))))))
+                          args `((hypb-ert-run-test ,lbl))))
+                   (t
+                    ;; All other expressions, display the action result in the 
minibuffer
+                    (if (string-match "\\b\\(delete\\|kill\\)-region\\'"
+                                      (symbol-name actype-sym))
+                        ;; With `delete-region' and `kill-region'
+                        ;; actions, if no args, either use any active
+                        ;; region or when none, use the region of the
+                        ;; action button itself, removing it from the
+                        ;; buffer.  The latter action is largely used
+                        ;; only in internal HyWiki tests.
+                        (progn (setq actype #'display-value)
+                               (if (= 1 (length action)) ;; No args
+                                   (if (use-region-p)
+                                       ;; Apply function to the active region
+                                       (setq args `((,actype-sym 
(region-beginning) (region-end))))
+                                     ;; Apply function to region of the action 
button itself,
+                                     ;; including delimiters
+                                     (setq args `((,actype-sym ,start-pos
+                                                               ,end-pos))))
+                                 (setq args `(',action))))
+                      (if testing-flag
+                          ;; Delete action button after activation when
+                          ;; running an ert test or in a string (so can
+                          ;; test this behavior interactively),
+                          (setq actype #'display-value-and-remove-region
+                                args `(,action ,start-pos ,end-pos))
+                        (setq actype #'display-value
+                              args `(,action)))))))
+
+           ;; Create implicit button object and store in symbol hbut:current.
+           (ibut:label-set lbl)
+           (ibut:create :name name :lbl-key lbl-key :lbl-start start-pos
+                        :lbl-end end-pos :categ 'ibtypes::action :actype actype
+                        :args args)
+
+            ;; Necessary so can return a null value, which actype:act cannot.
+            (let ((hrule:action
+                  (if (eq hrule:action #'actype:identity)
+                       #'actype:identity
+                     #'actype:eval)))
+              (if (eq hrule:action #'actype:identity)
+                 `(hact ',actype ,@args)
+                `(hact ',actype ,@(mapcar #'eval args))))))))))
 
 (defun action:help (hbut)
   "Display documentation for action button at point.
diff --git a/hsys-consult.el b/hsys-consult.el
index 45340ccc9c..13d11296d6 100644
--- a/hsys-consult.el
+++ b/hsys-consult.el
@@ -2,7 +2,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:     4-Jul-24 at 09:57:18
-;; Last-Mod:     30-Dec-25 at 14:42:23 by Mats Lidell
+;; Last-Mod:     19-Feb-26 at 00:52:16 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -43,8 +43,10 @@
 (declare-function hsys-org-directory-at-tags-p "hsys-org")
 (declare-function hsys-org-at-tags-p "hsys-org")
 
+(declare-function consult--async-command "ext:consult")
 (declare-function consult--grep "ext:consult")
 (declare-function consult--grep-make-builder "ext:consult")
+(declare-function consult--lookup-member "ext:consult")
 (declare-function consult--read "ext:consult")
 (declare-function consult--ripgrep-make-builder "ext:consult")
 (declare-function consult-grep "ext:consult")
diff --git a/hui-mini.el b/hui-mini.el
index edd5d7c3a8..8c307dc243 100644
--- a/hui-mini.el
+++ b/hui-mini.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    15-Oct-91 at 20:13:17
-;; Last-Mod:      1-Jan-26 at 18:18:17 by Mats Lidell
+;; Last-Mod:     19-Feb-26 at 00:16:53 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -1069,8 +1069,8 @@ support underlined faces as well."
           "Report on a HyWikiWord's attributes or HyWikiWords in general.")
         '("Info"           (id-info "(hyperbole)HyWiki")
           "Display Hyperbole manual section on HyWiki.")
-        '("Link"           hywiki-add-path-link
-          "Prompt for and add a HyWikiWord that links to a path and possible 
position.")
+        '("Link"           hywiki-insert-link
+          "Prompt for and insert at point a HyWikiWord#section reference.")
          '("ModeSet/"       (menu . cust-hywiki-mode)
           "Set hywiki-mode state to determine where HyWikiWord references are 
recognized.")
         '("Org-M-RET/"     (menu . cust-org)
diff --git a/hui.el b/hui.el
index 9d90f070c4..3486e7a8e5 100644
--- a/hui.el
+++ b/hui.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    19-Sep-91 at 21:42:03
-;; Last-Mod:     31-Dec-25 at 16:02:19 by Mats Lidell
+;; Last-Mod:     19-Feb-26 at 21:32:21 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -37,8 +37,10 @@
 (defvar completion-to-accept)           ; "completion.el"
 (defvar hyperbole-mode-map)             ; "hyperbole.el"
 
+(declare-function actypes::link-to-wikiword "hywiki")
 (declare-function bookmark-bmenu-bookmark "bookmark")
 (declare-function hui:menu-choose "hui-mini")
+(declare-function hywiki-referent-exists-p "hywiki")
 (declare-function kcell-view:absolute-reference "kotl/kview")
 (declare-function kcell-view:idstamp "kotl/kview")
 (declare-function klink:absolute "kotl/klink")
@@ -2010,6 +2012,7 @@ possible types.
 
 Referent Context         Possible Link Type Returned
 ----------------------------------------------------
+HyWikiWord Reference     link-to-wikiword
 Org Roam or Org Id       link-to-org-id
 Global Button            link-to-gbut
 Explicit Button          link-to-ebut
@@ -2034,7 +2037,9 @@ Buffer without File      link-to-buffer-tmp"
        hbut-sym
        lbl-key)
     (prog1 (delq nil
-                (list (cond ((and (featurep 'org-id)
+                (list (cond ((let ((ref (hywiki-referent-exists-p)))
+                               (and ref (list 'link-to-wikiword ref))))
+                             ((and (featurep 'org-id)
                                   (cond ((save-excursion
                                            (beginning-of-line)
                                            (when (looking-at "[ 
\t]*:\\(CUSTOM_\\)?ID:[ \t]+\\([^ \t\r\n\f]+\\)")
diff --git a/hywiki.el b/hywiki.el
index 0579093c4b..b48e1caf46 100644
--- a/hywiki.el
+++ b/hywiki.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    21-Apr-24 at 22:41:13
-;; Last-Mod:     15-Feb-26 at 20:39:33 by Bob Weiner
+;; Last-Mod:     19-Feb-26 at 19:29:13 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -38,7 +38,7 @@
 ;;    - non-special text buffers, when `hywiki-mode' is enabled;
 ;;    - comments and strings in programming buffers, when
 ;;      `hywiki-mode' is enabled.
-;;  
+;;
 ;;  As HyWikiWords are typed, highlighting occurs after a trailing
 ;;  whitespace or punctuation character is added, or when it is
 ;;  surrounded by a matching pair of characters such as curly braces
@@ -105,7 +105,7 @@
 ;;  properties with Org's publishing framework, so when in a HyWiki
 ;;  page, you can use the standard {C-c C-e P p} current project publish
 ;;  command.
-;;  
+;;
 ;;  There are a few publishing settings you can customize prior to
 ;;  loading Hyperbole's HyWiki code.
 ;;
@@ -223,7 +223,7 @@ See `current-time' function for the mod time format.")
 (defvar hywiki--pages-directory nil)
 (defvar hywiki--referent-alist nil
   "HyWiki alist generated from `hywiki--referent-hasht' for storage in cache.
-Each element is of the form: (wikiword . (referent-type . referent-value)).")
+Each element is of the form: (\"wikiword\" . (referent-type . 
referent-value)).")
 (defvar hywiki--referent-hasht nil
   "HyWiki hash table for fast WikiWord referent lookup.")
 
@@ -945,9 +945,10 @@ Function used to display is 
\"hywiki-display-<referent-type>\"."
 (defun hywiki-display-referent (&optional wikiword prompt-flag)
   "Display HyWiki WIKIWORD referent or a regular file with WIKIWORD nil.
 Return the WIKIWORD's referent if successfully found or nil otherwise.
-The referent is a cons of (<referent-type> . <referent-value>).
+
 For further details, see documentation for `hywiki-find-referent'.
 After successfully finding a referent, run `hywiki-display-referent-hook'."
+  (interactive (list (hywiki-read-page-reference)))
   (let ((in-page-flag (null wikiword))
        (in-hywiki-directory-flag (hywiki-in-page-p)))
     (if (or (stringp wikiword) in-hywiki-directory-flag)
@@ -1399,16 +1400,15 @@ After successfully adding a page, run 
`hywiki-add-page-hook'.
 
 Use `hywiki-get-referent' to determine whether a HyWiki page exists."
   (interactive (list (or (hywiki-word-at)
-                        (hywiki-word-read-new "Add/Edit HyWiki page: "))
+                        (hywiki-page-read-new "Add/Edit HyWiki page: "))
                     current-prefix-arg))
   (if (hywiki-word-is-p page-name)
       (when (or noninteractive
                (not (hash-empty-p (hywiki-get-referent-hasht)))
                (hyperb:stack-frame '(ert-run-test))
                (y-or-n-p (concat "Create new HyWiki page `" page-name "'? ")))
-       (when (match-string-no-properties 2 page-name)
-         ;; Remove any #section suffix in PAGE-NAME.
-         (setq page-name (match-string-no-properties 1 page-name)))
+       ;; Remove any #section suffix in PAGE-NAME.
+       (setq page-name (hywiki-get-singular-wikiword page-name))
 
        (let* ((page-file (hywiki-get-page-file page-name))
               (page-file-readable (file-readable-p page-file))
@@ -1484,8 +1484,7 @@ exists."
                prompt-flag)))
 
 (defun hywiki-completion-at-point ()
-  "Complete HyWiki references.
-Ensures that selecting a completion replaces only the text after the '#'."
+  "Complete HyWiki references, either the HyWikiWord or the #section."
   (let ((ref-start-end (and (hywiki-active-in-current-buffer-p)
                            (not (hywiki-non-hook-context-p))
                            (hywiki-word-at t t))))
@@ -1498,7 +1497,7 @@ Ensures that selecting a completion replaces only the 
text after the '#'."
              ;; Extract the WikiWord before the '#'
              (word (hywiki-word-from-reference ref)))
        (save-excursion
-         ;; 1. Look for the '#' delimiter on the current line
+         ;; CASE 1. Look for the '#' delimiter on the current line
          (if (re-search-backward "#" start t)
               (let ((hash-pos (point))
                     (page (expand-file-name (concat word ".org") 
hywiki-directory)))
@@ -1648,6 +1647,43 @@ nil, else return \\='(page . \"<page-file-path>\")."
   (and (or at-tag-flag (hsys-org-at-tags-p))
        (or (hywiki-in-page-p) (string-prefix-p "*HyWiki Tags*" 
(buffer-name)))))
 
+(defun hywiki-consult-page-and-line ()
+  "Return a list of the file and line selected by consult or nil.
+Use `hywiki-insert-reference' with the result of this function to insert a
+double-quoted HyWikiWord reference at point."
+  (interactive)
+  (let* ((dir (expand-file-name hywiki-directory))
+         (manual-builder
+          (lambda (input)
+            (let* (;; Define the regex inside the builder so it's always in 
scope
+                   (headline-pattern (concat "^\\* .*" input))
+                   ;; Ensure all arguments are evaluated as strings
+                   (args (list "rg"
+                               "--null"
+                               "--line-buffered"
+                               "--color=never"
+                               "--with-filename"
+                               "--line-number"
+                               "--smart-case"
+                               "-g" "*.org"
+                               "-e" headline-pattern
+                               dir)))
+              (cons args dir))))
+         (selected (consult--read
+                    (consult--async-command manual-builder)
+                    :prompt "HyWiki Headline: "
+                    :require-match t
+                    :lookup #'consult--lookup-member
+                    :category 'consult-grep)))
+
+    (when (stringp selected)
+      (if (string-match "\\`\\([^\0]+\\)\0\\([0-9]+\\):\\(.*\\)" selected)
+          (let ((file (match-string 1 selected))
+                (line (match-string 3 selected)))
+           (list file line))
+        (message "(hwiki-consult-file-and-line): Parse error on: %s" selected)
+        nil))))
+
 ;;;###autoload
 (defun hywiki-consult-grep (&optional regexp max-matches path-list prompt)
   "Interactively search with a consult package grep command.
@@ -2041,12 +2077,62 @@ This includes the delimiters: (), {}, <>, [] and \"\" 
(double quotes)."
            result
          (list nil nil))))))
 
+(defun hywiki-read-page-reference ()
+  "With consult package loaded, read a \"file^@line\" string, else a page 
name."
+  (interactive)
+  (if (featurep 'consult)
+      (hywiki-format-reference (hywiki-consult-page-and-line))
+    ;; Without consult, can only complete to a HyWiki page
+    ;; without a section
+    (hywiki-page-read "Link to HyWiki page: ")))
+
 ;;;###autoload
 (defun hywiki-insert-link ()
-  "Insert at point a link to a HyWiki page."
+  "Insert at point a link to a HyWiki page#section."
   (interactive "*")
-  (insert (hywiki-word-read "Link to HyWiki page: "))
-  (hywiki-maybe-highlight-reference))
+  (let ((ref (hywiki-read-page-reference)))
+    (when ref
+      (insert ref)
+      (skip-chars-backward "\"")
+      (goto-char (1- (point)))
+      (hywiki-maybe-highlight-reference))))
+
+;;;###autoload
+(defun hywiki-format-reference (page-and-line)
+  "Return a HyWikiWord#section reference from PAGE-AND-LINE.
+Call `hywiki-consult-page-and-line' to generate PAGE-AND-LINE.
+Add double quotes if the section contains any whitespace after trimming.
+
+Return t if PAGE-AND-LINE is a valid list, else nil.  If the page name
+therein is invalid, trigger an error."
+  (when (and page-and-line (listp page-and-line))
+    (cl-destructuring-bind (page line)
+       page-and-line
+      (setq page (file-name-base page))
+      (unless (and (string-match-p hywiki-word-regexp page)
+                   (hywiki-page-exists-p page))
+       (error "(hywiki-format-reference): Invalid HyWiki page name - \"%s\""
+              page))
+      ;; Drop '* ' prefix
+      (setq line (string-trim line "[ \t\n\r]*\\**[ \t\n\r]+"))
+      (format (if (string-match-p "\\s-" line)
+                 "\"%s#%s\""
+               "%s#%s")
+             page
+             line))))
+
+(defun hywiki-insert-reference (page-and-line)
+  "Insert a HyWiki page#section reference from PAGE-AND-LINE.
+Add double quotes if the section contains any whitespace after trimming.
+
+Return t if PAGE-AND-LINE is a valid list, else nil.  If the page name
+therein is invalid, trigger an error."
+  (let ((ref (hywiki-format-reference page-and-line)))
+    (when ref
+      (insert ref)
+      (skip-chars-backward "\"")
+      (goto-char (1- (point)))
+      t)))
 
 (defun hywiki-maybe-dehighlight-balanced-pairs ()
   "Before or after a balanced delimiter, dehighlight HyWikiWords within.
@@ -3233,36 +3319,37 @@ Customize this directory with:
   ;; spaces replaced with dashes, made unique when necessary.
   (org-publish-project "hywiki" all-pages-flag))
 
-(defun hywiki-referent-exists-p (&optional word start end)
-  "Return the HyWikiWord at point or optional HyWiki WORD, if has a referent.
+(defun hywiki-referent-exists-p (&optional ref start end)
+  "Return the HyWiki reference at point or optional REF, if has a referent.
 If no such referent exists, return nil.
 
-The HyWikiWord may be of the form:
+The HyWikiWord reference may be of the form:
  1. HyWikiWord#section with an optional #section.
- 2. If WORD is the symbol, :range, and there is a HyWikiWord at point
-    with an existing referent, return the tuple of values: \='(<word>
-    <word-start> <word-end>) instead of the word; otherwise, return the
-    tuple \='(nil nil nil).
-
-When using the word at point, a call to `hywiki-active-in-current-buffer-p'
-at point must return non-nil or this function will return nil."
-  (let ((save-input-word word))
-    (when (stringp word)
-      (setq word (hywiki-strip-org-link word)))
-    (if (or (stringp word)
-           (setq word (hywiki-word-get-range)))
-       (unless (hywiki-get-referent (if (stringp word) word (nth 0 word)))
-         (setq word nil))
-      (setq word nil))
-    (when (and (listp word) (= (length word) 3))
-      (setq start (nth 1 word)
-           end   (nth 2 word)
-           ;; `word' must be set last so list version can be referenced
+ 2. If REF is the symbol, :range, and there is a HyWikiWord at point
+    with an existing referent, return the tuple of values: \='(<ref>
+    <ref-start> <ref-end>) instead of the reference alone; otherwise,
+    return the tuple \='(nil nil nil).
+
+When using the reference at point, a call to
+`hywiki-active-in-current-buffer-p' at point must return non-nil or this
+function will return nil."
+  (let ((save-input-word ref))
+    (when (stringp ref)
+      (setq ref (hywiki-strip-org-link ref)))
+    (if (or (stringp ref)
+           (setq ref (hywiki-word-get-range)))
+       (unless (hywiki-get-referent (if (stringp ref) ref (nth 0 ref)))
+         (setq ref nil))
+      (setq ref nil))
+    (when (and (listp ref) (= (length ref) 3))
+      (setq start (nth 1 ref)
+           end   (nth 2 ref)
+           ;; `ref' must be set last so list version can be referenced
            ;; first above
-           word  (nth 0 word)))
+           ref  (nth 0 ref)))
     (if (eq save-input-word :range)
-       (list word start end)
-      word)))
+       (list ref start end)
+      ref)))
 
 (defun hywiki-section-to-headline-reference ()
   "Replace file#section dashes with spaces to match to an Org headline.
@@ -3742,11 +3829,10 @@ these are handled by the Org mode link handler."
                  (eq 0 (string-match
                         hywiki-word-with-optional-suffix-exact-regexp
                         word)))
-             (save-match-data
-               ;; If has a #section, ensure there are no invalid chars
-               (if (string-match "#" word)
-                   (string-match "#[^][#()<>{}\"\n\r\f]+\\'" word)
-                 t))))))
+             ;; If has a #section, ensure there are no invalid chars
+             (if (string-match-p "#" word)
+                 (string-match "#[^][#()<>{}\"\n\r\f]+\\'" word)
+               t)))))
 
 (defun hywiki-word-read (&optional prompt)
   "Prompt with completion for and return an existing HyWikiWord.
@@ -3764,13 +3850,35 @@ If point is on one, press RET immediately to use that 
one."
                     (hywiki-get-referent-hasht)
                     nil nil nil nil (hywiki-word-at-point))))
 
-(defun hywiki-page-read-new (&optional prompt)
-  "Prompt with completion for and return an existing/new HyWikiWord with a 
page.
+(defun hywiki-page-exists-p (word)
+  "Return HyWiki WORD iff it is an existing page reference."
+  (when (eq (car (hywiki-get-referent word)) 'page)
+    word))
+
+(defun hywiki-page-read (&optional prompt)
+  "Prompt with completion for and return an existing HyWiki page name.
 If point is on one, press RET immediately to use that one."
-  (let ((completion-ignore-case t))
-    (completing-read (if (stringp prompt) prompt "HyWikiWord page: ")
+  (let* ((completion-ignore-case t)
+         (wikiword (hywiki-word-at-point))
+         (page (hywiki-page-exists-p wikiword)))
+    (completing-read (if (stringp prompt) prompt "HyWiki page: ")
                     (hywiki-get-page-list)
-                    nil nil nil nil (hywiki-word-at-point))))
+                    nil t nil nil (when page wikiword))))
+
+(defun hywiki-page-read-new (&optional prompt)
+  "Prompt with completion for and return an existing/new HyWiki page name.
+If point is on one, press RET immediately to use that one."
+  (let ((completion-ignore-case t)
+        page)
+    (while (null page)
+      (setq page (completing-read
+                  (if (stringp prompt) prompt "HyWiki page: ")
+                 (hywiki-get-page-list)
+                 nil nil nil nil (hywiki-word-at-point)))
+      ;; Prevent selection of non-page HyWikiWords
+      (unless (memq (car (hywiki-get-referent page)) '(page nil))
+        (setq page nil)))
+    page))
 
 (defun hywiki-word-set-auto-highlighting (hywiki-from-mode hywiki-to-mode)
   "Set HyWikiWord auto-highlighting based on HYWIKI-FROM-MODE HYWIKI-TO-MODE.
@@ -3799,7 +3907,7 @@ occurs with one of these hooks, the problematic hook is 
removed."
         (hywiki-word-highlight-buffers
          (set:difference (hywiki-get-buffers hywiki-from-mode)
                          (hywiki-get-buffers hywiki-to-mode))))
-       (t 
+       (t
         (error "(hywiki-word-set-auto-highlighting): Inputs must be nil, 
:pages or :all, not '%s' and '%s'"
                hywiki-from-mode hywiki-to-mode))))
 
@@ -3885,6 +3993,11 @@ completion or no completion xandidates are returned."
   (when (called-interactively-p 'interactive)
     (message "HyWikiWord auto-highlighting disabled")))
 
+(defact link-to-wikiword (reference)
+  "Display the HyWikiword referent matching WikiWord#section REFERENCE."
+  (interactive (list (hywiki-word-read "Link to HyWiki word: ")))
+  (hywiki-find-referent reference))
+
 ;;; ************************************************************************
 ;;; Private functions
 ;;; ************************************************************************
diff --git a/kotl/kotl-mode.el b/kotl/kotl-mode.el
index 76f4b08d53..b997d739e1 100644
--- a/kotl/kotl-mode.el
+++ b/kotl/kotl-mode.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    6/30/93
-;; Last-Mod:     19-Jan-26 at 22:34:04 by Mats Lidell
+;; Last-Mod:     19-Feb-26 at 01:12:25 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -2491,8 +2491,10 @@ Optional prefix arg RELATIVE-LEVEL means one of the 
following:
 
  1. when = 0, add as the parent's first child cell (first cell in list);
  2. when < 0, add that number of cells as preceding siblings;
- 3. when \\='(4) (universal arg, \\`C-u'), add as the first child of the 
current cell;
- 4. when > 0 or nil (meaning 1), add that number of cells as following 
siblings."
+ 3. when \\='(4) (universal arg, \\`C-u'), add as the first child of the
+    current cell;
+ 4. when > 0 or nil (meaning 1), add that number of cells as following
+    siblings."
   (interactive "*P")
   (unless (or (integerp relative-level) (listp relative-level) )
     (error "(kotl-mode:add-cell): `relative-level' must be an integer or a 
list of integers, not '%s'" relative-level))
diff --git a/man/hyperbole.texi b/man/hyperbole.texi
index 6d6305428a..ef83bcf884 100644
--- a/man/hyperbole.texi
+++ b/man/hyperbole.texi
@@ -7,7 +7,7 @@
 @c Author:       Bob Weiner
 @c
 @c Orig-Date:     6-Nov-91 at 11:18:03
-@c Last-Mod:      7-Feb-26 at 10:10:33 by Bob Weiner
+@c Last-Mod:     18-Feb-26 at 23:43:55 by Bob Weiner
 
 @c %**start of header (This is for running Texinfo on a region.)
 @setfilename hyperbole.info
@@ -30,7 +30,7 @@
 @set txicodequoteundirected
 @set txicodequotebacktick
 
-@set UPDATED February 7, 2026
+@set UPDATED February 18, 2026
 @set UPDATED-MONTH February 2026
 @set EDITION 9.0.2pre
 @set VERSION 9.0.2pre
@@ -171,7 +171,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</P>
 
 <PRE>
 Edition 9.0.2pre
-Printed February 7, 2026.
+Printed February 18, 2026.
 
   Published by the Free Software Foundation, Inc.
   Author:    Bob Weiner
@@ -213,7 +213,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 @example
 Edition 9.0.2pre
-February 7, 2026 @c AUTO-REPLACE-ON-SAVE
+February 18, 2026 @c AUTO-REPLACE-ON-SAVE
 
 
   Published by the Free Software Foundation, Inc.
@@ -3887,6 +3887,7 @@ upon the referent context in which the Action Key is 
released.
 @example
 Referent Context         Link Type
 ----------------------------------------------------
+HyWikiWord Reference     link-to-wikiword
 Org Roam or Org Id       link-to-org-id
 Global Button            link-to-gbut
 Explicit Button          link-to-ebut


Reply via email to