branch: externals/hyperbole commit bccc18d08b5ff90b0f6b8e132e9e15a1578e4bc8 Author: bw <r...@gnu.org> Commit: bw <r...@gnu.org>
Latest rsw updates; hywiki hooks and hywiki-word-at rewritten --- ChangeLog | 85 +++++++ HY-ABOUT | 2 +- Makefile | 6 +- README.md | 2 +- README.toc.md | 19 +- hargs.el | 12 +- hibtypes.el | 17 +- hproperty.el | 9 +- hsys-org.el | 44 +++- hsys-xref.el | 6 +- hui-select.el | 4 +- hypb.el | 8 +- hyperbole.el | 4 +- hywiki.el | 649 ++++++++++++++++++++++++++++----------------------- man/hyperbole.texi | 30 +-- test/hywiki-tests.el | 72 +++--- test/kcell-tests.el | 4 +- 17 files changed, 607 insertions(+), 366 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2c0c59facf..12fb2a0713 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,88 @@ +2025-03-16 Bob Weiner <r...@gnu.org> + +* HY-ABOUT: + MELPA-RECIPE.el: + README.md: + man/hyperbole.texi: + hyperbole-pkg.el ("hyperbole"): Update requirement to Emacs version 28 from + 27.2 since Markdown mode now requires this. + +2025-03-14 Bob Weiner <r...@gnu.org> + +* hypb.el (hypb:in-string-p): Made return the start position of the string + +* hywiki.el (hywiki--page-name): Rename to 'hywiki--word-only'. + (hywiki--word-pre-command): Add for use in command hooks. + (hywiki-debuttonize-non-character-commands, + hywiki-buttonize-character-commands, + hywiki-buttonize-non-character-commands): Rewrite all of these + to capture the first HyWikiWord and then only dehighlight in post hooks + if the HyWikiWords has changed. + +2025-03-13 Bob Weiner <r...@gnu.org> + +* hywiki.el (hywiki-word-at): Clarify :range return value in doc string. + +2025-03-08 Bob Weiner <r...@gnu.org> + +* hywiki.el (hywiki-word-at): Fix so hy:WikiWord is recognized without any + delimiters. + hsys-org.el (hsys-org-link-at-p): Fix to handle Org links without square or + angle brackets, e.g. file:WikiWord.org. + (hsys-org-thing-at-p): Add to test if point is on a specific org + construct type, e.g. a link. + (hsys-org-link-at-p): Remove 'with-suppressed-warnings' since + inner call to 'hsys-org-thing-at-p' is now run only when in org mode. + +* test/hywiki-tests.el (hywiki-tests--convert-words-to-org-link): Fix expected +value to be [[WikiWord]] not [[file:WikiWord.org][WikiWord]]. + +2025-03-07 Bob Weiner <r...@gnu.org> + +* test/hywiki-tests.el (hywiki-tests--wikiword-identified-with-delimiters): + Fix to show which wikiword item fails. + +* hywiki.el (hywiki-maybe-highlight-page-name): Fix to eliminate narrowing + of region that hides any balanced delimiters around a HyWikiWord. Also + generalize HyWikiWord range finding using updated 'hywiki-word-at'. + (hywiki-word-at): Fix to allow whitespace between delimiters and + between #section words when delimited. Also, if not on a wikiword and + 'range-flag' is non-nil, return '(nil nil nil). + +2025-03-06 Bob Weiner <r...@gnu.org> + +* hywiki.el (hywiki-maybe-highlight-between-page-names): Allow for whitespace + after opening delimiter. + +2025-03-02 Bob Weiner <r...@gnu.org> + +* test/hywiki-tests.el (hywiki-tests--convert-words-to-org-link): Fix expected + link match strings. + +* hsys-xref.el (hsys-xref-identifier-at-point): Fix to return nil if xref returns + a pathname as an identifier. This fixes a problem with the Action Key + triggering the wrong action; it should recognize it as a pathname. + +* test/hywiki-tests.el (hywiki-tests--add-org-roam-node): Mock variable, + `cl-struct-org-roam-node-tags' needed when org-roam package is not installed. + +2025-02-27 Bob Weiner <r...@gnu.org> + +* hywiki.el (hywiki--maybe-de/highlight-sexp): Fix to narrow to region of + current HyWikiWord range before calling 'func'. + (hywiki--maybe-de/highlight-sexp): Add optional 'sexp-start' and + 'sexp-end' args when known; range should include delimiters. + (hywiki-buttonize-character-commands, + hywiki-buttonize-non-character-commands): Pass 'hywiki--buttonize-start' + and 'hywiki--buttonize-end' positions to 'hywiki--maybe-de/highlight-sexp' + when known rather than recomputing. + +* hproperty.el (hproperty:but-move): Add and use in "hywiki.el". + +* hywiki.el (hywiki-get-delimited-range): Rename to 'hywiki-at-range-delimiter'. + (hywiki-referent-exists-p): Fix return value doc when given :range + arg. Fix to send word string rather than tuple to 'hywiki-get-referent'. + 2025-02-25 Bob Weiner <r...@gnu.org> * hywiki.el (hywiki-word-with-optional-spaces-suffix-exact-regexp): Remove diff --git a/HY-ABOUT b/HY-ABOUT index da0954216c..9d43f05daa 100644 --- a/HY-ABOUT +++ b/HY-ABOUT @@ -11,7 +11,7 @@ GNU Hyperbole (pronounced Ga-new Hi-per-bo-lee), or just Hyperbole, is an efficient and programmable hypertextual information management system implemented as a GNU Emacs package. It works well on GNU Emacs -27.2 or above. (See also: "HY-WHY.kotl" for Hyperbole uses). +28 or above. (See also: "HY-WHY.kotl" for Hyperbole uses). Hyperbole includes easy-to-use, powerful hypertextual buttons without the need to learn a markup language; a hierarchical, record-based diff --git a/Makefile b/Makefile index 33ea6de4dd..15e2d16bef 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # Author: Bob Weiner # # Orig-Date: 15-Jun-94 at 03:42:38 -# Last-Mod: 30-Jan-25 at 09:30:52 by Mats Lidell +# Last-Mod: 16-Mar-25 at 10:20:44 by Bob Weiner # # Copyright (C) 1994-2023 Free Software Foundation, Inc. # See the file HY-COPY for license information. @@ -81,7 +81,7 @@ # # To interactively run a docker version of Emacs with Hyperbole: # make docker-run - default to running master -# make docker-run version=27.2 - run Emacs V27.2 +# make docker-run version=28.2 - run Emacs V28.2 # # To build and test a dockerized version of Emacs with Hyperbole: # make docker - defaults: version=master targets='clean bin test' @@ -181,7 +181,7 @@ HYPB_WEB_REPO_LOCATION = ../hyweb/hyperbole/ HYPB_WEB_REPO_LOCATION_DEVEL = $(HYPB_WEB_REPO_LOCATION)devel/ # CI/CD Emacs versions for local docker based tests -DOCKER_VERSIONS=27.2 28.2 29.4 master +DOCKER_VERSIONS=28.2 29.4 30.1 master ########################################################################## # NO CHANGES REQUIRED BELOW HERE. # diff --git a/README.md b/README.md index bf039a4d66..c91f9c8fe3 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ keyboard and mouse-based control of information display within multiple windows. It also provides point-and-click access to World-Wide Web URLs, Info manuals, ftp archives, etc. -Hyperbole works well on GNU Emacs 27.2 or above. It is designed and written +Hyperbole works well on GNU Emacs 28 or above. It is designed and written by Bob Weiner. It is maintained by him and Mats Lidell. Its main distribution site is: <https://www.gnu.org/software/hyperbole/>. If any term in here is new or unfamiliar to you, you can look it up in the diff --git a/README.toc.md b/README.toc.md index 9fb84e7230..c91f9c8fe3 100644 --- a/README.toc.md +++ b/README.toc.md @@ -2,12 +2,23 @@ [We work on Hyperbole as a gift to the Emacs community and request you send us a thank you or a testimonial describing your usage if you like - Hyperbole (mailto:r...@gnu.org)]. + Hyperbole to [r...@gnu.org](mailto:r...@gnu.org)]. [TOC]  +## Reference Manual + +Hyperbole has many features you can explore interactively or by reading sections of +the reference manual: + + - [Browse Hyperbole Web Manual](man/hyperbole.html) + + - [In Emacs, Browse Hyperbole Info Manual](man/hyperbole.info) + + - [Display or Print PDF Manual](man/hyperbole.pdf) + ## Videos If you prefer video introductions, visit the videos linked to below; @@ -135,7 +146,7 @@ keyboard and mouse-based control of information display within multiple windows. It also provides point-and-click access to World-Wide Web URLs, Info manuals, ftp archives, etc. -Hyperbole works well on GNU Emacs 27.2 or above. It is designed and written +Hyperbole works well on GNU Emacs 28 or above. It is designed and written by Bob Weiner. It is maintained by him and Mats Lidell. Its main distribution site is: <https://www.gnu.org/software/hyperbole/>. If any term in here is new or unfamiliar to you, you can look it up in the @@ -228,9 +239,9 @@ not a simple introduction. It is included in the "man/" subdirectory of the Hyperbole package directory in four forms: [hyperbole.info](man/hyperbole.info) - online Info browser version -[hyperbole.html](man/hyperbole.html) - web HTML version +[hyperbole.html](man/hyperbole.html) - web version [hyperbole.pdf](man/hyperbole.pdf) - printable version -[hyperbole.texi](man/hyperbole.texi) - source form +[hyperbole.texi](man/hyperbole.texi) - source version The Hyperbole package installation places the Info version of this manual where needed and adds an entry for Hyperbole into the Info directory under diff --git a/hargs.el b/hargs.el index 7e21020fc2..d22f3da1fc 100644 --- a/hargs.el +++ b/hargs.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 31-Oct-91 at 23:17:35 -;; Last-Mod: 25-Feb-25 at 02:16:13 by Bob Weiner +;; Last-Mod: 27-Feb-25 at 21:21:40 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -525,8 +525,14 @@ following sexpression is returned. Otherwise, the innermost sexpression that point is within is returned or nil if none." (let ((not-quoted '(condition-case () - (not (and (eq (char-syntax (char-after (- (point) 2))) ?\\) - (not (eq (char-syntax (char-after (- (point) 3))) ?\\)))) + (not (and (= (if (char-after (- (point) 2)) + (char-syntax (char-after (- (point) 2))) + 0) + ?\\) + (not (= (if (char-after (- (point) 3)) + (char-syntax (char-after (- (point) 3))) + 0) + ?\\)))) (error t)))) (save-excursion (ignore-errors diff --git a/hibtypes.el b/hibtypes.el index 91ba677533..25b424ddee 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: 29-Jan-25 at 10:07:49 by Mats Lidell +;; Last-Mod: 2-Mar-25 at 12:05:51 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -1591,10 +1591,19 @@ action type, function symbol to call or test to execute, i.e. ;; at the end of the buffer ;; or is followed by a space, punctuation or grouping character. (when (and lbl-key (or (null (char-before start-pos)) - (memq (char-syntax (char-before start-pos)) '(?\ ?\> ?\( ?\)))) - (not (memq (char-syntax (char-after (1+ start-pos))) '(?\ ?\>))) + (memq (if (char-before start-pos) + (char-syntax (char-before start-pos)) + 0) + '(?\ ?\> ?\( ?\)))) + (not (memq (if (char-after (1+ start-pos)) + (char-syntax (char-after (1+ start-pos))) + 0) + '(?\ ?\>))) (or (null (char-after end-pos)) - (memq (char-syntax (char-after end-pos)) '(?\ ?\> ?. ?\( ?\))) + (memq (if (char-after end-pos) + (char-syntax (char-after end-pos)) + 0) + '(?\ ?\> ?. ?\( ?\))) ;; Some of these characters may have symbol-constituent syntax ;; rather than punctuation, so check them individually. (memq (char-after end-pos) '(?. ?, ?\; ?: ?! ?\' ?\")))) diff --git a/hproperty.el b/hproperty.el index ca27419f7a..c7fcc9e364 100644 --- a/hproperty.el +++ b/hproperty.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 21-Aug-92 -;; Last-Mod: 18-Jan-25 at 20:53:20 by Bob Weiner +;; Last-Mod: 27-Feb-25 at 22:50:13 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -274,6 +274,13 @@ If optional PROPERTY and VALUE are given, return only the first button with that PROPERTY and VALUE." (car (hproperty:but-get-first-in-region pos (1+ pos) property value))) +(defun hproperty:but-move (hproperty-but start end &optional buffer) + "Set the endpoints of HPROPERTY-BUT to START and END in optional BUFFER. +If BUFFER is nil and HPROPERTY-BUT has no buffer, put it in the current buffer; +otherwise, if BUFFER is omitted, leave HPROPERTY-BUT in the same +buffer it presently inhabits." + (move-overlay hproperty-but start end buffer)) + (defun hproperty:but-start (hproperty-but) "Return the end position of an HPROPERTY-BUT. See `hproperty:but-get'." diff --git a/hsys-org.el b/hsys-org.el index a66d35b3c9..72ca860e15 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: 5-Jan-25 at 12:06:01 by Bob Weiner +;; Last-Mod: 9-Mar-25 at 10:47:48 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -497,19 +497,16 @@ Match to all todos if `keyword' is nil or the empty string." (looking-at org-babel-src-block-regexp)))) (defun hsys-org-link-at-p () - "Return non-nil iff point is on a square-bracketed Org mode link. + "Return non-nil iff point is on an Org mode link. Ignore [[hy:HyWiki]] buttons and return nil (handle these as implicit buttons). Assume caller has already checked that the current buffer is in `org-mode' or is looking for an Org link in another buffer type." (unless (or (smart-eolp) (smart-eobp)) - (with-suppressed-warnings nil - (let ((in-org-link (org-in-regexp org-link-bracket-re nil t))) - (when in-org-link - (save-match-data - ;; If this Org link matches a potential HyWiki word, ignore it. - (unless (and (fboundp 'hywiki-word-at) (hywiki-word-at)) - in-org-link))))))) + (when (eq 'link (plist-get (hsys-org-thing-at-p) :type)) + (save-match-data + ;; If this Org link matches a potential HyWiki word, ignore it. + (not (and (fboundp 'hywiki-word-at) (hywiki-word-at))))))) ;; Assume caller has already checked that the current buffer is in org-mode. (defun hsys-org-heading-at-p (&optional _) @@ -525,6 +522,35 @@ Assume caller has already checked that the current buffer is in `org-mode'." (hsys-org-face-at-p 'org-target)) +;; Derived from `org-open-at-point' in "org.el". +(defun hsys-org-thing-at-p () + "Return a plist of properties for Org thing at point or nil if none. +The plist form is: (:type <type> :value <value> :context <context>). +The thing can be a link, citation, timestamp, footnote, src-block or +tags. + +On top of syntactically correct links, this function also works +on links and timestamps in comments, node properties, and +keywords if point is on something looking like a timestamp or a +link." + (when (derived-mode-p 'org-mode) + (org-load-modules-maybe) + ;; Org's regex matching can fail in non-thing contexts; return nil then + (let* ((context + ;; Only consider supported types, even if they are not the + ;; closest one. + (org-element-lineage + (org-element-context) + '(citation citation-reference clock comment comment-block + footnote-definition footnote-reference headline + inline-src-block inlinetask keyword link node-property + planning src-block timestamp) + t)) + (type (org-element-type context)) + (value (org-element-property :value context))) + (when type + (list :type type :value value :context context))))) + (defun hsys-org-todo-at-p () "Return non-nil iff point is on an Org mode todo keyword. Assume caller has already checked that the current buffer is in `org-mode'." diff --git a/hsys-xref.el b/hsys-xref.el index d633699af1..798f598119 100644 --- a/hsys-xref.el +++ b/hsys-xref.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 24-Aug-91 -;; Last-Mod: 14-Apr-24 at 19:03:32 by Bob Weiner +;; Last-Mod: 2-Mar-25 at 18:11:45 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -49,7 +49,9 @@ (car (hsys-xref-definitions identifier))) (defun hsys-xref-identifier-at-point () - (xref-backend-identifier-at-point (xref-find-backend))) + ;; Return nil if xref returns a pathname as an identifier + (unless (hpath:at-p nil t) + (xref-backend-identifier-at-point (xref-find-backend)))) (defun hsys-xref-item-buffer (item) "Return the buffer in which xref ITEM is defined." diff --git a/hui-select.el b/hui-select.el index 7eded1718d..44e734f071 100644 --- a/hui-select.el +++ b/hui-select.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 19-Oct-96 at 02:25:27 -;; Last-Mod: 25-Feb-25 at 02:24:41 by Bob Weiner +;; Last-Mod: 27-Feb-25 at 21:24:19 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -915,7 +915,7 @@ end sexp delimiters, ignore it, and return nil." (when (region-active-p) (deactivate-mark)) (mark-sexp) t))) (ignore-errors - (let ((syn-after (char-syntax (char-after))) + (let ((syn-after (if (char-after) (char-syntax (char-after)) 0)) syn-before) (cond ((eq syn-after ?\() (funcall mark-sexp-func)) diff --git a/hypb.el b/hypb.el index 90fd98bbc6..afa75dc6ac 100644 --- a/hypb.el +++ b/hypb.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 6-Oct-91 at 03:42:38 -;; Last-Mod: 29-Jan-25 at 20:27:07 by Mats Lidell +;; Last-Mod: 15-Mar-25 at 09:06:58 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -666,7 +666,11 @@ This will this install the Emacs helm package when needed." (defun hypb:in-string-p () "Return non-nil iff point is in a double quoted string." (syntax-ppss-flush-cache (line-beginning-position)) - (nth 3 (syntax-ppss))) + (let ((sexp-state-list (syntax-ppss))) + ;; if in a string + (when (nth 3 sexp-state-list) + ;; return start of str position (opening quote) + (nth 8 sexp-state-list)))) (defun hypb:indirect-function (obj) "Return the function at the end of OBJ's function chain. diff --git a/hyperbole.el b/hyperbole.el index 6db1ed9c7e..30c4871bc1 100644 --- a/hyperbole.el +++ b/hyperbole.el @@ -9,12 +9,12 @@ ;; Maintainer: Robert Weiner <r...@gnu.org> ;; Maintainers: Robert Weiner <r...@gnu.org>, Mats Lidell <ma...@gnu.org> ;; Created: 06-Oct-92 at 11:52:51 -;; Last-Mod: 2-Feb-25 at 07:27:43 by Bob Weiner +;; Last-Mod: 16-Mar-25 at 10:11:31 by Bob Weiner ;; Released: 10-Mar-24 ;; Version: 9.0.2pre ;; Keywords: comm, convenience, files, frames, hypermedia, languages, mail, matching, mouse, multimedia, outlines, tools, wp ;; Package: hyperbole -;; Package-Requires: ((emacs "27.2")) +;; Package-Requires: ((emacs "28")) ;; URL: http://www.gnu.org/software/hyperbole ;; See the "HY-COPY" file for license information. diff --git a/hywiki.el b/hywiki.el index c542d31475..ee206ae029 100644 --- a/hywiki.el +++ b/hywiki.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 21-Acpr-24 at 22:41:13 -;; Last-Mod: 25-Feb-25 at 02:39:08 by Bob Weiner +;; Last-Mod: 16-Mar-25 at 10:05:36 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -236,7 +236,8 @@ Each element is of the form: (wikiword . (referent-type . referent-value)).") (defvar hywiki--end nil) (defvar hywiki--flag nil) (defvar hywiki--highlighting-done-flag t) -(defvar hywiki--page-name nil) +(defvar hywiki--word-pre-command nil) +(defvar hywiki--word-only nil) (defvar hywiki--range nil) (defvar hywiki--save-case-fold-search nil) (defvar hywiki--save-org-link-type-required nil) @@ -356,7 +357,7 @@ Presently, there are no key bindings; this is for future use.") "HyWiki string prefix type for Org links. Excludes trailing colon.") (defvar hywiki-org-link-type-required t - "When non-nil, HyWiki Org links must start with `hywiki-org-link-type'. + "When non-nil, HyWiki Org links must start with `hywiki-org-link-type':. Otherwise, this prefix is not needed and HyWiki word Org links override standard Org link lookups. See \"(org)Internal Links\".") @@ -465,7 +466,7 @@ Do not use a start or end line/string anchor in this regexp.") (defconst hywiki-word-section-regexp "\\(#[^][# \t\n\r\f]+\\)" - "Regexp that matches a HyWiki word #section extension. + "Regexp that matches a non-delimited HyWiki word #section extension. After the first # character, this may contain any non-square-bracket, non-# and non-whitespace characters.") @@ -556,22 +557,31 @@ Non-nil is the default." "Turn any HyWikiWords between point into highlighted Hyperbole buttons. Triggered by `post-self-insert-hook' for self-inserting characters. Highlight after inserting any non-word character." + ;; If `hywiki--flag' is set non-nil below, then + ;; `hywiki-buttonize-non-character-commands' on `post-command-hook' + ;; does nothing. (unless (setq hywiki--flag (hywiki-non-hook-context-p)) (setq hywiki--range nil) (save-excursion (cond ((marker-position hywiki--buttonize-start) - (goto-char (1- hywiki--buttonize-start))) - ((setq hywiki--range (hywiki-word-at :range)) - (cl-destructuring-bind (_ start end) - hywiki--range - (set-marker hywiki--buttonize-start start) - (set-marker hywiki--buttonize-end end)) - (goto-char (1- hywiki--buttonize-start)))) - (if (marker-position hywiki--buttonize-start) - (hywiki--maybe-de/highlight-sexp #'hywiki-maybe-highlight-page-names 1) - (hywiki-maybe-highlight-between-page-names))) - (set-marker hywiki--buttonize-start nil) - (set-marker hywiki--buttonize-end nil))) + ;; Point was before or after a WikiWord delimiter + (goto-char hywiki--buttonize-start) + (skip-chars-backward "-" (line-beginning-position)) + (goto-char (1- (point)))) + ((setq hywiki--range (hywiki-word-at :range)) + (cl-destructuring-bind (_ start end) + hywiki--range + (if (and start end) + (progn + ;; On a non-delimited HyWikiWord + (set-marker hywiki--buttonize-start start) + (set-marker hywiki--buttonize-end end) + (goto-char start) + (skip-chars-backward "-" (line-beginning-position)) + t) + (setq hywiki--range nil))))) + + (hywiki--rehighlight-at-point)))) (defun hywiki-buttonize-non-character-commands () "Highlight any HyWikiWord before or after point as a Hyperbole button. @@ -582,74 +592,73 @@ deletion commands and those in `hywiki-non-character-commands'." (and (symbolp this-command) (string-match-p "^\\(org-\\)?\\(delete-\\|kill-\\)\\|\\(-delete\\|-kill\\)\\(-\\|$\\)" (symbol-name this-command)))) (setq hywiki--range nil) + (save-excursion (cond ((marker-position hywiki--buttonize-start) - (goto-char hywiki--buttonize-start)) + ;; Point was before or after a WikiWord delimiter + (goto-char (1+ hywiki--buttonize-start))) ((setq hywiki--range (hywiki-word-at :range)) (cl-destructuring-bind (_ start end) hywiki--range - (set-marker hywiki--buttonize-start start) - (set-marker hywiki--buttonize-end end)) - (goto-char hywiki--buttonize-start))) - (if (marker-position hywiki--buttonize-start) - (hywiki--maybe-de/highlight-sexp #'hywiki-maybe-highlight-page-names 1) - (hywiki-maybe-highlight-between-page-names))) - (set-marker hywiki--buttonize-start nil) - (set-marker hywiki--buttonize-end nil)))) - - - ;; (when (and (marker-position hywiki--buttonize-start) - ;; (marker-position hywiki--buttonize-end)) - - ;; ;; When these markers are set, it means the command just - ;; ;; edited a char within a delimited range that now needs any - ;; ;; HyWikiWords inside to be re-highlighted. - ;; (save-excursion - ;; (goto-char hywiki--buttonize-start) - ;; ;; (message "%s" (point)) - ;; (let ((opening-char (char-after)) - ;; closing-char) - ;; (when (memq opening-char '(?\( ?\")) - ;; (delete-char 1)) - ;; (hywiki-maybe-highlight-between-page-names) - ;; (when (memq opening-char '(?\( ?\")) - ;; (insert-before-markers opening-char)) - - ;; (setq closing-char (char-before)) - ;; (when (memq closing-char '(?\) ?\")) - ;; ;; (delete-char -1) - ;; ;; (insert " ") - ;; ) - ;; ;; (goto-char hywiki--buttonize-start) - ;; (when (memq closing-char '(?\) ?\")) - ;; ;; (goto-char (+ 2 hywiki--buttonize-end)) - ;; ;; (delete-char -1) - ;; ;; (insert-before-markers closing-char) - ;; )))) - ;; (hywiki-maybe-highlight-between-page-names)))) + (if (and start end) + (progn + ;; On a non-delimited HyWikiWord + (set-marker hywiki--buttonize-start start) + (set-marker hywiki--buttonize-end end) + (goto-char start) + (skip-chars-backward "-" (line-beginning-position)) + t) + (setq hywiki--range nil))))) + + (hywiki--rehighlight-at-point))))) + +(defun hywiki--rehighlight-at-point () + ;; Dehighlight any existing word only if the editing command + ;; has changed the word-only part of the WikiWord reference + (when (and hywiki--word-pre-command hywiki--range + (not (equal hywiki--word-pre-command + (hywiki-get-singular-wikiword (car hywiki--range))))) + ;; Dehighlight if point is on or between a HyWikiWord + (hywiki-maybe-dehighlight-between-page-names)) + + ;; Highlight wikiwords around point as needed + (cond (hywiki--range + (hywiki-maybe-highlight-on-page-name)) + ((and (marker-position hywiki--buttonize-start) + (marker-position hywiki--buttonize-end)) + (hywiki--maybe-de/highlight-sexp + #'hywiki-maybe-highlight-page-names 1 + hywiki--buttonize-start hywiki--buttonize-end)) + (t (hywiki-maybe-highlight-between-page-names))) + + (set-marker hywiki--buttonize-start nil) + (set-marker hywiki--buttonize-end nil)) (defun hywiki-debuttonize-non-character-commands () - "Dehighlight any HyWikiWord before or after point. + "Store any HyWikiWord before or after point for later comparison. Triggered by `pre-command-hook' for non-character-commands, including deletion commands and those in `hywiki-non-character-commands'." + (setq hywiki--word-pre-command nil) (when (and (markerp hywiki--buttonize-start) (markerp hywiki--buttonize-end)) (set-marker hywiki--buttonize-start nil) (set-marker hywiki--buttonize-end nil)) (unless (hywiki-non-hook-context-p) + ;; Record the WikiWord from any WikiWord ref that point is on + (setq hywiki--word-pre-command (hywiki-get-singular-wikiword (hywiki-word-at))) (when (or (memq this-command hywiki-non-character-commands) (and (symbolp this-command) (string-match-p "^\\(org-\\)?\\(delete-\\|kill-\\)\\|\\(-delete\\|-kill\\)\\(-\\|$\\)" (symbol-name this-command)))) - (cl-destructuring-bind (start end) - ;; Get delimited region only if before or after delimiters, - ;; else return (nil nil). - (hywiki-get-delimited-range) ;; includes delimiters - ;; Use these to store any range of a delimited HyWikiWord#section - (set-marker hywiki--buttonize-start start) - (set-marker hywiki--buttonize-end end) - ;; Enable dehighlighting in HyWiki pages - (unless (and start end) - ;; Dehighlight any page name at point - (hywiki-maybe-dehighlight-between-page-names))))) + ;; Test if at delimiters surrounding a WikiWord and if so, + ;; record those for use by post hooks. + (save-excursion + (cl-destructuring-bind (start end) + ;; Get delimited region only if before or after delimiters, + ;; else return (nil nil). + (hywiki-at-range-delimiter) ;; includes delimiters + ;; Use these to store any range of a delimited HyWikiWord#section + (set-marker hywiki--buttonize-start start) + (set-marker hywiki--buttonize-end end) + start)))) (setq hywiki--flag nil)) (defun hywiki-buttonize-word (func start end face) @@ -1451,14 +1460,8 @@ Use when publishing a HyWiki file to another format, e.g. html. For example, the link: \"WikiWord#Multi-Word Section\" -or - \"[[hy:WikiWord#Multi-Word Section]]\" is converted to: - \"[[file:<hywiki-directory>/WikiWord.org::Multi-Word Section][WikiWord#Multi-Word Section]]\". - -If the reference is in a file within the `hywiki-directory', it -simplifies to: - \"[[file:WikiWord.org::Multi-Word Section][WikiWord#Multi-Word Section]]\". + \"[[hy:WikiWord#Multi-Word Section]]\". If the reference is within the WikiWord page to which it refers, it simplifies to: @@ -1660,8 +1663,8 @@ positions of each HyWikiWord and its optional #section." (overlays-in (point-min) (point-max))))) nil) -(defun hywiki-get-delimited-range () - "Before or after a balanced delimiter, return the delimited range list. +(defun hywiki-at-range-delimiter () + "Immediately before or after a balanced delimiter, return the delimited range. If no such range, return \\='(nil nil). This includes the delimiters: (), {}, <>, [] and \"\" (double quotes)." (save-excursion @@ -1867,11 +1870,28 @@ Return t if no errors and a pair was found, else nil." (when result t))))) (defun hywiki-maybe-dehighlight-between-page-names () - "Dehighlight any non-Org link HyWiki page#section names between point. + "Dehighlight any non-Org link HyWiki page#section between point. If in a programming mode, must be within a comment. Use `hywiki-word-face' to dehighlight." - (hywiki-maybe-dehighlight-off-page-name) - (hywiki-maybe-dehighlight-on-page-name)) + (cond ((hproperty:char-property-range (point) 'face hywiki-word-face) + ;; This is called in pre-command-hook before any chars are + ;; deleted, so have to dehighlight it though it still temporarily + ;; may be a valid HyWikiWord. + (hywiki-maybe-dehighlight-on-page-name)) + ((cl-destructuring-bind (start end) + (hywiki-at-range-delimiter) + (when (and start end) + (save-excursion + (goto-char (1+ start)) + (and (hproperty:char-property-range (point) 'face hywiki-word-face) + (equal (hywiki-referent-exists-p :range) + '(nil nil nil)) + ;; non-existing wikiword + (hywiki-maybe-dehighlight-on-page-name))) + t))) + ((looking-at "[ \t\n\r\f]") + (hywiki-maybe-dehighlight-off-page-name) + (hywiki-maybe-dehighlight-on-page-name)))) (defun hywiki-maybe-dehighlight-off-page-name () "Dehighlight any non-Org link HyWiki page#section at or one char before point. @@ -1882,7 +1902,7 @@ in a programming mode, must be within a comment." (hywiki-maybe-dehighlight-page-name ;; Flag on-page-name if on a whitespace character (or (= (point) (point-max)) - (eq (char-syntax (char-after)) ? )))) + (= (if (char-after) (char-syntax (char-after)) 0) ? )))) (defun hywiki-maybe-dehighlight-on-page-name () "Dehighlight any non-Org link HyWiki page#section at or one char before point. @@ -1893,7 +1913,7 @@ be within a comment." (hywiki-maybe-dehighlight-page-name ;; Flag on-page-name if not on a whitespace character (and (/= (point) (point-max)) - (/= (char-syntax (char-after)) ? )))) + (/= (if (char-after) (char-syntax (char-after)) 0) ? )))) ;;;###autoload (defun hywiki-maybe-dehighlight-page-name (&optional on-page-name) @@ -1947,10 +1967,10 @@ If in a programming mode, must be within a comment. Use (if (and (hywiki-maybe-at-wikiword-beginning) (looking-at hywiki--word-and-buttonize-character-regexp) (progn - (setq hywiki--page-name (match-string-no-properties 2) + (setq hywiki--word-only (match-string-no-properties 2) hywiki--start (match-beginning 1) hywiki--end (match-end 1)) - (hywiki-get-referent hywiki--page-name))) + (hywiki-get-referent hywiki--word-only))) (when (setq hywiki--buts (hproperty:but-get-all-in-region hywiki--start hywiki--end 'face hywiki-word-face)) @@ -1964,11 +1984,11 @@ If in a programming mode, must be within a comment. Use ;;;###autoload (defun hywiki-maybe-highlight-page-name (&optional on-page-name) - "Highlight any non-Org link HyWiki page#section at or one char before point. + "Highlight any non-Org link HyWikiWord#section at or one char before point. With optional ON-PAGE-NAME non-nil, assume point is within the page or -section name. Otherwise, if `pre-command-hook' has set +section name. Otherwise, if a HyWiki per-character hook has set `hywiki--buttonize-start' `hywiki--buttonize-end' global variables, -use these as the region in which to highlight. +use these as the region to highlight. If in a programming mode, must be within a comment. Use `hywiki-word-face' to highlight. Do not highlight references to @@ -1977,7 +1997,7 @@ the current page unless they have sections attached." (when (and (hywiki-active-in-current-buffer-p) (if (and (derived-mode-p 'prog-mode) (not (apply #'derived-mode-p hywiki-highlight-all-in-prog-modes))) - ;; Non-nil if match is inside a comment + ;; Non-nil if match is inside a comment or string (or (nth 4 (syntax-ppss)) (hypb:in-string-p)) t) ;; (or on-page-name @@ -1988,78 +2008,77 @@ the current page unless they have sections attached." (setq hywiki--highlighting-done-flag nil) (with-syntax-table hbut:syntax-table (save-excursion - (save-restriction - (when (and (marker-position hywiki--buttonize-start) - (marker-position hywiki--buttonize-end)) - (narrow-to-region hywiki--buttonize-start hywiki--buttonize-end) - (goto-char hywiki--buttonize-start)) + (when (and (marker-position hywiki--buttonize-start) + (marker-position hywiki--buttonize-end)) + (goto-char hywiki--buttonize-start)) - (unless on-page-name - ;; after page name - (skip-syntax-backward ">-")) + (unless on-page-name + ;; after page name + (skip-syntax-backward ">-")) - (unless (or hywiki--highlighting-done-flag - (hywiki-maybe-highlight-balanced-pairs)) + (unless (or hywiki--highlighting-done-flag + (hywiki-maybe-highlight-balanced-pairs)) - (unless on-page-name - ;; May be a closing delimiter that we have to skip past - (skip-chars-backward (regexp-quote (hywiki-get-buttonize-characters)))) - ;; Skip past HyWikiWord or section + (unless on-page-name + ;; May be a HyWikiWord ending character to skip past + (skip-chars-backward (hywiki-get-buttonize-characters) + (line-beginning-position))) + ;; Skip past HyWikiWord or section + (skip-syntax-backward "^-$()<>._\"\'") + (skip-chars-backward "-_*#:[:alnum:]") + + (setq hywiki--save-case-fold-search case-fold-search + case-fold-search nil + hywiki--save-org-link-type-required hywiki-org-link-type-required + hywiki-org-link-type-required t + hywiki--start nil + hywiki--end nil) + + (if (and (cl-destructuring-bind (word start end) + (hywiki-word-at :range) + (setq hywiki--word-only word + hywiki--start start + hywiki--end end)) + hywiki--start + (hywiki-get-referent hywiki--word-only) + (goto-char hywiki--start)) + (progn + (setq hywiki--current-page (hywiki-get-buffer-page-name)) + ;; Don't highlight current-page matches unless they + ;; include a #section. + (unless (string-equal hywiki--current-page + (buffer-substring-no-properties + hywiki--start hywiki--end)) + (if (setq hywiki--buts (hproperty:but-get-all-in-region + hywiki--start hywiki--end + 'face hywiki-word-face)) + (if (> (length hywiki--buts) 1) + (progn (hproperty:but-clear-all-in-list hywiki--buts) + (hywiki-maybe-highlight-page-names + hywiki--start hywiki--end)) + ;; There is only one existing button + (setq hywiki--buts (car hywiki--buts) + hywiki--but-start (hproperty:but-start hywiki--buts) + hywiki--but-end (hproperty:but-end hywiki--buts)) + (unless (and (= hywiki--start hywiki--but-start) + (= hywiki--end hywiki--but-end)) + (hproperty:but-delete hywiki--buts) + (hywiki-maybe-highlight-page-names + hywiki--start hywiki--end))) + (hywiki-maybe-highlight-page-names + hywiki--start hywiki--end)))) + ;; Remove any potential earlier highlighting since the + ;; previous word may have changed. (skip-syntax-backward "^-$()<>._\"\'") - (skip-chars-backward "-_*#:[:alnum:]") - - (setq hywiki--save-case-fold-search case-fold-search - case-fold-search nil - hywiki--save-org-link-type-required hywiki-org-link-type-required - hywiki-org-link-type-required t - hywiki--start nil - hywiki--end nil) - - (if (and (hywiki-maybe-at-wikiword-beginning) - (looking-at hywiki--word-and-buttonize-character-regexp) - (progn - (setq hywiki--page-name (match-string-no-properties 2) - hywiki--start (match-beginning 1) - ;; This excludes optional char after the page#section - hywiki--end (match-end 1)) - (hywiki-get-referent hywiki--page-name))) - (progn - (setq hywiki--current-page (hywiki-get-buffer-page-name)) - ;; Don't highlight current-page matches unless they - ;; include a #section. - (unless (string-equal hywiki--current-page - (buffer-substring-no-properties - hywiki--start hywiki--end)) - (if (setq hywiki--buts (hproperty:but-get-all-in-region - hywiki--start hywiki--end - 'face hywiki-word-face)) - (if (> (length hywiki--buts) 1) - (progn (hproperty:but-clear-all-in-list hywiki--buts) - (hywiki-maybe-highlight-page-names - hywiki--start hywiki--end)) - ;; There is only one existing button - (setq hywiki--buts (car hywiki--buts) - hywiki--but-start (hproperty:but-start hywiki--buts) - hywiki--but-end (hproperty:but-end hywiki--buts)) - (unless (and (= hywiki--start hywiki--but-start) - (= hywiki--end hywiki--but-end)) - (hproperty:but-delete hywiki--buts) - (hywiki-maybe-highlight-page-names - hywiki--start hywiki--end))) - (hywiki-maybe-highlight-page-names - hywiki--start hywiki--end)))) - ;; Remove any potential earlier highlighting since the - ;; previous word may have changed. - (skip-syntax-backward "^-$()<>._\"\'") - (if (setq hywiki--buts (hproperty:but-get-all-in-region + (when (setq hywiki--buts (hproperty:but-get-all-in-region (point) (1+ (point)) 'face hywiki-word-face)) - (if (> (length hywiki--buts) 1) - (hproperty:but-clear-all-in-list hywiki--buts) - ;; There is only one existing button - (setq hywiki--buts (car hywiki--buts) - hywiki--but-start (hproperty:but-start hywiki--buts) - hywiki--but-end (hproperty:but-end hywiki--buts)) - (hproperty:but-delete hywiki--buts)))))))))) + (if (> (length hywiki--buts) 1) + (hproperty:but-clear-all-in-list hywiki--buts) + ;; There is only one existing button + (setq hywiki--buts (car hywiki--buts) + hywiki--but-start (hproperty:but-start hywiki--buts) + hywiki--but-end (hproperty:but-end hywiki--buts)) + (hproperty:but-delete hywiki--buts))))))))) (defun hywiki-maybe-highlight-between-page-names () "Highlight any non-Org link HyWiki page#section names between point. @@ -2067,8 +2086,31 @@ the current page unless they have sections attached." If in a programming mode, must be within a comment. Use `hywiki-word-face' to highlight. Do not highlight references to the current page unless they have sections attached." - (hywiki-maybe-highlight-off-page-name) - (hywiki-maybe-highlight-on-page-name)) + (cond ((hproperty:char-property-range (point) 'face hywiki-word-face)) + ((cl-destructuring-bind (word start end) + (hywiki-word-at :range) + (when (and start end) + (save-excursion + (goto-char start) + (when (hywiki-referent-exists-p word) + ;; existing wikiword + (hywiki-maybe-highlight-on-page-name))) + t))) + ((cl-destructuring-bind (start end) + (hywiki-at-range-delimiter) + (when (and start end) + (save-excursion + (goto-char (1+ start)) + (skip-syntax-forward "-" (line-end-position)) + (unless (equal (hywiki-referent-exists-p :range) + '(nil nil nil)) + ;; existing wikiword + (hywiki-maybe-highlight-on-page-name))) + t))) + ((looking-at "[ \t\n\r\f]") + (hywiki-maybe-highlight-off-page-name) + (hywiki-maybe-highlight-on-page-name)) + (t (hywiki-maybe-highlight-on-page-name)))) (defun hywiki-maybe-highlight-off-page-name () "Highlight any non-Org link HyWiki page#section at or one char before point. @@ -2097,7 +2139,7 @@ the current page unless they have sections attached." (hywiki-maybe-highlight-page-name ;; flag on-page-name if not on a whitespace character (and (/= (point) (point-max)) - (/= (char-syntax (char-after)) ? )))) + (/= (if (char-after) (char-syntax (char-after)) 0) ? )))) (defun hywiki-maybe-dehighlight-org-element-backward () "Dehighlight HyWikiWords within a closing double/single square/angle bracket." @@ -2156,8 +2198,8 @@ interactively), limit dehighlighting to the region." 'face hywiki-word-face)) (unless (or region-start region-end) (setq hywiki-buffer-highlighted-state 'd)))) - -;;;###autoload +; +;;###autoload (defun hywiki-maybe-highlight-page-names (&optional region-start region-end skip-lookups-update-flag) "Highlight each non-Org link HyWiki page#section in a buffer/region. With optional REGION-START and REGION-END positions or markers (active @@ -2199,9 +2241,12 @@ value of `hywiki-word-highlight-flag' is changed." (narrow-to-region region-start region-end))) ((and region-start region-end) (narrow-to-region region-start region-end))) - ;; Enable dehighlighting in HyWiki pages - (let ((hywiki-word-highlight-flag)) - (hywiki-maybe-dehighlight-page-names)) + ;; Enable dehighlighting in HyWiki pages only when + ;; whole buffer is being processed; this prevents an + ;; error when called from `hywiki-maybe-highlight-sexp'. + (unless (and region-start region-end) + (let ((hywiki-word-highlight-flag)) + (hywiki-maybe-dehighlight-page-names))) (dolist (hywiki-words-regexp hywiki--any-wikiword-regexp-list) (goto-char (point-min)) (let ((highlight-in-comments-and-strings-only @@ -2426,7 +2471,7 @@ regexps of wikiwords, if the hash table is out-of-date." (defun hywiki-get-singular-wikiword (wikiword) "Return the singular version of the given WIKIWORD with any suffix removed. If `hywiki-allow-plurals-flag' is nil, return unchanged WIKIWORD name -with andy suffix removed." +with any suffix removed." (setq wikiword (hywiki-word-strip-suffix wikiword)) (if (or (not hywiki-allow-plurals-flag) (not (stringp wikiword))) @@ -2512,7 +2557,10 @@ save and potentially set `hywiki--directory-mod-time' and (or (file-writable-p save-file) (error "(hywiki-cache-save): Non-writable Environment file, \"%s\"" save-file)) (let ((buf (get-file-buffer save-file))) - (and buf (kill-buffer buf))) + (when buf + (if (buffer-modified-p buf) + (error "(hywiki-cache-save): Attempt to kill modified Environment file failed to save, \"%s\"" save-file) + (kill-buffer buf)))) (let ((dir (or (file-name-directory save-file) default-directory))) (or (file-writable-p dir) @@ -2538,8 +2586,9 @@ save and potentially set `hywiki--directory-mod-time' and (princ ")\n") (save-buffer) - (set-buffer-modified-p nil) - (kill-buffer standard-output))))) + (if (buffer-modified-p) + (error "(hywiki-cache-save): Attempt to kill modified Environment file failed to save, \"%s\"" save-file) + (kill-buffer standard-output)))))) (defun hywiki-make-referent-hasht () "Rebuld referent hasht from list of HyWiki page files and non-page entries." @@ -2766,35 +2815,35 @@ Customize this directory with: (advice-remove #'org-export-get-reference #'hywiki--org-export-get-reference))) (defun hywiki-referent-exists-p (&optional word start end) - "Return an optional HyWiki WORD or word at point, if has an existing referent. + "Return the HyWikiWord at point or optional HyWiki WORD, if has a referent. If no such referent exists, return nil. Word may be of 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. + 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." - (setq hywiki--page-name word) +at point must return non-nil or this function will return nil." + (setq hywiki--word-only word) (when (stringp word) (setq word (hywiki-strip-org-link word))) (if (or (stringp word) (setq word (hywiki-word-at word))) - (unless (hywiki-get-referent word) + (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 + ;; first above word (nth 0 word))) - (if (eq hywiki--page-name :range) - (if (and word (setq hywiki--range - (hproperty:char-property-range - (point) 'face hywiki-word-face))) - (list word (or start (car hywiki--range)) (or end (cdr hywiki--range))) - (list word start end)) + (if (eq hywiki--word-only :range) + (or (hywiki-word-at :range) + (list word start end)) word)) (defun hywiki-strip-org-link (link-str) @@ -2868,116 +2917,140 @@ Action Key press; with a prefix ARG, emulate an Assist Key press." (defun hywiki-word-at (&optional range-flag) "Return potential HyWikiWord and optional #section:Lnum:Cnum at point or nil. -Point should be on the HyWikiWord itself. +If the HyWikiWord is delimited, Point must be within the delimiters. With optional RANGE-FLAG, return a list of (HyWikiWord start-position -end-position); the positions are for only the HyWikiWord itself. +end-position); the positions include the entire +HyWikiWord#section:Lnum:Cnum string but exclude any delimiters. This does not test whether a referent exists for the HyWiki word; call `hywiki-referent-exists-p' without an argument for that. A call to `hywiki-active-in-current-buffer-p' at point must return non-nil or this will return nil." - (when (hywiki-active-in-current-buffer-p) - (if (setq hywiki--range - (hproperty:char-property-range (point) 'face hywiki-word-face)) - (let ((wikiword (buffer-substring-no-properties (car hywiki--range) (cdr hywiki--range)))) - (when (string-match hywiki-word-with-optional-suffix-exact-regexp wikiword) - (if range-flag - (list wikiword (car hywiki--range) (cdr hywiki--range)) - wikiword))) - (save-excursion - ;; Don't use `cl-destructuring-bind' here since the `hargs:delimited' call - ;; can return nil rather than the 3 arg list that would be required - (let* ((wikiword-start-end - (let ((start-regexp (concat "\\[\\[\\(" hywiki-org-link-type ":\\)?"))) - (save-excursion - (skip-chars-backward (concat hywiki-org-link-type ":[")) - (when (looking-at start-regexp) - (goto-char (match-end 0))) - (hargs:delimited (concat "\\[\\[\\(" hywiki-org-link-type ":\\)?") - "\\(\\]\\[\\|\\]\\]\\)" t t t)))) - (wikiword (nth 0 wikiword-start-end)) - (start (nth 1 wikiword-start-end)) - (end (nth 2 wikiword-start-end))) - (when (cond (wikiword - ;; Handle an Org link [[HyWikiWord]] [[hy:HyWikiWord]] - ;; or [[HyWikiWord#section][Description Text]]. - ;; Get the HyWikiWord link reference, ignoring any - ;; description given in the link - ;; Don't use next line so don't have to load all of Org - ;; mode just to check for HyWikiWords; however, disables - ;; support for Org mode aliases. - ;; (setq wikiword (org-link-expand-abbrev (org-link-unescape (string-trim wikiword)))) - (setq wikiword (hywiki-strip-org-link wikiword)) - (when (and wikiword end) - ;; Update start and end to newly stripped - ;; string positions - (save-excursion - (save-restriction - (narrow-to-region start end) - (goto-char (point-min)) - (when (search-forward wikiword nil t) - (setq start (match-beginning 0) - end (match-end 0)))))) - (hywiki-word-is-p wikiword)) - - ;; Handle when pre-command-hook has dehighlighted a - ;; delimited HyWikiWord reference with multiple - ;; words in its section, e.g. (WikiWord#one two - ;; three). That is, allow for spaces in the - ;; section. - ((with-syntax-table hywiki--org-mode-syntax-table - (unless (or (= (if (char-after) (char-syntax (char-after)) 0) ?\() - (= (if (char-before) (char-syntax (char-before)) 0) ?\))) + (if (hywiki-active-in-current-buffer-p) + ;; (if (setq hywiki--range + ;; (hproperty:char-property-range (point) 'face hywiki-word-face)) + ;; (let ((wikiword (buffer-substring-no-properties (car hywiki--range) (cdr hywiki--range)))) + ;; (if (string-match hywiki-word-with-optional-suffix-exact-regexp wikiword) + ;; (if range-flag + ;; (list wikiword (car hywiki--range) (cdr hywiki--range)) + ;; wikiword) + ;; (when range-flag + ;; '(nil nil nil)))) + (save-excursion + ;; Don't use `cl-destructuring-bind' here since the `hargs:delimited' call + ;; can return nil rather than the 3 arg list that would be required + (let* ((wikiword-start-end + (let ((start-regexp (concat "\\[\\[\\(" hywiki-org-link-type ":\\)?"))) + (save-excursion + (skip-chars-backward (concat hywiki-org-link-type ":[")) + (when (looking-at start-regexp) + (goto-char (match-end 0))) + (hargs:delimited (concat "\\[\\[\\(" hywiki-org-link-type ":\\)?") + "\\(\\]\\[\\|\\]\\]\\)" t t t)))) + (wikiword (nth 0 wikiword-start-end)) + (start (nth 1 wikiword-start-end)) + (end (nth 2 wikiword-start-end))) + (with-syntax-table hywiki--org-mode-syntax-table + (if (cond (wikiword + ;; Handle an Org link [[HyWikiWord]] [[hy:HyWikiWord]] + ;; or [[HyWikiWord#section][Description Text]]. + ;; Get the HyWikiWord link reference, ignoring any + ;; description given in the link + ;; Don't use next line so don't have to load all of Org + ;; mode just to check for HyWikiWords; however, disables + ;; support for Org mode aliases. + ;; (setq wikiword (org-link-expand-abbrev (org-link-unescape (string-trim wikiword)))) + (setq wikiword (hywiki-strip-org-link wikiword)) + (when (and wikiword end) + ;; Update start and end to newly stripped + ;; string positions (save-excursion - (condition-case () - (backward-up-list) - (error nil)) - (when (= (if (char-after) (char-syntax (char-after)) 0) ?\() - (goto-char (1+ (point))) - (when (looking-at (concat hywiki-word-regexp "\\(#[^][()<>{}\"\n\r\f]+\\)?" - hywiki-word-line-and-column-numbers-regexp "?")) + (save-restriction + (narrow-to-region start end) + (goto-char (point-min)) + (when (search-forward wikiword nil t) (setq start (match-beginning 0) - end (match-end 0) - ;; No following char - wikiword (string-trim - (buffer-substring-no-properties start end))))))))) - - ;; Handle a non-delimited HyWikiWord with optional - ;; #section:Lnum:Cnum; if it is an Org link, it may - ;; optionally have a hy: link-type prefix. Ignore - ;; wikiwords preceded by any non-whitespace - ;; character, except any of these: "([\"'`'" - (t (let ((case-fold-search nil)) - (progn - ;; May be a closing delimiter that we have to skip past - (skip-chars-backward (regexp-quote (hywiki-get-buttonize-characters))) - ;; Skip past HyWikiWord or section - (skip-syntax-backward "^-$()<>._\"\'") - (skip-chars-backward "-_*#:[:alnum:]")) - (when (hywiki-maybe-at-wikiword-beginning) - (cond ((looking-at hywiki--word-and-buttonize-character-regexp) - (setq start (match-beginning 1) - end (match-end 1) - wikiword (string-trim - (buffer-substring-no-properties start end)))) - ((and (looking-at hywiki-word-with-optional-suffix-regexp) - ;; Can't be followed by a # character - (/= (or (char-after (match-end 0)) 0) - ?#)) - (setq start (match-beginning 0) - end (match-end 0) - ;; No following char - wikiword (string-trim - (buffer-substring-no-properties start end))))))))) - (if range-flag - (list wikiword start end) - wikiword))))))) + end (match-end 0)))))) + (hywiki-word-is-p wikiword)) + + ;; Handle a delimited HyWikiWord reference + ;; with multiple words in its section, + ;; e.g. (MyWikiWord WikiWord#one two three). + ;; That is, allow for spaces in the section + ;; and possible multiple WikiWords within the + ;; delimiters. + ((let ((case-fold-search nil) + (bol (line-beginning-position)) + opoint) + ;; May be a HyWikiWord ending character to skip past + (skip-chars-backward (hywiki-get-buttonize-characters) bol) + (setq opoint (point)) + ;; Skip past HyWikiWord or section + (skip-syntax-backward "^$()<>._\"\'" bol) + (unless (= (or (char-before) 0) ?#) + (goto-char opoint) + (skip-syntax-backward "^-$()<>._\"\'" bol)) + (skip-chars-backward "-_*#:[:alnum:]" bol) + (skip-syntax-backward "-" bol) + (when (and (cl-find (char-before) "\[\(\{\<\"") + (progn + (skip-syntax-forward "-") + (hywiki-maybe-at-wikiword-beginning)) + (looking-at (concat hywiki-word-regexp "\\(#[^][#()<>{}\"\n\r\f]+\\)?" + hywiki-word-line-and-column-numbers-regexp "?")) + (progn (goto-char (match-end 0)) + (skip-chars-forward "-"))) + (setq start (match-beginning 0) + end (match-end 0) + ;; No following char + wikiword (string-trim + (buffer-substring-no-properties start end)))))) + + ;; Handle a non-delimited HyWikiWord with optional + ;; #section:Lnum:Cnum; if it is an Org link, it may + ;; optionally have a hy: link-type prefix. Ignore + ;; wikiwords preceded by any non-whitespace + ;; character, except any of these: "([\"'`'" + (t (let ((case-fold-search nil)) + (skip-syntax-forward "-") + (when (hywiki-maybe-at-wikiword-beginning) + (when (looking-at (concat hywiki-org-link-type ":")) + (goto-char (match-end 0))) + (cond ((looking-at hywiki--word-and-buttonize-character-regexp) + (setq start (match-beginning 1) + end (match-end 1) + wikiword (string-trim + (buffer-substring-no-properties start end)))) + ((and (looking-at hywiki-word-with-optional-suffix-regexp) + ;; Can't be followed by a # character + (/= (or (char-after (match-end 0)) 0) + ?#)) + (setq start (match-beginning 0) + end (match-end 0) + ;; No following char + wikiword (string-trim + (buffer-substring-no-properties start end))))))))) + (if range-flag + (list wikiword start end) + wikiword) + (when range-flag + '(nil nil nil)))))) + ;; ) + (when range-flag + '(nil nil nil)))) (defun hywiki-word-at-point () "Return singular HyWikiWord at point with its suffix stripped or nil. -Point should be on the HyWikiWord itself." +Point should be on the HyWikiWord itself. Suffix is anything after +the # symbol. + +This does not test whether a referent exists for the HyWiki word; call +`hywiki-referent-exists-p' without an argument for that. + +A call to `hywiki-active-in-current-buffer-p' at point must return non-nil +or this will return nil." (hywiki-get-singular-wikiword (hywiki-word-strip-suffix (hywiki-word-at)))) ;;;###autoload @@ -3220,19 +3293,21 @@ delimited grouping." ;; single delimiters - highlight (funcall func 1)))) -(defun hywiki--maybe-de/highlight-sexp (func direction-number) - "De/highlight HyWikiWord with FUNC on a single square/angle bracket. +(defun hywiki--maybe-de/highlight-sexp (func direction-number &optional sexp-start sexp-end) + "De/highlight HyWikiWord with FUNC on a single paired delimiter char. DIRECTION-NUMBER is 1 for forward scanning and -1 for backward scanning." - (let* ((sexp-start (point)) - (sexp-end (scan-sexps sexp-start direction-number))) - (when (and sexp-start sexp-end) - (cl-destructuring-bind (start end) - ;; Point may be at end of sexp, so start and end may - ;; need to be reversed. - (list (min sexp-start sexp-end) (max sexp-start sexp-end)) - ;; Increment sexp-start so regexp matching excludes the - ;; delimiter and starts with the HyWikiWord. But include any - ;; trailing delimiter or regexp matching will not work. + (setq sexp-start (or sexp-start (point)) + sexp-end (or sexp-end (scan-sexps sexp-start direction-number))) + (when (and sexp-start sexp-end) + (cl-destructuring-bind (start end) + ;; Point may be at end of sexp, so start and end may + ;; need to be reversed. + (list (min sexp-start sexp-end) (max sexp-start sexp-end)) + ;; Increment sexp-start so regexp matching excludes the + ;; delimiter and starts with the HyWikiWord. But include any + ;; trailing delimiter or regexp matching will not work. + (save-restriction + (narrow-to-region (1+ start) end) (prog1 (funcall func (1+ start) end) (setq hywiki--highlighting-done-flag nil)))))) diff --git a/man/hyperbole.texi b/man/hyperbole.texi index e66da842f1..438331af61 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: 19-Jan-25 at 18:06:31 by Bob Weiner +@c Last-Mod: 16-Mar-25 at 10:23:12 by Bob Weiner @c %**start of header (This is for running Texinfo on a region.) @setfilename hyperbole.info @@ -30,8 +30,8 @@ @set txicodequoteundirected @set txicodequotebacktick -@set UPDATED January, 2025 -@set UPDATED-MONTH January 2025 +@set UPDATED March, 2025 +@set UPDATED-MONTH March 2025 @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 January 19, 2025. +Printed March 16, 2025. 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 -January 19, 2025 +March 16, 2025 Published by the Free Software Foundation, Inc. Author: Bob Weiner @@ -538,8 +538,8 @@ Smart Keyboard Keys @chapter Introduction This edition of the GNU Hyperbole Manual is for use with any version -9.0.2pre or greater of GNU Hyperbole. Hyperbole runs atop GNU Emacs 27.2 -or higher. It will trigger an error if your Emacs is older. +9.0.2pre or greater of GNU Hyperbole. Hyperbole runs atop GNU Emacs +28 or higher. It will trigger an error if your Emacs is older. This chapter summarizes the structure of the rest of the manual, describes Hyperbole, lists some of its potential applications, and @@ -8628,8 +8628,8 @@ the GNU Emacs Manual}). @group ;; Below are the lines to add: -(when (< emacs-major-version 27) - (error "Hyperbole requires Emacs 27 or above, not %d" +(when (< emacs-major-version 28) + (error "Hyperbole requires Emacs 28 or above, not %d" emacs-major-version)) (require 'package) (unless (package-installed-p 'hyperbole) @@ -8677,8 +8677,8 @@ the GNU Emacs Manual}). @group ;; Below are the lines to add: -(when (< emacs-major-version 27) - (error "Hyperbole requires Emacs 27 or above, not %d" +(when (< emacs-major-version 28) + (error "Hyperbole requires Emacs 28 or above, not %d" emacs-major-version)) (require 'package) (add-to-list 'package-archives @@ -8724,8 +8724,8 @@ the GNU Emacs Manual}). @group ;; Use this in your Emacs init file to install Straight (progn - (when (< emacs-major-version 27) - (error "Hyperbole requires Emacs 27 or above, not %d" + (when (< emacs-major-version 28) + (error "Hyperbole requires Emacs 28 or above, not %d" emacs-major-version)) (defvar bootstrap-version) (setq package-enable-at-startup nil) @@ -8785,8 +8785,8 @@ lines to your personal Emacs initialization file, @file{~/.emacs}: @lisp @group (unless (and (featurep 'hyperbole) hyperbole-mode) - (when (< emacs-major-version 27) - (error "Hyperbole requires Emacs 27 or above, not %d" + (when (< emacs-major-version 28) + (error "Hyperbole requires Emacs 28 or above, not %d" emacs-major-version)) (push "<directory-ending-with-hyperbole-where-you-unpacked>" load-path) diff --git a/test/hywiki-tests.el b/test/hywiki-tests.el index e406e55a75..0984235223 100644 --- a/test/hywiki-tests.el +++ b/test/hywiki-tests.el @@ -3,7 +3,7 @@ ;; Author: Mats Lidell ;; ;; Orig-Date: 18-May-24 at 23:59:48 -;; Last-Mod: 24-Feb-25 at 00:46:05 by Bob Weiner +;; Last-Mod: 14-Mar-25 at 17:35:00 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -203,14 +203,16 @@ line 2 ;; Matches a WikiWord (dolist (v '("WikiWord" "[WikiWord]" "[[WikiWord]]" "{WikiWord}" "(WikiWord)" "<WikiWord>" "<<WikiWord>>" "{[[WikiWord]]}" "([[WikiWord]])" - "[WikiWord AnotherWord]" + "[WikiWord AnotherWord WikiWord WikiWord]" )) (with-temp-buffer (org-mode) (insert v) (newline nil t) (goto-char 6) - (should (string= "WikiWord" (hywiki-word-at))))) + (if (string= "WikiWord" (hywiki-word-at)) + (should t) + (should-not v)))) ;; Does not match as a WikiWord (dolist (v '("WikiWord#")) @@ -219,7 +221,9 @@ line 2 (insert v) (newline nil t) (goto-char 6) - (should-not (string= "WikiWord" (hywiki-word-at))))) + (if (string= "WikiWord" (hywiki-word-at)) + (should-not v) + (should t)))) ;; Identifies as org link (Note: Not checked if target ;; exists.) AND matches WikiWord @@ -231,7 +235,9 @@ line 2 (goto-char 6) (font-lock-ensure) (should (hsys-org-face-at-p 'org-link)) - (should (string= "WikiWord" (hywiki-word-at))))) + (if (string= "WikiWord" (hywiki-word-at)) + (should t) + (should-not v)))) ;; Identifies as org link (Note: Not checked if target ;; exists.) AND DOES NOT match WikiWord @@ -243,7 +249,9 @@ line 2 (goto-char 6) (font-lock-ensure) (should (hsys-org-face-at-p 'org-link)) - (should-not (string= "WikiWord" (hywiki-word-at)))))) + (if (string= "WikiWord" (hywiki-word-at)) + (should-not v) + (should t))))) (hywiki-mode 0) (hy-delete-dir-and-buffer hywiki-directory)))) @@ -503,6 +511,7 @@ Both mod-time and checksum must be changed for a test to return true." (declare (indent 0) (debug t)) `(progn (progn ,@body) + (setq this-command 'org-return) (funcall 'hywiki-debuttonize-non-character-commands) (funcall 'hywiki-buttonize-character-commands) (funcall 'hywiki-buttonize-non-character-commands))) @@ -561,17 +570,20 @@ Both mod-time and checksum must be changed for a test to return true." (hywiki-tests--remove-hywiki-hooks) (with-temp-buffer (hywiki-mode 1) - (with-hywiki-buttonize-and-insert-hooks (insert "Wikiord ")) - (goto-char 5) + (with-hywiki-buttonize-and-insert-hooks + (insert "Wikiord ") + (goto-char 5)) (should (looking-at-p "ord")) (should-not (hproperty:but-get (point) 'face hywiki-word-face)) - (with-hywiki-buttonize-and-insert-hooks (insert "W")) - (goto-char 5) + (with-hywiki-buttonize-and-insert-hooks + (insert "W") + (goto-char 5)) (should (looking-at-p "Word")) (should (hproperty:but-get (point) 'face hywiki-word-face)) - (with-hywiki-buttonize-and-insert-hooks (delete-char 1)) + (with-hywiki-buttonize-and-insert-hooks + (delete-char 1)) (should (looking-at-p "ord")) (should-not (hproperty:but-get (point) 'face hywiki-word-face)))) (hywiki-tests--add-hywiki-hooks) @@ -581,32 +593,35 @@ Both mod-time and checksum must be changed for a test to return true." (ert-deftest hywiki-tests--verify-face-property-when-editing-wikiword-first-char () "Verify face property changes when WikiWord is edited in the first char position." - (let* ((hywiki-directory (make-temp-file "hywiki" t)) - (wikipage (cdr (hywiki-add-page "WikiWord")))) - (skip-unless (not noninteractive)) - (unwind-protect - (progn - (hywiki-tests--remove-hywiki-hooks) - (with-temp-buffer + (skip-unless (not noninteractive)) + (with-temp-buffer + (let* ((hywiki-directory (make-temp-file "hywiki" t)) + (wikipage (cdr (hywiki-add-page "WikiWord")))) + (unwind-protect + (progn + (hywiki-tests--remove-hywiki-hooks) (hywiki-mode 1) (with-hywiki-buttonize-and-insert-hooks (insert "WikiWord ")) (goto-char 1) (should (looking-at-p "Wiki")) (should (hproperty:but-get (point) 'face hywiki-word-face)) - (delete-char 1) - (hywiki-maybe-dehighlight-page-name t) + (with-hywiki-buttonize-and-insert-hooks + (delete-char 1)) (should (looking-at-p "iki")) (should-not (hproperty:but-get (point) 'face hywiki-word-face)) - (with-hywiki-buttonize-and-insert-hooks (insert "W")) - (goto-char 1) + (with-hywiki-buttonize-and-insert-hooks + (insert "W") + (goto-char 1)) (should (looking-at-p "Wiki")) - (should (hproperty:but-get (point) 'face hywiki-word-face)))) - (hywiki-tests--add-hywiki-hooks) - (hywiki-mode 0) - (hy-delete-files-and-buffers (list wikipage)) - (hy-delete-dir-and-buffer hywiki-directory)))) + (should (hproperty:but-get (point) 'face hywiki-word-face))) + ;; !! FIXME: Uncomment these lines. + ;; (hywiki-tests--add-hywiki-hooks) + ;; (hywiki-mode 0) + ;; (hy-delete-files-and-buffers (list wikipage)) + ;; (hy-delete-dir-and-buffer hywiki-directory) + )))) (ert-deftest hywiki-tests--convert-words-to-org-link () "Verify `hywiki-convert-words-to-org-links' converts WikiWords to org links." @@ -894,7 +909,8 @@ Note special meaning of `hywiki-allow-plurals-flag'." (let* ((hywiki-directory (make-temp-file "hywiki" t)) (wikiword (hy-make-random-wikiword))) (unwind-protect - (mocklet (((hypb:require-package 'org-roam) => t) + (mocklet ((cl-struct-org-roam-node-tags => nil) + ((hypb:require-package 'org-roam) => t) ((org-roam-node-read) => "node") (org-roam-node-title => "node-title")) (hywiki-add-org-roam-node wikiword) diff --git a/test/kcell-tests.el b/test/kcell-tests.el index 5d4ddeba58..9fed2b5f6c 100644 --- a/test/kcell-tests.el +++ b/test/kcell-tests.el @@ -7,14 +7,14 @@ ;; e-mail: ma...@gnu.org ;; ;; orig-date: 16-Feb-22 at 23:28:49 -;; last-mod: 23-Aug-24 at 13:27:11 by Bob Weiner +;; last-mod: 16-Mar-25 at 10:15:23 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; ;; Copyright (C) 2021-2024 Free Software Foundation, Inc. ;; Licensed under the GNU General Public License, version 3. ;; -;; This file is not part of Emacs. It requires Emacs 27.2 or above. +;; This file is not part of Emacs. It requires Emacs 28 or above. ;; This file is part of Hyperbole. ;; ;;; Commentary: