branch: externals/org-remark commit 822e730fc8d32eef80686f637dab8bcf2cbcdf53 Merge: c33e2b7c77 7a9c0454b2 Author: Noboru Ota <m...@nobiot.com> Commit: Noboru Ota <m...@nobiot.com>
Merge branch 'dev/1.3.0' --- NEWS | 16 +- README-elpa | 34 +- README.org | 30 +- ...0T184309_C_how-to-set-icons-to-be-svg-images.md | 6 +- docs/org-remark.org | 195 ++++- org-remark-eww.el | 4 +- org-remark-icon.el | 80 +- org-remark-info.el | 29 +- org-remark-line.el | 425 +++++++++++ org-remark-nov.el | 6 +- org-remark.el | 806 ++++++++++++++------- 11 files changed, 1314 insertions(+), 317 deletions(-) diff --git a/NEWS b/NEWS index 39e86c24e1..5ee5010050 100644 --- a/NEWS +++ b/NEWS @@ -1,7 +1,19 @@ -Current development version - - fix: compiler warning ‘org-remark-highlight-save’ is an obsolete... +Current development version (release candidate 1.3.0) + + Features: + + - Ability to highlight a whole line and show a mark on the margin (#71) + + - Option to delete the whole notes entry when highlight is removed if + there is no notes body text (#21) + + - `org-remark-open` to open the associated marginal notes file if + cursor outside a highlight (#72) + + - fix: compiler warning ‘org-remark-highlight-save’ is an obsolete... Version 1.2.1 - 2023-08-20 + - fix: org-remark-icon-mode is a void symbol Version 1.2.0 - 2023-08-20 diff --git a/README-elpa b/README-elpa index eca34e0481..a86998ea22 100644 --- a/README-elpa +++ b/README-elpa @@ -37,7 +37,8 @@ _________________ For customization, refer to the customization group `org-remark' or user manual: [online] or Info node `(org-remark) Customizing'. A [separate online article] has been written to guide you on how to - customize an icon (to be incorporated into the user manual later). + customize an icon (also part of the user manual. Evaluate (info + "(or-gremark) How to Set Org-remark to Use SVG Icons"). An [introductory video] (8 minutes) and [V1.1.0 release introduction] (12 minutes) are available on YouTube. @@ -91,8 +92,7 @@ _________________ This package is available on: - [GNU-ELPA] (releases only; equivalent to MELPA-Stable) - - [GNU-devel ELPA] (unreleased development branch; equivalent to - MELPA) + - [GNU-devel ELPA] (unreleased main branch; equivalent to MELPA) GNU ELPA should be already set up in your Emacs by default. If you wish to add GNU-devel ELPA, simply add its URL to `package-archives' @@ -146,6 +146,34 @@ _________________ | (define-key org-remark-mode-map (kbd "C-c n d") #'org-remark-delete)) `---- + Alternatively, you can use `use-package' to set up Org-remark. The + example provided below should be equivalent to the setup described + above. + + ,---- + | (use-package org-remark + | :bind (;; :bind keyword also implicitly defers org-remark itself. + | ;; Keybindings before :map is set for global-map. + | ("C-c n m" . org-remark-mark) + | :map org-remark-mode-map + | ("C-c n o" . org-remark-open) + | ("C-c n ]" . org-remark-view-next) + | ("C-c n [" . org-remark-view-prev) + | ("C-c n r" . org-remark-remove) + | ("C-c n d" . org-remark-delete)) + | ;; Alternative way to enable `org-remark-global-tracking-mode' in + | ;; `after-init-hook'. + | ;; :hook (after-init . org-remark-global-tracking-mode) + | :init + | ;; It is recommended that `org-remark-global-tracking-mode' be + | ;; enabled when Emacs initializes. Alternatively, you can put it to + | ;; `:after-init-hook' to load it after Emacs has been initialized. + | (org-remark-global-tracking-mode +1) + | (use-package org-remark-info :after info :config (org-remark-info-mode +1)) + | (use-package org-remark-eww :after eww :config (org-remark-eww-mode +1)) + | (use-package org-remark-nov :after nov :config (org-remark-nov-mode +1))) + `---- + [GNU-ELPA] <https://elpa.gnu.org/packages/org-remark.html> diff --git a/README.org b/README.org index 7a005d9409..f3e69bc3c5 100644 --- a/README.org +++ b/README.org @@ -20,7 +20,7 @@ For installation and minimum configuration, refer to [[#installation][Installati Getting Started in the user manual will get you started in 5 minutes: [[https://nobiot.github.io/org-remark/#Getting-Started][online]] or or Info node `(org-remark) Getting Started'. -For customization, refer to the customization group `org-remark' or user manual: [[https://nobiot.github.io/org-remark/#Customizing][online]] or Info node `(org-remark) Customizing'. A [[https://github.com/nobiot/org-remark/tree/main/docs/articles/2023-08-20T184309_C_how-to-set-icons-to-be-svg-images.md][separate online article]] has been written to guide you on how to customize an icon (to be incorporated into the user manual later). +For customization, refer to the customization group `org-remark' or user manual: [[https://nobiot.github.io/org-remark/#Customizing][online]] or Info node `(org-remark) Customizing'. A [[https://github.com/nobiot/org-remark/tree/main/docs/articles/2023-08-20T184309_C_how-to-set-icons-to-be-svg-images.md][separate online article]] has been written to guide you on how to customize an icon (also part of the user manual. Evaluate (info "(or-gremark) How to Set Org-remark to Use SVG Icons"). An [[https://youtu.be/c8DHrAsFiLc][introductory video]] (8 minutes) and [[https://youtu.be/BTFuS21N00k][V1.1.0 release introduction]] (12 minutes) are available on YouTube. @@ -57,7 +57,7 @@ An [[https://youtu.be/c8DHrAsFiLc][introductory video]] (8 minutes) and [[https: This package is available on: - [[https://elpa.gnu.org/packages/org-remark.html][GNU-ELPA]] (releases only; equivalent to MELPA-Stable) -- [[https://elpa.gnu.org/devel/org-remark.html][GNU-devel ELPA]] (unreleased development branch; equivalent to MELPA) +- [[https://elpa.gnu.org/devel/org-remark.html][GNU-devel ELPA]] (unreleased main branch; equivalent to MELPA) GNU ELPA should be already set up in your Emacs by default. If you wish to add GNU-devel ELPA, simply add its URL to ~package-archives~ like this: @@ -103,6 +103,32 @@ Below are example keybindings you might like to consider: (define-key org-remark-mode-map (kbd "C-c n d") #'org-remark-delete)) #+end_src +Alternatively, you can use ~use-package~ to set up Org-remark. The example provided below should be equivalent to the setup described above. + +#+begin_src emacs-lisp + (use-package org-remark + :bind (;; :bind keyword also implicitly defers org-remark itself. + ;; Keybindings before :map is set for global-map. + ("C-c n m" . org-remark-mark) + :map org-remark-mode-map + ("C-c n o" . org-remark-open) + ("C-c n ]" . org-remark-view-next) + ("C-c n [" . org-remark-view-prev) + ("C-c n r" . org-remark-remove) + ("C-c n d" . org-remark-delete)) + ;; Alternative way to enable `org-remark-global-tracking-mode' in + ;; `after-init-hook'. + ;; :hook (after-init . org-remark-global-tracking-mode) + :init + ;; It is recommended that `org-remark-global-tracking-mode' be + ;; enabled when Emacs initializes. Alternatively, you can put it to + ;; `:after-init-hook' to load it after Emacs has been initialized. + (org-remark-global-tracking-mode +1) + (use-package org-remark-info :after info :config (org-remark-info-mode +1)) + (use-package org-remark-eww :after eww :config (org-remark-eww-mode +1)) + (use-package org-remark-nov :after nov :config (org-remark-nov-mode +1))) +#+end_src + * Contributing and Feedback Create issues, discussion, and/or pull requests in the GitHub repository. All welcome. diff --git a/docs/articles/2023-08-20T184309_C_how-to-set-icons-to-be-svg-images.md b/docs/articles/2023-08-20T184309_C_how-to-set-icons-to-be-svg-images.md index 508cd5e888..6c811b237c 100644 --- a/docs/articles/2023-08-20T184309_C_how-to-set-icons-to-be-svg-images.md +++ b/docs/articles/2023-08-20T184309_C_how-to-set-icons-to-be-svg-images.md @@ -1,14 +1,14 @@ --- -title: How to Set Icons to be SVG Images +title: How to Set Org-remark to Use SVG Icons created: 2023-08-20 -modified: 20 August 2023 +modified: 16 September 2023 category: #creation id: 2023-08-20T184309 --- # How to Set Org-remark to Use SVG Icons - + I have released version 1.2.0 of Org-remark. In this version, highlights can display an icon to visually indicate that annotations exist for them. This was implemented in response to a feature request I received via YouTube comments and recorded in [issue #64](https://github.com/nobiot/org-remark/issues/64). diff --git a/docs/org-remark.org b/docs/org-remark.org index 956bc0a494..0b1110b5ca 100644 --- a/docs/org-remark.org +++ b/docs/org-remark.org @@ -1,7 +1,7 @@ #+title: Org-remark User Manual #+author: Noboru Ota <m...@nobiot.com> -#+macro: version 1.2.x -#+macro: modified 20 August 2023 +#+macro: version 1.3.x +#+macro: modified 06 October 2023 #+language: en #+export_file_name: org-remark.texi #+texinfo_dir_category: Emacs @@ -10,11 +10,11 @@ #+texinfo: @paragraphindent asis #+options: toc:nil ':t -This manual is for Org-remark version {{{version}}}. Some new features are mentioned in this manual that are currently in development; these are mentioned clearly as such. +This manual is for Org-remark version {{{version}}}. The new features introduced with version {{{version}}} are currently only avaiable [[https://elpa.gnu.org/devel/org-remark.html][GNU-devel ELPA]] until the new version is released to [[https://elpa.gnu.org/packages/org-remark.html][GNU-ELPA]]. Last updated: {{{modified}}}. -Org-remark lets you highlight and annotate text files and websites (EWW). +Org-remark lets you highlight and annotate text files, websites (EWW), EPUB books (nov.el) and Info documentation (Info-mode). #+texinfo: @insertcopying @@ -23,7 +23,7 @@ Org-remark lets you highlight and annotate text files and websites (EWW). :COPYING: t :END: -Copyright \copy 2021-2022 Free Software Foundation, Inc. +Copyright \copy 2021-2023 Free Software Foundation, Inc. #+begin_quote Permission is granted to copy, distribute and/or modify this document @@ -45,7 +45,7 @@ modify this GNU manual.” This package is available on: - [[https://elpa.gnu.org/packages/org-remark.html][GNU-ELPA]] (releases only; equivalent to MELPA-Stable) -- [[https://elpa.gnu.org/devel/org-remark.html][GNU-devel ELPA]] (unreleased development branch; equivalent to MELPA) +- [[https://elpa.gnu.org/devel/org-remark.html][GNU-devel ELPA]] (unreleased main branch; equivalent to MELPA) GNU ELPA should be already set up in your Emacs by default. If you wish to add GNU-devel ELPA, simply add its URL to ~package-archives~ like this: @@ -54,6 +54,8 @@ GNU ELPA should be already set up in your Emacs by default. If you wish to add G '("gnu-devel" . "https://elpa.gnu.org/devel/") :append) #+END_SRC +** Basic Setup + After installation, we suggest you put the setup below in your configuration. #+name: basic-setup @@ -90,7 +92,37 @@ Below are example keybindings you might like to consider: (define-key org-remark-mode-map (kbd "C-c n o") #'org-remark-open) (define-key org-remark-mode-map (kbd "C-c n ]") #'org-remark-view-next) (define-key org-remark-mode-map (kbd "C-c n [") #'org-remark-view-prev) - (define-key org-remark-mode-map (kbd "C-c n r") #'org-remark-remove)) + (define-key org-remark-mode-map (kbd "C-c n r") #'org-remark-remove) + (define-key org-remark-mode-map (kbd "C-c n d") #'org-remark-delete)) +#+end_src + +** Setup with ~use-pacakge~ + +Alternatively, you can use ~use-package~ to set up Org-remark. The example provided below should be equivalent to the setup described above. + +#+name: setup-with-use-package +#+begin_src emacs-lisp + (use-package org-remark + :bind (;; :bind keyword also implicitly defers org-remark itself. + ;; Keybindings before :map is set for global-map. + ("C-c n m" . org-remark-mark) + :map org-remark-mode-map + ("C-c n o" . org-remark-open) + ("C-c n ]" . org-remark-view-next) + ("C-c n [" . org-remark-view-prev) + ("C-c n r" . org-remark-remove) + ("C-c n d" . org-remark-delete)) + ;; Alternative way to enable `org-remark-global-tracking-mode' in + ;; `after-init-hook'. + ;; :hook (after-init . org-remark-global-tracking-mode) + :init + ;; It is recommended that `org-remark-global-tracking-mode' be + ;; enabled when Emacs initializes. Alternatively, you can put it to + ;; `:after-init-hook' to load it after Emacs has been initialized. + (org-remark-global-tracking-mode +1) + (use-package org-remark-info :after info :config (org-remark-info-mode +1)) + (use-package org-remark-eww :after eww :config (org-remark-eww-mode +1)) + (use-package org-remark-nov :after nov :config (org-remark-nov-mode +1))) #+end_src * Getting Started @@ -101,15 +133,22 @@ Below are example keybindings you might like to consider: ** Highlighting and Annotating #+findex: org-remark-mark +#+findex: org-remark-mark-line #+findex: org-remark-open #+findex: org-remark-view #+cindex: Marginal notes file +#+cindex: line-highlight +#+cindex: range-highlight +#+vindex: org-remark-line-margin-side +#+vindex: org-remark-notes-display-buffer-action -Once you have installed and set it up ([[#installation][Installation]]), Org-remark is simple to use. Select a part of text and call ~M-x org-remark-mark~ to highlight it. You will see the selected text gets highlighted. +Once you have installed and set it up ([[#installation][Installation]]), Org-remark is simple to use. Select a part of text and call ~M-x org-remark-mark~ to highlight it. You will see the selected text gets highlighted. This is a range-highlight. With the new version 1.3, you can also highlight a whole line in addition to a range of text by calling ~org-remark-mark-line~. Visually, instead of adding a highlight to the line, it will add a mark on the margin of the buffer (the left margin [...] The menu bar item "Org-remark" is available when you turn on ~org-remark-mode~. It helps you discover Org-remark's main commands. If you use Emacs version 28 or newer, a context menu is also available by right-clicking your mouse. Turn on the Emacs built-in ~context-menu-mode~ to enable the context menu. -To display the marginal notes for the highlight you have just marked, place your cursor on the highlight and call ~M-x org-remark-open~ or ~M-x org-remark-view~. This will display a new buffer to the left of the current buffer you are editing. The ~open~ command takes the cursor to the marginal notes buffer for you to edit notes; whereas the ~view~ command keeps the cursor in the current buffer only to display the marginal notes. Both commands narrow the *marginal notes file* to the entr [...] +To display the marginal notes for the highlight you have just marked, place your cursor on the highlight and call ~M-x org-remark-open~ or ~M-x org-remark-view~. This will create a new buffer to the left of the current buffer you are editing. You can customize where the marginal notes buffer is to be placed (see the documentation of customizing variable ~org-remark-notes-display-buffer-action~). + +The ~open~ command takes the cursor to the marginal notes buffer for you to edit notes; the ~view~ command keeps the cursor in the current buffer only to display the marginal notes. Both commands narrow the *marginal notes file* to the entry for the highlight under the cursor. The marginal notes file is a normal Org file. Edit your notes just as you would do with any other Org files and save the buffer. ** Navigating from One Highlight to Another @@ -137,6 +176,7 @@ The ~C-c n~ part is the prefix key common to all of them. If you set the keybind Org-remark has a default highlighter pen function, and comes with a set of two additional pens by default: - ~org-remark-mark~ :: default highlighter pen +- ~org-remark-mark-line~ :: default line-highlighter pen, which adds a mark on the margin instead of a range of text - ~org-remark-mark-yellow~ :: yellow highlight with "important" category in the marginal notes entry - ~org-remark-mark-red-line~ :: wavy red underline with "review" category in the marginal notes entry and "Review this" in tool-tips @@ -196,11 +236,20 @@ This is all you need to get started. For more detail, refer to the rest of this - Macro: ~org-remark-create~ label &optional face properties :: Create and register new highlighter pen functions. The newly created pen function will be registered to variable ~org-remark-available-pens~. It is used by ~org-remark-change~ as a selection list. - LABEL is the name of the highlighter and mandatory. The function will be named ~org-remark-mark-LABEL~. + ~LABEL~ is the name of the highlighter and mandatory. The function will be named ~org-remark-mark-LABEL~. + + The highlighter pen function will apply ~FACE~ to the selected region. ~FACE~ can be an anonymous face. When ~FACE~ is nil, this macro uses the default face ~org-remark-highlighter~. - The highlighter pen function will apply FACE to the selected region. FACE can be an anonymous face. When FACE is nil, this macro uses the default face ~org-remark-highlighter~. + ~PROPERTIES~ is a plist of pairs of a symbol and value. Each highlighted text region will have a corresponding Org headline in the notes file, and it can have additional properties in the property drawer from the highlighter pen. To do this, prefix property names with "=org-remark-=" or use "=CATEGORY=". - PROPERTIES is a plist of pairs of a symbol and value. Each highlighted text region will have a corresponding Org headline in the notes file, and it can have additional properties in the property drawer from the highlighter pen. To do this, prefix property names with "=org-remark-=" or use "=CATEGORY=". +As of version 1.3, you can use ~org-remark-create~ to create a new line-highlighter pen. Use the ~PROPERTIES~ parameter like this example below to specify ~org-remark-type~ to be ~line~. This tells Org-remark that the highlighter pen function creates a line-highlight instead of a default range-highlight. The ~LABEL~ does not need to include "line" in it, but it is recommended for consistency with the default command ~org-remark-mark-line~. + +#+begin_src emacs-lisp + ;; This creates a custom command named org-remark-line-alt. + (org-remark-create "line-alt" + 'diff-hunk-header + '(org-remark-type line)) +#+end_src #+ATTR_TEXINFO: :tag NOTE #+begin_quote @@ -224,7 +273,6 @@ Without this global minor mode, you would need to remember to activate ~org-rema :END: *** Marginal Notes File - #+cindex: Marginal notes file #+cindex: Org-remark properties for highlights @@ -255,8 +303,29 @@ You can leave the marginal notes file as it is without writing any notes. In thi In addition to the properties above that Org-remark reserves for itself, you can add your own custom properties and ~CATEGORY~ property. Use "org-remark-" as the prefix to the property names (or "CATEGORY", which is the only exception), and Org-remark put them to the property drawer of highlight's headline entry in the marginal notes buffer. Define the custom properties in your own custom pen functions (for how to create your own pens, [[#create-custom-pens][How to Create Custom Highligh [...] -*** =*marginal-notes*= Buffer +*** Organize Headlines in Marginal Notes Buffer in Your Way +#+vindex: org-remark-line-heading-title-max-length +#+vindex: org-remark-line-ellipsis +When you highlight a range of text or a line, Org-remark creates a corresponding headline in the marginal notes buffer with using Org mode. By default, the headline's title is either the selected text for the range-highlight or the first 40 characters of the line (longer line will be truncated and replaced by an ellipsis "…" -- both the 40 character limit and the ellipsis string can be customized with customizing variables ~org-remark-line-heading-title-max-length~ and ~org-remark-line-e [...] + +These are only default initial headline titles -- you are free to change them as you see fit. For example, you may add a line-highlight to an Elisp script file on the line you define a function. The initial title of the corresponding headline in the marginal notes buffer will be something like this below. + +#+begin_example +,** (defun name-of-the-function (arg)...) + :PROPERTIES:... + I will revisit this function later. +#+end_example + +It may make sense to change this to something like this, especially if you would rather organize these as ~TODO~ items so as to appear in your agenda. + +#+begin_example +,** TODO review fun name-of-the-function + :PROPERTIES:... + I will revisit this function later. +#+end_example + +*** =*marginal-notes*= Buffer #+cindex: *marginal notes* buffer #+cindex: Echo text / Tool tip on the Highlight @@ -324,22 +393,28 @@ You can customize the variable to use absolute file names, or to use a function ** How to Remove and Delete Highlights #+findex: org-remark-remove #+findex: org-remark-delete +#+vindex: org-remark-notes-auto-delete -You can remove the highlight under the cursor with command `org-remark-remove`. This command does not delete the corresponding entry in the marginal notes file. This is intentional; Org-roam is conservative when it deletes anything that the user might have edited. +You can remove the highlight under the cursor with command ~org-remark-remove~. This command does not delete the corresponding entry in the marginal notes file. This is intentional; Org-roam is conservative when it deletes anything that the user might have edited. If you wish to delete the entry and the highlight at the same time, pass a universal argument to `org-remark-remove` (e.g. by adding ~C-u~ before ~M-x org-remark-remove~) or use ~org-remark-delete~. ~org-remark-delete~ is identical with adding ~C-u~ to ~org-remark-remove~. The delete function will prompt for confirmation if it detects any notes present in the corresponding entry for the highlight in question in the marginal notes buffer. -- Command ~org-remark-remove~ :: - Remove the highlight at point. - It will remove the highlight and the properties from the marginal notes file, but will keep the headline and annotations. This is to ensure to keep any notes you might have written intact. - You can let this command DELETE the entire heading subtree for the highlight along with the annotations you have written, by passing a universal argument with ~C-u~. If you have done so by error, you could still ~undo~ it in the marginal notes buffer, but not from within the current buffer as adding and removing overlays are not part of the undo tree. +#+ATTR_TEXINFO: :tag NOTE +#+begin_quote +Note that you can undo the deletion or removal *in the marginal notes buffer* -- not in the source buffer where you mark text with a highlighter. Technically, highlights are overlays and are therefore not part of the undo tree in the source buffer. +#+end_quote + +As of version 1.3, you can use a new optional feature, automatic deletion. When the feature is enabled, Org-remark will automatically delete the highlight's headline when you delete text that includes a highlight, provided there is no marginal notes for it. If marginal notes are present for the highlight's headline, Org-remark only removes the highlight, deleting the properties from the highlight headline -- same operation as ~org-remark-remove~. Your marginal notes will be kept intact. [...] + +You can enable it with the new user option ~org-remark-notes-auto-delete~ like this example below. + +#+begin_src emacs-lisp + (setopt org-remark-notes-auto-delete :auto-delete) +#+end_src -- Command ~org-remark-delete~ :: - Delete the highlight at POINT and marginal notes for it. - This function will prompt for confirmation if there is any notes present in the marginal notes buffer. When the marginal notes buffer is not displayed in the current frame, it will be temporarily displayed together with the prompt for the user to see the notes. - If there is no notes, this function will not prompt for confirmation and will remove the highlight and deletes the entry in the marginal notes buffer. This command is identical with passing a universal argument to `org-remark-remove'. +Furthermore, with v1.3, if you pass a universal argument to ~org-remark-delete~ (e.g. ~C-u M-x org-remark-delete~) you can manually get Org-remark to do automatic deletion for the highlight at point. You can also pass double universal arguments to ~org-remark-remove~ (e.g. ~C-u C-u M-x org-remark-remove~) for the same operation. This should make sense because passing a single universal argument to ~org-remark-remove~ is the same as ~org-remark-delete~. Refer to the documentation of the [...] ** What is Automatic Adjustment of Highlight Positions? :PROPERTIES: @@ -376,6 +451,43 @@ You can customize the icon itself and its face with the following customizing va - Option: ~org-remark-icon-position-adjusted~ - Face: ~org-remark-highlighter-warning~ +** How to Set Org-remark to Use SVG Icons +:PROPERTIES: +:CUSTOM_ID: icon +:END: + +As of v1.2, highlights can display an icon. With this option, you can customize Org-remark to visually indicate that marginal notes exist for them instead of the default ASCII string "(*)", or to indicate that the Org-roam has automatically adjusted the highlight position (default ASCII string "(d)"; refer to [[#auto-adjust][What is Automatic Adjustment of Highlight Positions?]]). + +There are mainly two ways to set up SVG icons. + +1. Use the new built-in `icons` library available as of Emacs version 29.1 +2. Create a custom function and use a third-party library such as [[https://github.com/rougier/svg-lib][~svg-lib~]] by Nicolas Rougier + +Below is a quick guide on the first option to use the built-in library + +1. Get or create an SVG icon +2. Put the downloaded SVG file somewhere in your local +3. Use define-icon macro to create an icon with the SVG file + +First, create or download an icon as an ~.svg~ file. For example, [[https://boxicons.com/][Boxicons]] has a collection of SVG icons, which [[https://boxicons.com/usage#license][are provided under The MIT License]]. Second, place the SVG file in your local directory, e.g. ~~/.config/emacs/.cache/svg/bx-pen.svg~. And finally, use ~define-icon~ to define the icon in your configuration like this example below. + +#+begin_src emacs-lisp + (define-icon annotation nil + '((image "~/.config/emacs/.cache/svg/bx-pen.svg" + :height (0.8 . em))) + "Notes svg icon for Org-remark" + :version 29.1) +#+end_src + +Now the icon has been defined, you can set it to customizing variable ~org-remark-icon-notes~ like so: + +#+begin_src emacs-lisp + ;; This example uses `setopt' that is made available as of 29.1. `setq' works too. + (setopt org-remark-icon-notes (icon-string 'annotation)) +#+end_src + +If you have a buffer with highlights already open, use ~revert-buffer~ to reload the highlights. You should see the icon you have defined instead of the default “(*)” string. + ** Other Commands #+findex: org-remark-toggle #+findex: org-remark-change @@ -467,6 +579,43 @@ Org-remark's user options are available in the customization group ~org-remark~. - Option ~org-remark-highlights-after-load-functions~ :: Abnormal hook run after Org-remark loads the highlights from the note org buffer. It is run with OVERLAYS and NOTES-BUF as arguments. OVERLAYS are highlights. It is run with the source buffer as current buffer. This hook is used by the automatic adjustment feature. To know more about the feature itself, refer to [[#auto-adjust][What is Automatic Adjustment of Highlight Positions?]]. + +** Customizing Line Highlights + +#+vindex: org-remark-line-highlighter +#+vindex: org-remark-line-icon +#+vindex: org-remark-line-minimum-margin-width +#+vindex: org-remark-line-margin-padding +#+vindex: org-remark-line-margin-side +#+vindex: org-remark-line-heading-title-max-length +#+vindex: org-remark-line-ellipsis + +These are user options for line highlights available as of v1.3. They are listed in customizing group ~org-remark-line~. + +- Face: ~org-remark-line-highlighter~ :: + Face for the default line highlighter pen. + +- Option: ~org-remark-line-icon~ :: + Glyph displayed on the margin to indicate the line-highlight. You can set an SVG icon to it. Refer to [[#icon][How to Set Org-remark to Use SVG Icons]]. + +- Option: ~org-remark-line-minimum-margin-width~ :: + Margin width in a natural number. It can be a single number or a cons cell of two. When it is a single number, both the left and right margin widths will be the +same. When this customizing variable is a cons cell, the format is as follows: (LEFT-MARGIN-WIDTH . RIGHT-MARGIN-WIDTH). + +- Option: ~org-remark-line-margin-padding~ :: + Padding between the main text area the glyph/icon on the margin. + +- Option: ~org-remark-line-margin-side~ :: + The side of margin to display line highlights. +Left or Right can be chosen. + +- Option: ~org-remark-line-heading-title-max-length~ :: + Maximum length of string included as the highlight title. + +- Option ~org-remark-line-ellipsis~ :: + Ellipsis used when the highlight title is longer than maximum. +The maximum is set in ~org-remark-line-heading-title-max-length~. + * Known Limitations - No export together with the source file :: There is no out-of-the-box feature to export marginal notes together with the source file. Nevertheless, the marginal notes is a normal Org file, thus if the source file is also an Org file, you could use the built-in =include= feature, for example, to include relevant parts of the marginal notes into the export output. diff --git a/org-remark-eww.el b/org-remark-eww.el index 7d8ee02769..2435c10978 100644 --- a/org-remark-eww.el +++ b/org-remark-eww.el @@ -6,7 +6,7 @@ ;; Noboru Ota <m...@nobiot.com> ;; URL: https://github.com/nobiot/org-remark ;; Created: 23 December 2022 -;; Last modified: 25 June 2023 +;; Last modified: 19 August 2023 ;; Package-Requires: ((emacs "27.1") (org "9.4")) ;; Keywords: org-mode, annotation, note-taking, marginal-notes, wp @@ -41,7 +41,7 @@ (define-minor-mode org-remark-eww-mode "Enable Org-remark to work with EWW." :global t - :group 'org-remark + :group 'org-remark-eww (if org-remark-eww-mode ;; Enable (progn diff --git a/org-remark-icon.el b/org-remark-icon.el index b2c8a79e25..8fca035524 100644 --- a/org-remark-icon.el +++ b/org-remark-icon.el @@ -28,9 +28,11 @@ ;;; Code: (require 'cl-macs) +;; Silence compiler +(defvar org-remark-default-feature-modes) (defgroup org-remark-icon nil - "Highlight and annotate any text files with using Org mode." + "Enable `org-remark' to display glyph/icon indicators." :group 'org-remark :prefix "org-remark-icon" :link '(url-link :tag "GitHub" "https://github.com/nobiot/org-remark")) @@ -46,7 +48,16 @@ and/or text-property to the string. This means you can return a string with a display property to show an SVG icon instead of the underlying string. -Nil means no icon is to be displayed." +Nil means no icon is to be displayed. + +If you wants to use image icons (e.g. SVG image icon created with +package `icons', available Emacs 29.1 or higher), you're limited +to a single character with no space before and after the +character. This limitation does not apply to string of characters +without images, but it is generally assumed that the the value +set to this customizing variable will be a short string (e.g 3 +characters long with a pair of parentheses before and after a +single character, such as the default value.)" :safe #'stringp :type '(choice (string "(*)") @@ -63,12 +74,25 @@ and/or text-property to the string. This means you can return a string with a display property to show an SVG icon instead of the underlying string. -Nil means no icon is to be displayed." +Nil means no icon is to be displayed. + +If you wants to use image icons (e.g. SVG image icon created with +package `icons', available Emacs 29.1 or higher), you're limited +to a single character with no space before and after the +character. This limitation does not apply to string of characters +without images, but it is generally assumed that the the value +set to this customizing variable will be a short string (e.g 3 +characters long with a pair of parentheses before and after a +single character, such as the default value." :safe #'stringp :type '(choice (string "(d)") (function))) +;; Register a mode for automatic enablement at the same time as +;; `org-remark-mode'. +(add-to-list 'org-remark-default-feature-modes #'org-remark-icon-mode) + ;;;###autoload (define-minor-mode org-remark-icon-mode "Enable Org-remark to display icons. @@ -85,26 +109,22 @@ The icons currently available are defined in `org-remark-icons'." ;; Add-icons should be the last function because other functions may do ;; something relevant for an icon -- e.g. adjust-positon." (add-hook 'org-remark-highlights-after-load-functions - #'org-remark-highlights-add-icons 80 :local)) + #'org-remark-highlights-add-icons-maybe 80 :local)) ;; Disable (remove-hook 'org-remark-highlights-toggle-hide-functions #'org-remark-icon-toggle-hide :local) (remove-hook 'org-remark-highlights-toggle-show-functions #'org-remark-icon-toggle-show :local) (remove-hook 'org-remark-highlights-after-load-functions - #'org-remark-highlights-add-icons :local))) + #'org-remark-highlights-add-icons-maybe :local))) (defvar org-remark-icons (list (list 'notes - (lambda (ov) - (and org-remark-icon-notes - (overlay-get ov '*org-remark-note-body))) + #'org-remark-icon-notes-p nil) (list 'position-adjusted - (lambda (ov) - (and org-remark-icon-position-adjusted - (overlay-get ov '*org-remark-position-adjusted))) + #'org-remark-icon-position-adjusted-p 'org-remark-highlighter-warning)) "List of icons enabled. It is an alist. Each element is a list of this form: @@ -121,6 +141,14 @@ for ICON-NAME will be added to the highlight. DEFAULT FACE must be a named face. It is optinal and can be nil.") +(defun org-remark-icon-notes-p (ov) + (and org-remark-icon-notes + (overlay-get ov '*org-remark-note-body))) + +(defun org-remark-icon-position-adjusted-p (ov) + (and org-remark-icon-position-adjusted + (overlay-get ov '*org-remark-position-adjusted))) + (defun org-remark-icon-toggle-hide (highlight) (overlay-put highlight '*org-remark-icons (overlay-get highlight 'after-string)) (overlay-put highlight 'after-string nil)) @@ -129,10 +157,11 @@ DEFAULT FACE must be a named face. It is optinal and can be nil.") (overlay-put highlight 'after-string (overlay-get highlight '*org-remark-icons)) (overlay-put highlight '*org-remark-icons nil)) -(defun org-remark-highlights-add-icons (overlays _notes-buf) +(defun org-remark-highlights-add-icons-maybe (overlays _notes-buf) "Add icons to OVERLAYS. Each overlay is a highlight." (dolist (ov overlays) + ;; icons added to line highlighters differently from normal ones. (cl-flet ((add-icon-maybe (icon) (cl-destructuring-bind (icon-name pred default-face) icon @@ -140,7 +169,22 @@ Each overlay is a highlight." (org-remark-icon-propertize icon-name ov default-face))))) (let ((icon-string (mapconcat #'add-icon-maybe org-remark-icons))) - (when icon-string (overlay-put ov 'after-string icon-string)))))) + ;; `mapconcat' returns "" when all function calls for SEQUENCE + ;; return nil, I guess to guarantee the result is a string + (when (and icon-string + (not (string= icon-string ""))) + (org-remark-icon-overlay-put + ov icon-string + (overlay-get ov 'org-remark-type))))))) + +(cl-defgeneric org-remark-icon-overlay-put (_ov _icon-string _org-remark-type) + "Default method to deal with icon. + This is used when a method specific \\='org-remark-type\\=' not + implemented." + (ignore)) + +(cl-defmethod org-remark-icon-overlay-put (ov icon-string (_org-remark-type (eql nil))) + (overlay-put ov 'after-string icon-string)) (defun org-remark-icon-propertize (icon-name highlight default-face) "Return a propertized string. @@ -168,11 +212,19 @@ of them. All it needs to do is to return a string that represents an icon, typically propertized with a face." (let ((icon (symbol-value (intern (concat "org-remark-icon-" (symbol-name icon-name))))) - (highlight-face (overlay-get highlight 'face)) + (highlight-face (org-remark-icon-highlight-get-face + highlight + (overlay-get highlight 'org-remark-type))) (default-face default-face)) (if (functionp icon) (funcall icon icon-name highlight-face default-face) (propertize icon 'face (if default-face default-face highlight-face))))) +(cl-defgeneric org-remark-icon-highlight-get-face (highlight _org-remark-type) + "Return the face of the HIGHLIGHT overlay. +This is default method for range-highlights." + (overlay-get highlight 'face)) + + (provide 'org-remark-icon) ;;; org-remark-icon.el ends here diff --git a/org-remark-info.el b/org-remark-info.el index d2b817ae7f..1a8ce90a6e 100644 --- a/org-remark-info.el +++ b/org-remark-info.el @@ -5,7 +5,7 @@ ;; Author: Noboru Ota <m...@nobiot.com> ;; URL: https://github.com/nobiot/org-remark ;; Created: 16 July 2023 -;; Last modified: 20 August 2023 +;; Last modified: 06 October 2023 ;; Package-Requires: ((emacs "27.1") (org "9.4")) ;; Keywords: org-mode, annotation, note-taking, marginal-notes, wp @@ -60,13 +60,13 @@ (defvar org-remark-prop-source-file) (defvar org-remark-mode) (declare-function org-remark-highlights-load "org-remark") -(declare-function org-remark-mode "orgremark") +(declare-function org-remark-mode "org-remark") ;;;###autoload (define-minor-mode org-remark-info-mode "Enable Org-remark to work with `Info-mode' for Info documentation reader." :global t - :group 'org-remark + :group 'org-remark-info (if org-remark-info-mode ;; Enable (progn @@ -90,12 +90,14 @@ It is necessary as this function is intended to be used as part of advice for `Info-goto-node', which gets arguments passed to it. `org-remark-highlights-load' should be called with no arguments for the purpose of `org-remark-info-mode'." - ;; Enabling `org-remark-mode' runs `org-remark-highlight', which would - ;; result in duplicating the highlights if - ;; `org-remark-highlights-load' is run again. As this function must be - ;; run only once for initial load and only once for subsequent - ;; re-load, initial load and re-load needs to be differentiated. This - ;; `if' clause is meant to do this. + ;; Enabling `org-remark-mode' runs `org-remark-highlights-load', which + ;; would result in duplicating the highlights. As this function should + ;; be run only once for initial load or only once for subsequent + ;; re-load. This `if' statement is to differentiate the initial load + ;; when no Info node has been opened from subsequent reloads when the + ;; user moves to another Info node. In addition, `featurep' is used + ;; because variable `org-remark-mode' may not have been loaded yet to + ;; avoid symbol void. (if (or (not (featurep 'org-remark)) (not org-remark-mode)) (org-remark-mode +1) @@ -110,9 +112,7 @@ arguments for the purpose of `org-remark-info-mode'." (defun org-remark-info-link (_filname _point) "Return \"info:\" link with current point in `Info-mode' buffer. - This function only works when the mode is `Info-mode'. - Assume the point is on the highlight in source Info document buffer and `ol-info' is loaded. The latter is necessary for `org-store-link' to work wiht Info buffer." @@ -121,11 +121,8 @@ buffer and `ol-info' is loaded. The latter is necessary for (cl-defmethod org-remark-highlight-get-constructors (&context (major-mode Info-mode)) "Construct lists for creating MAJOR-MODE specific hierarchy. - -This method is for `Info-mode'. - -Return the value in a alist like this: - +This method is for `Info-mode'. Return the value in a alist like +this: (SOURCE-FILENAME-FN TITLE-FN PROP-TO-FIND)" (let* ((headline-1 (list ;; SOURCE-FILENAME-FN diff --git a/org-remark-line.el b/org-remark-line.el new file mode 100644 index 0000000000..c8b4661f03 --- /dev/null +++ b/org-remark-line.el @@ -0,0 +1,425 @@ +;;; org-remark-line.el --- Enable Org-roam to highlight a line -*- lexical-binding: t; -*- + +;; Copyright (C) 2021-2023 Free Software Foundation, Inc. + +;; Author: Noboru Ota <m...@nobiot.com> +;; URL: https://github.com/nobiot/org-remark +;; Created: 01 August 2023 +;; Last modified: 05 October 2023 +;; Package-Requires: ((emacs "27.1") (org "9.4")) +;; Keywords: org-mode, annotation, note-taking, marginal-notes, wp + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or (at +;; your option) any later version. + +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;;; Code: + +(require 'org-remark) + +(defgroup org-remark-line nil + "Enable`org-remark' to highlight and annotate whole lines." + :group 'org-remark + :prefix "org-remark-line" + :link '(url-link :tag "GitHub" "https://github.com/nobiot/org-remark")) + +(defcustom org-remark-line-icon " * " + "Glyph displayed on the margin to indicate the line-highlight. +If you wants to use image icons (e.g. SVG image icon created with +package `icons', available Emacs 29.1 or higher), you're limited +to a single character with no space before and after the +character. This limitation does not apply to string of characters +without images, but it is generally assumed that the the value +set to this customizing variable will be a short string (e.g 3 +characters long with a padding spaces before and after a single +character, such as the default value.)" + :local t + :type 'string + :safe 'stringp) + +(defcustom org-remark-line-minimum-margin-width 3 + "Margin width in a natural number. +It can be a single number or a cons cell of two. When it is a +single number, both the left and right margin widths will be the +same. When this customizing variable is a cons cell, the format +is as follows: (LEFT-MARGIN-WIDTH . RIGHT-MARGIN-WIDTH)." + :local t + :type '(choice + (natnum :tag "Minimum margin width for both left and right margins" 3) + (cons :tag "Left and right margin widths" natnum natnum))) + +(defcustom org-remark-line-margin-padding 1 + "Padding between the main text area the glyph/icon on the margin." + :local t + :type 'natnum) + +(defcustom org-remark-line-margin-side 'left-margin + "The side of margin to display line highlights. +Left or right can be chosen." + :local t + :type '(radio + (const :tag "Left margin" left-margin) + (const :tag "Right margin" right-margin))) + +(defcustom org-remark-line-heading-title-max-length 40 + "Maximum length of string included as the highlight title." + :type 'natnum) + +(defcustom org-remark-line-ellipsis "…" + "Ellipsis used when the highlight title is longer than maximum. +The maximum is set in `org-remark-line-heading-title-max-length'." + :type 'string + :safe 'stringp) + +(defface org-remark-line-highlighter + '((((class color) (min-colors 88) (background light)) + :foreground "#dbba3f" :inherit default) + (((class color) (min-colors 88) (background dark)) + :foreground "#e2d980" :inherit default) + (t + :inherit highlight)) + "Face for the default line highlighter pen.") + +(defvar-local org-remark-line-minimum-left-margin-width nil + "Computed minimum `left-margin' width.") + +(defvar-local org-remark-line-minimum-right-margin-width nil + "Computed minimum `right-margin' width.") + +(defvar-local org-remark-line-margins-original '() + "Original window margin width values. +It is the original margins returned by function `window-margins' +in cons cell (or nil) before function +`org-remark-line-set-window-margins' set margins.") + +(defvar-local org-remark-line-margins-set-p nil + "Status indicating if margins are set by `org-remark-line'.") + +;;;###autoload +(define-minor-mode org-remark-line-mode + "Enable Org-remark to highlight and annotate the whole line." + :global nil + :group 'org-remark + (if org-remark-line-mode + ;; Enable + (progn + ;; Depth is deeper than the default one for range-highlight. + ;; This is to prioritize it over line-highlight when the fomer + ;; is at point and yet on the same line of another + ;; line-highlight. + (add-hook 'org-remark-find-dwim-functions + #'org-remark-line-find 80 :local) + ;; olivetti sets DEPTH to t (=90). We need go lower priority than it + (add-hook 'window-size-change-functions + #'org-remark-line-set-window-margins 95 :local) + ;; Need to reload to cater to margin changes done by `olivetti'. + (add-hook 'window-size-change-functions + #'org-remark-line-highlights-redraw 96 :local) + (add-hook 'org-remark-highlight-other-props-functions + #'org-remark-line-prop-line-number-get) + (org-remark-line-set-window-margins)) + ;; Disable + (remove-hook 'org-remark-find-dwim-functions #'org-remark-line-find :local) + (remove-hook 'window-size-change-functions + #'org-remark-line-set-window-margins :local) + (remove-hook 'window-size-change-functions + #'org-remark-line-highlights-redraw :local) + (remove-hook 'org-remark-highlight-other-props-functions + #'org-remark-line-prop-line-number-get) + (when org-remark-line-margins-set-p + (setq left-margin-width (car org-remark-line-margins-original)) + (setq right-margin-width (cdr org-remark-line-margins-original)) + (set-window-margins nil left-margin-width right-margin-width) + (set-window-buffer (get-buffer-window) (current-buffer) nil) + (setq org-remark-line-minimum-left-margin-width nil) + (setq org-remark-line-minimum-right-margin-width nil) + (setq org-remark-line-margins-set-p nil)))) + +;; Default line-highlighter pen + +;;;###autoload +(defun org-remark-mark-line (_beg _end &optional _id _mode) + "Apply face to the region selected by BEG and END. +Dummy function definition to let autoload work. +The actual implementation is added when this library is loaded +and macro `org-remark-create' creates the actual function.") + +(org-remark-create "line" + `org-remark-line-highlighter + `(org-remark-type line)) + +(defun org-remark-line-set-window-margins (&optional window) + "Set the margins of WINDOW or window that displays current buffer. +Return a cons of the form (LEFT-WIDTH . RIGHT-WIDTH). If a +marginal area does not exist, return nil." + (let ((window (or window (get-buffer-window)))) + (when (and (windowp window) (not (window-minibuffer-p window))) + (cl-destructuring-bind (left-width . right-width) (window-margins) + (unless org-remark-line-margins-set-p + (setq org-remark-line-margins-original (window-margins)) + (setq org-remark-line-margins-set-p t) + (setq org-remark-line-minimum-left-margin-width + (+ (if (numberp org-remark-line-minimum-margin-width) + org-remark-line-minimum-margin-width + (car org-remark-line-minimum-margin-width)) + org-remark-line-margin-padding)) + (setq org-remark-line-minimum-right-margin-width + (+ (if (numberp org-remark-line-minimum-margin-width) + org-remark-line-minimum-margin-width + (cdr org-remark-line-minimum-margin-width)) + org-remark-line-margin-padding))) + (if (or (eq left-width nil) (< left-width + org-remark-line-minimum-left-margin-width)) + (setq left-margin-width org-remark-line-minimum-left-margin-width) + (setq left-margin-width left-width)) + (if (or (eq right-width nil) (< right-width + org-remark-line-minimum-right-margin-width)) + (setq right-margin-width org-remark-line-minimum-right-margin-width) + (setq right-margin-width right-width)) + ;; For `set-window-margins' window should be specified. + ;; Howerver, `set-window-buffer' should get nil for window. + ;; Otherwise, the minibuffer also gets the margins. It's a + ;; little tricky behaviour. Both functions seem to be required. + ;; The former changes the current window's margin display + ;; immediately. The latter makes the margin widths the default + ;; for future, when window gets split, etc. + (set-window-margins window left-margin-width right-margin-width) + (set-window-buffer nil (current-buffer) 'keep-margins) + (window-margins))))) + +(defun org-remark-line-pos-bol (pos) + "Return the beginning of the line position for POS." + (save-excursion + (goto-char pos) + (pos-bol))) + +(defun org-remark-line-highlight-p (highlight) + "Return t if HIGHLIGHT is one for the line. +HIGHLIGHT is an overlay." + (eql 'line (overlay-get highlight 'org-remark-type))) + +(defun org-remark-line-find (&optional point) + "Return the line-highight (overlay) of the current line. +When POINT is passed, one for the line it belongs to. If there +are multiple line-hilights, return the car of the list returned +by `overlays-in'." + (let* ((point (or point (point))) + (bol (org-remark-line-pos-bol point)) + (highlights (overlays-in bol bol))) + (seq-find #'org-remark-line-highlight-p highlights))) + +(defun org-remark-line-prop-line-number-get (highlight) + "Return the line number for HIGHLIGHT overlay. +This happens only when HIGHLIGHT is a line-highlight." + (when (org-remark-line-highlight-p highlight) + (list 'org-remark-line-number + (number-to-string (org-current-line (overlay-start highlight)))))) + +(cl-defmethod org-remark-beg-end ((_org-remark-type (eql 'line))) + "Return beg and end for ORG-REMARK-TYPE line." + (let ((bol (org-remark-line-pos-bol (point)))) + (list bol bol))) + +(defun org-remark-line-make-spacer-overlay (pos) + "Return a spacer overlay at POS." + (let* ((left-margin (or (car (window-margins)) left-margin-width)) + ;;(right-margin (or (cdr (window-margins)) right-margin-width)) + (string-length (length org-remark-line-icon)) + (spaces-base-length (if (eql org-remark-line-margin-side 'right-margin) + org-remark-line-margin-padding + (- left-margin + (+ string-length org-remark-line-margin-padding)))) + (spaces-length (if (> spaces-base-length 0) spaces-base-length 0)) + (spacer-ov (make-overlay pos pos nil :front-advance))) + ;; Add a spacing overlay before the line-highlight overlay but we + ;; only need one of these; remove it if one already exits + (remove-overlays (overlay-start spacer-ov) (overlay-end spacer-ov) + 'category 'org-remark-spacer) + (overlay-put spacer-ov 'before-string + (propertize " " + 'display + `((margin ,org-remark-line-margin-side) + (space . (:width ,spaces-length))))) + (overlay-put spacer-ov 'category 'org-remark-spacer) + spacer-ov)) + +(defun org-remark-line-highlights-redraw (&optional window) + "Redraw line-highlights to adjust the spaces/padding. +When WINDOW is nil, this function gets window that current buffer is displayed." + (let ((window (or window (get-buffer-window)))) + (when (and (windowp window) (not (window-minibuffer-p window))) + (org-with-wide-buffer + (let ((highlights + (seq-filter (lambda (ov) (eql 'line (overlay-get ov 'org-remark-type))) + org-remark-highlights))) + (dolist (ov highlights) + (let* ((beg (overlay-start ov)) + (spacer-ov (org-remark-line-make-spacer-overlay beg)) + (copied-highlight (copy-overlay ov)) + (display-props + (get-text-property 0 'display (overlay-get copied-highlight 'before-string)))) + (setf (car display-props) `(margin ,org-remark-line-margin-side)) + (push copied-highlight org-remark-highlights) + (copy-overlay spacer-ov) + (delete-overlay ov) + (org-remark-highlights-housekeep) + (org-remark-highlights-sort)))))))) + +(defun org-remark-line-highlight-propertize (ov icon-string) + "Propertize ICON-STRING and add it to OV." + ;; If the icon-string has a display properties, assume it is an icon image + (let ((display-prop (get-text-property 0 'display icon-string))) + (cond (display-prop ; svg-based icon + (let* ((display-prop (list `(margin ,org-remark-line-margin-side) display-prop)) + ;; TODO margin needs to be calculated + ;; (list `(margin ,org-remark-line-margin-side) + ;; (append display-prop '(:margin (10 . 0)))))) + (icon-face (get-text-property 0 'face icon-string)) + (icon-string (propertize " " 'display display-prop))) + (when icon-face + (setq icon-string (propertize icon-string 'face icon-face))) + (overlay-put ov 'before-string icon-string))) + (icon-string ; text/string-based icon + (let ((icon-string icon-string)) + (overlay-put + ov + 'before-string + (propertize + " " 'display + (list `(margin ,org-remark-line-margin-side) icon-string))))) + (t (ignore))))) + +(cl-defmethod org-remark-highlight-make-overlay (beg end face (_org-remark-type (eql 'line))) + "Make and return a highlight overlay in BEG END for line-highlight. +This function adds FACE to line icon string. If FACE is nil, this +function uses default `org-remark-line-highlighter'. Return nil +when no window is created for current buffer." + (when (get-buffer-window) + (unless org-remark-line-mode (org-remark-line-mode +1)) + (let* ((face (or face 'org-remark-line-highlighter)) + (string (propertize org-remark-line-icon 'face face)) + (spacer-ov (org-remark-line-make-spacer-overlay beg)) + (ov (make-overlay beg end nil :front-advance))) + ;; line-highlight overlay + (org-remark-line-highlight-propertize ov string) + ;; Let highlight overlay to take care of the spacer movement + (overlay-put ov 'insert-in-front-hooks (list 'org-remark-line-highlight-modified)) + ;; Copy spacer overlay. It is put after the line-highlight to + ;; limit and reset the face added by the line-highlight back to + ;; default. This is especially done for RTL languages and when the + ;; face include a background color different from that of default. + ;; Without it, the background color goes all the way to the end of + ;; the right margin. + (copy-overlay spacer-ov) + ov))) + +(defun org-remark-line-highlight-find-spacers (pos) + "Find the two spacers for POS." + (let ((highlights (overlays-in pos pos))) + (seq-filter (lambda (ov) + (eql 'org-remark-spacer (overlay-get ov 'category))) + highlights))) + +(defun org-remark-line-highlight-modified (ov after-p beg _end &optional _length) + "Move spacers and lighlight OV to follow the point. +Without this function, the line-highlighter mark does not move +when the user press RET to add a newline at the beginning of the +line-highlight. This is unintuitive for the user. + +This function is meant to be added to insert-in-front-hooks of +the overlay that represents line-highlight. It must be called +AFTER-P is non-nil and move BEG to one position forward." + (when after-p + (save-excursion (goto-char beg) + (when (looking-at "\n") + ;; The sequence must be 1. spacer; 2. highlight 3. spacer + (let ((spacers (org-remark-line-highlight-find-spacers beg))) + (when spacers + (move-overlay (pop spacers) (1+ beg) (1+ beg))) + (move-overlay ov (1+ beg) (1+ beg)) + (when spacers + (move-overlay (pop spacers) (1+ beg) (1+ beg)))))))) + +(cl-defmethod org-remark-highlight-headline-text (ov (_org-remark-type (eql 'line))) + "Return the first N characters of the highlighted line OV. +N is customized with `org-remark-line-heading-title-max-length'. +If the line starts with any space or tab, they will be trimmed. +If the line (after trimming) is shorter than N, then this +function will include the charcters up to the newline char. + +In addition, if the text happens to be empty, the function uses +\"Empty line highlight\" as the fallback; headlines with no title +is not considered valid for the purpose of `org-remark' and thus +risks unexpected results (mostly the highlight skipped when +loading highlights)." + (let ((line-text (buffer-substring-no-properties + (overlay-start ov) (pos-eol)))) + (if (or (eq line-text nil) + (string= line-text "")) + "Empty line highlight" + (setq line-text (string-trim-left line-text)) + (if (length< line-text + (1+ org-remark-line-heading-title-max-length)) + line-text + (concat (substring line-text 0 org-remark-line-heading-title-max-length) + org-remark-line-ellipsis))))) + +(cl-defmethod org-remark-highlights-adjust-positions-p ((_org-remark-type (eql 'line))) + "Return t if adjust-positions feature is relevant. +For line-highlights, adjust-positions is not relevant." + nil) + +(cl-defmethod org-remark-highlights-housekeep-delete-p (_ov (_org-remark-type (eql 'line))) + "Always return nil when ORG-REMARK-TYPE is \\='line\\='. +Line-highlights are designed to be zero length with the start and +end of overlay being identical." + nil) + +(cl-defmethod org-remark-highlights-housekeep-per-type (ov (_org-remark-type (eql 'line))) + "Ensure line-highlight OV is always at the beginning of line." + ;; if `pos-bol' is used to move, you can actually get the highlight to + ;; always follow the point, keeping the original place unless you + ;; directly change the notes. That's not really an intutive behaviour, + ;; though in some cases, it imay be useful. + ;; (if (not (overlay-start ov)) (delete-overlay ov) + (when (overlay-buffer ov) + (let* ((ov-start (overlay-start ov)) + (ov-line-bol (org-remark-line-pos-bol ov-start))) + (unless (= ov-start ov-line-bol) + (move-overlay ov ov-line-bol ov-line-bol))))) + +(cl-defmethod org-remark-icon-overlay-put (ov icon-string (_org-remark-type (eql 'line))) + "Add ICON-STRING to OV. +Each overlay is a highlight. Return nil when no window is created +for current buffer." + (when (get-buffer-window) + (org-remark-line-highlight-propertize ov icon-string))) + +(cl-defmethod org-remark-icon-highlight-get-face (highlight (_org-remark-type (eql 'line))) + "Return the face of HIGHLIGHT in margin for line-highlight." + (let* ((before-string (overlay-get highlight 'before-string)) + (face (get-text-property 0 'face before-string))) + ;; When the highlight already is an SVG icon, face is in the display + ;; property of before-string + (unless face + (let ((display-string + (cadr (get-text-property 0 'display before-string)))) + (when (stringp display-string) + (setq face (get-text-property 0 'face display-string))))) + face)) + +(provide 'org-remark-line) +;;; org-remark-line.el ends here diff --git a/org-remark-nov.el b/org-remark-nov.el index 8674f219fa..42dab16ff0 100644 --- a/org-remark-nov.el +++ b/org-remark-nov.el @@ -5,7 +5,7 @@ ;; Author: Noboru Ota <m...@nobiot.com> ;; URL: https://github.com/nobiot/org-remark ;; Created: 9 January 2023 -;; Last modified: 29 July 2023 +;; Last modified: 06 October 2023 ;; Package-Requires: ((emacs "27.1") (org "9.4")) ;; Keywords: org-mode, annotation, note-taking, marginal-notes, wp @@ -50,7 +50,7 @@ (error "Org-remark: package `nov' is missing")) (require 'org-remark-global-tracking) (declare-function org-remark-highlights-load "org-remark") -(declare-function org-store-link "org") +(declare-function org-store-link "ol") (defvar org-remark-prop-source-file) ;; To silence flymake (defvar nov-file-name) @@ -62,7 +62,7 @@ (define-minor-mode org-remark-nov-mode "Enable Org-remark to work with `nov-mode' for eub." :global t - :group 'org-remark + :group 'org-remark-nov (if org-remark-nov-mode ;; Enable (progn diff --git a/org-remark.el b/org-remark.el index aa7e52cecd..040819c4c5 100644 --- a/org-remark.el +++ b/org-remark.el @@ -1,4 +1,4 @@ -;;; org-remark.el --- Highlight & annotate any text files -*- lexical-binding: t; -*- +;;; org-remark.el --- Highlight & annotate text, Info, EPUB, EWW -*- lexical-binding: t; -*- ;; Copyright (C) 2020-2023 Free Software Foundation, Inc. @@ -6,7 +6,7 @@ ;; URL: https://github.com/nobiot/org-remark ;; Version: 1.2.1 ;; Created: 22 December 2020 -;; Last modified: 21 August 2023 +;; Last modified: 06 October 2023 ;; Package-Requires: ((emacs "27.1") (org "9.4")) ;; Keywords: org-mode, annotation, note-taking, marginal-notes, wp, @@ -38,7 +38,6 @@ (require 'org) (require 'org-id) (require 'org-remark-global-tracking) -(require 'org-remark-icon) (declare-function org-remark-convert-legacy-data "org-remark-convert-legacy") @@ -95,6 +94,27 @@ detail and expected elements of the list." buffer with this name." :type 'string) +(defcustom org-remark-notes-auto-delete nil + "How Org-remark removes entries when user deletes highlights. +This controls the behavior of Org-remark when the user deletes +the highlight in the source (for example, when deleting a whole +line including a highlight). The default behavior is to remove +only the properties in the notes buffer and keeping the headline +title and any notes in the entry (with using +`org-remark-remove'). + +If this behavior leads to cluttering the marginal notes org file, +you can set this customizing variable to \\=':auto-delete\\='. +With this option, Org-remark will delete the entire entry when it +contains no notes without a prompt asking for confirmation. This +is the same behavior as passing a single `universal-argument' +\(\\[universal-argument]) to`org-remark-delete' or double `universal-argument' +\(\\[universal-argument] \\[universal-argument]) to `org-remark-remove'." + :type '(radio + (const :tag "Keep entries (default)" nil) + (const :tag "Delete entries automatically when no notes exist" + :auto-delete))) + (defvaralias 'org-remark-source-path-function 'org-remark-source-file-name) @@ -138,6 +158,33 @@ highlights. It is run with the source buffer as current buffer." ;;;; Variables +(defvar org-remark-default-features '(org-remark-icon org-remark-line) + "Extension features to be enabled by default. +It is suggested to keep them as the default, but you can choose to disable them") + +(defvar org-remark-default-feature-modes '() + "Extension minor modes to be enabled together with `org-remark-mode'. +These minor modes should be registered to this variable by the +respective feature where required. As an example, see +`org-remark-line'.") + +(defvar org-remark-find-dwim-functions nil + "Functions to find the highlight overlays. +These functions should be registered to this variable by the +respective feature where required. As an example, see +`org-remark-line'.") + +(defvar org-remark-last-notes-buffer nil + "The cloned indirect buffer visiting the notes file. +It is meant to exist only one of these in each Emacs session.") + +(defvar org-remark-available-pens (list #'org-remark-mark) + "A list of pens available. +Each pen is a function. Users can create a new custom pen with +using `org-remark-create', which automatically add a new pen +function this list. It is used by `org-remark-change' as a +selection list.") + (defvar-local org-remark-highlights '() "All the highlights in current source buffer. It is a local variable and is a list of overlays. Each overlay @@ -160,18 +207,7 @@ killed so that this needs to be checked with `buffer-live-p'.") (defvar-local org-remark-source-setup-done nil "Local indicator that sync with notes buffer is set up.") -(defvar org-remark-last-notes-buffer nil - "The cloned indirect buffer visiting the notes file. -It is meant to exist only one of these in each Emacs session.") - -(defvar org-remark-available-pens (list #'org-remark-mark) - "A list of pens available. -Each pen is a function. Users can create a new custom pen with -using `org-remark-create', which automatically add a new pen -function this list. It is used by `org-remark-change' as a -selection list.") - -(defvar org-remark-highlights-toggle-hide-functions nil +(defvar-local org-remark-highlights-toggle-hide-functions nil "Functions to be called when toggling to hide highlights. Each function is called with one argument HIGHLIGHT, which is an overlay that shows the highlight. It also stores properties to @@ -180,7 +216,7 @@ control visibility such as \\=':face\\='. This variable is an abnormal hook and is intended to be used to add additional controls for the overlay properties.") -(defvar org-remark-highlights-toggle-show-functions nil +(defvar-local org-remark-highlights-toggle-show-functions nil "Functions to be called when toggling to show highlights. Each function is called with one argument HIGHLIGHT, which is an overlay that shows the highlight. It also stores properties to @@ -197,7 +233,6 @@ add additional controls for the overlay properties") ;;;; Macros to create user-defined highlighter pen functions - (defmacro org-remark-create (label &optional face properties) "Create and register new highlighter pen functions. @@ -219,11 +254,13 @@ property drawer from the highlighter pen. To do this, prefix property names with \"org-remark-\" or use \"CATEGORY\"." (if (or (not label) (stringp label) (user-error "org-remark-create: Label is missing or not string")) - `(progn - ;; Define custom pen function - (defun ,(intern (format "org-remark-mark-%s" label)) - (beg end &optional id mode) - ,(format "Apply the following face to the region selected by BEG and END. + (let ((org-remark-type + `(quote ,(plist-get (eval properties) 'org-remark-type)))) + `(progn + ;; Define custom pen function + (defun ,(intern (format "org-remark-mark-%s" label)) + (beg end &optional id mode) + ,(format "Apply the following face to the region selected by BEG and END. %s @@ -245,27 +282,35 @@ highlight. In this case, no new ID gets generated. When the pen itself defines the help-echo property, it will have the priority over the excerpt of the marginal notes." - (or face "`org-remark-highlighter'") properties) - (interactive (org-remark-region-or-word)) - (org-remark-highlight-mark beg end id mode ,label ,face ,properties)) - - ;; Register to `org-remark-available-pens' - (add-to-list 'org-remark-available-pens - (intern (format "org-remark-mark-%s" ,label))) - - ;; Add the custom pen function to the minor-mode menu - (define-key-after org-remark-pen-map - [,(intern (format "org-remark-mark-%s" label))] - '(menu-item ,(format "%s pen" label) ,(intern (format "org-remark-mark-%s" label)))) - - ;; Add the custom pen change function to the minor-mode menu - (define-key-after org-remark-change-pen-map - [,(intern (format "org-remark-change-to-%s" label))] - '(menu-item ,(format "%s pen" label) - (lambda () - (interactive) - (org-remark-change - #',(intern (format "org-remark-mark-%s" label))))))))) + (or face "`org-remark-highlighter'") properties) + (interactive (org-remark-beg-end ,org-remark-type)) + (org-remark-highlight-mark beg end id mode ,label ,face ,properties)) + + ;; Register to `org-remark-available-pens' + (add-to-list 'org-remark-available-pens + (intern (format "org-remark-mark-%s" ,label))) + + ;; Add function prop This is for `org-remark-change' to show + ;; only the pens of the same type + (when 'org-remark-type (function-put + (intern (format "org-remark-mark-%s" ,label)) + 'org-remark-type + ,org-remark-type)) + + ;; Add the custom pen function to the minor-mode menu + (define-key-after org-remark-pen-map + [,(intern (format "org-remark-mark-%s" label))] + '(menu-item ,(format "%s pen" label) ,(intern (format "org-remark-mark-%s" label)))) + + ;; Add the custom pen change function to the minor-mode menu + (define-key-after org-remark-change-pen-map + [,(intern (format "org-remark-change-to-%s" label))] + '(menu-item ,(format "%s pen" label) + (lambda () + (interactive) + (org-remark-change + #',(intern (format "org-remark-mark-%s" label)))) + :enable (org-remark-pen-same-type-at-point-p ,org-remark-type))))))) ;;;; Minor mode @@ -311,12 +356,17 @@ recommended to turn it on as part of Emacs initialization. (cond (org-remark-mode ;; Activate - (org-remark-icon-mode +1) ;; automatically enabled by default + (dolist (feature org-remark-default-features) + (unless (featurep feature) (require feature nil 'noerror))) + (dolist (feature-mode org-remark-default-feature-modes) + (when (functionp feature-mode) (funcall feature-mode +1))) (org-remark-highlights-load) - (add-hook 'after-save-hook #'org-remark-save nil t) + (add-hook 'org-remark-find-dwim-functions + #'org-remark-find-overlay-at-point nil :local) + (add-hook 'after-save-hook #'org-remark-save nil :local) (add-hook 'org-remark-highlight-link-to-source-functions #'org-remark-highlight-link-to-source-default 80) - (add-hook 'after-revert-hook #'org-remark-highlights-load) + (add-hook 'after-revert-hook #'org-remark-highlights-load :local) (add-hook 'clone-buffer-hook #'org-remark-highlights-load 80 :local)) (t ;; Deactivate @@ -324,11 +374,14 @@ recommended to turn it on as part of Emacs initialization. (dolist (highlight org-remark-highlights) (delete-overlay highlight))) (setq org-remark-highlights nil) - (org-remark-icon-mode -1) + (dolist (feature-mode org-remark-default-feature-modes) + (funcall feature-mode -1)) + (remove-hook 'org-remark-find-dwim-functions + #'org-remark-find-overlay-at-point :local) (remove-hook 'after-save-hook #'org-remark-save t) (remove-hook 'org-remark-highlight-link-to-source-functions #'org-remark-highlight-link-to-source-default) - (remove-hook 'after-revert-hook #'org-remark-highlights-load) + (remove-hook 'after-revert-hook #'org-remark-highlights-load :local) (remove-hook 'clone-buffer-hook #'org-remark-highlights-load :local)))) @@ -339,35 +392,42 @@ recommended to turn it on as part of Emacs initialization. (define-key-after org-remark-menu-map [org-remark-open] '(menu-item "Open" org-remark-open - :help "Display and move to marginal notes for highlight at point")) + :help "Display and move to marginal notes for highlight at point" + :enable (org-remark-find-dwim))) (define-key-after org-remark-menu-map [org-remark-view] '(menu-item "View" org-remark-view - :help "Display marginal notes for highlight at point; stay in current buffer")) + :help "Display marginal notes for highlight at point; stay in current buffer" + :enable (org-remark-find-dwim))) (define-key-after org-remark-menu-map [org-remark-view-next] - '(menu-item "View next" org-remark-view-next)) + '(menu-item "View next" org-remark-view-next + :enable org-remark-highlights)) (define-key-after org-remark-menu-map [org-remark-view-prev] - '(menu-item "View previous" org-remark-view-prev)) + '(menu-item "View previous" org-remark-view-prev + :enable org-remark-highlights)) (define-key-after org-remark-menu-map [org-remark-toggle] '(menu-item "Toggle" org-remark-toggle - :help "Toggle showing/hiding of highlights in current buffer")) + :help "Toggle showing/hiding of highlights in current buffer" + :enable org-remark-highlights)) (define-key-after org-remark-menu-map [org-remark-remove] '(menu-item "Remove" org-remark-remove - :help "Remove highlight at point, keeping the marginal notes entry")) + :help "Remove highlight at point, keeping the marginal notes entry" + :enable (org-remark-find-dwim))) (define-key-after org-remark-menu-map [org-remark-delete] '(menu-item "Delete" org-remark-delete - :help "Delete highlight at point and the marginal notes entry")) + :help "Delete highlight at point and the marginal notes entry" + :enable (org-remark-find-dwim))) ;; Make pen functions menu (defvar org-remark-pen-map @@ -383,15 +443,18 @@ recommended to turn it on as part of Emacs initialization. (define-key-after org-remark-change-pen-map [org-remark-change] - '(menu-item "default pen" (lambda () - (interactive) - (org-remark-change #'org-remark-mark)))) + '(menu-item "default pen" + (lambda () + (interactive) + (org-remark-change #'org-remark-mark)) + :enable (org-remark-pen-same-type-at-point-p nil))) ;; Add change menu to the parent menu (define-key-after org-remark-menu-map [org-remark-change-pens] - (list 'menu-item "Change to..." org-remark-change-pen-map) - 'org-remark-toggle) + `(menu-item "Change to..." ,org-remark-change-pen-map + :enable (org-remark-find-dwim) + 'org-remark-toggle)) ;; Add pen menu to the parent menu (define-key org-remark-menu-map @@ -403,6 +466,11 @@ recommended to turn it on as part of Emacs initialization. [menu-bar org-remark] (list 'menu-item "Org-remark" org-remark-menu-map)) +(defun org-remark-pen-same-type-at-point-p (org-remark-type) + "Return t if the highlight's type is the same as ORG-REMARK-TYPE." + (eql org-remark-type + (overlay-get (org-remark-find-dwim (point)) 'org-remark-type))) + ;;;; Commands @@ -431,10 +499,10 @@ MODE is also an argument which can be passed from Elisp. It determines whether or not highlight is to be saved in the marginal notes file. The expected values are nil, :load and :change." - (interactive (org-remark-region-or-word)) - ;; FIXME - ;; Adding "nil" is different to removing a prop - ;; This will do for now + (interactive (org-remark-beg-end nil)) ;; passing org-remark-type nil + ;; FIXME + ;; Adding "nil" is different to removing a prop + ;; This will do for now (org-remark-highlight-mark beg end id mode nil nil (list 'org-remark-label "nil"))) @@ -475,9 +543,10 @@ The marginal notes will be narrowed to the relevant headline to show only the highlight at point. This function creates a cloned indirect buffer for the marginal -notes file. You can edit it as a normal Org buffer. Once you -have done editing, you can simply save and kill the buffer or -keep it around. +notes file. You can edit it as a normal Org buffer. Once you have +done editing, you can simply save and kill the buffer or keep it +around. Org-remark ensures that there is only one cloned buffer +for notes file by tracking it. The marginal notes file gets displayed by the action defined by `org-remark-notes-display-buffer-action' (by default in a left @@ -488,30 +557,49 @@ You can customize the name of the marginal notes buffer with `org-remark-notes-buffer-name'. By default, the cursor will go to the marginal notes buffer for -further editing. When VIEW-ONLY is non-nil \(e.g. by passing a -universal argument with \\[universal-argument]\), you can display -the marginal notes buffer with the cursor remaining in the -current buffer. - -This function ensures that there is only one cloned buffer for -notes file by tracking it." +further editing. When VIEW-ONLY is \\=':view-only\\=' \(e.g. +Elisp program to pass the value), you can view the marginal notes +buffer with the cursor remaining in the current buffer. + +If you pass a single universal argument with +\\[universal-argument]\), you open the marginal notes buffer +associated with the current buffer with `find-file' without +narrowing it to a specific node or cloning it as indirect buffer.. + +If you pass any other values to VIEW-ONLY, this function behaves +in the way as passing \\=':view-only\\=' to it and simply let you +view the marginal notes in a cloned indirect buffer in the +side-window (as defined by user option +`org-remark-notes-display-buffer-action')." (interactive "d\nP") - (when-let ((id (get-char-property point 'org-remark-id)) - (ibuf (org-remark-notes-buffer-get-or-create)) - (cbuf (current-buffer))) - (pop-to-buffer ibuf org-remark-notes-display-buffer-action) - (widen) - (when-let (p (org-find-property org-remark-prop-id id)) - ;; Somehow recenter is needed when a highlight is deleted and move to a - ;; previous highlight. Otherwise, the cursor is too low to show the - ;; entire entry. It looks like there is no entry. - (goto-char p)(org-narrow-to-subtree)(org-end-of-meta-data t)(recenter)) - ;; Run hook with the current-buffer being the note's buffer - (run-hooks 'org-remark-open-hook) - ;; Avoid error when buffer-action is set to display a new frame - (when-let ((view-only view-only) - (window (get-buffer-window cbuf))) - (select-window window)))) + (let ((ov (org-remark-find-dwim point))) + ;; If C-u is used or the cursor is not on a highlight, we don't want + ;; to open in a normal way but open the margnal notes buffer with + ;; find-file. + (if (or (eql (prefix-numeric-value current-prefix-arg) 4) + ;; :view-only should not open the marginal notes buffer + (and (null ov) (not (eql view-only :view-only)))) + (let ((notes-file (org-remark-notes-get-file-name))) + (when (file-exists-p notes-file) (find-file notes-file))) + ;; Open marginal notes normally as an indirect buffer in a side + ;; window. + (when-let* + ((ov ov) ;; OV must be present here. + (id (overlay-get ov 'org-remark-id)) + (ibuf (org-remark-notes-buffer-get-or-create)) + (cbuf (current-buffer))) + (pop-to-buffer ibuf org-remark-notes-display-buffer-action) + (widen) + (when-let (p (org-find-property org-remark-prop-id id)) + ;; Somehow recenter is needed when a highlight is deleted and move to a + ;; previous highlight. Otherwise, the cursor is too low to show the + ;; entire entry. It looks like there is no entry. + (goto-char p) (org-narrow-to-subtree) (org-end-of-meta-data t) (recenter)) + ;; Run hook with the current-buffer being the note's buffer + (run-hooks 'org-remark-open-hook) + ;; Avoid error when buffer-action is set to display a new frame + (when view-only + (select-window (get-buffer-window cbuf))))))) (defun org-remark-view (point) "View marginal notes for highlight at POINT. @@ -524,6 +612,7 @@ Also see the documentation of `org-remark-open'." (interactive "d") (org-remark-open point :view-only)) + (defun org-remark-next () "Move to the next highlight, if any. If there is none below the point but there is a highlight in the @@ -594,59 +683,121 @@ and the current source buffer." This function will show you a list of available pens to choose from." (interactive) - (when-let* ((ov (org-remark-find-overlay-at-point)) - (id (overlay-get ov 'org-remark-id)) - (beg (overlay-start ov)) - (end (overlay-end ov))) - (let ((new-pen (if pen pen - (intern - (completing-read "Which pen?:" org-remark-available-pens))))) - (org-remark-highlight-clear ov) - (funcall new-pen beg end id :change)))) + (if-let* ((ov (org-remark-find-dwim)) + (id (overlay-get ov 'org-remark-id)) + (beg (overlay-start ov)) + (end (overlay-end ov))) + (let* ((available-pens (seq-filter + (lambda (pen-fn) + (let ((type (overlay-get ov 'org-remark-type))) + (eql type (function-get pen-fn 'org-remark-type)))) + org-remark-available-pens)) + (new-pen + (if pen pen + (intern + ;; To guard against minibuffer quit error when + ;; the user quit without selecting any pen. + (unwind-protect + (completing-read "Which pen?:" + available-pens)))))) + (org-remark-highlight-clear ov) + (funcall new-pen beg end id :change)) + ;; if ov or any other variables are not found + (message "No highlight here."))) (defun org-remark-remove (point &optional delete) "Remove the highlight at POINT. -It will remove the highlight and the properties from the -marginalia, but will keep the headline and annotations. This is -to ensure to keep any notes you might have written intact. - -You can let this command DELETE the entire heading subtree for -the highlight, along with the annotations you have written, by -passing a universal argument with \\[universal-argument]. -If you have done so by error, you could still `undo' it in the -marginal notes buffer, but not in the current buffer as adding -and removing overlays are not part of the undo tree." +By default, it will remove the highlight from the source buffer +and the properties of entry from the marginal notes buffer, but +will keep the headline title and any notes in it. This is to +ensure to keep any notes you might have written intact. + +Optionally, you can let this command delete the entire heading +subtree for the highlight along with the notes you have written, +by passing universal argument in DELETE. For deletion, this +command differentiates a single or double universal arguments as +follows: + +- \\[universal-argument] + + This is the same behavior as function `org-remark-delete'. + + Look for notes in the entry. If there is any, the side-window + will show them and a prompt will ask the user for confirmation. + The function will delete the entry only when the user confirms + with \\='y\\='. When \\='n\\=', it will only remove the entry's + properties. + + If there are no notes in the entry, the command will delete the + entry without the prompt. + +- \\[universal-argument] \\[universal-argument] + + This is automatic deletion. This command will delete the entry + without asking the user when there is no notes in the entry. If + there are any notes, only the entry's properties will be + removed. This is the same behavior as passing a single + `universal-argument' to function `org-remark-delete'. + +If you have removed or deleted a highlight by error, you can +still `undo' it in the marginal notes buffer and not in the +current buffer. This is because adding and removing overlays are +not part of the undo tree. You can undo the deletion in the +marginal notes buffer and then save it to sync the highlight back +in the source." (interactive "d\nP") - (when-let ((ov (org-remark-find-overlay-at-point point)) - (id (overlay-get ov 'org-remark-id))) - ;; Remove the highlight overlay and id Where there is more than one, - ;; remove only one. It should be last-in-first-out in general but - ;; overlays functions don't guarantee it (when delete - ;; (org-remark-open point :view-only)) - (org-remark-highlight-clear ov) - ;; Update the notes file accordingly - (org-remark-notes-remove id delete) - (org-remark-highlights-housekeep) - (org-remark-highlights-sort) - t)) + (let* ((ov (org-remark-find-dwim point)) + (id (overlay-get ov 'org-remark-id))) + (when (and ov id) + ;; Remove the highlight overlay and id. If there is more than one, + ;; remove only one. It should be last-in-first-out in general but + ;; overlays functions don't guarantee it (when delete + ;; (org-remark-open point :view-only)) + (org-remark-highlight-clear ov) + ;; Update the notes file accordingly + (org-remark-notes-remove id delete) + (org-remark-highlights-housekeep) + (org-remark-highlights-sort) + t))) -(defun org-remark-delete (point) +(defun org-remark-delete (point &optional arg) "Delete the highlight at POINT and marginal notes for it. This function will prompt for confirmation if there is any notes -present in the marginal notes buffer. When the marginal notes -buffer is not displayed in the current frame, it will be +present in the highlight's entry in the marginal notes buffer. +When it is not displayed in the current frame, it will be temporarily displayed together with the prompt for the user to -see the notes. +see the notes to help with confirmation. -If there is no notes, this function will not prompt for -confirmation and will remove the highlight and deletes the entry -in the marginal notes buffer. +If there are no notes, this function will not prompt for +confirmation and will remove the highlight in the source buffer +and delete the entry in the marginal notes buffer. -This command is identical with passing a universal argument to -`org-remark-remove'." - (interactive "d") - (org-remark-remove point :delete)) +This is the same behavior as passing a single `universal-argument' +to function `org-remark-remove'. + +Optionally, you can pass `universal-argument' to this function +with ARG and it will behave as follows. + +- \\[universal-argument] + + This is automatic deletion. Delete the entry without asking the + user when there is no notes in the entry. If there are any + notes, remove the entry's properties only. This is the same + behavior as passing double universal-arguments to function + `org-remark-remove'. + +If you have removed or deleted a highlight by error, you can +still `undo' it in the marginal notes buffer and not in the +current buffer. This is because adding and removing overlays are +not part of the undo tree. You can undo the deletion in the +marginal notes buffer and then save it to sync the highlight +back in the source." + (interactive "d\nP") + (let ((delete (if (eql 4 (prefix-numeric-value arg)) + '(16) ;; make it universal-arg x 2 + :delete))) + (org-remark-remove point delete))) ;;;; Private Functions @@ -700,6 +851,23 @@ Look through `org-remark-highlights' list (in descending order)." ;; `org-remark-highlights' is sorted in the descending order . (seq-find (lambda (p) (< p (point))) points (nth 0 points)))) +(defun org-remark-find-dwim (&optional point) + "Return one highlight overlay for the context. + +It is a generic wrapper function to get and return as what the +context requires. This is achieved via abnormal hook that passed +the POINT as a single argument. + +The highligh to be returned can be the range-highlight at point. +POINT is optional and if not passed, the current point is used. +It can also be a line-highlight for the line, which is a zero +length overlay put to the beginning of the line. For the latter, +the user's point can be anywhere." + (or (run-hook-with-args-until-success + 'org-remark-find-dwim-functions point) + ;; Fallback + (org-remark-find-overlay-at-point point))) + (defun org-remark-find-overlay-at-point (&optional point) "Return one org-remark overlay at POINT. When point is nil, use the current point. @@ -743,6 +911,21 @@ Optionally ID can be passed to find the exact ID match." ;; functions here mostly assume the current buffer is the source ;; buffer. +(cl-defgeneric org-remark-highlight-make-overlay (_beg _end _face _org-remark-type) + "Make overlay and return it. +Put FACE and other necessary properties to the highlight OV" + (ignore)) + +(cl-defmethod org-remark-highlight-make-overlay (beg end face + (_org-remark-type (eql nil))) + "Make overlay BEG END and add FACE to it. +If FACE is nil, this function uses defaul face `org-remark-highlighter'. +This is a method for highlights of default ORG-REMARK-TYPE, that +is for a character range." + (let ((ov (make-overlay beg end nil :front-advance))) + (overlay-put ov 'face (if face face 'org-remark-highlighter)) + ov)) + (defun org-remark-highlight-mark (beg end &optional id mode label face properties) "Apply the FACE to the region selected by BEG and END. @@ -778,45 +961,57 @@ round-trip back to the notes file." ;; When highlights are toggled hidden, only the new one gets highlighted in ;; the wrong toggle state. (when org-remark-highlights-hidden (org-remark-highlights-show)) - (let ((ov (make-overlay beg end nil :front-advance)) - ;; UUID is too long; does not have to be the full length - (id (if id id (substring (org-id-uuid) 0 8))) - (filename (org-remark-source-find-file-name))) - (if (not filename) - (message (format "org-remark: Highlights not saved.\ + (org-with-wide-buffer + (let* ((org-remark-type (plist-get properties 'org-remark-type)) + (ov (org-remark-highlight-make-overlay beg end face org-remark-type)) + ;;(make-overlay beg end nil :front-advance)) + ;; UUID is too long; does not have to be the full length + (id (if id id (substring (org-id-uuid) 0 8))) + (filename (org-remark-source-find-file-name))) + (if (not filename) + (message (format "org-remark: Highlights not saved.\ This buffer (%s) is not supported" (symbol-name major-mode))) - (org-with-wide-buffer - (overlay-put ov 'face (if face face 'org-remark-highlighter)) - (while properties - (let ((prop (pop properties)) - (val (pop properties))) - (overlay-put ov prop val))) - (when label (overlay-put ov 'org-remark-label label)) - (overlay-put ov 'org-remark-id id) - ;; Keep track of the overlay in a local variable. It's a list that is - ;; guaranteed to contain only org-remark overlays as opposed to the one - ;; returned by `overlay-lists' that lists all overlays. - (push ov org-remark-highlights) - ;; for mode, nil and :change result in saving the highlight. :load - ;; bypasses save. - (unless (eq mode :load) - (let* ((notes-buf (find-file-noselect - (org-remark-notes-get-file-name))) - (source-buf (current-buffer)) - ;; Get props for create and change modes - (notes-props - (org-remark-highlight-add ov source-buf notes-buf))) - (when notes-props - (org-remark-highlight-put-props ov notes-props)) - ;; Save the notes buffer when not loading - (unless (eq notes-buf (current-buffer)) - (with-current-buffer notes-buf (save-buffer)))))) - (deactivate-mark) - (org-remark-highlights-housekeep) - (org-remark-highlights-sort) - (setq org-remark-source-setup-done t) - ;; Return overlay - ov))) + ;; OV may not be created for line-highlights when the user opens + ;; the buffer for the first time, as the window may not have been + ;; created to display the buffer yet. This is necessary for the + ;; margin width to be calculated. + (when ov + (while properties + (let ((prop (pop properties)) + (val (pop properties))) + (overlay-put ov prop val))) + (when label (overlay-put ov 'org-remark-label label)) + (overlay-put ov 'org-remark-id id) + ;; Keep track of the overlay in a local variable. It's a list that is + ;; guaranteed to contain only org-remark overlays as opposed to the one + ;; returned by `overlay-lists' that lists all overlays. + (push ov org-remark-highlights) + ;; for mode, nil and :change result in saving the highlight. :load + ;; bypasses save. + (unless (eq mode :load) + (let* ((notes-buf (find-file-noselect + (org-remark-notes-get-file-name))) + (source-buf (current-buffer)) + ;; Get props for create and change modes + (notes-props + (org-remark-highlight-add ov source-buf notes-buf))) + (when notes-props + (org-remark-highlight-put-props ov notes-props)) + ;; Save the notes buffer when not loading + (unless (eq notes-buf (current-buffer)) + ;; Force tiggering the update save for :change:operation. + ;; The line-icons do not get updated because :change: to + ;; the same pen does not involve buffer modificaiton and + ;; thus the sync does not get triggered to update icons. + (with-current-buffer notes-buf + (unless (buffer-modified-p) (restore-buffer-modified-p t)) + (save-buffer)))))) + (deactivate-mark) + (org-remark-highlights-housekeep) + (org-remark-highlights-sort) + (setq org-remark-source-setup-done t) + ;; Return overlay + ov)))) (defun org-remark-highlight-get-title () "Return the title of the source buffer. @@ -963,6 +1158,31 @@ buffer for automatic sync." ;;; Return notes-props notes-props)) +(cl-defgeneric org-remark-highlight-headline-text (_ov _org-remark-type) + "Return title text of highlight.") + +(cl-defmethod org-remark-highlight-headline-text (ov (_org-remark-type (eql nil))) + "Return title text of highlight OV of default type. +Assume it is called within `org-with-wide-buffer' of the source." + (replace-regexp-in-string + "\n" " " + (buffer-substring-no-properties (overlay-start ov) (overlay-end ov)))) + +(defvar org-remark-highlight-other-props-functions nil + "Abnormal hook to be run when adding or updating headline. +It is called with one argument HIGHLIGHT, which is the overlay +that represents the current highlight being worked on. The +function is run with source buffer as the current buffer.") + +(defun org-remark-highlight-collect-other-props (highlight) + "Return other properties for HIGHLIGHT. +Assume to be run in the source buffer." + (let ((props nil)) + (dolist (fn org-remark-highlight-other-props-functions props) + (let ((plist (funcall fn highlight))) + (when plist + (setq props (append props plist))))))) + (defun org-remark-highlight-add-or-update-highlight-headline (highlight source-buf notes-buf) "Add a new HIGHLIGHT headlne to the NOTES-BUF or update it. Return notes-props as a property list. @@ -972,26 +1192,27 @@ HIGHLIGHT is an overlay from the SOURCE-BUF. Assume the current buffer is NOTES-BUF and point is placed on the beginning of source-headline, which should be one level up." ;; Add org-remark-link with updated line-num as a property - (let (title beg end props id text filename link orgid) + (let (title beg end props id text filename link orgid org-remark-type other-props) (with-current-buffer source-buf (setq title (org-remark-highlight-get-title) beg (overlay-start highlight) end (overlay-end highlight) props (overlay-properties highlight) id (plist-get props 'org-remark-id) + org-remark-type (overlay-get highlight 'org-remark-type) text (org-with-wide-buffer - (replace-regexp-in-string - "\n" " " - (buffer-substring-no-properties beg end))) + (org-remark-highlight-headline-text highlight org-remark-type)) filename (org-remark-source-get-file-name (org-remark-source-find-file-name)) link (run-hook-with-args-until-success 'org-remark-highlight-link-to-source-functions filename beg) - orgid (org-remark-highlight-get-org-id beg)) + orgid (org-remark-highlight-get-org-id beg) + other-props (org-remark-highlight-collect-other-props highlight)) ;; TODO ugly to add the beg end after setq above (plist-put props org-remark-prop-source-beg (number-to-string beg)) (plist-put props org-remark-prop-source-end (number-to-string end)) - (when link (plist-put props "org-remark-link" link))) + (when link (plist-put props "org-remark-link" link)) + (when other-props (setq props (append props other-props)))) ;;; Make it explicit that we are now in the notes-buf, though it is ;;; functionally redundant. (with-current-buffer notes-buf @@ -1042,7 +1263,8 @@ Assume the current buffer is the source buffer." (let ((fn (intern (concat "org-remark-mark-" label)))) (unless (functionp fn) (setq fn #'org-remark-mark)) (setq ov (funcall fn beg end id :load)) - (org-remark-highlight-put-props ov props) + (when ov + (org-remark-highlight-put-props ov props)) ;; Return highlight overlay ov))) @@ -1176,13 +1398,35 @@ Return the point of begining of current heading." (defun org-remark-notes-remove (id &optional delete) "Remove the note entry for highlight ID. -By default, it deletes only the properties of the entry keeping -the headline intact. You can pass DELETE and delete the entire -note entry. +Return t. + +By default, this function only removes the properties of the +entry, keeping the headline title and any notes in it intact. + +You can pass DELETE to delete the entire entry. Elisp can pass +the following value to differentiate the deletion behavior (for +example, with commands `org-remark-remove' or +`org-remark-delete'): + +- :auto-delete or a list that has a single element 16, that is + \\='(16)\\='. The latter value is generated when the user uses + a command with \\[universal-argument] \\[universal-argument] :: -Return t if an entry is removed or deleted." + Delete the entry without asking the user when there is no notes + in the entry. If there are any notes, remove the entry's + properties only. + +- Any other non-nil value :: + + Look for notes in the entry. If there is any, ask the user for + confirmation. Delete the entire entry only when the user + confirms with \\='y\\='. When \\='n\\=', remove the entry's + properties only. + + If there are no notes, do not prompt for confirmation and + delete the entry in the marginal notes buffer." (let* ((ibuf (org-remark-notes-buffer-get-or-create)) - (window? (get-buffer-window ibuf))) + (window (get-buffer-window ibuf))) (with-current-buffer ibuf (org-with-wide-buffer (when-let ((id-headline (org-find-property org-remark-prop-id id))) @@ -1194,26 +1438,37 @@ Return t if an entry is removed or deleted." (when delete ;; CATEGORY prop won't be deleted. Move to line after props (org-end-of-meta-data t) - (when-let (ok-to-delete? - (if (looking-at ".") - ;; If there is a content, display and prompt for - ;; confirmation - (progn - ;; This does not display the location correctly - ;; TODO This comment does not make sense now. Delete? - (display-buffer ibuf - org-remark-notes-display-buffer-action) - (y-or-n-p "Highlight removed but notes exist. \ -Do you really want to delete the notes?")) - ;; If there is no content, it's OK - t)) - (delete-region (point-min)(point-max)) - (message "Deleted the marginal notes entry"))))) + (let ((delete (if (eql 16 (prefix-numeric-value delete)) + :auto-delete + delete)) + (notes-exist-p (looking-at ".")) + (ok-to-delete-p nil)) + (cond + ;; Deletion Case 1. No notes. Auto-delete. OK to Delete + ((and (not notes-exist-p) (eql delete :auto-delete)) + (setq ok-to-delete-p t)) + ;; Deletion Case 2. No notes. Normal delete. OK to Delete + ((and (not notes-exist-p) (not (eql delete :auto-delete))) + (setq ok-to-delete-p t)) + ;; Deletion Case 3. Notes exist. Auto-delete. Keep. + ((and notes-exist-p (eql delete :auto-delete)) + (setq ok-to-delete-p nil)) + ;; Deletion Case 4. Notes exist. Normal delete. Prompt Y/N + ((and notes-exist-p (not (eql delete :auto-delete))) + ;; default behavior: when notes exist, ask the user + (display-buffer ibuf + org-remark-notes-display-buffer-action) + (setq ok-to-delete-p + (y-or-n-p "Highlight removed but notes exist. \ +Do you also want to delete the notes?")))) + (when ok-to-delete-p + (delete-region (point-min)(point-max)) + (message "Deleted the marginal notes entry")))))) (when (buffer-modified-p) (save-buffer)) ;; Quit the marginal notes indirect buffer if it was not there ;; before the remove/delete -- go back to the original state. (when-let (ibuf-window (get-buffer-window ibuf)) - (unless window? (quit-window nil ibuf-window )))) + (unless window (quit-window nil ibuf-window )))) t)) (defun org-remark-notes-buffer-get-or-create () @@ -1264,6 +1519,7 @@ properties, add prefix \"*\"." (let ((p (pop props)) (v (pop props))) (when (symbolp p) (setq p (symbol-name p))) + (when (symbolp v) (setq v (symbol-name v))) (when (or (string-equal "CATEGORY" (upcase p)) (and (> (length p) 11) (string-equal "org-remark-" (downcase (substring p 0 11))))) @@ -1381,6 +1637,13 @@ highlight is a property list in the following properties: highlights))))) highlights)))))) +(defun org-remark-highlights-delay-load (window) + "Delay load until WINDOW for current buffer is created." + (when (windowp window) + (remove-hook 'window-state-change-functions + #'org-remark-highlights-delay-load 'local) + (org-remark-highlights-load))) + ;;;###autoload (defun org-remark-highlights-load (&optional update) "Visit notes file & load the saved highlights onto current buffer. @@ -1389,34 +1652,38 @@ output a message in the echo. Non-nil value for UPDATE is passed for the notes-source sync process." - ;; Some major modes such as nov.el reuse the current buffer, deleting - ;; the buffer content and insert a different file's content. In this - ;; case, obsolete highlight overlays linger when you switch from one - ;; file to another. Thus, in order to update the highlight overlays we - ;; need to begin loading by clearing them first. This way, we avoid - ;; duplicate of the same highlight. - (org-remark-highlights-clear) - ;; Loop highlights and add them to the current buffer - (let (overlays) ;; highlight overlays - (when-let* ((notes-filename (org-remark-notes-get-file-name)) - (default-dir default-directory) - (notes-buf (or (find-buffer-visiting notes-filename) - (find-file-noselect notes-filename))) - (source-buf (current-buffer))) - (with-demoted-errors - "Org-remark: error during loading highlights: %S" - ;; Load highlights with demoted errors -- this makes the loading - ;; robust against errors in loading. - (dolist (highlight (org-remark-highlights-get notes-buf)) - (push (org-remark-highlight-load highlight) overlays)) - (unless update (org-remark-notes-setup notes-buf source-buf)) - (if overlays - (progn (run-hook-with-args 'org-remark-highlights-after-load-functions - overlays notes-buf) - ;; Return t - t) - ;; if there is no overlays loaded, return nil - nil))))) + (if (not (get-buffer-window)) + (add-hook 'window-state-change-functions + #'org-remark-highlights-delay-load 95 'local) + ;; Some major modes such as nov.el reuse the current buffer, deleting + ;; the buffer content and insert a different file's content. In this + ;; case, obsolete highlight overlays linger when you switch from one + ;; file to another. Thus, in order to update the highlight overlays we + ;; need to begin loading by clearing them first. This way, we avoid + ;; duplicate of the same highlight. + (org-remark-highlights-clear) + ;; Loop highlights and add them to the current buffer + (let (overlays) ;; highlight overlays + (when-let* ((notes-filename (org-remark-notes-get-file-name)) + (default-dir default-directory) + (notes-buf (or (find-buffer-visiting notes-filename) + (find-file-noselect notes-filename))) + (source-buf (current-buffer))) + (with-demoted-errors + "Org-remark: error during loading highlights: %S" + ;; Load highlights with demoted errors -- this makes the loading + ;; robust against errors in loading. + (dolist (highlight (org-remark-highlights-get notes-buf)) + (let ((ov (org-remark-highlight-load highlight))) + (when ov (push ov overlays)))) + (unless update (org-remark-notes-setup notes-buf source-buf)) + (if overlays + (progn (run-hook-with-args 'org-remark-highlights-after-load-functions + overlays notes-buf) + ;; Return t + t) + ;; if there is no overlays loaded, return nil + nil)))))) (defun org-remark-highlights-clear () "Delete all highlights in the buffer. @@ -1506,7 +1773,9 @@ Return t. This is a private function; house keep is automatically done on mark, save, and remove -- before sort-highlights. -Case 1. Both start and end of an overlay are identical +Case 1. Both start and end of an overlay are identical _and_ it's + not a line-highlight, which is designed to be zero length + with the start and end identical This should not happen when you manually mark a text region. A typical cause of this case is when you delete a @@ -1531,25 +1800,53 @@ Case 2. The overlay points to no buffer ;; the overlay-start and overlay-end properties. To guard against ;; this, we check if the buffer is write-able and only remove the ;; annotation when it is. - (when (and (overlay-buffer ov) - (= (overlay-start ov) (overlay-end ov))) - (when (and (not buffer-read-only) - (not (derived-mode-p 'special-mode))) - ;; Buffer-size 0 happens for a package like nov.el. It erases - ;; the buffer (size 0) and renders a new page in the same - ;; buffer. In this case, buffer is writable. - ;; - ;; TODO Relying on the current major mode being derived from - ;; special-mode may not be the best. - (org-remark-notes-remove (overlay-get ov 'org-remark-id))) - ;; Removing the notes here is meant to be to automatically remove - ;; notes when you delete a region that contains a higlight - ;; overlay. - (delete-overlay ov)) - (unless (overlay-buffer ov) - (setq org-remark-highlights (delete ov org-remark-highlights)))) + (let ((org-remark-type (overlay-get ov 'org-remark-type))) + (when (and (overlay-buffer ov) + (= (overlay-start ov) (overlay-end ov)) + (org-remark-highlights-housekeep-delete-p + ov org-remark-type)) + ;; When the buffer is writable and visitnig a file to change it. + ;; That is, a "normal" buffer. If it is writable and yet derived + ;; from a special mode, we consider the case to be in the + ;; rendering process of the mode, and thus do not want to put into + ;; the bin as part of housekeeping. + (when (and (not buffer-read-only) + (not (derived-mode-p 'special-mode))) + ;; Buffer-size 0 happens for a package like nov.el. It erases + ;; the buffer (size 0) and renders a new page in the same + ;; buffer. In this case, buffer is writable. + ;; + ;; TODO Relying on the current major mode being derived from + ;; special-mode may not be the best. + ;; Removing the notes here is meant to be to automatically remove + ;; notes when you delete a region that contains a higlight + ;; overlay. + (let ((id (overlay-get ov 'org-remark-id)) + (arg org-remark-notes-auto-delete)) + (org-remark-notes-remove id arg))) + (delete-overlay ov)) + ;; Before deleting `org-remark-highlights', add a handler per + ;; org-remark-type + (org-with-wide-buffer + (org-remark-highlights-housekeep-per-type ov org-remark-type)) + ;; Update `org-remark-highlights' by removing the deleted overlays + (unless (overlay-buffer ov) + (setq org-remark-highlights (delete ov org-remark-highlights))))) + t) + +(cl-defgeneric org-remark-highlights-housekeep-delete-p (_ov _org-remark-type) + "Additional predicate to delete during housekeep. +Default is always t. Implement method specific to +\\='\org-remark-type\=' and return nil the highlight must be +kept. + +The current buffer is source buffer." t) +(cl-defgeneric org-remark-highlights-housekeep-per-type (_ov _org-remark-type) + "Housekeep highlights per type." + (ignore)) + (defun org-remark-highlights-adjust-positions (overlays _notes-buf) "Run dolist and delgate the actual adjustment to another function. @@ -1564,14 +1861,21 @@ extensions." (let ((highlight-text (overlay-get ov '*org-remark-original-text))) ;; Check that the original text exists AND it is different to the ;; current text - (when (and highlight-text - (not (org-remark-string= - highlight-text - (buffer-substring-no-properties - (overlay-start ov) (overlay-end ov))))) + (when + (and (org-remark-highlights-adjust-positions-p (overlay-get ov 'org-remark-type)) + highlight-text + (not (org-remark-string= + highlight-text + (buffer-substring-no-properties + (overlay-start ov) (overlay-end ov))))) (org-remark-highlight-adjust-position-after-load ov highlight-text))))) +(cl-defgeneric org-remark-highlights-adjust-positions-p (_org-remark-type) + "Return t if adjust-positions feature is relevant. +Default is t and evaluated per ORG-REMARK-TYPE." + t) + ;;;;; Other utilities (defun org-remark-source-get-file-name (filename) @@ -1588,6 +1892,10 @@ If FILENAME is nil, return nil." (with-current-buffer (find-file-noselect (org-remark-notes-get-file-name)) (funcall org-remark-source-file-name filename)))) +(cl-defgeneric org-remark-beg-end (_org-remark-type) + "Return beg and end for default ORG-REMARK-TYPE." + (org-remark-region-or-word)) + (defun org-remark-region-or-word () "Return beg and end of the active region or of the word at point. It is meant to be used within `interactive' in place for \"r\"