diff --git a/ChangeLog b/ChangeLog index 7f4d1e62..4d4c3198 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,54 @@ +2026-02-19 Bob Weiner + +* 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 + +* hasht.el (hash-make): Fix to trigger an error if key is not given as a string. + +2026-02-16 Bob Weiner + +* 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 * hsys-org.el: require 'find-func for find-library--from-load-history. diff --git a/README.md b/README.md index 50d10248..2124a699 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 18cb88f5..0231bc28 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 [rsw@gnu.org](mailto:rsw@gnu.org)]. - - - [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 b20b0a05..4ad4af71 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 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 96c53c23..850fe36b 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 3c465fa4..1786e178 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, ." - (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 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 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 45340ccc..13d11296 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 edd5d7c3..8c307dc2 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 9d90f070..3486e7a8 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 0579093c..b48e1caf 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-\"." (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 ( . ). + 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 . \"\")." (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: \='( - ) 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: \='( + ) 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 76f4b08d..b997d739 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 6d630542..ef83bcf8 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.

 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