branch: externals/hyperbole
commit 3709b590883dfbf4ad46088c7124f197e5c1aa9b
Author: bw <r...@gnu.org>
Commit: bw <r...@gnu.org>

    hywiki.el - Add hywiki minor mode and highlighting after punct
---
 ChangeLog     |  24 +++++++
 hui-em-but.el |  34 ++++++----
 hywiki.el     | 207 +++++++++++++++++++++++++++++++++++++++++-----------------
 3 files changed, 194 insertions(+), 71 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7659ae3216..4596b25827 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,27 @@
+2024-05-18  Bob Weiner  <r...@gnu.org>
+
+* hui-em-but.el (hproperty:but-clear-all-in-list, 
hproperty:but-get-all-in-region):
+    Add.
+
+* hywiki.el (org-mode-hook): Don't make 'find-file-hook' buffer local, as this
+    is not recommended.
+            (hywiki-pages-hasht): Rename to 'hywiki--pages-hasht'.
+           (hywiki-at-wikiword): Allow preceding char to be whitespace or
+    additionally any of these chars: (["'`'
+            ((hywiki-highlight-page-names, hywiki-at-wikiword,
+    hywiki-highlight-page-name): Don't limit to files within 
'hywiki-directory';
+    use whenever 'hywiki-mode' is enabled.
+            (hywiki-at-wikiword): Remove 'hywiki-allowed-modes' since now test
+    that hywiki-mode is enabled.
+            (hywiki-initialize-mode-map): Add and make punct. and 
non-square-bracket
+    and non-angle-bracket balanced expressions highlight HyWiki page name 
references.
+
+2024-05-15  Bob Weiner  <r...@gnu.org>
+
+* hywiki.el (org-mode-hook): Remove 'post-self-insert-hook' and instead 
highlight
+    HyWikiWords via new 'hywiki-mode' minor-mode and its SPC and RET key 
bindings
+    bound to hywiki-buttonize.
+
 2024-05-12  Bob Weiner  <r...@gnu.org>
 
 * hact.el (actype:act, action:params): Add Emacs 30 closure support.
diff --git a/hui-em-but.el b/hui-em-but.el
index 45543d676b..689da05227 100644
--- a/hui-em-but.el
+++ b/hui-em-but.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:    21-Aug-92
-;; Last-Mod:      5-May-24 at 09:44:17 by Bob Weiner
+;; Last-Mod:     18-May-24 at 10:42:36 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -182,6 +182,10 @@ de-highlighted."
     (remove-overlays nil nil 'face hproperty:but-face)
     (remove-overlays nil nil 'face hproperty:ibut-face)))
 
+(defun hproperty:but-clear-all-in-list (hbut-list)
+  "Delete all HBUT-LIST hproperties."
+  (mapc #'delete-overlay hbut-list))
+
 (defun hproperty:but-create (&optional regexp-match)
   "Highlight all named Hyperbole buttons in buffer.
 De-highlight buttons unless `hproperty:but-highlight-flag' is set.
@@ -229,20 +233,26 @@ moves over it."
 See `hproperty:but-get'."
   (overlay-end hproperty-but))
 
+(defun hproperty:but-get-all-in-region (start end &optional property value)
+  "Return all buttons in the current buffer between START and END.
+If optional PROPERTY and VALUE are given, return only the first button
+with that PROPERTY and VALUE."
+  (delq nil
+       (mapcar (lambda (overlay)
+                 (when (memq (overlay-get overlay (or property 'face))
+                             (if property
+                                 (list value)
+                               (list hproperty:but-face
+                                     hproperty:ibut-face
+                                     hproperty:flash-face)))
+                   overlay))
+               (overlays-in start end))))
+
 (defun hproperty:but-get (&optional pos property value)
-  "Get button at optional POS or point.
+  "Return button at optional POS or point.
 If optional PROPERTY and VALUE are given, return only the first button
 with that PROPERTY and VALUE."
-  (car (delq nil
-            (mapcar (lambda (overlay)
-                      (when (memq (overlay-get overlay (or property 'face))
-                                  (if property
-                                      (list value)
-                                    (list hproperty:but-face
-                                          hproperty:ibut-face
-                                          hproperty:flash-face)))
-                        overlay))
-                    (overlays-at (or pos (point)))))))
+  (car (hproperty:but-get-all-in-region pos (1+ pos) property value)))
 
 (defun hproperty:but-start (hproperty-but)
   "Return the end position of an HPROPERTY-BUT.
diff --git a/hywiki.el b/hywiki.el
index ffe46942c0..103df390e7 100644
--- a/hywiki.el
+++ b/hywiki.el
@@ -3,7 +3,7 @@
 ;; Author:       Bob Weiner
 ;;
 ;; Orig-Date:     21-Apr-24 at 22:41:13
-;; Last-Mod:      5-May-24 at 09:46:52 by Bob Weiner
+;; Last-Mod:     18-May-24 at 11:22:07 by Bob Weiner
 ;;
 ;; SPDX-License-Identifier: GPL-3.0-or-later
 ;;
@@ -40,7 +40,8 @@
 ;;  for any delimiters.  Simply type them out, e.g. Emacs and if a
 ;;  page exists for the word, it is automatically highlighted when:
 ;;    - a HyWiki page file is read in
-;;    - a whitespace character is inserted following a HyWiki word
+;;    - a whitespace character, ')', '}', or Org-mode punctuation
+;;      character is inserted following a HyWiki word
 ;;    - the Action Key is pressed to activate a HyWiki word button.
 ;;
 ;;  HyWiki links can also link to a section headline within a page by
@@ -85,10 +86,6 @@
 (defvar hywiki-file-suffix ".org"
   "File suffix (including period) to use when creating HyWiki pages.")
 
-(defvar hywiki-allowed-modes '(text-mode wiki-mode)
-  "Parent modes where HyWiki words are recognized without delimiters.
-Applies only when the file is below `hywiki-directory'.")
-
 (defconst hywiki-directory '"~/hywiki/"
   "Directory in which to find HyWiki page files.")
 
@@ -100,8 +97,6 @@ Applies only when the file is below `hywiki-directory'.")
 Otherwise, this prefix is not needed and HyWiki word Org links
 override standard Org link lookups.  See \"(org)Internal Links\".")
 
-(defvar hywiki-pages-hasht nil)
-
 (defconst hywiki-word-regexp
   "\\<\\([[:upper:]][[:alpha:]]+\\)\\>"
   "Regexp that matches a HyWiki word only.")
@@ -171,6 +166,80 @@ PROMPT-FLAG is 'exists, return nil unless the page already 
exists."
     (when page-file
       (hpath:find (concat page-file section)))))
 
+;;; ************************************************************************
+;;; hywiki minor mode
+;;; ************************************************************************
+
+(defun hywiki-buttonize ()
+  "Turn expression one character before point into a highlighted Hyperbole 
button.
+Do this only if the expression is an implicit button of hywiki type."
+  (interactive "*")
+  (insert last-input-event)
+  (hywiki-highlight-page-name))
+
+;; (defun hywiki-setup-org-mode-punctuation-remaps ()
+;;   "Remap punctuation keys in `org-mode` to `hywiki-buttonize`."
+;;   (let ((punctuation-chars ",.;:'\"-/\\?!()[]{}"))
+;;     (dolist (char punctuation-chars)
+;;       (let ((key (concat "<" char ">")))
+;;         (when (bound-and-true-p org-mode-map))))))
+
+(defun hywiki-get-org-insertion-punctuation-keys ()
+  "Return a string of Org self-insert keys that have punctuation syntax."
+  (let (key
+       cmd
+       key-cmds
+       result)
+    ;; org-self-insert-command bindings are just remaps inherited from
+    ;; global-map.  Create key-cmds list of parsable (key . cmd)
+    ;; combinations where key may be a (start-key . end-key) range of keys.
+    (map-keymap (lambda (key cmd) (setq key-cmds (cons (cons key cmd) 
key-cmds))) (current-global-map))
+    (dolist (key-cmd key-cmds (concat (nreverse result)))
+      (setq key (car key-cmd)
+           cmd (cdr key-cmd))
+      (when (eq cmd 'self-insert-command)
+       (cond ((and (characterp key)
+                   (= (char-syntax key) ?.))
+              ;; char with punctuation syntax
+              (setq result (cons key result)))
+             ((and (consp key)
+                   (characterp (car key))
+                   (characterp (cdr key))
+                   (<= (cdr key) 256))
+              ;; ASCII char range, some of which has punctuation syntax
+              (with-syntax-table org-mode-syntax-table
+                (dolist (k (number-sequence (car key) (cdr key)))
+                  (when (= (char-syntax k) ?.)
+                    (setq result (cons k result)))))))))))
+
+(defun hywiki-remap-org-insertion-punctuation-keys ()
+  "Remap Org self-insert punct. keys in `hywiki-mode` to `hywiki-buttonize`."
+  (mapc (lambda (c) (define-key hywiki-mode-map (char-to-string c) 
'hywiki-buttonize))
+       (hywiki-get-org-insertion-punctuation-keys)))
+
+;; Define the keymap for hywiki-mode.
+(defvar hywiki-mode-map nil
+  "Keymap for `hywiki-mode'.")
+
+;; Initialize hywiki-mode-map when null.
+(defun hywiki-initialize-mode-map ()
+  (setq hywiki-mode-map (make-sparse-keymap))
+  (hywiki-remap-org-insertion-punctuation-keys)
+  (define-key hywiki-mode-map ")" 'hywiki-buttonize)
+  (define-key hywiki-mode-map "]" 'hywiki-buttonize)
+  (define-key hywiki-mode-map ">" 'hywiki-buttonize)
+  (define-key hywiki-mode-map "}" 'hywiki-buttonize)
+  (define-key hywiki-mode-map (kbd "SPC") 'hywiki-buttonize)
+  (define-key hywiki-mode-map (kbd "RET") 'hywiki-buttonize))
+
+(unless hywiki-mode-map
+  (hywiki-initialize-mode-map))
+
+(define-minor-mode hywiki-mode
+  "A minor mode for HyWiki."
+  :lighter " HyWiki"
+  :keymap hywiki-mode-map)
+
 ;;; ************************************************************************
 ;;; Public functions
 ;;; ************************************************************************
@@ -194,33 +263,42 @@ nil, else return the file name of the page."
            (goto-char (if start-flag (point-min) (point-max)))
            page-file))))))
 
+(defun hywiki-maybe-at-wikiword-beginning ()
+  "Return non-nil if previous character is one preceding a HyWiki word.
+Does not test whether or not a page exists for the HyWiki word.
+Use `hywiki-get-page' to determine whether a HyWiki page exists."
+  ;; Ignore wikiwords preceded by any non-whitespace character, except
+  ;; any of these: ({"'`'
+  (when (or (bolp)
+           (memq (char-before) '(?\( ?\{ ?\" ?\' ?\` ?\  ?\t ?\n ?\r ?\f)))
+    t))
+
 (defun hywiki-at-wikiword (&optional org-link-flag)
   "Return HyWiki word and optional #section at point or nil if not on one.
 Does not test whether or not a page exists for the HyWiki word.
 Use `hywiki-get-page' to determine whether a HyWiki page exists."
-  (let (wikiword)
-    (if (or org-link-flag (hsys-org-link-at-p))
-       ;; Handle an Org link [[HyWiki word]] [[hy:HyWiki word]] or [[HyWiki 
word#section]]
-       (progn
-         (setq wikiword
-               (org-link-expand-abbrev
-                (org-link-unescape
-                 (string-trim (match-string-no-properties 1)))))
-         ;; Ignore hy:word hywiki:word since Org mode will display those
-         (when (hywiki-is-wikiword wikiword)
-           wikiword))
-      ;; Handle a HyWiki word with optional #section; if it is an Org
-      ;; link, it may or may not have a hy: link-type prefix.
-      (and (apply #'derived-mode-p hywiki-allowed-modes)
-          (string-prefix-p (expand-file-name hywiki-directory)
-                           (or buffer-file-name ""))
-          (save-excursion
-             (let ((case-fold-search nil))
-              (skip-chars-backward "-*#[:alnum:]")
-              ;; Ignore wikiwords preceded by any non-whitespace character
-              (and (or (bolp) (memq (preceding-char) '(?\[ ?\  ?\t ?\n ?\r 
?\f)))
-                   (looking-at hywiki-word-optional-section-regexp)
-                   (string-trim (match-string-no-properties 0)))))))))
+  (when hywiki-mode
+    (let (wikiword)
+      (if (or org-link-flag (hsys-org-link-at-p))
+         ;; Handle an Org link [[HyWiki word]] [[hy:HyWiki word]] or [[HyWiki 
word#section]].
+         (progn
+           (setq wikiword
+                 (org-link-expand-abbrev
+                  (org-link-unescape
+                   (string-trim (match-string-no-properties 1)))))
+           ;; Ignore hy:word hywiki:word since Org mode will display those.
+           (when (hywiki-is-wikiword wikiword)
+             wikiword))
+       ;; Handle a HyWiki word with optional #section; if it is an Org
+       ;; link, it may optionally have a hy: link-type prefix.
+       (save-excursion
+          (let ((case-fold-search nil))
+           (skip-chars-backward "-*#[:alnum:]")
+           ;; Ignore wikiwords preceded by any non-whitespace
+           ;; character, except any of these: (["'`'
+           (and (hywiki-maybe-at-wikiword-beginning)
+                (looking-at hywiki-word-optional-section-regexp)
+                (string-trim (match-string-no-properties 0)))))))))
 
 ;; Globally set these values to avoid using 'let' with stack allocations
 ;; within `hywiki-highlight-page-name' frequently.
@@ -236,18 +314,19 @@ Use `hywiki-get-page' to determine whether a HyWiki page 
exists."
       hywiki--start nil)
 
 (defun hywiki-highlight-page-names ()
-  "Highlight all non-Org link HyWiki page names in the buffer.
+  "Highlight all non-Org link HyWiki page names in a HyWiki buffer.
 Use `hywiki-word-face' to highlight.  Does not highlight references to
 the current page unless they have sections attached.
 
-Used as a `find-file-hook'."
+Automatically called as a `find-file-hook'."
   (interactive)
-  ;; Avoid doing any lets in this `post-self-insert-function' for efficiency
-
-  ;; Highlight HyWiki words only in files below `hywiki-directory'
+  ;; Avoid doing any lets for efficiency.
+  ;; Highlight HyWiki words in buffers where `hywiki-mode' is enabled
+  ;; or with attached files below `hywiki-directory'.
   (when (and hywiki-word-highlight-flag
-            (string-prefix-p (expand-file-name hywiki-directory)
-                             (or buffer-file-name "")))
+            (or hywiki-mode
+                (string-prefix-p (expand-file-name hywiki-directory)
+                                 (or buffer-file-name ""))))
     (save-excursion
       (save-restriction
        (setq hywiki--any-page-regexp (regexp-opt (hywiki-get-page-list) 'words)
@@ -263,9 +342,8 @@ Used as a `find-file-hook'."
                hywiki--end   (match-end 0))
          (save-excursion
            (goto-char hywiki--start)
-           ;; Ignore wikiwords preceded by any non-whitespace character
-           (when (or (bolp) (memq (preceding-char) '(?\  ?\t)))
-             ;; Include any #section
+           (when (hywiki-maybe-at-wikiword-beginning)
+             ;; Include any #section.
              (skip-syntax-forward "^-\)$\>.\"\'")
              (skip-chars-forward "-#[:alnum:]")
              (setq hywiki--end (point))
@@ -283,9 +361,7 @@ With optional ON-PAGE-NAME non-nil, assume point is within 
the page or
 section name.
 
 Use `hywiki-word-face' to highlight.  Does not highlight references to
-the current page unless they have sections attached.
-
-Used as a `post-self-insert-hook'."
+the current page unless they have sections attached."
   (interactive)
   (when (and hywiki-word-highlight-flag
             (or on-page-name
@@ -293,30 +369,40 @@ Used as a `post-self-insert-hook'."
                      (not (eq ?# last-command-event))
                      (memq (char-syntax last-command-event) '(?\  ?\) ?\$ ?\> 
?. ?\" ?\'))))
              (not executing-kbd-macro)
-             (not noninteractive)
-            (string-prefix-p (expand-file-name hywiki-directory)
-                             (or buffer-file-name "")))
+             (not noninteractive))
     (save-excursion
+      (when (= (char-syntax (char-before)) ?\))
+       ;; Clear any HyWikiWord highlighting that may just be a part
+       ;; of a larger balanced delimiter text with multiple words.
+       ;; If there is just a single HyWikiWord, it will be
+       ;; re-highlighted later in this function.
+       (ignore-errors
+         (let* ((sexp-end (point))
+                (sexp-start (scan-sexps sexp-end -1)))
+           (when sexp-start
+             (hproperty:but-clear-all-in-list
+              (hproperty:but-get-all-in-region sexp-start sexp-end 'face 
hywiki-word-face))))))
+
       (unless on-page-name
        ;; after page name
        (goto-char (max (1- (point)) (point-min))))
-      (skip-syntax-backward "^-\)$\>.\"\'")
+      (skip-syntax-backward "^-$().\"\'")
       (skip-chars-backward "#[:alpha:]")
 
       (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)
-      (if (and (looking-at hywiki-word-optional-section-regexp)
-              ;; Ignore wikiwords preceded by any non-whitespace character
-              (or (bolp) (memq (preceding-char) '(?\  ?\t ?\n ?\r ?\f)))
+      (if (and (hywiki-maybe-at-wikiword-beginning)
+              (looking-at hywiki-word-optional-section-regexp)
               (progn
                 (setq hywiki--page-name (match-string-no-properties 1)
                       hywiki--start (match-beginning 0)
                       hywiki--end   (match-end 0))
                 (and (hywiki-get-page hywiki--page-name)
                      ;; Ignore wikiwords preceded by any non-whitespace 
character
-                     (or (bolp) (memq (preceding-char) '(?\  ?\t))))))
+                     ;; (or (bolp) (memq (preceding-char) '(?\  ?\t)))
+                     )))
          (progn
            (setq hywiki--current-page (hywiki-get-buffer-page-name))
            ;; Don't highlight current-page matches unless
@@ -333,7 +419,7 @@ Used as a `post-self-insert-hook'."
                (hproperty:but-add hywiki--start hywiki--end 
hywiki-word-face))))
        ;; Remove any potential earlier highlighting since the
        ;; previous word may have changed.
-       (skip-syntax-backward "^-\)$\>.\"\'")
+       (skip-syntax-backward "^-$().\"\'")
        (hproperty:but-clear (point) 'face hywiki-word-face)))))
 
 (defun hywiki-is-wikiword (word)
@@ -378,7 +464,7 @@ These may have any alphanumeric file suffix, if files were 
added manually."
 
 (defun hywiki-get-page-hasht ()
   "Return hash table of existing HyWiki pages."
-  (or hywiki-pages-hasht (hywiki-make-pages-hasht)))
+  (or hywiki--pages-hasht (hywiki-make-pages-hasht)))
 
 (defun hywiki-get-page-list ()
   (hash-map #'cdr (hywiki-get-page-hasht)))
@@ -411,7 +497,7 @@ Use `hywiki-get-page' to determine whether a HyWiki page 
exists."
         (page-elts (mapcar (lambda (file)
                              (cons file (file-name-sans-extension 
(file-name-nondirectory file))))
                            page-files)))
-    (setq hywiki-pages-hasht (hash-make page-elts))))
+    (setq hywiki--pages-hasht (hash-make page-elts))))
 
 (when (featurep 'company)
 (defun hywiki-company-hasht-backend (command &optional _arg &rest ignored)
@@ -458,9 +544,12 @@ Use `hywiki-get-page' to determine whether a HyWiki page 
exists."
 
 (add-hook 'org-mode-hook
          (lambda ()
-           (make-local-variable 'find-file-hook)
-           (make-local-variable 'post-self-insert-hook)
-           (add-hook 'find-file-hook #'hywiki-highlight-page-names t)
-           (add-hook 'post-self-insert-hook #'hywiki-highlight-page-name t)))
+           (add-hook 'find-file-hook #'hywiki-highlight-page-names t)))
+
+;;; ************************************************************************
+;;; Private variables
+;;; ************************************************************************
+
+(defvar hywiki--pages-hasht nil)
 
 (provide 'hywiki)

Reply via email to