diff --git a/ChangeLog b/ChangeLog index 4a457347..ffda2fb0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,58 @@ +2026-07-14 Bob Weiner + +* hsys-org.el (hsys-org-link-at-p): Fix code at the end to remove hy: prefix + from 'label'. + +* test/smart-org-tests.el (smart-org-mode-with-smart-keys-buttons-on-org-link-activates): + Replace 'org-open-at-point' with 'org-link-open-from-string' to get more + flexibility in link syntax handling. + (smart-org-mode-with-smart-keys-buttons-on-org-link-activates, + smart-org-mode-with-smart-keys-on-org-link-activates): + Replace 'org-open-at-point' with 'org-link-open-from-string' to get more + flexibility in link syntax handling. + + +* hactypes.el (link-to-ibut): Fix so 'key-src-buf-flag' is only set non-nil + when key-src is or is set to a buffer object. + +2026-07-13 Bob Weiner + +* man/hyperbole.texi (Keyboard Drags): Add table of contexts and + associated link action types in decreasing priority order, used + when creating links in a 2-window source-target configuration. + +* hui.el (hui:link-possible-types): + hbut.el (ibut:insert-text): Add 'link-to-denote' support. + +* hactypes.el (link-to-ibut): Fix so if find key-src buffer from a buffer + name, then set the key-src to the buffer object or else later 'ibut:get' + call will fail. + +* hsys-org.el (hsys-org-link-at-p): Change to allow a Wikiword in an Org + link description only if it is the entire description. + +2026-07-12 Bob Weiner + +* hui-mouse.el (require 'hsys-denote): Add and handle denote links ahead + of other Org link types so can handle Hyperbole #section syntax too. + +* hui-mouse.el (smart-org): Change 'org-link' to 'org-link-open-from-string' + to better control the link string sent when Org does not highlight the + full link reference string, typically when using the prefix: syntax + without double bracket delimiters. + +* hbut.el (hbut:label-to-key): Remove any label properties so potentially + long property list does not show in help buffers. + +2026-07-11 Bob Weiner + +* hsys-denote.el: Add with ibtype 'denote-link', actype 'link-to-denote', + and functions 'hsys-denote-file-at-p', 'hsys-denote-link-at-p', + 'hsys-denote-get-description-from-id' and + 'sys-denote-get-id-from-description'. + MANIFEST, Makefile: Add hsys-denote.el entry. + hywiki.el (hywiki-wikiword-to-string): Add. + 2026-07-11 Mats Lidell * Makefile (package): Depend on tar-file. diff --git a/MANIFEST b/MANIFEST index be06a94a..ba036836 100644 --- a/MANIFEST +++ b/MANIFEST @@ -111,7 +111,7 @@ topwin.py - Python script to find the topmost macOS app window at a s * --- EXTERNAL SYSTEM ENCAPSULATIONS --- hsys-activities.el - GNU Hyperbole support functions for Activities hsys-consult.el - Hyperbole interactive consult-grep convenience functions -hsys-ert.el - Hyperbole support for jumping to ert 'should' source lines +hsys-denote.el - GNU Hyperbole support functions for denote note-taking hsys-flymake.el - Add missing source buffer keymap to flymake linter hsys-org.el - GNU Hyperbole support functions for Org mode hsys-org-roam.el - GNU Hyperbole support functions for Org Roam diff --git a/Makefile b/Makefile index 7501a7e4..d9114e73 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # Author: Bob Weiner # # Orig-Date: 15-Jun-94 at 03:42:38 -# Last-Mod: 11-Jul-26 at 21:49:31 by Mats Lidell +# Last-Mod: 14-Jul-26 at 02:12:12 by Bob Weiner # # Copyright (C) 1994-2026 Free Software Foundation, Inc. # See the file HY-COPY for license information. @@ -215,7 +215,7 @@ EL_COMPILE = hact.el hactypes.el hargs.el hbdata.el hbmap.el hbut.el \ hinit.el hload-path.el hmail.el hmh.el hmoccur.el hmouse-info.el \ hmouse-drv.el hmouse-key.el hmouse-mod.el hmouse-sh.el hmouse-tag.el \ hpath.el hproperty.el hrmail.el hsettings.el hsmail.el hsys-consult.el \ - hsys-ert.el hsys-flymake.el hsys-activities.el \ + hsys-denote.el hsys-ert.el hsys-flymake.el hsys-activities.el \ hsys-org.el hsys-org-roam.el hsys-www.el hsys-xref.el hsys-youtube.el htz.el \ hycontrol.el hui-jmenu.el hui-menu.el hui-mini.el hui-mouse.el hui-select.el \ hui-treemacs.el hui-window.el hui.el hvar.el hversion.el hynote.el hypb.el hyperbole.el \ diff --git a/hactypes.el b/hactypes.el index 58cf1862..62502adb 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: 10-Jul-26 at 12:29:32 by Bob Weiner +;; Last-Mod: 14-Jul-26 at 00:15:46 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -549,10 +549,11 @@ on the implicit button to which to link." (let ((ibut-name-key (ibut:at-p t))) (cond (ibut-name-key (list ibut-name-key (or (hypb:buffer-file-name) (buffer-name)) (point))) - ;; !! TODO: If default is null below and are creating, rather than editing - ;; the link, it would be better to throw an error than create - ;; an invalid link, but it is difficult to tell which operation - ;; is in progress, so ignore this for now. -- RSW, 01-25-2020 + ;; !! TODO: If default is null below and are creating, rather + ;; than editing the link, it would be better to throw an error + ;; than create an invalid link, but it is difficult to tell which + ;; operation is in progress, so ignore this for now. -- RSW, + ;; 01-25-2020 ;; When not on an ibut and editing the link, use existing arguments ((and (bound-and-true-p hargs:defaults) (listp hargs:defaults) hargs:defaults) @@ -564,7 +565,10 @@ on the implicit button to which to link." (hypb:error "(link-to-ibut): Point must be on an implicit button to create a link-to-ibut")) (let* ((key-src-buf-flag (or (bufferp key-src) - (and (stringp key-src) (get-buffer key-src)))) + (and (stringp key-src) + (setq key-src (or (get-buffer key-src) + key-src)) + (bufferp key-src)))) (but (if key-src-buf-flag (ibut:get name-key key-src) (ibut:get name-key nil key-src))) diff --git a/hbut.el b/hbut.el index 8769bb65..c344b4f1 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: 10-Jul-26 at 12:26:12 by Bob Weiner +;; Last-Mod: 13-Jul-26 at 19:23:07 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -1539,7 +1539,8 @@ whitespace sequences with `_'." "\\`[ \t\n\r]+\\|\\([\\?][ \t\n\r]+\\'\\)\\|[ \t\n\r]+\\'" "\\1" label) label (replace-regexp-in-string "_" "__" label nil t)) - (replace-regexp-in-string "[ \t\n\r]+" "_" label nil t))) + (substring-no-properties + (replace-regexp-in-string "[ \t\n\r]+" "_" label nil t)))) (defun hbut:list (&optional file loc-p) "Return list of button labels from in FILE or the current buffer. @@ -2734,6 +2735,7 @@ Summary of operations based on inputs (name arg from \\='hbut:current attrs): ('annot-bib (insert "[" arg1 "]")) ('exec-shell-cmd (insert "\"!" arg1 "\"")) ('exec-window-cmd (insert "\"&" arg1 "\"")) + ('link-to-denote (insert "\"" arg1 "\"")) ('link-to-gbut (insert "")) ('link-to-ebut (progn (insert ". This library together with "hywiki.el" +;; interfaces denote to Hyperbole via a link action and support functions. + +;;; Code: + +;;; ************************************************************************ +;;; Other required Elisp libraries +;;; ************************************************************************ + +(require 'hywiki) ;; requires "hpath" and "hypb". + +;;; ************************************************************************ +;;; Public declarations +;;; ************************************************************************ + +(declare-function denote-extract-id-from-string "ext:denote") +(declare-function denote-file-prompt "ext:denote") +(declare-function denote-get-link-description "ext:denote") +(declare-function denote-get-path-by-id "ext:denote") + +(defvar denote-date-identifier-regexp) + +;;; ************************************************************************ +;;; Public functions +;;; ************************************************************************ + +(defib denote-link () + "If on a denote id with optional #section, jump to its associated note. +The id must be prefixed with \"denote:\" and can be in double quotes if the +section contains spaces (though '-' is preferred instead of space so that +Org mode links are highlighted and selected properly). + +Two forms of denote links are accepted: + + 1. The hyperbole link form with syntax: + \\(<[Description]>\\)? \"denote:\\(#section-name-or-uuid\\)?\" + + 2. The org link form with syntax: + [[denote:\\(::*?section-name\\)?][Description]] + or [[denote:\\(::#?section-id\\)?][Description]]" + (let* ((id-start-end (hsys-denote-link-at-p)) + (id-reference (nth 0 id-start-end)) + (start (nth 1 id-start-end)) + (end (nth 2 id-start-end))) + (when id-start-end + (ibut:label-set id-reference start end) + (hact 'link-to-denote id-reference)))) + +(defun hys-denote-get-link-regexp () + "Return regexp matching a denote id plus optional #section implicit button. +Fail with an error when the denote library has not been required +by the caller. + +Group 1 is the denote file id; +Group 3 is the optional #section or ::[#*]?section referring to a section +heading within the file. +Group 4 is the '#' or '::[#*]?' section prefix. +Group 5 is the section without the prefix." + ;; Note: this must stay as a function and not a constant since the denote + ;; variable used herein may not be defined until some other function is + ;; called. + (concat + "denote:" denote-date-identifier-regexp + "\\(\\(::[#*]?\\|#\\)\\([^][(){}<>'`\"*#:; \t\n\r\f][^][ \t\n\r\f\"]*\\)\\)?")) + +(defun hsys-denote-link-at-p (&optional org-link-start org-link-end) + "If in a denote link, return (link-string link-start link-end), else nil. +Start and end positions exclude any delimiters and any description part of a +link. Optional ORG-LINK-START and ORG-LINK-END include any delimiters; +this means point is within an Org link but still need to check if it is a +denote link." + (when (require 'denote nil t) + (let (link-start-end + start + end) + (cond + ;; Previously detected Org link + ((and (integerp org-link-start) (integerp org-link-end) + (save-excursion + (goto-char org-link-start) + (looking-at "\\(\\[\\[\\)?\\(denote:[^][\n\r\f\"]+\\)"))) + (setq start (match-beginning 2) + end (min (match-end 2) org-link-end)) + (list (buffer-substring-no-properties start end) start end)) + + ;; String delimited + ((setq link-start-end (hypb:in-string-p 1 :range)) + (when (string-match-p "\\`denote:" (car link-start-end)) + link-start-end)) + + ;; Org link (may or may not have square bracket delimiters); if does + ;; not, then can't allow spaces in the section name + ((setq link-start-end (hsys-org-link-at-p) + org-link-start (nth 0 link-start-end) + org-link-end (nth 0 link-start-end)) + (when (save-excursion + (goto-char org-link-start) + (or + ;; Don't allow section spaces + (looking-at "\\(\\)\\(denote:[^][ \t\n\r\f\"]+\\)") + ;; Do allow section spaces + (looking-at "\\(\\[\\[\\)\\(denote:[^][\n\r\f\"]+\\)"))) + (list (match-string-no-properties 2) (match-beginning 2) (match-end 2)))) + + ;; Non-delimited + (t (let ((id-regexp (hys-denote-get-link-regexp)) + (eol (line-end-position)) + (opoint (point)) + found) + (save-excursion + (goto-char (line-beginning-position)) + (while (and (not found) (re-search-forward id-regexp eol t)) + (when (and (<= (match-beginning 0) opoint) + (> (point) opoint)) + (setq found t))) + found) + (when found + ;; Return (id-reference ref-start ref-end) + (list (substring-no-properties (match-string-no-properties 0)) + (match-beginning 0) (match-end 0))))))))) + +;;;###autoload +(defact link-to-denote (id-and-section &optional file) + "Display a denote entry given by ID-AND-SECTION and optional FILE. +ID-AND-SECTION optionally may be prefixed with \"denote:\" or \"dn:\" and +may be suffixed with: + + 1. #section or ::*section, where section is any exact match to a denote + in-file heading; +or + 2. ::#section-id, where section-id is an Org id linking to a section. + +Optional FILE is the denote file to display. If given, the file is +displayed ignoring any information in ID-AND-SECTION. + +This does nothing if denote has not been installed. If installed +and ID-AND-SECTION or FILE is not found, trigger an error." + (interactive + (if (require 'denote nil t) + (let ((file (denote-file-prompt + nil ;; for default: (denote-get-identifier-at-point) + "Get denote entry id" nil t))) + (if (stringp file) + (list (denote-extract-id-from-string file) file) + '(nil nil))) + '(nil nil))) + (when (require 'denote nil t) + (if (and (stringp file) (not (string-empty-p file))) + (if (file-readable-p file) + (hpath:find file) + (hypb:error "(link-to-denote): File is unreadable: \"%s\"" + file)) + (if (stringp id-and-section) + ;; Remove any "denote:" or "dn:" prefix + (let ((file-id (denote-extract-id-from-string id-and-section)) + (section (when (and (string-match (hys-denote-get-link-regexp) + id-and-section) + ;; section start, if any + (match-beginning 5)) + ;; Don't use `match-string-no-properties' here + ;; because it doesn't allow spaces in the section + ;; name + (substring-no-properties id-and-section + (match-beginning 5)))) + (section-prefix (match-string-no-properties 4 id-and-section))) + (if (and section-prefix + ;; An Org section/heading id + (or (string-equal "::#" section-prefix) + (hsys-org-uuid-is-p section))) + (hact 'link-to-org-id section) + (setq file (denote-get-path-by-id file-id)) + (if (and file (file-readable-p file)) + ;; Change the section prefix to # since `hpath:find' + ;; only understands the # syntax + (hpath:find (if section (concat file "#" section) file)) + (hypb:error "(link-to-denote): File from ID \"%s\" is unreadable: \"%s\"" + id-and-section file)))) + (hypb:error "(link-to-denote): Invalid file ID and optional section: \"%s\"" + id-and-section))))) + +(defun hsys-denote-file-at-p () + "In a denote file or on a dired denote filename, return a link to it, else nil. +If in the file, add any section that point is within, plus relative line and +column number." + (when (require 'denote nil t) + (let ((file (or buffer-file-name + (and (derived-mode-p 'dired-mode) + (dired-get-filename nil t))))) + (when (and file + ;; This ensures has a file-id + (denote-file-has-denoted-filename-p file)) + (let* ((file-id (denote-retrieve-filename-identifier file)) + (org-flag (derived-mode-p 'org-mode)) + (heading (when org-flag (org-get-heading))) + (section-id (when org-flag (org-id-get)))) + (concat + (when section-id + (concat ibut:label-start heading ibut:label-end " ")) + "denote:" file-id + (cond (section-id + (concat "#" section-id)) + (heading + (concat "#" heading))) + (when buffer-file-name + (let ((line-num (if org-flag + (count-lines + (save-excursion + (condition-case nil + (progn (org-back-to-heading t) + (point)) + (error (point-min)))) + ;; Without 1+, line count is often off by one + (min (1+ (point)) (point-max))) + (current-line))) + (col-num (current-column))) + (concat (unless (<= line-num 1) + (format ":L%d" line-num)) + (unless (= col-num 0) + (format ":C%d" col-num))))))))))) + +;;;###autoload +(defun hsys-denote-get-description-from-id (id) + "Given a denote entry id, return its description string or nil if none." + (denote-get-link-description + (denote-get-path-by-id + (denote-extract-id-from-string id)))) + +;;;###autoload +(defun hsys-denote-get-id-from-description (id) + "Given a denote entry description, return its id or nil if none." + (denote-get-link-description + (denote-get-path-by-id + (denote-extract-id-from-string id)))) + +(provide 'hsys-denote) diff --git a/hsys-org.el b/hsys-org.el index ec33a40c..b427ccb1 100644 --- a/hsys-org.el +++ b/hsys-org.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 2-Jul-16 at 14:54:14 -;; Last-Mod: 16-Jun-26 at 15:48:04 by Bob Weiner +;; Last-Mod: 14-Jul-26 at 02:41:34 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -549,52 +549,65 @@ Assume caller has already checked that the current buffer is in (string-prefix-p "file://" str) t))) (thing-at-point 'email)) - (and - ;; If this Org link matches a potential HyWikiWord, ignore it. - (not (and (fboundp 'hywiki-word-at) (hywiki-word-at))) - ;; Org will throw a warning that `org-element-property' must be - ;; used only within an `org-mode' buffer, but it works in buffers - ;; outside of `org-mode' when on an Org link, so just suppress any - ;; warning. Using this allows recognition of Org links that are - ;; not surrounded by double sqare brackets, - ;; e.g. file:my-file::my-text. - (with-suppressed-warnings - ((org-element)) - (or - (org-in-regexp - org-link-any-re - (let - ((origin - (point))) - (max - (save-excursion - (backward-paragraph) - (count-lines - (point) - origin)) - (save-excursion - (forward-paragraph) - (count-lines origin - (point)))))) - (org-in-regexp org-ts-regexp-both nil t) - (org-in-regexp org-tsr-regexp-both nil t)))))) + (let (start-end) + (and + ;; Org will throw a warning that `org-element-property' must be + ;; used only within an `org-mode' buffer, but it works in buffers + ;; outside of `org-mode' when on an Org link, so just suppress any + ;; warning. Using this allows recognition of Org links that are + ;; not surrounded by double sqare brackets, + ;; e.g. file:my-file::my-text. + (setq start-end + (with-suppressed-warnings + ((org-element)) + (or + (org-in-regexp + org-link-any-re + (let ((origin (point))) + (max + (save-excursion + (backward-paragraph) + (count-lines + (point) + origin)) + (save-excursion + (forward-paragraph) + (count-lines origin + (point)))))) + (org-in-regexp org-ts-regexp-both nil t) + (org-in-regexp org-tsr-regexp-both nil t)))) + ;; Don't treat this as an Org link if its entire description is a + ;; HyWikiWord, e.g. [[hy:WikiWord]], [[WikiWord]] or + ;; [[link][WikiWord]], as these are handled as implicit buttons. + (let* ((org-link (buffer-substring-no-properties (car start-end) (cdr start-end))) + (wikiword (and org-link (fboundp 'hywiki-word-at) (hywiki-word-at))) + (label-start-end (when wikiword (hsys-org-link-label-start-end))) + (label (car label-start-end))) + (when (and label (string-match-p (concat hywiki-org-link-type ":") label)) + (setq label (substring label (1+ (length hywiki-org-link-type))))) + ;; If WikiWord is the entire label, ignore it and allow ibtype + ;; handling instead + (unless (and wikiword label (string-equal wikiword label)) + start-end)))))) (defun hsys-org-link-label-start-end () "With point on an Org link, return the list of (