/org-marginalia

Highlight text; write margin notes (marginalia) for any text files in a separate Org file

Primary LanguageEmacs LispGNU General Public License v3.0GPL-3.0

Org-marginalia

https://img.shields.io/badge/License-GPLv3-blue.svg

Org-marginalia lets you highlight text, and write margin notes (marginalia) for any text file in a separate Org file.

./resources/images/2020-12-24T101116_Title.png Figure 1. Left: Org-mode with text enlarged; Right marginalia file with the inline image display on

Screenshots

Refer to the screenshots below for a teaser of what it can do.

./resources/images/2020-12-22T141331-OM-screen-shot-01.png Figure 2. Left: main note with some text highlighted in green; Right: margin notes in a marginalia file

./resources/images/2021-08-17T220032.png Figure 3. Left: org-roam-buffer showing backlinks from the marginal notes; Right: main Org note. Org-marinalia can automatically add links with Org-ID from marginal notes back to the main file. This works well with Org-roam’s backlink (V2)

./resources/images/2020-12-22T141331-OM-screen-shot-03.png Figure 4. Main note can be any text files. Left: an .el file with a highlight; Right: marginalia file

News

New Features

Auto-activation of Org-marginalia Mode when Opening Files with Marginal Notes
A new global minor mode org-marginalia-global-tracking-mode has been introduced as of 0.0.6. It saves and tracks files that have marginal notes. When it is active, visiting a file being tracked automatically turns on org-marginalia-mode, loading highlights previously saved in the marginalia file. The files being tracked are saved in org-marginalia-tracking-file, which you can customize. The default file is named .org-marginalia-tracking in your Emacs configuration directory (user-emacs-directory).
Support for Org ID and Org-roam
This is only applicable when the main source note is an Org file. As of 0.0.6, seamless workflow with Org-roam has been added by generating ID links automatically. If user option org-marginalia-use-org-id is non-nil (default) and Org ID is available in the main note, Org-marginalia will create a link back to the source note with using an Org-ID link instead of a normal file link. Org-marginalia looks at property inheritance for ID properties. When the immediate headline where a highlight belongs does not have an ID, the ID of a higher headline or file property is used when available. When none is available, Org-marginalia falls back to a normal file link. When a new marginalia file is created and org-marginalia-use-org-id is

non-nil, Org-marginalia will add an ID property to the file level. This is mainly to support Org-roam’s backlink feature for marginalia files.

Removing limitations

Use of Overlays, no longer Text-Properties
This makes easier to add a highlighter overlay on top of text regions that already have faces (e.g. syntax-highlighted part of source code).
Now turning off minor mode will turn off the highlights
It is still recommended to use org-marginalia-toggle to temporarily hide/show highlights. This is because turning off org-marginalia-mode will stop tracking of the locations of highlights in the current buffer. **Any** (however minor) change will likely result in mismatching the locations of saved highlights and the current buffer’s text content.

Contents

Installation

Manual

This package is not available on MELPA. Manual installation is required.

Ensure to have Org Mode 9.4 or later (tested on 9.4.2). This package uses org-collect-keywords, which does not exist in an earlier version.

Store both of the .el files in the repo in your load-path, and put this in your init file:

(add-to-list 'load-path "~/local-repos/org-marginalia/")
(require 'org-marginalia-global-tracking)
(require 'org-marginalia)

By loading org-marginalia, it will also pull in Org mode. You might like to defer loading of Org as it might take long time. As of version 0.0.6, you can do so with loading only org-marginalia-global-tracking, which does not load org automatically.

For example, I use this in my init file.

;; Set `load-path'
(add-to-list 'load-path "~/local-repos/org-marginalia")

;; Load only `org-marginalia-global-tracking'
;; and turn it on for automatic loading of highlights
;; for the files tracked
(load-library "org-marginalia-global-tracking")
(org-marginalia-global-tracking-mode 1)

;; Set keybindings `org-marginalia-mark' is bound to global-map so that you can
;; call it globally before the library is loaded.  In order to make
;; `org-marginalia-mark' and `org-marginalia-mode' callable, use `autoload'.
;; When this package is available in MELPA, `autoload' should not be required.
(autoload #'org-marginalia-mark "org-marginalia" nil t)
(autoload #'org-marginalia-mode "org-marginalia" nil t)
(define-key global-map (kbd "C-c m") #'org-marginalia-mark)
;; The rest of keybidings are done only on loading `org-marginalia'
(with-eval-after-load 'org-marginalia
  (define-key org-marginalia-mode-map (kbd "C-c n o") #'org-marginalia-open)
  (define-key org-marginalia-mode-map (kbd "C-c n ]") #'org-marginalia-next)
  (define-key org-marginalia-mode-map (kbd "C-c n [") #'org-marginalia-prev)
  (define-key org-marginalia-mode-map (kbd "C-c n r") #'org-marginalia-remove))

Usage

Commands

org-marginalia-global-tracking-mode

A global minor mode to save and track files that have marginal notes. When active, visiting a file being tracked automatically turns on org-marginalia-mode, which loads highlights previously saved in the marginalia file.

The files being tracked are saved in org-marginalia-tracking-file, which you can customize. The default file is named .org-marginalia-tracking in your Emacs configuration directory (user-emacs-directory).

org-marginalia-mode

Org-marginalia is a local minor mode. Toggle it on/off with using org-marginalia-mode. On activating, it loads your saved highlights from the marginalia file (defined by org-marginalia-notes-file-path), and enables automatic saving of highlights. The automatic saving is achieved via function org-marginalia-save added to after-save-hook.

org-marginalia-mark

Select a region of text, and call org-marginalia-mark to highlight the region. It will generate a new ID, and start tracking the location – so you can edit text around the highlighted text. Do not cut, copy and paste as the highlight will disappear (you can immediately undo to recover the text region along the highlights). To create a new marginal note entry in the marginalia file, save the buffer.

org-marginalia-save

By default, Org-marginalia automatically creates or updates corresponding entries in the marginalia file with location and text of highlights on saving the buffer. Nevertheless, you can manually call org-marginalia-save to do so (automatic process also call this command).

If user option org-marginalia-use-org-id is non-nil, Org-marginalia will create a link back to the source note with using an Org-ID link instead of a normal file link.

When a new marginalia file is created and org-marginalia-use-org-id is non-nil, Org-marginalia will add an ID property to the file level. This is mainly to support Org-roam’s backlink feature for marginalia files.

org-marginalia-open

Move your cursor on the highlighted text, and call org-marginalia-open to open the relevant margin notes in a separate window. Your cursor will move to the marginalia buffer narrowed to the relevant margin notes entry. You can edit the marginalia buffer as a normal Org buffer. Once you have done editing, you may simply save and close the it (kill it or close the window) as per your normal workflow. Technically, the marginalia buffer is a cloned indirect buffer of the marginalia file.

org-marginalia-load

This command visits the marginalia file and loads the saved highlights onto the current buffer. If there is no margin notes for it, it will output a message in the echo. Highlights tracked locally by Org-marginalia cannot persist when you kill the buffer, or quit Emacs. When you re-launch Emacs, ensure to turn on org-marginalia-mode to load the highlights. Loading is automatically done when you activate the minor mode.

org-marginalia-remove

This command removes the highlight at point. It will remove the highlight, and remove the properties from the marginalia, but will keep the headline and notes in tact.

You can pass a universal argument (C-u by default). If this is the case, the command additionally deletes the entire heading subtree, along with the notes you have written, for the highlight.

org-marginalia-next

Move to the next highlight if any. If there is none below the cursor, and there is a highlight above, loop back to the top one. If the point has moved to the next highlight, this function enables transient map with `set-transient-map’. You don’t have to press the keybinding prefix again to move further to the next. That is, you can do a key sequence like this:

C-c n ] ] ] ]

If you have the same prefix for `org-marginalia-prev’, you can combine it in the sequence like so:

C-c n ] ] [ [ This lets your cursor back to where you started (next next prev prev)

org-marginalia-prev

Move to the previous highlight if any. If there is none above the cursor, and there is a highlight below, loop back to the bottom one. This function enables transient map. See org-marginalia-next for detail.

org-marginalia-toggle

Toggle showing/hiding of highlighters in current buffer. It only affects the display of the highlighters. When hidden, highlights’ locations are still kept tracked; thus, upon buffer-save the correct locations are still recorded in the marginalia file.

Keybindings Examples

`Org-marginalia` only provides its mode map, and does not bind any keys to it. As an example, you coud do something like this below.

(define-key org-marginalia-mode-map (kbd "C-c n o") #'org-marginalia-open)
(define-key org-marginalia-mode-map (kbd "C-c m") #'org-marginalia-mark)
(define-key org-marginalia-mode-map (kbd "C-c n ]") #'org-marginalia-next)
(define-key org-marginalia-mode-map (kbd "C-c n [") #'org-marginalia-prev)

Composing Personal Workflow

Currently only “elementary” functions are defined in the package; for example, mark , save, and open are all separate functions. You can string these together to compose a more fluid operation to suite your own workflow. A very useful set of such chained commands have been suggesetd by holtzermann17 in Org-roam’s Discourse discussion (adjusted to reflect the change of the prefix from om/ to org-marginalia-) .

I will try to incorporate these into the package when I have more time to focus on it – I find them useful, but there are some plans I have had, and want to think of how I can incoprate these suggestions better with my ideas.

(defun org-marginalia-make-annotation ()
  (interactive)
  (let ((mark-end (region-end)))
    (org-marginalia-mark (region-beginning) (region-end))
    (org-marginalia-save)
    (org-marginalia-open (1- mark-end))
    (end-of-buffer)))

(define-key org-marginalia-mode-map (kbd "C-c M")
  #'org-marginalia-make-annotation)

(defun org-marginalia-browse-forward ()
  (interactive)
  (let ((buf (current-buffer)))
    (org-marginalia-next) (org-marginalia-open (point))
    (pop-to-buffer buf nil t)))

(define-key org-marginalia-mode-map (kbd "C-c n }")
  #'org-marginalia-browse-forward)

(defun org-marginalia-browse-backward ()
  (interactive)
  (let ((buf (current-buffer)))
    (org-marginalia-prev) (org-marginalia-open (point))
    (pop-to-buffer buf nil t)))

(define-key org-marginalia-mode-map (kbd "C-c n {")
  #'org-marginalia-browse-backward)

Customizing

  • You can customize settings in the org-marginalia group.
  • Highlight’s face can be changed via org-marginalia-highlighter
  • Marginalia file is defined by org-marginalia-notes-file-path
  • Your files with marginal notes are saved and tracked in org-marginalia-tracking-file (when tracking is turned on via the global minor mode org-marginalia-global-tracking-mode)
  • You can use Org-ID to create links from marginal notes back to their main notes when org-marginalia-use-org-id is on (default is on). This option also enables Org-marginalia to add an ID property when a new marginalia file is being created. This is to support seamless workflow with Org-roam.

Known Limitations

Copy & pasting loses highlights
Overlays are not part of the kill; thus cannot be yanked.
Undo highlight does not undo it
Overlays are not part of the undo list; you cannot undo highlighting. Use org-marginalia-remove command instead.
Moving source files and marginalia file
Move your files and marginalia file to another directory does not update the source path recorded in the marginalia file. It will be confusing. Try not to do this.

Changelog

0.0.6

Feature:

  • feat: Add org-marginalia-global-tracking-mode with a separate .el file
  • feat: Use Org-ID to create a link from the marginal notes back to the main file Add Customizable variable org-marginalia-use-org-id; default is t

Change:

  • chg: Highlights are now overlay; no longer text-properties

Improvement to existing functions

  • add: Deactivate mark after highlighting
  • add: org-marginalia-remove can take C-u to delete

Fix & Internal Refactor

  • intrnl: Add housekeeping for org-marginalia-highlights variable
  • fix: org-id-uuid is not found
  • fix: Add highlighter face def for terminal

0.0.5

  • break: Replace the prefix “om/” in the source code with “org-marginalia”
  • break: Remove default keybindings; add examples in readme instead. Addresses [#3](nobiot#3)

0.0.4

  • feat: Add transient navigation to next/prev See § Credits for the piece of code to achieve the transient map I used.

0.0.3

  • feat: Add om/toggle for show/hide highlighters

0.0.2

  • feat: Add om/next and /prev
  • break: Change om/open-at-point to org-marginalia-open
  • break: Change om/save-all to org-marginalia-save

0.0.1

Initial alpha release. I consider it to be the minimal viable scope.

Credits

To create this package, I was inspired by the following packages. I did not copy any part of them, but borrowed some ideas from them – e.g. saving the margin notes in a separate file.

Ov-highlight
John Kitchin’s (author of Org-ref). Great UX for markers with hydra. Saves the marker info and comments directly within the Org file as Base64 encoded string. It uses overlays with using `ov` package.
Annotate.el
Bastian Bechtold’s (author of Org-journal). Unique display of annotations right next to (or on top of) the text. It seems to be designed for very short annotations, and perhaps for code review (programming practice); I have seen recent issues reported when used with variable-pitch fonts (prose).
Org-annotate-file
Part of Org’s contrib library. It seems to be designed to annotate a whole file in a separate Org file, rather than specific text items.
InPlaceAnnotations (ipa-mode)
It looks similar to Annotate.el above.
Transient navigation feature
To implement the transient navigation feature, I liberally copied the relevant code from a wonderful Emacs package, Binder by Paul W. Rankin (GitHub user rnkn).

Feedback

Feedback welcome in this repo, or in Org-roam Discourse forum.

Edit: Now the features 1 & 2 have been implemented… I want to add a little more, to attend to the known limitations to see if I can remove some of them.

I am aiming to keep this package to be small and focused. I plan to add the following features, and probably consider it to be feature complete for my purposes.

  1. DONE v0.0.3 om/toggle to toggle show/hide of highlights without losing them
  2. DONE om/next and om/prev to easily navigate highlighted regions in the buffer This is done (v0.0.2), but I would like to try a transient (don’t want to repeat the prefix everytime): transient done with v0.0.4.

License

This work is licensed under a GPLv3 license. For a full copy of the licese, refer to LICENSE.

Marginalia for org-marginalia.el

This section is used as a demonstration and a collection of my ideas for this package.

Deleted Notes

I need to think it through. Do I want to reveal invisible elments to move, or keep it hidden. At the moment, om/list-highlights-positions has been changed to return beginning points of visible ones only – this can be changed to make it opsitonal arg. For example, if I want to list all, including the hidden ones, do I want to just visible ones?

Deleted Notes on jit-lock-register

org-marginalia

Some syntactic elements keep their faces descpite being marked. It appears to be the way font-lock-mode works. Experimenting. This might also lead to a way for copy and paste (need to deal with duplicate IDs)

;; Comment dddd
;; This is considered
;; Comment

;;Comment  dd
jit-lock-register

(defun)

(jit-lock-register #'my/font-lock-fn)
(font-lock-unfontify-buffer)
(jit-lock-refontify)
(font-lock-fontify-buffer)

(point);; comment
;; Comment 
(my/font-lock-fn 155 160)

(let ((beg 1)
      (end 10))
  (list beg end))

(defun my/font-lock-fn (beg end &optional context)
  (unless context
    (list beg)
    (if (get-char-property beg 'om/id)
        (font-lock-unfontify-region beg end))))

org-marginalia-load

org-marginalia

set-buffer-modified-p

org-marginalia

Adding overlay does not set the buffer modified. It’s more fluid with save operation. You cannot use `undo’ to undo highlighter.

overlay-put

org-marginalia

Do not add the evaporate t property for the highlight’s overlay. By remaining in the buffer, undo puts overlays in their original location when text regions get killed and subsequently the kill gets undone.

Using overlays instead of text-properties has an advantage of easy composition of faces; e.g. when marking on a comment line in emacs-lisp-mode, the highlighters face won’t be composed onto the underlying syntax face for comments. Overlay can make it easy to add an additional face to comments and other syntactically font-locked regions.

(make-overlay beg end nil ‘FRONT-ADVANCE)

org-marginalia

It’s more intuitive if editing the text both on the beg and end points of the highlight overlay does not extend it. Pass FRONT-ADVANCE; keep REAR-ADVANCE as default.

The arguments FRONT-ADVANCE and REAR-ADVANCE specify the marker insertion type for the start of the overlay and for the end of the overlay, respectively. *Note Marker Insertion Types::. If they are both ‘nil’, the default, then the overlay extends to include any text inserted at the beginning, but not text inserted at the end. If FRONT-ADVANCE is non-‘nil’, text inserted at the beginning of the overlay is excluded from the overlay. If REAR-ADVANCE is non-‘nil’, text inserted at the end of the overlay is included in the overlay.

Local Variables