diff --git a/ChangeLog b/ChangeLog index e847f038..ebb6820a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2026-07-10 Bob Weiner + +* hywiki.el (hywiki-referent-menu): Add Denote support. + (hywiki-add-denote, hywiki-display-denote, hywiki-string-to-wikiword) + hywiki-denote-file-to-wikiword, hywiki-denote-id-to-wikiword): Add + these functions. + (hywiki-create-referent-and-display): If point is not on 'wikiword' + when called, then insert it. If buffer is read-only in this case, trigger + an error. + 2026-07-07 Bob Weiner * hsys-youtube.el (hsys-youtube-end-format): YouTube no longer allows end times or the diff --git a/hactypes.el b/hactypes.el index b4eeb092..58cf1862 100644 --- a/hactypes.el +++ b/hactypes.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 23-Sep-91 at 20:34:36 -;; Last-Mod: 28-Jun-26 at 09:48:58 by Bob Weiner +;; Last-Mod: 10-Jul-26 at 12:29:32 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -536,11 +536,11 @@ the .info suffix in the format with parentheses." (id-info string) (hypb:error "(link-to-Info-node): Invalid Info node: `%s'" string))) -(defact link-to-ibut (name-key &optional key-src point) - "Activate implicit button given by NAME-KEY, optional KEY-SRC and POINT. +(defact link-to-ibut (name-key &optional key-src _point) + "Activate implicit button given by NAME-KEY, optional KEY-SRC and _POINT. NAME-KEY must be a normalized key for an ibut <[name]>. KEY-SRC defaults to the current buffer's file or if there is no -attached file, then to its buffer name. POINT defaults to the +attached file, then to its buffer name. _POINT defaults to the current point. When the button with this action type is created, point must be diff --git a/hbut.el b/hbut.el index ba122259..8769bb65 100644 --- a/hbut.el +++ b/hbut.el @@ -3,11 +3,11 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 18-Sep-91 at 02:57:09 -;; Last-Mod: 25-Jun-26 at 17:06:59 by Bob Weiner +;; Last-Mod: 10-Jul-26 at 12:26:12 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; -;; Copyright (C) 1991-2025 Free Software Foundation, Inc. +;; Copyright (C) 1991-2026 Free Software Foundation, Inc. ;; See the "HY-COPY" file for license information. ;; ;; This file is part of GNU Hyperbole. @@ -82,10 +82,21 @@ Use the function, (hbut:max-len), to read the proper value.") ;; Move up internal defconst to appear before their use (defconst ebut:label-start "<(" "String matching the start of a Hyperbole explicit hyper-button.") - (defconst ebut:label-end ")>" "String matching the end of a Hyperbole explicit hyper-button.") +(defconst ibut:label-start "<[" + "String matching the start of a Hyperbole implicit button optional name.") +(defconst ibut:label-end "]>" + "String matching the end of a Hyperbole implicit button optional name.") + +(defconst hbut:label-delimiters-regexp + (format (concat "%s[^][()<>\"]+\\([\n\r][^][()<>\"]*\\)?%s" + "\\|%s[^][()<>\"]+\\([\n\r][^][()<>\"]*\\)?%s") + (regexp-quote ibut:label-start) (regexp-quote ibut:label-end) + (regexp-quote ebut:label-start) (regexp-quote ebut:label-end)) + "Regular expression matching ibut names and ebut text/labels.") + (defconst hbut:instance-sep ":" "String of one character, separates an ebut label from its instance num.") @@ -2172,9 +2183,6 @@ If a new button is created, store its attributes in the symbol, [&optional ["&optional" arg &rest arg]] &optional ["&rest" arg]))) -(defvar ibut:label-start) -(defvar ibut:label-end) - (defun ibut:delete (&optional but-sym) "Delete Hyperbole implicit button based on optional BUT-SYM. If it is a named button, delete all occurrences in the buffer; otherwise, delete @@ -3031,12 +3039,6 @@ Return the symbol for the button if found, else nil." (when (ibut:is-p ibut) (hattr:get ibut 'categ))) -;;; ------------------------------------------------------------------------ -(defconst ibut:label-start "<[" - "String matching the start of a Hyperbole implicit button optional name.") -(defconst ibut:label-end "]>" - "String matching the end of a Hyperbole implicit button optional name.") - ;;; ======================================================================== ;;; ibtype class - Implicit button types ;;; ======================================================================== @@ -3133,9 +3135,12 @@ regular expression. Hyperbole automatically creates a doc string for the type but you can override this by providing an optional DOC string. -TEXT-REGEXP must match to the text found between a button's delimiters -in order for this type to activate. The matched text is applied -to LINK-EXPR to produce the link's referent, which is then displayed. +TEXT-REGEXP must match to part of the text found between a button's +delimiters in order for this type to activate; to ensure it matches +to the full text, start the REGEXP with \"\\`\" and end it with +\"\\'\", to match to the start and end of string. The matched text +is applied to LINK-EXPR to produce the link's referent, which is then +displayed. LINK-EXPR may be: (1) a brace-delimited key series; diff --git a/hywiki.el b/hywiki.el index 8f64e9aa..290cfd55 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: 28-Jun-26 at 18:53:13 by Bob Weiner +;; Last-Mod: 10-Jul-26 at 12:24:52 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -170,9 +170,9 @@ (declare-function Info-current-filename-sans-extension "hui-mouse") (declare-function Info-read-index-item-name "hui-mouse") -(declare-function activities-completing-read "activities" (:prompt prompt :default default)) -(declare-function activities-new "activities" (name)) -(declare-function activities-resume "activities" (activity :resetp resetp)) +(declare-function activities-completing-read "ext:activities" (:prompt prompt :default default)) +(declare-function activities-new "ext:activities" (name)) +(declare-function activities-resume "ext:activities" (activity :resetp resetp)) (declare-function bookmark-completing-read "bookmark" (prompt &optional default)) (declare-function bookmark-location "bookmark" (bookmark-name-or-record)) (declare-function consult--async-command "ext:consult") @@ -183,6 +183,11 @@ (declare-function consult--async-throttle "ext:consult") (declare-function consult--lookup-member "ext:consult") (declare-function consult--read "ext:consult") +(declare-function denote-file-prompt "ext:denote") +(declare-function denote-get-link-description "ext:denote") +(declare-function denote-get-path-by-id "ext:denote") +(declare-function denote-open-or-create "ext:denote") +(declare-function denote-retrieve-filename-identifier "ext:denote") (declare-function hsys-org-at-tags-p "hsys-org") (declare-function hyperbole-mode "hyperbole") (declare-function hyrolo-fgrep "hyrolo") @@ -1107,6 +1112,9 @@ After successfully finding a referent, run `hywiki-display-referent-hook'." "Add a HyWikiWord that jumps to an Emacs bookmark.") '("Command" (hywiki-add-command hkey-value) "Add a HyWikiWord that runs an Emacs command or Hyperbole action type.") + (when (fboundp #'denote-open-or-create) + '("Denote" (hywiki-add-denote hkey-value) + "Add a HyWikiWord that jumps to a Denote note stored by ID.")) '("Elisp" (hywiki-add-elisp hkey-value) "Add a HyWikiWord that evaluates an Elisp sexpression.") '("Find" (hywiki-add-find hkey-value) @@ -1307,6 +1315,54 @@ calling this function." (t (error "(hywiki-display-command): Unbound referent command, '%s'" command)))) +(defun hywiki-add-denote (wikiword) + "Make WIKIWORD display a denote file when the `denote' package is available. + +When called interactively or with WIKIWORD nil or the empty string, then set +WIKIWORD to any wikiword at point; otherwise, convert the description from +the denote file chosen to a wikiword. + +After successfully adding the link to a denote file, run +`hywiki-add-referent-hook'. + +Use `hywiki-get-referent' to determine whether WIKIWORD exists prior to +calling this function." + (interactive (list (hywiki-word-at))) + (hypb:require-package 'denote) + (let* ((denote-file (denote-file-prompt + nil + (if (stringp wikiword) + (format "Link `%s' HyWikiWord to denote" wikiword) + ;; Will use denote file description as `wikiword' + "Add HyWikiWord denote link to") + nil t)) + (denote-desc (denote-get-link-description denote-file)) + (denote-id (denote-retrieve-filename-identifier denote-file))) + (unless (or (stringp wikiword) (string-empty-p wikiword)) + (setq wikiword (hywiki-string-to-wikiword denote-desc))) + (unless (equal wikiword (hywiki-get-singular-wikiword (hywiki-word-at))) + (if buffer-read-only + (error "(hywiki-add-denote): Since buffer is read-only, this must be used with point on a HyWikiWord") + (insert wikiword))) + (hywiki-add-referent wikiword (cons 'denote (cons denote-desc denote-id))))) + +(defun hywiki-display-denote (_wikiword denote-desc-and-id) + (let ((denote-desc (car denote-desc-and-id)) + (denote-id (cdr denote-desc-and-id)) + denote-file) + (cond ((not (stringp denote-id)) + (error "(hywiki-display-denote): `denote-id' must be a string, not`%s'" + denote-id)) + ((and (setq denote-file (denote-get-path-by-id denote-id)) + (file-readable-p denote-file)) + (hpath:find denote-file)) + ((not (stringp denote-file)) + (error "(hywiki-display-denote): denote file not found for desc: \"%s\", id: \"%s\"" + denote-desc denote-id)) + (t ;; denote-file not readable + (error "(hywiki-display-denote): Unreadable demote file: \"%s\"" + denote-file))))) + (defun hywiki-add-elisp (wikiword) "Make WIKIWORD evaluate a prompted for Elisp sexpression and return it. @@ -1452,8 +1508,9 @@ calling this function." (defun hywiki-add-org-id (wikiword) "Make WIKIWORD display an Org file or headline with an Org id. -Point must be in the buffer with the id. If no id exists, it is created. -Return the referent created with the form: \\='(org-id . ). +Point must be within the entry with with the id. If no id exists, it is +created. Return the referent created with the form: \\='(org-id +. ). If WIKIWORD is invalid, trigger an error if called interactively or return nil if not. @@ -1622,9 +1679,8 @@ After successfully adding the spec, run `hywiki-add-referent-hook'." This replaces any existing referent the WIKIWORD may have. With either `hywiki-referent-prompt-flag' set or optional prefix -REF-TYPE-FLAG, prompt for and choose a typed referent, otherwise, create -and/or display a HyWiki page. See `hywiki-referent-menu' for valid referent -types. +REF-TYPE-FLAG, prompt for and choose a typed referent, otherwise, create a +HyWiki page. See `hywiki-referent-menu' for valid referent types. Use `hywiki-get-referent' to test for and retrieve an existing HyWikiWord referent." @@ -1727,21 +1783,27 @@ for WIKIWORD, add a page for it. Use `hywiki-get-referent' to determine whether a HyWikiWord referent or page exists." - (interactive (list (or (hywiki-word-at) - (hywiki-word-read-new + (interactive (list nil current-prefix-arg)) + (let (at-wikiword-flag) + (unless (stringp wikiword) + (setq wikiword (or (setq at-wikiword-flag (hywiki-word-at)) + (hywiki-word-read-new (format "Add/Edit and display HyWiki %s: " - (if current-prefix-arg "referent" "page")))) - current-prefix-arg)) - (when (and (not prompt-flag) hywiki-referent-prompt-flag - (called-interactively-p 'interactive)) - (setq prompt-flag t)) - (let* ((normalized-word (hywiki-get-singular-wikiword wikiword)) - (referent (hywiki-find-referent wikiword prompt-flag))) - (cond (referent) - ((hywiki-word-is-p normalized-word) - (when (hywiki-add-page normalized-word) - (hywiki-display-page normalized-word))) - (t (user-error "(hywiki-create-referent-and-display): Invalid HyWikiWord: '%s'; must be capitalized, all alpha" wikiword))))) + (if current-prefix-arg "referent" "page")))))) + (unless at-wikiword-flag + (if buffer-read-only + (error "(hywiki-create-referent-and-display): Since buffer is read-only, this must be used with point on a HyWikiWord") + (insert wikiword))) + (when (and (not prompt-flag) hywiki-referent-prompt-flag + (called-interactively-p 'interactive)) + (setq prompt-flag t)) + (let* ((normalized-word (hywiki-get-singular-wikiword wikiword)) + (referent (hywiki-find-referent wikiword prompt-flag))) + (cond (referent) + ((hywiki-word-is-p normalized-word) + (when (hywiki-add-page normalized-word) + (hywiki-display-page normalized-word))) + (t (user-error "(hywiki-create-referent-and-display): Invalid HyWikiWord: '%s'; must be capitalized, all alpha" wikiword)))))) (defun hywiki-display-page (&optional wikiword file-name) "Display an optional WIKIWORD page and return the page file. @@ -2020,6 +2082,26 @@ Use `hywiki-get-referent' to determine whether a HyWiki page exists." "\[\(\{\<\"'`\t\n\r\f ")) (or (char-before) 0))) +(defun hywiki-denote-file-to-wikiword (denote-file) + "Return a hywikiword from the denote description associated with DENOTE-FILE." + (interactive + (list (denote-file-prompt nil "HyWiki denote file: " + nil t))) + (if denote-file + (hywiki-string-to-wikiword + (denote-get-link-description denote-file)) + (error "(hywiki-denote-file-to-wikiword): Denote file not found: \"%s\"" + denote-file))) + +(defun hywiki-denote-id-to-wikiword (denote-id) + "Return a hywikiword from the denote description associated with DENOTE-ID." + (let ((denote-file (denote-get-path-by-id denote-id))) + (if denote-file + (hywiki-string-to-wikiword + (denote-get-link-description denote-file)) + (error "(hywiki-denote-id-to-wikiword): Denote file not found for id: \"%s\"" + denote-id)))) + (defun hywiki-directory-edit () "Edit HyWiki pages in current `hywiki-directory'. Use `dired' unless `action-key-modeline-buffer-id-function' is set to @@ -3805,6 +3887,15 @@ contains no spaces." (concat file (hpath:dashes-to-spaces-markup-anchor section))) link))) +(defun hywiki-string-to-wikiword (str) + "Convert a string to a single PascalCase HyWikiWord. +Removes only whitespace, dashes and underscores. Capitalizes +each term in the string." + (unless (stringp str) + (error "(hywiki-string-to-wikiword): `str' must be a string, not `%s'" str)) + (let ((words (split-string str "[-_ \t\n\r\f]+" t split-string-default-separators))) + (apply #'concat (mapcar #'capitalize words)))) + (defun hywiki-strip-org-link (link-str) "Return the hy:HyWikiWord#section part of an Org link string. Strip any square bracket delimiters, description and leading or diff --git a/man/hyperbole.html b/man/hyperbole.html index 5c02d164..14e45983 100644 --- a/man/hyperbole.html +++ b/man/hyperbole.html @@ -4,7 +4,7 @@