branch: externals/hyperbole
commit 15c494d18c5b3157490e9b433070246a183c8822
Author: bw <[email protected]>
Commit: bw <[email protected]>
hywiki.el - Add in-buffer completion of HyWikiWord#section refs
---
ChangeLog | 18 +++++++++++++
hywiki.el | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 97 insertions(+), 10 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index d2716734c8..2218bc1fb0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2026-02-12 Bob Weiner <[email protected]>
+
+* hywiki.el (hywiki-word-add-completion-at-point): Add in-buffer
+ HyWikiWord reference completion (Complete HyWiki words and sections.
+ Completion requires typing at least two of the first characters of
+ the completion.)
+ (hywiki-word-highlight-buffers,
+ hywiki-word-dehighlight-buffers): Use above func.
+
+2026-02-11 Bob Weiner <[email protected]>
+
+* hywiki.el (hywiki-word-at): Add optional 2nd arg, 'pound-sign-only-flag'
+ to allow matching to a WikiWord# without any heading name for use when
+ completing on headings.
+ (hywiki-completion-at-point,
+ hywiki-get-page-headings,
+ hywiki-word-from-reference): Add and use in completions.
+
2026-02-09 Bob Weiner <[email protected]>
* test/hywiki-tests.el (hywiki-tests--edit): Report expected errors as messages
diff --git a/hywiki.el b/hywiki.el
index 2bed8db980..c0a0ca3476 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: 8-Feb-26 at 18:11:52 by Bob Weiner
+;; Last-Mod: 12-Feb-26 at 00:30:44 by Bob Weiner
;;
;; SPDX-License-Identifier: GPL-3.0-or-later
;;
@@ -1441,6 +1441,40 @@ exists."
(null prompt-flag))
prompt-flag)))
+(defun hywiki-completion-at-point ()
+ "Complete HyWiki references.
+Ensures that selecting a completion replaces only the text after the '#'."
+ (let ((ref-start-end (and (hywiki-active-in-current-buffer-p)
+ (not (hywiki-non-hook-context-p))
+ (hywiki-word-at t t))))
+ (when ref-start-end
+ (let* ((case-fold-search nil)
+ (opoint (point))
+ (ref (nth 0 ref-start-end))
+ (start (nth 1 ref-start-end))
+ (end (nth 2 ref-start-end))
+ ;; Extract the WikiWord before the '#'
+ (word (hywiki-word-from-reference ref)))
+ (save-excursion
+ ;; 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)))
+ ;; 2. Validate the WikiWord and page existence
+ (when (and (not (string-empty-p word))
+ (file-readable-p page))
+ (let ((headings (hywiki-get-page-headings page)))
+ ;; 3. Return completion data
+ (list (1+ hash-pos) ;; START: Right after '#'
+ opoint ;; END: Current cursor
+ headings ;; CANDIDATES
+ :exclusive 'no))))
+
+ ;; CASE 2: Standard WikiWord completion (no '#' found)
+ (list start end
+ (hywiki-get-page-list)
+ :exclusive 'no)))))))
+
(defun hywiki-create-referent-and-display (wikiword &optional prompt-flag)
"Display the HyWiki referent for WIKIWORD if not in an ert test; return it.
@@ -2675,6 +2709,19 @@ These must end with `hywiki-file-suffix'."
hywiki-directory nil (concat "^" hywiki-word-regexp
(regexp-quote hywiki-file-suffix) "$")))))
+(defun hywiki-get-page-headings (page)
+ "Return a list of all headings found in FILE.
+Strip any leading '*' and space characters from the headings."
+ (when (and (stringp page) (file-readable-p page))
+ (let ((grep-command (format "grep -E '^\\*+ ' %s" (shell-quote-argument
page))))
+ (with-temp-buffer
+ (shell-command grep-command (current-buffer))
+ (goto-char (point-min))
+ (let (headings)
+ (while (re-search-forward "^\\*+ +\\(.*\\)$" nil t)
+ (push (match-string-no-properties 1) headings))
+ (nreverse headings))))))
+
(defun hywiki-get-page-list ()
"Return the list of HyWikiWords with existing pages."
(delq nil (hash-map (lambda (referent-type)
@@ -3256,7 +3303,7 @@ Action Key press; with a prefix ARG, emulate an Assist
Key press."
(hywiki-find-referent word)
(hkey-either arg))))
-(defun hywiki-word-at (&optional range-flag)
+(defun hywiki-word-at (&optional range-flag hash-sign-only-flag)
"Return potential HyWikiWord and optional #section:Lnum:Cnum at point or nil.
`hywiki-mode' must be enabled or this will return nil.
@@ -3451,10 +3498,12 @@ non-nil or this will return nil."
(setq start (match-beginning 1)
end (match-end 1)
wikiword (string-trim
(match-string-no-properties 1))))
- ((and (looking-at
hywiki-word-with-optional-suffix-regexp)
- ;; Can't be followed by a #
character
- (/= (or (char-after (match-end
0)) 0)
- ?#))
+ ((or (and (looking-at
hywiki-word-with-optional-suffix-regexp)
+ ;; Can't be followed by a #
character
+ (/= (or (char-after
(match-end 0)) 0)
+ ?#))
+ (and hash-sign-only-flag
+ (looking-at (concat
hywiki-word-regexp "#"))))
(setq start (match-beginning 0)
end (match-end 0)
;; No following char
@@ -3465,10 +3514,15 @@ non-nil or this will return nil."
;; One set of \n\r characters is allowed but no
;; whitespace at the end of the reference.
(if (and (stringp wikiword) (string-match "#" wikiword))
- (when (string-match
"#[^][#()<>{}\"\f]*[^][#()<>{}\"\f\t\n\r ]" wikiword)
- (setq end (- end (- (length wikiword)
- (match-end 0)))
- wikiword (substring wikiword 0 (match-end 0))))
+ (let ((section-regexp
"#[^][#()<>{}\"\f]*[^][#()<>{}\"\f\t\n\r ]"))
+ (when (string-match
+ (if hash-sign-only-flag
+ (concat "#\\'\\|" section-regexp)
+ section-regexp)
+ wikiword)
+ (setq end (- end (- (length wikiword)
+ (match-end 0)))
+ wikiword (substring wikiword 0 (match-end
0)))))
t))
(if range-flag
(list wikiword start end)
@@ -3612,6 +3666,12 @@ Default to any HyWikiWord at point."
(hywiki-consult-grep (concat "\\b" (regexp-quote word) "\\b"))
(user-error "(hywiki-word-consult-grep): Invalid HyWikiWord: '%s'; must be
capitalized, all alpha" word)))
+(defun hywiki-word-from-reference (ref)
+ "Return the HyWikiWord part of a reference (part before the #)."
+ (when (and (stringp ref)
+ (string-match hywiki-word-with-optional-suffix-exact-regexp ref))
+ (match-string 1 ref)))
+
(defun hywiki-word-grep (wikiword)
"Grep for occurrences of WIKIWORD with `consult-grep' or normal-grep'.
Search across `hywiki-directory'."
@@ -3730,9 +3790,17 @@ occurs with one of these hooks, the problematic hook is
removed."
(hywiki-get-referent-hasht)
(hywiki-maybe-directory-updated))
+(defun hywiki-word-add-completion-at-point ()
+ "Add HyWikiWord in-buffer completion to `completion-at-point-functions'.
+Completion requires typing at least the two first characters of the
+completion or no completion xandidates are returned."
+ (add-hook 'completion-at-point-functions
+ #'hywiki-completion-at-point nil t))
+
(defun hywiki-word-highlight-buffers (buffers)
"Setup HyWikiWord auto-highlighting and highlight in BUFFERS."
(interactive)
+ (add-hook 'after-change-major-mode-hook 'hywiki-word-add-completion-at-point)
(add-hook 'after-change-major-mode-hook
'hywiki-word-highlight-in-current-buffer)
(add-hook 'window-buffer-change-functions 'hywiki-word-highlight-in-frame)
(add-to-list 'yank-handled-properties
@@ -3761,6 +3829,7 @@ occurs with one of these hooks, the problematic hook is
removed."
"Disable HyWikiWord auto-highlighting and dehighlight in BUFFERS."
(interactive)
(remove-hook 'after-change-major-mode-hook
'hywiki-word-highlight-in-current-buffer)
+ (remove-hook 'after-change-major-mode-hook
'hywiki-word-add-completion-at-point)
(remove-hook 'window-buffer-change-functions 'hywiki-word-highlight-in-frame)
(setq yank-handled-properties
(delete '(hywiki-word-face . hywiki-highlight-on-yank)