branch: externals/hyperbole commit deb608fa5f37f641c92f57d4e7da4939f9a22f25 Author: bw <r...@gnu.org> Commit: bw <r...@gnu.org>
hywiki.el - Many bug fixes and use section names for Org html ids --- ChangeLog | 64 ++++++++ hpath.el | 18 ++- hui-mini.el | 8 +- hywiki.el | 518 +++++++++++++++++++++++++++++++++++++++++++----------------- 4 files changed, 454 insertions(+), 154 deletions(-) diff --git a/ChangeLog b/ChangeLog index 46b5c47b3e..85099acc42 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2025-02-23 Bob Weiner <r...@gnu.org> + +* hywiki.el (hywiki-get-referent): Fix suffix match to use group 3 and add + suffix to referent-value here. + (hywiki-display-referent): Remove adding suffix to referent-value here. + +2025-02-22 Bob Weiner <r...@gnu.org> + +* hywiki.el (hywiki-publish-to-html): Advise 'org-export-get-reference' to use + modified Org headings as html ids. + (hywiki--org-export-get-reference, + hywiki--org-export-new-title-reference, + hywiki--org-format-reference): Add to implement the above new html + id generation scheme. + * hui-select.el (hui-select-punctuation): Add 'hui-select-markup-pair' so if on the opening char of an HTML tag for example with punctuation syntax, it is treated as a markup pair from `hui-select-thing'. @@ -10,6 +25,16 @@ so does not include extra whitespace and matches the result when 'hui-select-thing' is called interactively. +* hywiki.el (hywiki-referent-exists-p): + Fix bug where 'word' was given as the symbol :range to use as a flag + but then the call to 'hywiki-strip-org-link' mistakenly set it to nil. + (hywiki-word-set-auto-highlighting): Add so can call interactively + to restore HyWikiWord auto-highlighting after a command hook error. Call + when enabling 'hywiki-mode'. + + Fix bug where :range flag was not passed to 'hywiki-word-at' call; + manifested as selecting an entire string rather than the wikiword at point. + * hsys-ert.el (ert-should): Constrain matches for this ibtype to the current line when not in 'ert-results-mode'. This fixes a problem of having this ibtype trigger in the "*scratch*" buffer for example where an @@ -31,12 +56,33 @@ When displaying Assist Key help, remove actype and action attributes from button or actype display. +* hywiki.el (hywiki-directory-dired-edit): Remove bash-specific file + filtering since names the dir after the filter regex and this is + unattractive. Using directory-files to filter instead works fine. + Also, use 'hywiki-word-regexp' to match to page names rather than + a hardcoded regexp. + * hycontrol.el (require 'zoom-frm): Wrap in an 'ignore-errors' so if its required library, 'frame-cmds' is not installed, no error occurs and HyControl behaves works without the library. +* test/hact-tests.el (hact-tests--action-params-with-lambdas): Eliminate + byte compiler 'unused args' errors by starting args with underscore. + 2025-02-19 Bob Weiner <r...@gnu.org> +* hywiki.el (hywiki-maybe-dehighlight-page-name, + hywiki-maybe-highlight-page-name, + hywiki-maybe-highlight-page-names): In + non-'hywiki-highlight-all-in-prog-modes', highlight only in strings + as well as comments. + (hywiki-buttonize-non-character-commands, + hywiki-debuttonize-non-character-commands): Don't trigger these + pre- and post-command hooks in non-'hywiki-highlight-all-in-prog-modes' when + outside of strings and comments. + (hywiki-word-at): Whe match to wikiword via face highlight, ensure + it matches to the wikiword format regexp. + * hyrolo.el (hyrolo-expand-path-list): Fix to include a default file name even when the file does not yet exist. @@ -58,6 +104,24 @@ When compile, add (require 'hbut) for 'hbut:syntax-table'. Fix string selection in 'text-mode' by using 'hbut:syntax-table'. +* hywiki.el (hywiki-org-link-export): In html and markdown conversion, call + 'hpath:spaces-to-dashes-markup-anchor'. + (hywiki-referent-menu): Fix missing s typo in 'hywiki-add-sexpression'. + (hywiki-convert-words-to-org-links, hywiki-org-link-export): + Update doc string with specific formatting. + (hywiki-org-link-resolve): Rewrite to return full referent when not + a pathname. + (hywiki-referent-menu): Rename 'LinkPath' to 'pathLink' and properly + alphabetize entries by invocation character (first capital letter). + (hywiki-word-to-org-link): Add to convert a single HyWikiWord reference + to an Org link for use during publishing. Use in `hywiki-convert-words-to-org-links'. + +2025-02-09 Bob Weiner <r...@gnu.org> + +* hpath.el hpath:spaces-to-dashes-markup-anchor): Add. + (hpath:normalize-markup-anchor): Rename to + 'hpath:dashes-to-spaces-markup-anchor'. + 2025-02-08 Mats Lidell <ma...@gnu.org> * hywiki.el (hywiki--sitemap-file): Helper function for getting the sitemap diff --git a/hpath.el b/hpath.el index eafe1f2171..0b9a54822d 100644 --- a/hpath.el +++ b/hpath.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 1-Nov-91 at 00:44:23 -;; Last-Mod: 2-Feb-25 at 07:38:26 by Bob Weiner +;; Last-Mod: 16-Feb-25 at 10:04:57 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -1612,8 +1612,18 @@ but locational suffixes within the file are utilized." (kotl-mode:to-valid-position)) (current-buffer))))))))))) -(defun hpath:normalize-markup-anchor (anchor) - "Convert ANCHOR from current buffer into a a string matching its referent." +(defun hpath:spaces-to-dashes-markup-anchor (anchor) + "Replace dashes with spaces in ANCHOR if not a prog mode and no existing dashes." + (if (or (derived-mode-p 'prog-mode) + (string-match-p "-.* \\| .*-" anchor)) + anchor + ;; In Markdown or outline modes '-' characters in `anchor' are + ;; converted to dashes in references unless anchor contains both + ;; '-' and space characters, in which case no conversion occurs. + (subst-char-in-string ?\ ?- anchor))) + +(defun hpath:dashes-to-spaces-markup-anchor (anchor) + "Replace spaces with dashes with spaces in ANCHOR if not a prog mode and no existing dashes." (if (or (derived-mode-p 'prog-mode) (string-match-p "-.* \\| .*-" anchor)) anchor @@ -1652,7 +1662,7 @@ of the buffer." ;; Markdown or outline link ids are case ;; insensitive. (case-fold-search (not prog-mode)) - (anchor-name (hpath:normalize-markup-anchor anchor)) + (anchor-name (hpath:dashes-to-spaces-markup-anchor anchor)) (referent-regexp (format (cond ((or (derived-mode-p 'outline-mode) ;; Includes Org mode ;; Treat all caps filenames without suffix like outlines, e.g. README, INSTALL. diff --git a/hui-mini.el b/hui-mini.el index b5187ee54c..75a12aedab 100644 --- a/hui-mini.el +++ b/hui-mini.el @@ -3,7 +3,7 @@ ;; Author: Bob Weiner ;; ;; Orig-Date: 15-Oct-91 at 20:13:17 -;; Last-Mod: 30-Jan-25 at 19:44:11 by Mats Lidell +;; Last-Mod: 22-Feb-25 at 22:15:38 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -1035,13 +1035,13 @@ support underlined faces as well." (list '("HyWiki>") '("Act" hywiki-word-activate - "Activate HyWikiWord link at point or emulate a press of a Smart Key.") + "Create and display page for HyWikiWord at point or when none, emulate a press of a Smart Key.") '("Create" hywiki-word-create-and-display - "Create and display a new HyWiki referent, prompting with any existing referent names.") + "Create and display a new or existing HyWikiWord referent, prompting with any existing referent names.") '("EditPages" hywiki-directory-edit "Display and edit HyWiki directory.") '("FindReferent" hywiki-find-referent - "Prompt with completion for and display a HyWiki page ready for editing.") + "Prompt with completion for and display a HyWikiWord referent.") (when (fboundp 'consult-grep) ;; allow for autoloading '("GrepConsult" hywiki-consult-grep "Grep over HyWiki pages with interactive consult-grep.")) diff --git a/hywiki.el b/hywiki.el index ddd716e0e2..ff3d69cf9c 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: 9-Feb-25 at 10:10:14 by Bob Weiner +;; Last-Mod: 23-Feb-25 at 02:21:03 by Bob Weiner ;; ;; SPDX-License-Identifier: GPL-3.0-or-later ;; @@ -36,7 +36,8 @@ ;; the following contexts: ;; - HyWiki page buffers; ;; - non-special text buffers, when `hywiki-mode' is enabled; -;; - comments of programming buffers, when `hywiki-mode' is enabled. +;; - comments and strings in programming buffers, when +;; `hywiki-mode' is enabled. ;; ;; As HyWikiWords are typed, highlighting occurs after a trailing ;; whitespace or punctuation character is added, or when it is @@ -70,22 +71,29 @@ ;; The custom setting, `hywiki-word-highlight-flag' (default = t), ;; means HyWikiWords will be auto-highlighted within HyWiki pages. ;; Outside of such pages, `hywiki-mode' must also be enabled for such -;; auto-highlighting. -;; +;; auto-highlighting. Auto-highlighting depends on pre- and +;; `post-command-hook' settings. If an error occurs running one of +;; these, the associated hook is removed. To restore the auto-highlight +;; hooks either use {M-x hywiki-word-set-auto-highlighting RET} or +;; {C-u C-h h h h m} to toggle `hywiki-mode'; this also enables +;; auto-highlighting if `hywiki-word-highlight-flag' is non-nil. + ;; The custom setting, `hywiki-exclude-major-modes' (default = nil), is ;; a list of major modes to exclude from HyWikiWord auto-highlighting ;; and recognition. ;; ;; Within programming modes, HyWikiWords are highlighted/hyperlinked -;; within comments only. For programming modes in which you want -;; HyWikiWords recognized everywhere, add them to the custom setting, -;; `hywiki-highlight-all-in-prog-modes' (default = -;; '(lisp-interaction-mode)). +;; within comments and double-quoted strings only. For programming +;; modes in which you want HyWikiWords recognized everywhere, add +;; them to the custom setting, `hywiki-highlight-all-in-prog-modes' +;; (default = '(lisp-interaction-mode)). ;; -;; HyWiki adds one implicit button type to Hyperbole: -;; `hywiki-word' - creates and displays HyWikiWord pages; -;; This is one of the lowest priority implicit button types so that -;; it triggers only when other types are not recognized first. +;; HyWiki adds two implicit button types to Hyperbole: +;; `hywiki-word' - creates and displays HyWikiWord referents; +;; `hywiki-existing-word' - display an existing HyWikiWord referent. +;; +;; `hywiki-word' is one of the lowest priority implicit button types +;; so that it triggers only when other types are not recognized first. ;; ;; A HyWiki can be exported to HTML for publishing to the web via Org ;; mode's publish a project feature. {M-x hywiki-publish-to-html RET} @@ -128,7 +136,7 @@ ;;; Other required Elisp libraries ;;; ************************************************************************ -(require 'cl-lib) ;; For `cl-find' +(require 'cl-lib) ;; For `cl-find' and `cl-incf' (require 'hactypes) ;; For `link-to-file-interactively' (require 'hargs) (require 'hasht) @@ -451,7 +459,8 @@ Nil by default." (defconst hywiki-word-regexp "\\<\\([[:upper:]][[:alpha:]]+\\)\\>" - "Regexp that matches a HyWiki word only.") + "Regexp that matches a HyWiki word only. +Do not use a start or end line/string anchor in this regexp.") (defconst hywiki-word-section-regexp "\\(#[^][# \t\n\r\f]+\\)" @@ -561,35 +570,40 @@ Triggered by `post-command-hook' for non-character-commands, including deletion commands and those in `hywiki-non-character-commands'." (unless (or (minibuffer-window-active-p (selected-window)) (and (boundp 'edebug-active) edebug-active - (active-minibuffer-window))) + (active-minibuffer-window)) + (and (derived-mode-p 'prog-mode) + (not (apply #'derived-mode-p hywiki-highlight-all-in-prog-modes)) + ;; Not inside a comment or a string + (not (or (nth 4 (syntax-ppss)) (hypb:in-string-p))))) (when (or (memq this-command hywiki-non-character-commands) (and (symbolp this-command) (string-match-p "^\\(org-\\)?\\(delete-\\|kill-\\)\\|\\(-delete\\|-kill\\|insert\\)\\(-\\|$\\)" (symbol-name this-command)))) - (if (and (marker-position hywiki--buttonize-start) - (marker-position hywiki--buttonize-end)) - ;; This means the command just deleted an opening or closing - ;; delimiter of a range that now needs any HyWikiWords - ;; inside to be re-highlighted. - (save-excursion + (when (and (marker-position hywiki--buttonize-start) + (marker-position hywiki--buttonize-end)) + ;; This means the command just deleted an opening or closing + ;; delimiter of a range that now needs any HyWikiWords + ;; inside to be re-highlighted. + (save-excursion + (goto-char hywiki--buttonize-start) + (let ((opening-char (char-after)) + closing-char) + (when (memq opening-char '(?\( ?\")) + (delete-char 1)) + (goto-char hywiki--buttonize-end) + (setq closing-char (char-before)) + (when (memq closing-char '(?\) ?\")) + (delete-char -1) + (insert " ")) (goto-char hywiki--buttonize-start) - (let ((opening-char (char-after)) - closing-char) - (when (memq opening-char '(?\( ?\")) - (delete-char 1)) - (goto-char hywiki--buttonize-end) - (setq closing-char (char-before)) - (when (memq closing-char '(?\) ?\")) - (delete-char -1) - (insert " ")) - (goto-char hywiki--buttonize-start) - (hywiki-maybe-highlight-between-page-names) - (when (memq opening-char '(?\( ?\")) - (insert opening-char)) - (when (memq closing-char '(?\) ?\")) - (goto-char (1+ hywiki--buttonize-end)) - (delete-char -1) - (insert closing-char)))) - (hywiki-maybe-highlight-between-page-names))))) + (hywiki-maybe-highlight-between-page-names) + (when (memq opening-char '(?\( ?\")) + (insert opening-char)) + (when (memq closing-char '(?\) ?\")) + (goto-char (1+ hywiki--buttonize-end)) + (delete-char -1) + (insert closing-char) + )))) + (hywiki-maybe-highlight-between-page-names)))) (defun hywiki-debuttonize-non-character-commands () "Dehighlight any HyWikiWord before or after point. @@ -598,10 +612,15 @@ deletion commands and those in `hywiki-non-character-commands'." (when (and (markerp hywiki--buttonize-start) (markerp hywiki--buttonize-end)) (set-marker hywiki--buttonize-start nil) (set-marker hywiki--buttonize-end nil)) - (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)))) + (when (and (or (memq this-command hywiki-non-character-commands) + (and (symbolp this-command) + (string-match-p "\\`\\(org-\\)?\\(delete-\\|kill-\\)\\|-delete-\\|-kill-" + (symbol-name this-command)))) + (or (not (derived-mode-p 'prog-mode)) + (apply #'derived-mode-p hywiki-highlight-all-in-prog-modes) + ;; Inside a comment or a string + (nth 4 (syntax-ppss)) + (hypb:in-string-p))) (cl-destructuring-bind (start end) (hywiki-get-delimited-range) ;; includes delimiters ;; Use these to store any range of a delimited HyWikiWord#section @@ -694,7 +713,8 @@ See the Info documentation at \"(hyperbole)HyWiki\". (unless hywiki-mode-map (setq hywiki-mode-map (make-sparse-keymap))) ;; Next line triggers a call to `hywiki-maybe-highlight-wikiwords-in-frame' - (set-variable 'hywiki-word-highlight-flag t)) + (set-variable 'hywiki-word-highlight-flag t) + (hywiki-word-set-auto-highlighting 1)) ;; disable mode ;; Dehighlight HyWikiWords in this buffer when 'hywiki-mode' is ;; disabled and this is not a HyWiki page buffer. If this is a @@ -760,8 +780,8 @@ After successfully finding a page and reading it into a buffer, run (unless (hypb:buffer-file-name) (error "(hywiki-display-referent): No `wikiword' given; buffer must have an attached file")) (setq wikiword (file-name-sans-extension (file-name-nondirectory (hypb:buffer-file-name))))) - (let* ((suffix (when (string-match hywiki-word-suffix-regexp wikiword) - (substring wikiword (match-beginning 0)))) + (let* ((_suffix (when (string-match hywiki-word-suffix-regexp wikiword) + (substring wikiword (match-beginning 0)))) (referent (cond (prompt-flag (hywiki-create-referent wikiword)) ((hywiki-get-referent wikiword)) @@ -769,9 +789,6 @@ After successfully finding a page and reading it into a buffer, run (if (not referent) (error "(hywiki-display-referent): Invalid `%s' referent: %s" wikiword referent) - ;; If a referent type that can include a # or :L line - ;; number suffix, append it to the referent-value. - (setq referent (hywiki--add-suffix-to-referent suffix referent)) ;; Ensure highlight any page name at point in case called as a ;; Hyperbole action type (hywiki-maybe-highlight-page-name t) @@ -810,28 +827,25 @@ After successfully finding a page and reading it into a buffer, run "Add a HyWikiWord that activates a named Hyperbole global button.") '("HyRolo" (hywiki-add-hyrolo hkey-value) "Add a HyWikiWord that searches `hyrolo-file-list' for matches.") - ;; "{key series}" wikiword) - '("Keys" (hywiki-add-key-series hkey-value) - "Add a HyWikiWord that executes a key series.") ;; "(hyperbole)action implicit button" '("InfoIndex" (hywiki-add-info-index hkey-value) "Add a HyWikiWord that displays an Info index item.") ;; "(hyperbole)Smart Keys" + '("pathLink" (hywiki-add-path-link hkey-value) + "Add a HyWikiWord that links to a path and possible position.") '("infoNode" (hywiki-add-info-node hkey-value) "Add a HyWikiWord that displays an Info node.") - '("LinkPath" (hywiki-add-path-link hkey-value) - "Add a HyWikiWord that links to a path and possible position.") ;; "ID: org-id" '("OrgID" (hywiki-add-org-id hkey-value) "Add a HyWikiWord that displays an Org section given its Org ID.") - '("orgRoamNode" (hywiki-add-org-roam-node hkey-value) - "Add a HyWikiWord that displays an Org Roam node given its title.") ;; "pathname:line:col" ;; "#in-buffer-section" '("Page" (hywiki-add-page hkey-value) "Add/Reset a HyWikiWord to link to its standard HyWiki page.") ;; e.g. (kbd "key sequence") - '("Sexp" (hywiki-add-sexpresion hkey-value) + '("orgRoamNode" (hywiki-add-org-roam-node hkey-value) + "Add a HyWikiWord that displays an Org Roam node given its title.") + '("Sexp" (hywiki-add-sexpression hkey-value) "Add a HyWikiWord that evaluates an Elisp sexpression."))) "*Menu of HyWikiWord custom referent types of the form: \(LABEL-STRING ACTION-SEXP DOC-STR)." @@ -1294,7 +1308,7 @@ Use `hywiki-get-referent' to determine whether a HyWiki page exists." (called-interactively-p 'interactive)) (setq prompt-flag t)) (let* ((normalized-word (hywiki-get-singular-wikiword wikiword)) - (referent (hywiki-find-referent normalized-word prompt-flag))) + (referent (hywiki-find-referent wikiword prompt-flag))) (cond (referent) ((and (null referent) (hywiki-word-is-p normalized-word)) (when (hywiki-add-page normalized-word) @@ -1408,28 +1422,88 @@ per file to the absolute value of MAX-MATCHES, if given and not 0. If regexp max-matches (or path-list (list hywiki-directory))))) (defun hywiki-convert-words-to-org-links () - "Convert all highlighted HyWiki words in current buffer to Org links." + "Convert all highlighted HyWiki words in current buffer to Org links. +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]]\". + +If the reference is within the WikiWord page to which it refers, it +simplifies to: + \"[[Multi-Word Section]]\". + +The finalized Org link is then exported to html format by the Org +publish process." (barf-if-buffer-read-only) (hywiki-maybe-highlight-page-names) (let ((make-index (hywiki-org-get-publish-property :makeindex)) - wiki-word) + org-link + wikiword-and-section + wikiword) (hywiki-map-words (lambda (overlay) - (goto-char (overlay-end overlay)) - (if make-index - (progn - (setq wiki-word (buffer-substring-no-properties - (overlay-start overlay) - (overlay-end overlay))) - (when (string-match (concat hywiki-org-link-type ":") - wiki-word) - (setq wiki-word (substring wiki-word (match-end 0)))) - (insert "]]\n#+INDEX: " wiki-word "\n")) - (insert "]]")) + (setq wikiword-and-section + (buffer-substring-no-properties + (overlay-start overlay) + (overlay-end overlay))) (goto-char (overlay-start overlay)) - (if (looking-at (concat hywiki-org-link-type ":")) - (insert "[[") - (insert "[[" hywiki-org-link-type ":")) - (delete-overlay overlay))))) + (delete-region (overlay-start overlay) + (overlay-end overlay)) + (delete-overlay overlay) + (if (setq org-link (hywiki-word-to-org-link wikiword nil)) + (insert org-link) + (message + "(hywiki-convert-words-to-org-links): \"%s\" in \"%s\" produced nil org link output" + wikiword-and-section (buffer-name))) + (when make-index + (when (string-match (concat hywiki-org-link-type ":") + wikiword-and-section) + (setq wikiword (substring wikiword-and-section (match-end 0)))) + (insert "\n#+INDEX: " wikiword "\n")))))) + +(defun hywiki-word-to-org-link (link &optional description) +;; \"[[file:<hywiki-directory>/WikiWord.org::Multi-Word Section][WikiWord#Multi-Word Section]]\". + (let ((resolved-link (hywiki-org-link-resolve link :full-data))) + (when (stringp (car resolved-link)) + (let* ((path-word-suffix resolved-link) + (path (file-relative-name (nth 0 path-word-suffix))) + (path-stem (when path + (file-name-sans-extension path))) + (word (nth 1 path-word-suffix)) + (suffix (nth 2 path-word-suffix)) + (desc (cond (description) + (suffix (when word + (format "%s%s" word suffix))) + (word))) + suffix-no-hashmark) + (unless (and suffix (not (string-empty-p suffix))) + (setq suffix nil)) + (setq suffix-no-hashmark (when suffix (substring suffix 1))) + (when (string-equal path (file-name-nondirectory buffer-file-name)) + (setq path nil)) + (cond (desc + (if path + (if suffix + ;; "[[file:path-stem.org::suffix][desc]" + (format "[[file:%s.org::%s][%s]]" + path-stem suffix-no-hashmark desc) + ;; "[[file:path-stem.org][desc]]") + (format "[[file:%s.org][%s]]" path-stem desc)) + (if suffix + ;; "[[suffix][desc]]" + (format "[[%s][%s]]" suffix desc) + ;; "[[desc]]" + (format "[[%s]]" desc)))) + (path + ;; "[[file:path-stem.org][word]]" + (format "[[file:%s.org][%s]]" path-stem word))))))) (defun hywiki-maybe-at-wikiword-beginning () "Return non-nil if previous character is one preceding a HyWiki word. @@ -1452,16 +1526,12 @@ Use `dired' unless `action-key-modeline-buffer-id-function' is set to (defun hywiki-directory-dired-edit () "Use `dired' to edit HyWiki pages in current `hywiki-directory'." (interactive) - (let ((case-fold-search nil) - (shell-name (or shell-file-name ""))) - (if (string-match-p "bash\\(\\.exe\\)?$" shell-name) - (dired (concat hywiki-directory - "[[:upper:]][[:alpha:]]*" - (regexp-quote hywiki-file-suffix))) - (dired (cons hywiki-directory - (directory-files hywiki-directory nil - (format "^[A-Z][A-Za-z]*%s$" - (regexp-quote hywiki-file-suffix)))))))) + (let ((case-fold-search nil)) + (dired (cons hywiki-directory + (directory-files hywiki-directory nil + (format "^%s%s$" + hywiki-word-regexp + (regexp-quote hywiki-file-suffix))))))) (defun hywiki-directory-treemacs-edit () "Use `treemacs' to edit HyWiki pages in current `hywiki-directory'." @@ -1814,8 +1884,8 @@ If in a programming mode, must be within a comment. Use (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 - (nth 4 (syntax-ppss)) + ;; Non-nil if match is inside a comment or a string + (or (nth 4 (syntax-ppss)) (hypb:in-string-p)) t) (or on-page-name (cl-find (char-syntax last-command-event) @@ -1883,7 +1953,7 @@ the current page unless they have sections attached." (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 - (nth 4 (syntax-ppss)) + (or (nth 4 (syntax-ppss)) (hypb:in-string-p)) t) ;; (or on-page-name ;; (cl-find (char-syntax last-command-event) @@ -2109,13 +2179,13 @@ value of `hywiki-word-highlight-flag' is changed." (hywiki-maybe-dehighlight-page-names)) (dolist (hywiki-words-regexp hywiki--any-wikiword-regexp-list) (goto-char (point-min)) - (let ((highlight-in-comments-only + (let ((highlight-in-comments-and-strings-only (and (derived-mode-p 'prog-mode) (not (apply #'derived-mode-p hywiki-highlight-all-in-prog-modes))))) (while (re-search-forward hywiki-words-regexp nil t) - (when (if highlight-in-comments-only - ;; Non-nil if match is inside a comment - (nth 4 (syntax-ppss)) + (when (if highlight-in-comments-and-strings-only + ;; Non-nil if match is inside a comment or a string + (or (nth 4 (syntax-ppss)) (hypb:in-string-p)) t) (setq hywiki--start (match-beginning 1) hywiki--end (match-end 1)) @@ -2245,18 +2315,20 @@ value returns nil." If it is a pathname, expand it relative to `hywiki-directory'." (when (and (stringp wikiword) (not (string-empty-p wikiword)) (string-match hywiki-word-with-optional-suffix-exact-regexp wikiword)) - (let* ((_suffix (cond ((match-beginning 2) + (let* ((suffix (cond ((match-beginning 2) (prog1 (substring wikiword (match-beginning 2)) ;; Remove any #section suffix in `wikiword'. (setq wikiword (match-string-no-properties 1 wikiword)))) - ((match-beginning 4) - (prog1 (substring wikiword (match-beginning 4)) + ((match-beginning 3) + (prog1 (substring wikiword (match-beginning 3)) ;; Remove any :Lnum:Cnum suffix in `wikiword'. (setq wikiword (match-string-no-properties 1 wikiword)))))) (referent (hash-get (hywiki-get-singular-wikiword wikiword) (hywiki-get-referent-hasht)))) - referent))) + ;; If a referent type that can include a # or :L line + ;; number suffix, append it to the referent-value. + (setq referent (hywiki--add-suffix-to-referent suffix referent))))) (defun hywiki-get-page-files () "Return the list of existing HyWiki page file names. @@ -2549,22 +2621,28 @@ backend." (pcase format (`ascii (format "[%s] <%s:%s>" hywiki-org-link-type desc path)) (`html (format "<a href=\"%s.html%s\">%s</a>" - path-stem (or suffix "") + path-stem + (hpath:spaces-to-dashes-markup-anchor + (or suffix "")) desc)) (`latex (format "\\href{%s}{%s}" (replace-regexp-in-string "[\\{}$%&_#~^]" "\\\\\\&" path) desc)) - (`md (format "[%s](%s)" desc path)) + (`md (format "[%s](%s.md%s)" desc path-stem + (hpath:spaces-to-dashes-markup-anchor + (or suffix "")))) (`texinfo (format "@uref{%s,%s}" path desc)) (_ path)) link))) (defun hywiki-org-link-resolve (link &optional full-data) - "Resolve HyWiki word LINK to page. + "Resolve HyWikiWord LINK to its referent file or other type of referent. +If the referent is not a file type, return (referent-type . referent-value). + +Otherwise: Link may end with optional suffix of the form: (#|::)section:Lnum:Cnum. -With optional FULL-DATA non-nil, return a list in the form of (filename -word suffix); otherwise, with a section, return filename::section, with -just line and optionally column numbers, return filename:Lnum:Cnum and -without any suffix, return just the filename. Filename excludes the path. -If the page is not found, return nil." +With optional FULL-DATA non-nil, return a list in the form of (pathname +word suffix); otherwise, with a section, return pathname::section, with +just line and optionally column numbers, return pathname:Lnum:Cnum and +without any suffix, return just the pathname." (when (stringp link) (when (string-match (concat "\\`" hywiki-org-link-type ":") link) ;; Remove hy: link prefix @@ -2576,16 +2654,19 @@ If the page is not found, return nil." (substring link 0 (match-beginning 0)) link)) (referent (and word (hywiki-get-referent word))) - (filename (cdr referent))) - (when (stringp filename) - (cond - (full-data - (list filename word (concat suffix-type suffix))) - ((and suffix (not (string-empty-p suffix))) - (if (equal suffix-type ":L") - (concat filename suffix-type suffix) - (concat filename "::" suffix))) - (t filename)))))) + (referent-type (car referent)) + (pathname (when (memq referent-type '(page path-link)) + (cdr referent)))) + (if (stringp pathname) + (cond + (full-data + (list pathname word (concat suffix-type suffix))) + ((and suffix (not (string-empty-p suffix))) + (if (equal suffix-type ":L") + (concat pathname suffix-type suffix) + (concat pathname "::" suffix))) + (t pathname)) + referent)))) (defun hywiki-org-link-store () "Store a link to a HyWiki word at point, if any." @@ -2650,7 +2731,14 @@ Files are saved in: Customize this directory with: {M-x customize-variable RET hywiki-org-publishing-directory RET}." (interactive "P") - (org-publish-project "hywiki" all-pages-flag)) + ;; Export Org to html with useful link ids. + ;; Instead of random ids like \"orga1b2c3\", use heading titles, + ;; made unique when necessary." + (unwind-protect + (progn + (advice-add #'org-export-get-reference :override #'hywiki--org-export-get-reference) + (org-publish-project "hywiki" all-pages-flag)) + (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. @@ -2664,10 +2752,11 @@ Word may be of form: 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 - word (hywiki-strip-org-link word)) + (setq hywiki--page-name word) + (when (stringp word) + (setq word (hywiki-strip-org-link word))) (if (or (stringp word) - (setq word (hywiki-word-at))) + (setq word (hywiki-word-at word))) (unless (hywiki-get-referent word) (setq word nil)) (setq word nil)) @@ -2753,7 +2842,7 @@ Action Key press; with a prefix ARG, emulate an Assist Key press." (hkey-either arg)))) (defun hywiki-word-at (&optional range-flag) - "Return HyWikiWord and optional #section:Lnum:Cnum at point or nil. + "Return potential HyWikiWord and optional #section:Lnum:Cnum at point or nil. Point should be on the HyWikiWord itself. With optional RANGE-FLAG, return a list of (HyWikiWord start-position @@ -2768,9 +2857,10 @@ or this will return nil." (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)) + 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 @@ -2864,9 +2954,11 @@ these are handled by the Org mode link handler." (and (stringp word) (not (string-empty-p word)) (let (case-fold-search) (or (string-match hywiki-word-with-optional-suffix-exact-regexp word) - ;; For now this next version allows spaces and tabs in the suffix part - (eq (string-match hywiki-word-with-optional-spaces-suffix-exact-regexp word) - 0))))) + ;; For now this next version allows spaces and tabs in + ;; the suffix part + (eq 0 (string-match + hywiki-word-with-optional-spaces-suffix-exact-regexp + word)))))) (defun hywiki-word-read (&optional prompt) "Prompt with completion for and return an existing HyWikiWord. @@ -2890,27 +2982,46 @@ Function is called with 4 arguments: (SYMBOL SET-TO-VALUE OPERATION WHERE). Highlight/dehighlight HyWiki page names across all frames on change." (unless (memq operation '(let unlet)) ;; not setting global value (set symbol set-to-value) - (if set-to-value - ;; enabled - (progn (add-hook 'pre-command-hook 'hywiki-debuttonize-non-character-commands 95) - (add-hook 'post-command-hook 'hywiki-buttonize-non-character-commands 95) - (add-hook 'post-self-insert-hook 'hywiki-buttonize-character-commands) - (add-hook 'window-buffer-change-functions - 'hywiki-maybe-highlight-wikiwords-in-frame) - (add-to-list 'yank-handled-properties - '(hywiki-word-face . hywiki-highlight-on-yank)) - (hywiki-maybe-highlight-wikiwords-in-frame t)) - ;; disabled - (remove-hook 'pre-command-hook 'hywiki-debuttonize-non-character-commands) - (remove-hook 'post-command-hook 'hywiki-buttonize-non-character-commands) - (remove-hook 'post-self-insert-hook 'hywiki-buttonize-character-commands) - (hywiki-mode 0) ;; also dehighlights HyWiki words outside of HyWiki pages - (remove-hook 'window-buffer-change-functions - 'hywiki-maybe-highlight-wikiwords-in-frame) - (hywiki-maybe-highlight-wikiwords-in-frame t) - (setq yank-handled-properties - (delete '(hywiki-word-face . hywiki-highlight-on-yank) - yank-handled-properties))))) + (hywiki-word-set-auto-highlighting set-to-value))) + +(defun hywiki-word-set-auto-highlighting (arg) + "With a prefix ARG, turn on HyWikiWord auto-highlighting. +Otherwise, turn it off. + +Auto-highlighting uses pre- and post-command hooks. If an error +occurs with one of these hooks, the problematic hook is removed. +Invoke this command with a prefix argument to restore the +auto-highlighting." + (interactive "P") + (if arg + ;; enable + (progn + (when hywiki-word-highlight-flag + (add-hook 'pre-command-hook 'hywiki-debuttonize-non-character-commands 95) + (add-hook 'post-command-hook 'hywiki-buttonize-non-character-commands 95) + (add-hook 'post-self-insert-hook 'hywiki-buttonize-character-commands) + (add-hook 'window-buffer-change-functions + 'hywiki-maybe-highlight-wikiwords-in-frame) + (add-to-list 'yank-handled-properties + '(hywiki-word-face . hywiki-highlight-on-yank)) + (hywiki-maybe-highlight-wikiwords-in-frame t)) + (when (called-interactively-p 'interactive) + (if hywiki-word-highlight-flag + (message "HyWikiWord page auto-highlighting enabled") + (message "`hywiki-word-highlight-flag' must first be set to t to enable auto-highlighting")))) + ;; disable + (remove-hook 'pre-command-hook 'hywiki-debuttonize-non-character-commands) + (remove-hook 'post-command-hook 'hywiki-buttonize-non-character-commands) + (remove-hook 'post-self-insert-hook 'hywiki-buttonize-character-commands) + (hywiki-mode 0) ;; also dehighlights HyWiki words outside of HyWiki pages + (remove-hook 'window-buffer-change-functions + 'hywiki-maybe-highlight-wikiwords-in-frame) + (hywiki-maybe-highlight-wikiwords-in-frame t) + (setq yank-handled-properties + (delete '(hywiki-word-face . hywiki-highlight-on-yank) + yank-handled-properties)) + (when (called-interactively-p 'interactive) + (message "HyWikiWord page auto-highlighting disabled")))) ;;; ************************************************************************ ;;; Private functions @@ -2942,8 +3053,9 @@ invalid. Appended only if the referent-type supports suffixes." referent)))))) (defun hywiki--extend-yanked-region (start end) - "Return a list of (START END) with the specified range extended to include any delimited regions. -Typically used to extend a yanked region to fully include any strings or balanced pair delimiters." + "Extend range (START END) with any delimited regions and return the new range. +Typically used to extend a yanked region to fully include any strings +or balanced pair delimiters." (let ((delim-distance 0) (result (list start end)) opoint) @@ -3069,6 +3181,120 @@ DIRECTION-NUMBER is 1 for forward scanning and -1 for backward scanning." (funcall func (1+ start) end) (setq hywiki--highlighting-done-flag nil))))) +;;; ************************************************************************ +;;; Private Org export override functions +;;; ************************************************************************ + +;; Thanks to alphapapa for the GPLed code upon which these hywiki--org +;; functions are based. These change the html ids that Org export +;; generates to use the text of headings rather than randomly +;; generated ids. + +(require 'cl-extra) ;; for `cl-some' +(require 'ox) ;; for `org-export-get-reference' +(require 'url-util) ;; for `url-hexify-string' + +(defun hywiki--org-export-get-reference (datum info) + "Return a unique reference for DATUM, as a string. +Like `org-export-get-reference' but uses modified heading strings as +link ids rather than generated ids. To form an id, spaces in headings +are replaces with dashes and to make each id unique, heading parent +ids are prepended separated by '--'. + +DATUM is either an element or an object. INFO is the current +export state, as a plist. + +References for the current document are stored in +‘:internal-references’ property. Its value is an alist with +associations of the following types: + + (REFERENCE . DATUM) and (SEARCH-CELL . ID) + +REFERENCE is the reference string to be used for object or +element DATUM. SEARCH-CELL is a search cell, as returned by +‘org-export-search-cells’. ID is a number or a string uniquely +identifying DATUM within the document. + +This function also checks ‘:crossrefs’ property for search cells +matching DATUM before creating a new reference." + (let ((cache (plist-get info :internal-references))) + (or (car (rassq datum cache)) + (let* ((crossrefs (plist-get info :crossrefs)) + (cells (org-export-search-cells datum)) + ;; Preserve any pre-existing association between + ;; a search cell and a reference, i.e., when some + ;; previously published document referenced a location + ;; within current file (see + ;; `org-publish-resolve-external-link'). + ;; + ;; However, there is no guarantee that search cells are + ;; unique, e.g., there might be duplicate custom ID or + ;; two headings with the same title in the file. + ;; + ;; As a consequence, before reusing any reference to + ;; an element or object, we check that it doesn't refer + ;; to a previous element or object. + (new (or (when (org-element-property :raw-value datum) + ;; Heading with a title + (hywiki--org-export-new-title-reference datum cache)) + (cl-some + (lambda (cell) + (let ((stored (cdr (assoc cell crossrefs)))) + (when stored + (let ((old (org-export-format-reference stored))) + (and (not (assoc old cache)) stored))))) + cells) + (org-export-format-reference + (org-export-new-reference cache)))) + (reference-string new)) + ;; Cache contains both data already associated to + ;; a reference and in-use internal references, so as to make + ;; unique references. + (dolist (cell cells) (push (cons cell new) cache)) + ;; Retain a direct association between reference string and + ;; DATUM since (1) not every object or element can be given + ;; a search cell (2) it permits quick lookup. + (push (cons reference-string datum) cache) + (plist-put info :internal-references cache) + reference-string)))) + +(defun hywiki--org-export-new-title-reference (datum cache) + "Return new heading title reference for DATUM that is unique in CACHE." + (let* ((title (org-element-property :raw-value datum)) + (ref (hywiki--org-format-reference title)) + (parent (org-element-property :parent datum)) + raw-parent) + (while (--any (equal ref (car it)) + cache) + ;; Title not unique: make it so. + (if parent + ;; Append ancestor title. + (setq raw-parent (org-element-property :raw-value parent) + title (if (and (stringp raw-parent) (not (string-empty-p raw-parent))) + (concat raw-parent "--" title) + title) + ref (hywiki--org-format-reference title) + parent (org-element-property :parent parent)) + ;; No more ancestors: add and increment a number. + (when (string-match "\\`\\([[:unibyte:]]\\)+?\\(--\\([0-9]+\\)\\)?\\'" + ref) + (let ((num (match-string 3 ref))) + (setq parent (match-string 1 ref) + parent (if (stringp parent) (concat parent "--") "") + num (if num + (string-to-number num) + 0) + ref (format "%s%s" parent (cl-incf num))))))) + ref)) + +(defun hywiki--org-format-reference (title) + "Format TITLE string as an html id." + (url-hexify-string + (replace-regexp-in-string "\\[\\[\\([a-z]+:\\)?\\|\\]\\[\\|\\]\\]" "" + (subst-char-in-string + ?\ ?- + (substring-no-properties title))))) + ;;; ************************************************************************ ;;; Private initializations ;;; ************************************************************************