From 92804b83082a7f99f28dffe91f78b4c75c03375c Mon Sep 17 00:00:00 2001 From: Simon Parri Date: Thu, 14 Aug 2025 10:34:18 -0500 Subject: Add version 0.50 --- .gitignore | 1 + README.org | 9 ++ org-margin.el | 288 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 298 insertions(+) create mode 100644 .gitignore create mode 100644 README.org create mode 100644 org-margin.el diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/README.org b/README.org new file mode 100644 index 0000000..c42f1a5 --- /dev/null +++ b/README.org @@ -0,0 +1,9 @@ +#+TITLE: org-margin +#+SUBTITLE: Smart, rules-based Org-agenda sorting + +* =org-margin= +/Org-mode notes for arbitrary documents./ + +=org-margin= provides tools for working with notes linked to documents. For those familiar with =org-noter=, =org-margin= can be described as a more general version of =org-noter=, because it works with arbitrary links. Please read the package’s description and function docstrings to learn how to use it. + +*Warning: This project is still version < 1.0, and is therefore /not guaranteed to be stable/. I maintain it for myself, but do not have capacity to improve or test it beyond my needs.* If you find a bug, feel free to report it, but don’t expect there not to be any. diff --git a/org-margin.el b/org-margin.el new file mode 100644 index 0000000..992fa97 --- /dev/null +++ b/org-margin.el @@ -0,0 +1,288 @@ +;;; org-margin.el --- Org-mode notes for arbitrary documents -*- lexical-binding: t; -*- + +;; Copyright (C) 2025 Simon Parri + +;; Author: Simon Parri +;; Keywords: multimedia, hypermedia, outlines, docs +;; Package-Requires: ((emacs "24.3") (org "9.6") (compat "29.1")) +;; Version: 0.50 + +;; 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 . + +;;; Commentary: + +;; This package allows users to associate headings in an Org buffer (known as +;; the "notes" buffer) with links to an arbitrary document (using Org's +;; linking facilities). It also provides commands to make linking between the +;; document and the notes easy (`org-margin-doc-set-link' and +;; `org-margin-org-set-link'), and to navigate between headings with margin +;; links (`org-margin-org-next-heading' and `org-margin-org-previous-heading'). +;; See these commands, as well as the Customization group, for more +;; information on how to use this package. + +;; For users familiar with the `org-noter' package, this package essentially +;; does the same thing, but is more generalized, since it uses arbitrary Org +;; links instead of expecting the medium to be paged. + +;; Some uses for this package could be: + +;; + To take notes on code using Org file: links (which remmeber the line as +;; well as the file) + +;; + To take notes on a PDF or EPUB file (using `nov.el' to read EPUBs, and my +;; package `ol-file-paged' for per-page links to DocView and `pdf-tools' +;; buffers) + +;;; Code: + +(require 'org) + +;; Custom variables + +(defgroup org-margin () + "Org-mode notes for arbitrary documents." + :group 'org) + +(defcustom org-margin-find-org-buffer-functions + '(org-margin-find-org-buffer-fallback) + "List of functions to be used to find an appropriate Org notes buffer. + +They will be called in order with no arguments and the value returned by +the first one to return non-nil will be used as the notes buffer." + :type 'hook + :options '(org-margin-find-org-buffer-fallback + org-margin-find-other-buffer)) + +(defcustom org-margin-doc-modes + '(doc-view-mode pdf-view-mode nov-mode) + "List of modes to be considered as document modes." + :type '(set symbol)) + +(defcustom org-margin-find-doc-buffer-functions + '(org-margin-find-doc-buffer-fallback + org-margin-find-other-buffer) + "List of functions to be used to find an appropriate document buffer. + +They will be called in order with no arguments and the value returned by +the first one to return non-nil will be used as the document buffer." + :type 'hook + :options '(org-margin-find-doc-buffer-fallback + org-margin-find-other-buffer)) + +(defcustom org-margin-org-fold-others t + "When t, org-margin-org command fold other headings after moving. + +When this is nil, `org-margin-org-next-heading' and +`org-margin-org-previous-heading' will leave the folding as-is after +moving." + :type 'boolean) + +;; Buffer-finding code + +(defun org-margin--find (list) + "Search LIST of functions for the first one that returns non-nil. + +Returns the value returned by the function. + +Each function is called with no arguments." + (cl-loop for f in list + do (when-let ((it (funcall f))) + (cl-return it)))) + +(defun org-margin-find-other-buffer () + "Get the buffer from the \"other\" window. + +Other window is determined by `other-window-for-scrolling'." + (with-selected-window (other-window-for-scrolling) + (current-buffer))) + +(defun org-margin-find-org-buffer-fallback () + "Try to find an Org-mode buffer that is currently displayed. + +First tries the \"other\" window (as determined by +`other-window-for-scrolling'), then tries any other displayed window. + +Returns nil if no appropriate buffer is found." + (or + (with-selected-window (other-window-for-scrolling) + (when (derived-mode-p 'org-mode) + (current-buffer))) + (when-let ((w (cl-find-if + (lambda (x) + (with-selected-window x + (derived-mode-p 'org-mode))) + (window-list)))) + (with-selected-window w + (current-buffer))))) + +(defun org-margin-find-org-buffer () + "Find an appropriate Org buffer for the current document buffer. + +Uses `org-margin-find-org-buffer-functions', which see for how to +customize its behavior." + (org-margin--find org-margin-find-org-buffer-functions)) + +(defun org-margin-find-doc-buffer-fallback () + "Find a relevant buffer in one of `org-margin-doc-modes'. + +Goes through all visible windows and returns the first displayed buffer +that is in one of the modes listed in `org-margin-doc-modes'." + (when-let ((w (cl-find-if + (lambda (x) + (with-selected-window x + (derived-mode-p org-margin-doc-modes))) + (window-list)))) + (with-selected-window w + (current-buffer)))) + +(defun org-margin-find-doc-buffer () + "Find an appropriate Org buffer for the current document buffer. + +Uses `org-margin-find-doc-buffer-functions', which see for how to +customize its behavior." + (org-margin--find org-margin-find-org-buffer-functions)) + +(cl-defmacro org-margin--with-buffer ((buffer &optional error) &rest body) + "Evaluate BODY in BUFFER, raising ERROR if BUFFER is nil. + +If ERROR is not given, a generic \"No buffer found\" error is raised +instead." + (declare (indent 1)) + (let ((b (gensym 'b))) + `(if-let ((,b ,buffer)) + (with-current-buffer ,b + ,@body) + (user-error ,(or error "No buffer found."))))) + +;; Margin-doc mode code + +;;;###autoload +(defun org-margin-doc-set-link () + "Link the current heading in the notes buffer to the current view. + +Uses `org-store-link' to get the most appropriate link for the current +buffer and position, and stores it in the MARGIN_LINK property of the +current heading in the notes buffer (as found by +`org-margin-find-org-buffer')." + (interactive nil org-margin-doc-mode) + (let ((link + (let (org-stored-links) + (org-store-link nil)))) + (org-margin--with-buffer + ((org-margin-find-org-buffer) + "No Org buffer found.") + (org-entry-put nil "MARGIN_LINK" link)))) + +(defvar-keymap org-margin-doc-mode-map + "C-M-." 'org-margin-doc-set-link) + +;;;###autoload +(define-minor-mode org-margin-doc-mode + "Minor mode for Org-Margin in the document buffer." + :lighter " Margin-Doc" + :interactive t) + +;; Margin-org mode code + +;;;###autoload +(defun org-margin-org-set-link () + "Link the current heading to the current view of the document buffer. + +See `org-margin-find-doc-buffer' for how the document buffer is +determined and `org-margin-doc-set-link' for how the link is determined +and set." + (interactive nil org-mode) + (org-margin--with-buffer + ((org-margin-find-doc-buffer) + "No other window.") + (org-margin-doc-set-link))) + +(defun org-margin-org-get-link () + "Get the margin link of the current heading." + (when (derived-mode-p 'org-mode) + (org-entry-get nil "MARGIN_LINK"))) + +;;;###autoload +(defun org-margin-org-open-link () + "Open the margin link of the current heading." + (interactive nil org-mode) + (let ((w (selected-window))) + (org-link-open-from-string + (or (org-margin-org-get-link) + (user-error "No link for this heading."))) + (select-window w t))) + +(defun org-margin-org--goto-heading-with-property (dir) + "Go to the nearest heading with a MARGIN_LINK property in direction DIR. + +If DIR is greater than 0, look forwards, otherwise look backwards. + +Returns the value of the heading's MARGIN_LINK property." + (org-back-to-heading) + (if (> dir 0) + (outline-next-heading) + (outline-previous-heading)) + (while (not (or (org-entry-get nil "MARGIN_LINK") + (if (> dir 0) + (eobp) + (bobp)))) + (if (> dir 0) + (outline-next-heading) + (outline-previous-heading))) + (org-entry-get nil "MARGIN_LINK")) + +;;;###autoload +(defun org-margin-org-next-heading (&optional arg) + "Move to the next heading with a margin link and follow the link. + +With ARG repeats, or moves backward if negative." + (interactive "p" org-mode) + (unless arg (setq arg 1)) + (let ((last nil) + (point (point))) + (when org-margin-org-fold-others + (save-excursion + (org-back-to-heading-or-point-min) + (org-fold-hide-entry))) + (dotimes (_ (abs arg)) + (setq last (org-margin-org--goto-heading-with-property arg))) + (org-reveal t) + (when org-margin-org-fold-others + (org-fold-show-entry t)) + (if last + (org-margin-org-open-link) + (goto-char point) + (user-error "No other heading with margin link")))) + +;;;###autoload +(defun org-margin-org-previous-heading (&optional arg) + "Move to the previous heading with a margin link and follow the link. + +With ARG repeats, or moves forward if negative." + (interactive "p" org-mode) + (org-margin-org-next-heading (- arg))) + +(defvar-keymap org-margin-org-mode-map + "C-M-." 'org-margin-org-set-link + "C-M-," 'org-margin-org-open-link + "C-M-n" 'org-margin-org-next-heading + "C-M-p" 'org-margin-org-previous-heading) + +;;;###autoload +(define-minor-mode org-margin-org-mode + "Minor mode for Org-Margin in the Org mode buffer." + :lighter " Margin-Org") + +(provide 'org-margin) +;;; org-margin.el ends here -- cgit v1.2.3