branch: externals/hyperbole commit 8eaf97e901ab88704d8266daaf0a67309ba23850 Merge: c1636759c0 ad181e27e0 Author: Robert Weiner <r...@gnu.org> Commit: GitHub <nore...@github.com>
Merge pull request #733 from rswgnu/rsw hywiki-word-at - Allow #section spaces only in single delim WikiWord ref --- ChangeLog | 20 +++++ hargs.el | 11 +-- hypb.el | 24 ++++-- hywiki.el | 292 +++++++++++++++++++++++++++++++++----------------------------- 4 files changed, 199 insertions(+), 148 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6e1f1e44bc..624b898e65 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,23 @@ +2025-05-23 Bob Weiner <r...@gnu.org> + +* hywiki.el (hywiki-word-with-optional-suffix-regexp): Change #section + expression ordinality from "??" to "?" to get longest rather than + shortest match. + (hywiki-word-with-optional-suffix-exact-regexp): Fix to not + allow # within a section. + (hywiki-word-at): Fix to allow spaces in #section only if the + delimited string is a single HyWikiWord reference. + +2025-05-22 Bob Weiner <r...@gnu.org> + +* hypb.el (hypb:in-string-p): Doc optional MAX-LINES arg. Add optional + RANGE-FLAG arg that returns (string-matched start-pos end pos) for + a match. + +* hywiki.el (hywiki-delimited-p): Ensure string check is limited to 2-lines + to match limit in the documentation. Make any non-nil value returned + be a list of (string-matched start-pos end-pos). + 2025-05-20 Mats Lidell <ma...@gnu.org> * test/kotl-mode-tests.el (kotl-mode--add-prior-cell): Add test. diff --git a/hargs.el b/hargs.el index d22f3da1fc..9b800c650a 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: 27-Feb-25 at 21:21:40 by Bob Weiner +;; Last-Mod: 22-May-25 at 22:49:03 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -128,10 +128,11 @@ line. START-DELIM and END-DELIM are strings that specify the argument delimiters. With optional START-REGEXP-FLAG non-nil, START-DELIM is treated as a regular expression. END-REGEXP-FLAG is similar. With optional LIST-POSITIONS-FLAG, return list -of (string-matched start-pos end-pos). Optional -EXCLUDE-REGEXP is compared against the match string with its delimiters -included; any string that matches this regexp is ignored. With optional -AS-KEY = \\='none, return t rather than the string result. Any other +of (string-matched start-pos end-pos), where the positions +exclude the delimiters. Optional EXCLUDE-REGEXP is compared +against the match string with its delimiters included; any string +that matches this regexp is ignored. With optional AS-KEY = +\\='none, return t rather than the string result. Any other non-nil value, means return the string normalized as a Hyperbole button key (no spaces)." (let* ((opoint (point)) diff --git a/hypb.el b/hypb.el index ec6bb56db2..b744533766 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: 20-May-25 at 23:46:09 by Mats Lidell +;; Last-Mod: 22-May-25 at 23:01:15 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -680,8 +680,13 @@ This will this install the Emacs helm package when needed." (error "(hypb:hkey-help-file): Non-existent file: \"%s\"" help-file)))))) -(defun hypb:in-string-p (&optional max-lines) - "Return t iff point is within a string. +(defun hypb:in-string-p (&optional max-lines range-flag) + "Return non-nil iff point is within a string. + +With optional MAX-LINES, an integer, match only within that many +lines from point. With optional RANGE-FLAG, return list +of (string-matched start-pos end-pos), where the positions +exclude the delimiters. To prevent searching back to the buffer start and producing slow performance, this limits its count of quotes found prior to point @@ -705,12 +710,15 @@ Quoting conventions recognized are: ;; `change-log-mode' due to its syntax-table. (let ((opoint (point)) (start (point-min)) - (open-match-string "")) + (open-match-string "") + str-start + str-end) (cl-destructuring-bind (open-regexp close-regexp) (eval hypb:in-string-modes-regexps) (save-match-data (when (and (re-search-backward open-regexp nil t) - (setq open-match-string (match-string 2)) + (setq open-match-string (match-string 2) + str-start (match-end 2)) ;; If this is the start of a string, it must be ;; at the start of line, preceded by whitespace ;; or preceded by another string end sequence. @@ -748,7 +756,11 @@ Quoting conventions recognized are: (regexp-quote open-match-string)) start (point)))) (re-search-forward close-regexp nil t) - t))))))))) + (if range-flag + (progn + (setq str-end (match-beginning 2)) + (list (buffer-substring-no-properties str-start str-end) str-start str-end)) + t)))))))))) (defun hypb:indirect-function (obj) "Return the function at the end of OBJ's function chain. diff --git a/hywiki.el b/hywiki.el index ab81e64d65..627bc6d376 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: 4-May-25 at 10:46:18 by Bob Weiner +;; Last-Mod: 23-May-25 at 02:39:29 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -495,7 +495,7 @@ Group 6 is any optional 0-based column number to jump to for any file-based referents.") (defconst hywiki-word-with-optional-suffix-exact-regexp - (concat "\\`" hywiki-word-regexp "\\(#[^][\n\r\f]+\\)??" + (concat "\\`" hywiki-word-regexp "\\(#[^][#\n\r\f]+\\)??" hywiki-word-line-and-column-numbers-regexp "?\\'") "Exact match regexp for a HyWiki word with an optional #section. The section may contain spaces or tabs but not square brackets; @@ -551,7 +551,7 @@ Non-nil is the default." (defun hywiki-debuttonize-non-character-commands () "Store any HyWikiWord before or after point for later comparison. -Triggered by `pre-command-hook' for non-character commands, including +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) (set-marker hywiki--buttonize-start nil) @@ -2982,6 +2982,7 @@ A call to `hywiki-active-in-current-buffer-p' at point must return non-nil or this will return nil." (if (hywiki-active-in-current-buffer-p) (save-excursion + ;; First look for an Org-type [[hy:WikiWord]] reference. ;; 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 @@ -2997,141 +2998,156 @@ or this will return nil." (end (nth 2 wikiword-start-end))) (with-syntax-table hywiki--org-mode-syntax-table (if (and (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 delimited HyWikiWord references with - ;; multiple words in their sections, - ;; e.g. (MyWikiWord WikiWord#one two three) - ((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)) - (when (hywiki-delimited-p) - (unless (progn - ;; Skip past HyWikiWord or section with - ;; possible whitespace - (skip-syntax-backward "^$()<>._\"\'" bol) - (unless (= (or (char-before) 0) ?#) - (goto-char opoint) - (skip-syntax-backward "^-$()<>._\"\'" bol)) - ;; Move to start of wikiword reference - (skip-chars-backward "-_*#:[:alnum:]" bol) - (skip-syntax-backward "-" bol) - ;; Preceding char must now be the - ;; opening delimiter or else there may - ;; be multiple non-section words within - ;; the delimiters, so reprocess and do - ;; not allow spaces in the #section part - (memq (char-syntax (or (char-before) 0)) - '(?\( ?\< ?\"))) - (goto-char opoint) - (skip-syntax-backward "^-$()<>._\"\'" bol) - ;; Move to start of wikiword reference - (skip-chars-backward "-_*#:[:alnum:]" bol) - (skip-syntax-backward "-" bol)) - (when (and - ;; (or (bolp) - ;; (string-match (regexp-quote - ;; (char-to-string (char-before))) - ;; "\[\(\{\<\"")) - (progn - (skip-chars-forward " \t") - (hywiki-maybe-at-wikiword-beginning)) - (looking-at (concat - hywiki-word-regexp - "\\(#[^][#()<>{}\"\n\r\f]+\\)?" - hywiki-word-line-and-column-numbers-regexp "?")) - ;; Can't be followed by a # character - (/= (or (char-after (match-end 0)) 0) - ?#) - (progn (goto-char (match-end 0)) - (skip-syntax-forward "-"))) - (setq start (match-beginning 0) - end (match-end 0) - ;; No following char - wikiword (string-trim - (buffer-substring-no-properties start end))))))) - - ;; Handle non-delimited HyWikiWord references - ;; with multiple dash-separated words in their sections, - ;; e.g. WikiWord#one-two-three. - ((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)) - (goto-char opoint) - (skip-syntax-backward "^-$()<>._\"\'" bol) - ;; Move to start of wikiword reference - (skip-chars-backward "-_*#:[:alnum:]" bol) - (skip-syntax-backward "-" bol) - (when (and (or (bolp) - (string-match (regexp-quote - (char-to-string (char-before))) - "\[\(\{\<\"")) - (progn - (skip-chars-forward " \t") - (hywiki-maybe-at-wikiword-beginning)) - (looking-at (concat - hywiki-word-regexp - "\\(#[^][#()<>{}\" \t\n\r\f]+\\)?" - hywiki-word-line-and-column-numbers-regexp "?")) - ;; Can't be followed by a # character - (/= (or (char-after (match-end 0)) 0) - ?#) - (goto-char (match-end 0))) - (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-chars-forward " \t") - (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) + ;; 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 delimited HyWikiWord references with + ;; multiple words in their sections, + ;; e.g. (MyWikiWord WikiWord#one two three) + ((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)) + (when (setq wikiword-start-end (hywiki-delimited-p)) ;; limited to 2 lines + (setq start (nth 1 wikiword-start-end) + end (nth 2 wikiword-start-end)) + (goto-char start) + (if (and (save-restriction + (narrow-to-region (point) end) + (looking-at hywiki-word-with-optional-suffix-exact-regexp)) + ;; WikiWord ref is the entirety of the string + (= end (match-end 0)) ;; Can't be followed by a # character (/= (or (char-after (match-end 0)) 0) ?#)) + (setq wikiword (match-string-no-properties 0) + start (match-beginning 0) + end (match-end 0)) + (goto-char opoint) + (unless (or (progn + (skip-chars-backward "-_*#:[:alnum:]" bol) + (hywiki-maybe-at-wikiword-beginning)) + (progn + ;; Skip past HyWikiWord or section with + ;; possible whitespace + (skip-syntax-backward "^$()<>._\"\'" bol) + (unless (= (or (char-before) 0) ?#) + (goto-char opoint) + (skip-syntax-backward "^-$()<>._\"\'" bol)) + ;; Move to start of wikiword reference + (skip-chars-backward "-_*#:[:alnum:]" bol) + (skip-syntax-backward "-" bol) + ;; Preceding char must now be the + ;; opening delimiter or else there may + ;; be multiple non-section words within + ;; the delimiters, so reprocess and do + ;; not allow spaces in the #section part + (memq (char-syntax (or (char-before) 0)) + '(?\( ?\< ?\")))) + (goto-char opoint) + (skip-syntax-backward "^-$()<>._\"\'" bol) + ;; Move to start of wikiword reference + (skip-chars-backward "-_*#:[:alnum:]" bol) + (skip-syntax-backward "-" bol)) + (when (and + ;; (or (bolp) + ;; (string-match (regexp-quote + ;; (char-to-string (char-before))) + ;; "\[\(\{\<\"")) + (progn + (skip-chars-forward " \t") + (hywiki-maybe-at-wikiword-beginning)) + (looking-at (concat + hywiki-word-regexp + "\\(#[^][#()<>{}\" \t\n\r\f]+\\)?" + hywiki-word-line-and-column-numbers-regexp "?")) + ;; Can't be followed by a # character + (/= (or (char-after (match-end 0)) 0) + ?#) + (progn (goto-char (match-end 0)) + (skip-syntax-forward "-"))) (setq start (match-beginning 0) end (match-end 0) ;; No following char wikiword (string-trim - (buffer-substring-no-properties start end))))))))) + (buffer-substring-no-properties start end)))))))) + + ;; Handle non-delimited HyWikiWord references + ;; with multiple dash-separated words in their sections, + ;; e.g. WikiWord#one-two-three. + ((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)) + (goto-char opoint) + (skip-syntax-backward "^-$()<>._\"\'" bol) + ;; Move to start of wikiword reference + (skip-chars-backward "-_*#:[:alnum:]" bol) + (skip-syntax-backward "-" bol) + (when (and (or (bolp) + (string-match (regexp-quote + (char-to-string (char-before))) + "\[\(\{\<\"")) + (progn + (skip-chars-forward " \t") + (hywiki-maybe-at-wikiword-beginning)) + (looking-at (concat + hywiki-word-regexp + "\\(#[^][#()<>{}\" \t\n\r\f]+\\)?" + hywiki-word-line-and-column-numbers-regexp "?")) + ;; Can't be followed by a # character + (/= (or (char-after (match-end 0)) 0) + ?#) + (goto-char (match-end 0))) + (setq start (match-beginning 0) + end (match-end 0) + ;; No following char + wikiword (string-trim (match-string-no-properties 0)))))) + + ;; 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-chars-forward " \t") + (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 (match-string-no-properties 1)))) + ((and (looking-at hywiki-word-with-optional-suffix-regexp) + ;; Can't be followed by a # character + (/= (or (char-after (match-end 0)) 0) + ?#)) + (setq start (match-beginning 0) + end (match-end 0) + ;; No following char + wikiword (string-trim (match-string-no-properties 0))))))))) ;; If `wikiword' has a #section, ensure there are ;; no invalid chars (if (and (stringp wikiword) (string-match "#" wikiword)) @@ -3165,6 +3181,7 @@ or this will return nil." (defun hywiki-delimited-p (&optional pos) "Return non-nil if optional POS or point is surrounded by matching delimiters. +Any non-nil value returned is a list of (string-matched start-pos end-pos). The delimited range must be two lines or less. Use `hywiki-word-at', which calls this, to determine whether there is @@ -3172,12 +3189,13 @@ a HyWikiWord at point." (save-excursion (when (natnump pos) (goto-char pos)) - (or (hypb:in-string-p) - (let ((range (hargs:delimited-p "[\[<\(\{]" "[\]\}\)\>]" t t t))) - (when range - ;; Ensure closing delimiter is a match for the opening one - (= (matching-paren (char-before (nth 1 range))) - (char-after (nth 2 range)))))))) + (or (hypb:in-string-p 2 t) + (let ((range (hargs:delimited "[\[<\(\{]" "[\]\}\)\>]" t t t))) + (and range + ;; Ensure closing delimiter is a match for the opening one + (= (matching-paren (char-before (nth 1 range))) + (char-after (nth 2 range))) + range))))) (defun hywiki-word-face-at-p (&optional pos) "Non-nil if but at point or optional POS has `hywiki-word-face' property."