branch: externals/org-remark commit 50bfb96d6964ca241cef768ff7ec2ca906c71890 Merge: 0c6aad188a a7de41bdfa Author: Noboru Ota <m...@nobiot.com> Commit: Noboru Ota <m...@nobiot.com>
Merge branch 'dev/name-change' --- .dir-locals.el | 11 + .github/workflows/gh-docs.yml | 28 ++ Makefile | 15 + NEWS | 18 + README.org | 18 +- docs/.nojekyll | 0 docs/Makefile | 34 ++ docs/fdl.texi | 505 ++++++++++++++++++++ docs/org-remark-manual.org | 324 +++++++++++++ docs/resources/manual.css | 75 +++ org-marginalia-global-tracking.el | 114 ----- org-marginalia.el | 633 ------------------------- org-remark-convert-legacy.el | 101 ++++ org-remark-global-tracking.el | 129 +++++ org-remark.el | 955 ++++++++++++++++++++++++++++++++++++++ 15 files changed, 2212 insertions(+), 748 deletions(-) diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000000..75fe4f4a01 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,11 @@ +((nil + (time-stamp-format . "%02d %:B %Y") + (time-stamp-start . "modified:[ ]+\\\\?") + (time-stamp-end . "$") + ;; "Last modified in source code files are at line 20" + (time-stamp-line-limit . 20) + ;; Need this locale to be "C" or "en_US.UTF-8" or something to standardize the + ;; time stamp with English + (system-time-locale . "C")) + (fill-column . 80) + (indent-tabs-mode . nil)) diff --git a/.github/workflows/gh-docs.yml b/.github/workflows/gh-docs.yml new file mode 100644 index 0000000000..af9ac15a58 --- /dev/null +++ b/.github/workflows/gh-docs.yml @@ -0,0 +1,28 @@ +name: GH-Page AutoGen + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + continue-on-error: false + steps: + - uses: actions/checkout@v2 + - uses: purcell/setup-emacs@master + with: + version: '27.2' + - name: Install dependencies + run: sudo apt-get install texinfo + - name: Run docs/make gh-html + run: | + cd docs + make gh-html + - name: Deploy 🚀 + uses: JamesIves/github-pages-deploy-action@4.1.7 + with: + branch: gh-pages # The branch the action should deploy to. + folder: docs # The folder the action should deploy. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..14bd50fd27 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +org-remark.org: docs/org-remark-manual.org + -emacs --batch -L ../org-transclusion -l org-transclusion $< \ + --eval '(progn (org-transclusion-add-all) (write-region nil nil "org-transclusion.org"))' + +.PHONY: test-compile +test-compile: + emacs --batch --eval "(add-to-list 'load-path default-directory)" \ + -f batch-byte-compile ./*.el + # Check declare-function + emacs --batch --eval "(check-declare-directory default-directory)" + +.PHONY: clean +clean: + find . -name "*.elc" -delete + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000000..e6e4dce70b --- /dev/null +++ b/NEWS @@ -0,0 +1,18 @@ +- Changes :: + + - Visit and open: for open you will stay in the current buffer. For further + edititing, use visit. + + - Removing all use of "marginalia" + + + The default value of org-remark-notes-file-path is changed from + "marginalia.org" to "marginal-notes.org". Adjust the filenames or change + the value. + + - When updating the existing headline and position properties, don't update + the headline text when it already exists. Let the user decide how to manage + headlines. + +- Add :: + + - add ::line-number to file line \ No newline at end of file diff --git a/README.org b/README.org index 212bca7bad..4b8e6b86d2 100644 --- a/README.org +++ b/README.org @@ -1,11 +1,12 @@ [[file:https://img.shields.io/badge/License-GPLv3-blue.svg]] -#+TITLE: Org-marginalia +#+TITLE: Org-remark #+PROPERTY: LOGGING nil # Note: I use the readme template that alphapapa shares on his GitHub repo <https://github.com/alphapapa/emacs-package-dev-handbook#template>. It works with the org-make-toc <https://github.com/alphapapa/org-make-toc> package, which automatically updates the table of contents. +<<<<<<< HEAD * ❦❦❦ IMPORTANT NOTICE ❦❦❦ [This notice written on 3 January 2022] @@ -78,6 +79,8 @@ non-nil, Org-marginalia will add an ID property to the file level. This is mainl - [[#feedback][Feedback]] - [[#license][License]] - [[#local-variables][Local Variables]] +- [[#org-remark][org-remark]] + - [[#defun][defun]] :END: * Installation @@ -332,3 +335,16 @@ This work is licensed under a GPLv3 license. For a full copy of the licese, refe # org-export-with-title: t # line-spacing: 4 # End: + + + +* org-remark +:PROPERTIES: +:org-remark-file: ~/src/org-remark/org-remark.el +:END: + +** defun +:PROPERTIES: +:CATEGORY: correction +:END: +[[file:~/src/org-remark/org-remark.el::211][org-remark]] diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000000..ff7be120f8 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,34 @@ +.POSIX: +EMACS = emacs +MAKEINFO = makeinfo +INSTALLINFO = install-info +MV = mv +RM = rm +MANUAL_HTML_ARGS =--html --no-split --footnote-style=separate --css-ref=resources/manual.css + +## Build ############################################################# + +.PHONY: gh-html +gh-html: index.html clean + +.PHONY: all +all: index.html org-remark.info clean + +index.html: org-remark.texi + @printf "\n\n### Generating manual .html files \n\n" + $(MAKEINFO) $(MANUAL_HTML_ARGS) $< -o index.html + +org-remark.texi: org-remark-manual.org + @printf "\n\n### Generating manual .texi file \n\n" + $(EMACS) --batch -L --file $< \ + -f org-texinfo-export-to-texinfo + +org-remark.info: org-remark.texi + @printf "\n\n### Generating manual .info file \n\n" + makeinfo org-remark.texi -o org-remark.info + + +.PHONY: clean +clean: + @printf "\n\n### Clear .texi file \n\n" + $(RM) org-remark.texi* diff --git a/docs/fdl.texi b/docs/fdl.texi new file mode 100644 index 0000000000..eaf3da0e92 --- /dev/null +++ b/docs/fdl.texi @@ -0,0 +1,505 @@ +@c The GNU Free Documentation License. +@center Version 1.3, 3 November 2008 + +@c This file is intended to be included within another document, +@c hence no sectioning command or @node. + +@display +Copyright @copyright{} 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. +@uref{https://fsf.org/} + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. +@end display + +@enumerate 0 +@item +PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document @dfn{free} in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of ``copyleft'', which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + +@item +APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The ``Document'', below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as ``you''. You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A ``Modified Version'' of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A ``Secondary Section'' is a named appendix or a front-matter section +of the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall +subject (or to related matters) and contains nothing that could fall +directly within that overall subject. (Thus, if the Document is in +part a textbook of mathematics, a Secondary Section may not explain +any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The ``Invariant Sections'' are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The ``Cover Texts'' are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A ``Transparent'' copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not ``Transparent'' is called ``Opaque''. + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, La@TeX{} input +format, SGML or XML using a publicly available +DTD, and standard-conforming simple HTML, +PostScript or PDF designed for human modification. Examples +of transparent image formats include PNG, XCF and +JPG@. Opaque formats include proprietary formats that can be +read and edited only by proprietary word processors, SGML or +XML for which the DTD and/or processing tools are +not generally available, and the machine-generated HTML, +PostScript or PDF produced by some word processors for +output purposes only. + +The ``Title Page'' means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, ``Title Page'' means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +The ``publisher'' means any person or entity that distributes copies +of the Document to the public. + +A section ``Entitled XYZ'' means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as ``Acknowledgements'', +``Dedications'', ``Endorsements'', or ``History''.) To ``Preserve the Title'' +of such a section when you modify the Document means that it remains a +section ``Entitled XYZ'' according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + +@item +VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + +@item +COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + +@item +MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +@enumerate A +@item +Use in the Title Page (and on the covers, if any) a title distinct +from that of the Document, and from those of previous versions +(which should, if there were any, be listed in the History section +of the Document). You may use the same title as a previous version +if the original publisher of that version gives permission. + +@item +List on the Title Page, as authors, one or more persons or entities +responsible for authorship of the modifications in the Modified +Version, together with at least five of the principal authors of the +Document (all of its principal authors, if it has fewer than five), +unless they release you from this requirement. + +@item +State on the Title page the name of the publisher of the +Modified Version, as the publisher. + +@item +Preserve all the copyright notices of the Document. + +@item +Add an appropriate copyright notice for your modifications +adjacent to the other copyright notices. + +@item +Include, immediately after the copyright notices, a license notice +giving the public permission to use the Modified Version under the +terms of this License, in the form shown in the Addendum below. + +@item +Preserve in that license notice the full lists of Invariant Sections +and required Cover Texts given in the Document's license notice. + +@item +Include an unaltered copy of this License. + +@item +Preserve the section Entitled ``History'', Preserve its Title, and add +to it an item stating at least the title, year, new authors, and +publisher of the Modified Version as given on the Title Page. If +there is no section Entitled ``History'' in the Document, create one +stating the title, year, authors, and publisher of the Document as +given on its Title Page, then add an item describing the Modified +Version as stated in the previous sentence. + +@item +Preserve the network location, if any, given in the Document for +public access to a Transparent copy of the Document, and likewise +the network locations given in the Document for previous versions +it was based on. These may be placed in the ``History'' section. +You may omit a network location for a work that was published at +least four years before the Document itself, or if the original +publisher of the version it refers to gives permission. + +@item +For any section Entitled ``Acknowledgements'' or ``Dedications'', Preserve +the Title of the section, and preserve in the section all the +substance and tone of each of the contributor acknowledgements and/or +dedications given therein. + +@item +Preserve all the Invariant Sections of the Document, +unaltered in their text and in their titles. Section numbers +or the equivalent are not considered part of the section titles. + +@item +Delete any section Entitled ``Endorsements''. Such a section +may not be included in the Modified Version. + +@item +Do not retitle any existing section to be Entitled ``Endorsements'' or +to conflict in title with any Invariant Section. + +@item +Preserve any Warranty Disclaimers. +@end enumerate + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled ``Endorsements'', provided it contains +nothing but endorsements of your Modified Version by various +parties---for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + +@item +COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled ``History'' +in the various original documents, forming one section Entitled +``History''; likewise combine any sections Entitled ``Acknowledgements'', +and any sections Entitled ``Dedications''. You must delete all +sections Entitled ``Endorsements.'' + +@item +COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + +@item +AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an ``aggregate'' if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + +@item +TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled ``Acknowledgements'', +``Dedications'', or ``History'', the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + +@item +TERMINATION + +You may not copy, modify, sublicense, or distribute the Document +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense, or distribute it is void, and +will automatically terminate your rights under this License. + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, receipt of a copy of some or all of the same material does +not give you any rights to use it. + +@item +FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +@uref{https://www.gnu.org/licenses/}. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License ``or any later version'' applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. If the Document +specifies that a proxy can decide which future versions of this +License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the +Document. + +@item +RELICENSING + +``Massive Multiauthor Collaboration Site'' (or ``MMC Site'') means any +World Wide Web server that publishes copyrightable works and also +provides prominent facilities for anybody to edit those works. A +public wiki that anybody can edit is an example of such a server. A +``Massive Multiauthor Collaboration'' (or ``MMC'') contained in the +site means any set of copyrightable works thus published on the MMC +site. + +``CC-BY-SA'' means the Creative Commons Attribution-Share Alike 3.0 +license published by Creative Commons Corporation, a not-for-profit +corporation with a principal place of business in San Francisco, +California, as well as future copyleft versions of that license +published by that same organization. + +``Incorporate'' means to publish or republish a Document, in whole or +in part, as part of another Document. + +An MMC is ``eligible for relicensing'' if it is licensed under this +License, and if all works that were first published under this License +somewhere other than this MMC, and subsequently incorporated in whole +or in part into the MMC, (1) had no cover texts or invariant sections, +and (2) were thus incorporated prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site +under CC-BY-SA on the same site at any time before August 1, 2009, +provided the MMC is eligible for relicensing. + +@end enumerate + +@page +@heading ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + +@smallexample +@group + Copyright (C) @var{year} @var{your name}. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover + Texts. A copy of the license is included in the section entitled ``GNU + Free Documentation License''. +@end group +@end smallexample + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the ``with@dots{}Texts.''@: line with this: + +@smallexample +@group + with the Invariant Sections being @var{list their titles}, with + the Front-Cover Texts being @var{list}, and with the Back-Cover Texts + being @var{list}. +@end group +@end smallexample + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. + +@c Local Variables: +@c ispell-local-pdict: "ispell-dict" +@c End: diff --git a/docs/org-remark-manual.org b/docs/org-remark-manual.org new file mode 100644 index 0000000000..ea9e54f80c --- /dev/null +++ b/docs/org-remark-manual.org @@ -0,0 +1,324 @@ +#+title: Org-remark User Manual +#+author: Noboru Ota <m...@nobiot.com> +#+macro: version 1.0.x +#+macro: modified 17 January 2022 + +#+language: en +#+export_file_name: org-remark.texi +#+texinfo_dir_category: Emacs +#+texinfo_dir_title: Org-remark: (org-remark) +#+texinfo_dir_desc: Highlight and annotate any text file +#+texinfo: @paragraphindent asis + +#+options: toc:nil ':t + +#+ATTR_TEXINFO: :tag CAUTION +#+begin_quote +This manual is still in a draft version. +#+end_quote + +This manual is for Org-remark version {{{version}}}. + +Last updated: {{{modified}}}. + +Org-remark lets you highlight and annotate any text file with using Org mode. + +#+texinfo: @insertcopying + +* COPYING +:PROPERTIES: +:COPYING: t +:END: + +Copyright \copy 2021-2022 Free Software Foundation, Inc. + +#+begin_quote +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.3 or +any later version published by the Free Software Foundation; with no +Invariant Sections, with the Front-Cover Texts being “A GNU Manual,” and +with the Back-Cover Texts as in (a) below. A copy of the license is +included in the section entitled “GNU Free Documentation License.” + +(a) The FSF’s Back-Cover Text is: “You have the freedom to copy and +modify this GNU manual.” +#+end_quote + +* Installation +:PROPERTIES: +:CUSTOM_ID: installation +:END: + +This package is not available on ELPA or MELPA yet. 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 all the =.el= files in the repo in your load-path and put this in your +init file: + +#+begin_src emacs-lisp + ;; Set `load-path' , load `org-remark-global-tracking', and turn it on for + ;; automatic loading of highlights for the files tracked + (add-to-list 'load-path "~/local-repos/org-remark") + (require 'org-remark-global-tracking) + (org-remark-global-tracking-mode +1) +#+end_src + +Unless you explicitly load =org= during Emacs initialization, I suggest to defer loading =org-remark= (thus there is no =(require 'org-remark)= in the example above). This is because it will also pull in =org=, which can slow down initialization. By autoloading some commands in similar ways as the example keybindings below, you can control the timing of loading =org-remark=. + +Below are example keybindings you might like to consider: + +#+begin_src emacs-lisp + ;; Key-bind `org-remark-mark' to global-map so that you can call it globally + ;; before the library is loaded. In order to make `org-remark-mark' and + ;; `org-remark-mode' callable, use `autoload'. + (autoload #'org-remark-mark "org-remark" nil t) + (autoload #'org-remark-mode "org-remark" nil t) + (define-key global-map (kbd "C-c n m") #'org-remark-mark) + + ;; The rest of keybidings are done only on loading `org-remark' + (with-eval-after-load 'org-remark + (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)) +#+end_src + +* Getting Started +** Highlighting and Annotating + +#+findex: org-remark-mark +#+findex: org-remark-open +#+findex: org-remark-view +#+cindex: marginal notes file + +Once you have installed and set it up (refer to section [[#installation][Installation]]), Org-remark is simple to use. Select a part of text[fn:1] and call =M-x org-remark-mark= to highlight it. You will see the selected text gets highlighted. That's it. + +To add or 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 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 entry [...] + +[fn:1] Set a mark and activate a region in Emacs terminology. + +** Navigating from One Highlight to Another + +#+findex: org-remark-view-next +#+findex: org-remark-view-prev +#+findex: org-remark-remove + +After you have added a couple of highlights in the text, you can jump around to the next or previous highlights easily. Use =org-remark-view-next= and =org-remark-view-prev= to brows the marginal notes as you move from one highlight to another. They display the marginal notes on the side-window. Or use =org-remark-next= and =org-remark-prev= if you simply move to though highlights without displaying marginal notes for them. + +To make it easy to navigate, you can use the same "prefix key" to Org-remark commands, like this: + +- =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= + +The =C-c n= are the prefix key common to all of them. If you set the keybindings like this, you can use =C-c n ]= once to view the next highlight, and simply keep using a single key =]= or =[= to browse the next/previous highlights. After you have reached the one you like to act on, press =o= to open it, or =r= to remove it. + +** Create Your Own Custom Highlighter Pens + +#+findex: org-remark-create +#+findex: org-remark-mark-yellow +#+findex: org-remark-mark-red-line + +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-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 + +Org-remark does not stop there; it lets you create your own custom pen functions with =org-remark-create=. Use the yellow and red line pens as examples, and create your own. Refer to [[#create-custom-pens][Create Your Own Custom Pens]] for how to do it. + +This is it. It's all to get you started. For more detail, refer to the rest of this user manual, especially the [[#usage][Usage]] and [[#customizing][Customizing]] sections. There is more detail to the commands introduced in this section and more ways in which you can customize Org-remark. + +* Usage +:PROPERTIES: +:CUSTOM_ID: usage +:END: + +** Create Your Own Custom Highlighter Pen +:PROPERTIES: +:CUSTOM_ID: create-custom-pens +:END: + +#+cindex: user-defined custom highlighter pen functions +#+cindex: Org-remark properties for highlights +#+findex: org-remark-mark +#+findex: org-remark-mark-yellow +#+findex: org-remark-mark-red-line +#+findex: org-remark-create + +=org-remark-create= is a macro that lets create your own custom pen functions. Org-remark comes with two additional pens that are created by default. Use them as examples to learn how to create your own. + +#+begin_src elisp + (org-remark-create "red-line" + '(:underline (:color "dark red" :style wave)) + '(CATEGORY "review" help-echo "Review this")) + (org-remark-create "yellow" + '(:underline "gold" :background "lemon chiffon") + '(CATEGORY "important")) +#+end_src + +- 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=. + + 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". + +#+ATTR_TEXINFO: :tag NOTE +#+begin_quote +Don't use =category= (all lowercase, symbol) as a property -- it's a special one for text properties. If you use it, the value also need to be a symbol; otherwise, you will get an error. You can use =CATEGORY= (all uppercase, symbol), which will result in adding =CATEGORY= with the value in the property drawer in marginal notes Org files. +#+end_quote + +** Tracking to Automatically Load Highlights after Re-starting Emacs + +#+findex: org-remark-global-tracking-mode +#+findex: org-remark-mode +#+vindex: org-remark-tracking-file + +It is recommended that =org-remark-global-tracking-mode= be turned on as part of your Emacs initialization. This should be done before you start adding highlights in any file. + +Once you have added highlights to some files, quit Emacs, and re-start it, active =org-remark-global-tracking-mode= will automatically turn on =org-remark-mode= and load the highlights from your previous sessions for the files being globally tracked. + +When activated, =org-remark-global-tracking-mode= will also start remembering and tracking the files to which you add highlights and annotations. When you quit Emacs, it will save the tracked files in a file in your Emacs config directory (=user-emacs-directory=). [[#customizing][By default]], this file is named =.org-remark-tracking=. + +Without this global minor mode, you would need to remember to activate =org-remark-mode= for each file where you add highlights and annotation. This is often unpractical. + +** Marginal Notes File + +#+cindex: marginal notes file +#+cindex: Org-remark properties for highlights + +When you mark a part of text with a highlighter pen function, Org-remark will automatically create a *marginal notes file*. [[#customizing][By default]], it will be named =marginalia.org= and created in the same directory as the file you are editing. + +The important thing to note is that Org-remark uses following properties in the property drawer of the headline to remember the highlights: + +- :org-remark-beg: +- :org-remark-end: +- :org-remark-id: +- :org-remark-label: + +Essentially, the marginal notes file is a database in the plain text with using Org mode. As a plain text database, you can easily edit these properties manually if necessary. + +You can leave the marginal notes file as it is without writing any notes. In this case, the entries in marginal notes file simply save the locations of your highlighted text. After you quit Emacs, re-start it, and visit the same main file, Org-remark uses this information to highlight the text again. You can also directly edit the marginal notes file as a normal Org file. + +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 (refer to [[create-custom-pens][Create Your Own Custom Pens]]). + +** \*marginal-notes\* Buffer + +#+cindex: *marginal notes* buffer + +When you display the marginal notes with =org-remark-view= or =org-remark-open= for a given highlight, Org-remark creates a cloned indirect buffer visiting the marginal notes file. [[#customizing][By default]], it is a dedicated side-window opened to the left part of the current frame, and it is named \*marginal notes\*. You can change the behavior of =display-buffer= function and the name of the buffer. + +Org-remark displays the marginal notes buffer narrowed to the highlight the cursor is on. + +* Customizing +:PROPERTIES: +:CUSTOM_ID: customizing +:END: + +#+vindex: org-remark-highlighter +#+vindex: org-remark-create-default-pen-set +#+vindex: org-remark-notes-display-buffer-action +#+vindex: org-remark-notes-buffer-name +#+vindex: org-remark-use-org-id +#+vindex: org-remark-tracking-file + +Org-remark's user options are available in the =org-remark= group. + +- Face: org-remark-highlighter :: + Default face for =org-remark-mark= + +- Customizable variable: org-remark-create-default-pen-set :: + When non-nil, Org-remark creates default pen set. Set to nil if you prefer for it not to. + +- Customizable variable: org-remark-notes-file-path :: + Define the file path to store the location of highlights and write annotations. +The default is one file per directory. Ensure that it is an Org +file. + +- Customizable variable: org-remark-notes-display-buffer-action :: + Define how Org-remark opens the notes buffer. +The default is to use a dedicated side-window on the left. It is +an action list for =display-buffer=. Refer to its documentation +for more detail and expected elements of the list. + +- Customizable variable: org-remark-notes-buffer-name :: + Define the buffer name of the marginal notes. +=org-remark-open= creates an indirect clone buffer with this +name. + +- Customizable variable: org-remark-use-org-id :: + Define if Org-remark use Org-ID to link back to the main note. + +- Customizable variable: org-remark-tracking-file :: + Define file path to save the files `org-remark' tracks. +When `org-remark-global-tracking-mode' is active, opening a file +saved in =org-remark-tracking-file= automatically loads highlights. + +* 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-remark-remove= command instead. + +- Moving source files and remark file :: Move your files and remark file to another directory does not update the source path recorded in the remark file. It will be confusing. Try not to do it. + +* 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. + +- [[https://github.com/jkitchin/ov-highlight][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. + +- [[https://github.com/bastibe/annotate.el][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). + +- [[https://github.com/tkf/org-mode/blob/master/contrib/lisp/org-annotate-file.el][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. + +- [[https://github.com/IdoMagal/ipa.el][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, [[https://github.com/rnkn/binder/blob/24d55db236fea2b405d4bdc69b4c33d0f066059c/binder.el#L658-L665][Binder]] by Paul W. Rankin (GitHub user [[https://github.com/rnkn][rnkn]]). + +* Feedback + +Feedback welcome in this repo, or in [[https://org-roam.discourse.group/t/prototype-org-marginalia-write-margin-notes-with-org-mode/1080][Org-roam Discourse forum]]. + +* Contributing + +To be added + +* Index - Features +:PROPERTIES: +:CUSTOM_ID: cindex +:APPENDIX: t +:INDEX: cp +:DESCRIPTION: Key concepts & features +:END: + +* Index - Commands +:PROPERTIES: +:APPENDIX: t +:INDEX: fn +:DESCRIPTION: Interactive functions +:END: + +* Index - User Options +:PROPERTIES: +:APPENDIX: t +:INDEX: vr +:DESCRIPTION: Customizable variables & faces +:END: + +* GNU Free Documentation License +:PROPERTIES: +:appendix: t +:END: + +#+texinfo: @include fdl.texi + +# Local Variables: +# time-stamp-start: "modified +\\\\?" +# End: diff --git a/docs/resources/manual.css b/docs/resources/manual.css new file mode 100644 index 0000000000..05c8418c48 --- /dev/null +++ b/docs/resources/manual.css @@ -0,0 +1,75 @@ +/** Based on Org-roam's css + https://github.com/org-roam/org-roam/blob/master/doc/assets/page.css + **/ +:root { + --border: #526980; + --code: #007; +} + +body { + margin: 5ex auto; + max-width: 850px; + line-height: 1.5; + font-family: sans-serif; +} + +h1, h2, h3 { + font-weight: normal; +} + +.settitle { + display: none; +} + +pre, code { + font-family: x, monospace; +} + +pre { + padding: 1ex; + background: #eee; + border: solid 1px #ddd; + min-width: 0; + font-size: 80%; + overflow: auto; +} + +code { + color: var(--code); +} + +img { + max-width: 100%; +} + +table { + border-collapse: collapse; + width: 100%; +} + +pre.menu-comment { + background: none; + border: none; + font-family: sans-serif; + padding: 0; + margin: 0; + font-size: 100%; +} + +thead { + border-bottom: 1px solid var(--border); +} + +tfoot { + border-top: 1px solid var(--border); +} + +blockquote { + margin-left: 1rem; + font-style: italic; + font-family: serif; + border-left: 3px solid; + border-left-color: currentcolor; + border-color: var(--text-color); + padding-left: 1em; +} diff --git a/org-marginalia-global-tracking.el b/org-marginalia-global-tracking.el deleted file mode 100644 index 330d7799ed..0000000000 --- a/org-marginalia-global-tracking.el +++ /dev/null @@ -1,114 +0,0 @@ -;;; org-marginalia-global-tracking.el --- Track files with marginal notes -*- lexical-binding: t; -*- - -;; Copyright (C) 2020 Noboru Ota - -;; Author: Noboru Ota <m...@nobiot.com> -;; URL: https://github.com/nobiot/org-marginalia -;; Version: 0.0.6 -;; Last Modified: 2021-08-18 -;; Package-Requires: ((emacs "27.1") (org "9.4")) -;; Keywords: org-mode, annotation, writing, note-taking, margin-notes - -;; 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: -;; This file is part of Org-marginalia - -;;; Code: - -(declare-function org-marginalia-mode 'org-marginalia) - -(defgroup org-marginalia-global-tracking nil - "Write margin notes (marginalia) for any text file in a -separate Org file" - :group 'org-marginalia - :prefix "org-marginalia-" - :link '(url-link :tag "Github" "https://github.com/nobiot/org-marginalia")) - -(defcustom org-marginalia-tracking-file - (locate-user-emacs-file ".org-marginalia-tracking" nil) - "File name where the files `org-marginalia' tracks is saved. -When `org-marginalia-global-tracking-mode' is active, opening a file -saved in `org-marginalia-tracking-file' automatically loads highlights." - :group 'org-marginalia-global-tracking - :type 'file) - -(defvar org-marginalia-tracking-file-loaded nil) - -(defvar org-marginalia-files-tracked nil) - -;;;###autoload -(define-minor-mode org-marginalia-global-tracking-mode - "Global mode. When enabled, check files saved in -`org-marginalia-tracking-file' and opening them automatically -activates `org-marginalia-mode' locally for the file opened." - :init-value nil - :lighter " marginalia-tracking" - :global t - (cond - (org-marginalia-global-tracking-mode - ;; Activate - (when (and (not org-marginalia-tracking-file-loaded) - (file-exists-p org-marginalia-tracking-file)) - (org-marginalia-tracking-load)) - (add-hook 'find-file-hook #'org-marginalia-tracking-auto-on) - (add-hook 'kill-emacs-hook #'org-marginalia-tracking-save)) - (t - ;; Deactivate - (setq org-marginalia-files-tracked nil) - (setq org-marginalia-tracking-file-loaded nil) - (remove-hook 'find-file-hook #'org-marginalia-tracking-auto-on) - (remove-hook 'kill-emacs-hook #'org-marginalia-tracking-save)))) - -;;;; Private Functions - -(defun org-marginalia-tracking-auto-on () - "Activate `org-marginalia-mode' when file is being tracked. -The files being tracked are loaded on to -`org-marginalia-files-tracked'. Refer to -`org-marginalia-tracking-load'." - (when (and org-marginalia-files-tracked - (member (abbreviate-file-name (buffer-file-name)) - org-marginalia-files-tracked)) - (unless (featurep 'org-marginalia) (require 'org-marginalia)) - (org-marginalia-mode +1))) - -(defun org-marginalia-tracking-load () - "Load files being tracked from `org-marginalia-tracking-file'. -It has one filename each line. The filename is obtrained -`abbreviated-file-names'. This function reloads the content of -the file regardless if it is already done in this Emacs session -or not." - (with-temp-buffer - (condition-case nil - (progn - (insert-file-contents org-marginalia-tracking-file) - (setq org-marginalia-files-tracked - (split-string (buffer-string) "\n")) - (setq org-marginalia-tracking-file-loaded t))))) - -(defun org-marginalia-tracking-save () - "Save files being tracked in `org-marginalia-tracking-file'. -Files with marginal notes are tracked with variable -`org-marginalia-files-tracked'." - (interactive) - (when org-marginalia-files-tracked - (with-temp-file org-marginalia-tracking-file - (insert (mapconcat 'identity org-marginalia-files-tracked "\n"))))) - -(provide 'org-marginalia-global-tracking) - -;;; org-marginalia-global-tracking.el ends here diff --git a/org-marginalia.el b/org-marginalia.el deleted file mode 100644 index b4750f1b9e..0000000000 --- a/org-marginalia.el +++ /dev/null @@ -1,633 +0,0 @@ -;;; org-marginalia.el --- highlight & annotate -*- lexical-binding: t; -*- - -;; Copyright (C) 2020 Noboru Ota - -;; Author: Noboru Ota <m...@nobiot.com> -;; URL: https://github.com/nobiot/org-marginalia -;; Version: 0.0.6 -;; Last Modified: 2021-08-18 -;; Package-Requires: ((emacs "27.1") (org "9.4")) -;; Keywords: org-mode, annotation, writing, note-taking, margin-notes - -;; 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: - -;; This package lets you write margin notes (marginalia) for any text file in -;; a separate Org file. - -;; Refer to README.org and docstring of variables and functions. - -;;; Code: - -;;;; Requirements - -(require 'org) -(require 'org-id) -(require 'org-marginalia-global-tracking) -(declare-function org-collect-keywords 'org) - -;;;; Customization - -(defgroup org-marginalia nil - "Write margin notes (marginalia) for any text file in a -separate Org file" - :group 'org - :prefix "org-marginalia-" - :link '(url-link :tag "Github" "https://github.com/nobiot/org-marginalia")) - -(defface org-marginalia-highlighter - '((((class color) (min-colors 88) (background light)) - :underline "#aecf90" :background "#ecf7ed") - (((class color) (min-colors 88) (background dark)) - :underline "#00422a" :background "#001904") - (t - :inherit highlight)) - "Face for highlighters." - :group 'org-marginalia) - -(defcustom org-marginalia-notes-file-path "marginalia.org" - "Specify the file path for the marginalia.org file. -The default is \"./marginalia.org\", thus one marginalia file per directory. -Ensure that it is an Org file." - :type 'file - :group 'org-marginalia) - -(defcustom org-marginalia-use-org-id t - "Define if Org-marginalia use Org-ID to link back to the main note." - :type 'boolean - :group 'org-marginalia) - -;;;; Variables - -(defvar-local org-marginalia-loaded nil) - -(defvar-local org-marginalia-highlights '() - "Keep track of all the highlights. -It is a local variable and is a list of multiple highlights. -Each element is an overlay representing a highlighted text region. - -On save-buffer each highlight will be persisted in the marginalia file -(defined by `org-marginalia-notes-file-path').") - -(defvar org-marginalia-last-notes-buffer nil - "Stores the cloned indirect buffer for the margin notes. -It is meant to exist only one of these in each Emacs session.") - -;; Const for the names of properties in Org Mode -(defconst org-marginalia-prop-id "marginalia-id") -(defconst org-marginalia-prop-source-file "marginalia-source-file") -(defconst org-marginalia-prop-source-beg "marginalia-source-beg") -(defconst org-marginalia-prop-source-end "marginalia-source-end") - -;;;; Commands - -;;;###autoload -(define-minor-mode org-marginalia-mode - "Highlight text, write margin notes for any text file in Org Mode. -This is a local minor-mode. - -On activation, it loads your saved highlights from the marginalia -file and enables automatic saving of highlights. - -The automatic saving is achieved via function -`org-marginalia-save' added to `after-save-hook'. - -On deactivation, it removes all the overlays and stops tracking -the highlights in this buffer by setting variable -`org-marginalia-highlights' to nil. Be careful of behavior, if -you still wish to retain the locations of highlights. - -It is recommended to use `org-marginalia-toggle' if you wish to -temporarily hide highlights in the current buffer. It keeps -`org-marginalia-highlights' unchanged. - -While the tracking of highlights is stopped, -editing the buffer will likely result in mismatch between the -saved highlights' locations and the current buffer's text -content. - -Highlights tracked by variable `org-marginalia-highlights' cannot -persist when you kill the buffer or quit Emacs. When you -re-launch Emacs and visit the same file, ensure to turn on -`org-marginalia-mode' to load the highlights from the marginalia -file. `org-marginalia-global-tracking-mode' can automate this. - -\\{org-marginalia-mode-map}" - :init-value nil - :lighter " marginalia" - :global nil - :keymap (let ((map (make-sparse-keymap))) - map) - (cond - (org-marginalia-mode - ;; Activate - (org-marginalia-load) - (add-hook 'after-save-hook #'org-marginalia-save nil t) - (add-hook 'kill-buffer-hook #'org-marginalia-tracking-save nil t)) - (t - ;; Deactivate - (when org-marginalia-highlights - (dolist (highlight org-marginalia-highlights) - (delete-overlay highlight))) - (setq org-marginalia-highlights nil) - (setq org-marginalia-loaded nil) - (org-marginalia-tracking-save) - (remove-hook 'after-save-hook #'org-marginalia-save t) - (remove-hook 'kill-buffer-hook #'org-marginalia-tracking-save t)))) - -;;;###autoload -(defun org-marginalia-mark (beg end &optional id) - "Highlight the selected region (BEG and END). -When used interactively. it will generate a new ID, always -assuming it is a new highlighted text region, and start tracking -the highlight's location, so that you can edit the text around. - -It will not create a marginalia entry yet. Save the current -buffer or call `org-marginalia-save' to create a new entry (it is -automatic with `after-save-hook'). - -When this function is called from Elisp, ID can be optionally -passed. If so, no new ID gets generated. - -Every highlighted text region in the current buffer is tracked by -local variable `org-marginalia-highlights'. The highlights are -sorted in the ascending order; this is a property of the variable -used for `org-marginalia-next' and `org-marginalia-prev'." - (interactive "r") - ;; Ensure to turn on the local minor mode - (unless org-marginalia-mode (org-marginalia-mode +1)) - ;; UUID is too long; does not have to be the full length - (when (not id) (setq id (substring (org-id-uuid) 0 8))) - ;; Add highlight to the text - (org-with-wide-buffer - (let ((ov (make-overlay beg end nil 'FRONT-ADVANCE))) - (overlay-put ov 'face 'org-marginalia-highlighter) - (overlay-put ov 'org-marginalia-id id) - ;; Keep track it in a local variable. It's a list overlays, guranteed to - ;; contain only marginalia overlays as opposed to the one returned by - ;; `overlay-lists' - - ;; TODO Do we need to consider this for overlay? - ;; `set-marker-insertion-type' to - ;; set the type t is necessary to move the cursor in sync with the - ;; font-lock-face property of the text property. - (push ov org-marginalia-highlights) - ;; Adding overlay does not set the buffer modified. - ;; It's more fluid with save operation. - ;; You cannot use `undo' to undo highlighter. - (deactivate-mark) - (unless (buffer-modified-p) (set-buffer-modified-p t)))) - (org-marginalia-housekeep) - (org-marginalia-sort-highlights-list)) - -;;;###autoload -(defun org-marginalia-load () - "Open the marginalia file and load the saved highlights onto current buffer. -If there is no margin notes for it, it will output a message in -the echo. - -Highlights tracked locally by variable -`org-marginalia-highlights' 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. -`org-marginalia-global-tracking-mode' can automate this." - (interactive) - ;; Open the marginalia file - ;; Read all the positions - (unless org-marginalia-mode (org-marginalia-mode +1)) - (unless org-marginalia-loaded - (when-let* ((filename (buffer-file-name)) - (margin-buf (find-file-noselect org-marginalia-notes-file-path)) - (source-path (abbreviate-file-name filename))) - ;; Get hilights: each highlighlight is stored as an alist - ;; (id beg . end) - ;; TODO check if there is any relevant notes for the current file - (let ((highlights '())) - (with-current-buffer margin-buf - (org-with-wide-buffer - (let ((heading (org-find-property - org-marginalia-prop-source-file source-path))) - (if (not heading) - (message "No marginalia written for %s." source-path) - (goto-char (org-find-property - org-marginalia-prop-source-file source-path)) - ;; Narrow to only subtree for a single file - ;; `org-find-property' ensures that it is the beginning of H1 - (org-narrow-to-subtree) - ;; It's important that the headline levels are fixed - ;; H1: File - ;; H2: Higlighted region (each one has a dedicated H2 subtree) - (while (not (org-next-visible-heading 1)) - (when-let ((id (org-entry-get (point) "marginalia-id")) - (beg (string-to-number - (org-entry-get (point) - "marginalia-source-beg"))) - (end (string-to-number - (org-entry-get (point) - "marginalia-source-end")))) - (push (cons id (cons beg end)) highlights))))))) - ;; Back to the current buffer - ;; Look highilights and add highlights to the current buffer - (dolist (highlight highlights) - (let ((id (car highlight)) - (beg (car (cdr highlight))) - (end (cdr (cdr highlight)))) - (org-marginalia-mark beg end id))))) - ;; Tracking - (when org-marginalia-global-tracking-mode - (add-to-list 'org-marginalia-files-tracked - (abbreviate-file-name (buffer-file-name)))) - (setq org-marginalia-loaded t))) - -(defun org-marginalia-save () - "Save all the highlights tracked in current buffer to marginalia file. -The marginalia file is defined in `org-marginalia-notes-file-path' variable. - -This funcion is automatically called when you save the buffer. This is -achieved via `after-save-hook' (added via `org-marginalia-mode' when you -activate the minor mode). - -`org-marginalia-highlights' is the local variable that tracks every highlight -in the current buffer. Each highlight is represented by an overlay." - - (interactive) - (let* ((filename (buffer-file-name)) - (source-path (abbreviate-file-name filename)) - (title (or (cadr (assoc "TITLE" (org-collect-keywords '("TITLE")))) - (file-name-sans-extension - (file-name-nondirectory (buffer-file-name)))))) - (org-marginalia-housekeep) - (org-marginalia-sort-highlights-list) - (dolist (h org-marginalia-highlights) - (let ((orgid (and org-marginalia-use-org-id - (org-entry-get (overlay-start h) "ID" 'INHERIT)))) - (org-marginalia-save-single-highlight h title source-path orgid))) - ;; Tracking - (when org-marginalia-global-tracking-mode - (add-to-list 'org-marginalia-files-tracked - (abbreviate-file-name (buffer-file-name)))))) - -(defun org-marginalia-open (point) - "Open the margin notes at POINT, narrowed to the relevant headline. -It creates a cloned indirect buffer of the marginalia file -\(`org-marginalia-notes-file-path'\). You can edit the margin notes as a -normal Org file. Once you have done editing, you can simply save and close the -buffer (kill or close the window) as per your normal workflow. - -This package ensures that there is only one cloned buffer for marginalia by -tracking it." - (interactive "d") - (when (buffer-live-p org-marginalia-last-notes-buffer) - (kill-buffer org-marginalia-last-notes-buffer)) - (when-let ((id (get-char-property point 'org-marginalia-id)) - (ibuf (make-indirect-buffer - (find-file-noselect org-marginalia-notes-file-path) - "*marginalia*" 'clone))) - (setq org-marginalia-last-notes-buffer ibuf) - (org-switch-to-buffer-other-window ibuf) - (widen)(goto-char (point-min)) - (when (org-find-property org-marginalia-prop-id id) - (goto-char (org-find-property org-marginalia-prop-id id)) - (org-narrow-to-subtree)))) - -(defun org-marginalia-remove (point &optional arg) - "Remove the highlight at POINT. -It will remove the highlight and the properties from the -marginalia, but will keep the headline and notes. This is to -ensure to keep any notes you might have written intact. - -You can let this command delete the entire heading subtree, along -with the notes you have written, for the highlight by pass a -universal argument with \\[universal-argument] (ARG). If you have -done so by error, you could still `undo' it in the marginalia -buffer" - (interactive "d\nP") - ;; TODO There may be multple overlays - (when-let* ((id (get-char-property point 'org-marginalia-id))) - ;; Remove the highlight overlay and id - (dolist (ov (overlays-at (point))) - ;; Remove the element in the variable org-marginalia-highlights - (when (overlay-get ov 'org-marginalia-id) - (delete ov org-marginalia-highlights) - (delete-overlay ov))) - (org-marginalia-sort-highlights-list) - ;; Update the marginalia note file accordingly - (org-marginalia-remove-marginalia id arg) - t)) - -(defun org-marginalia-next () - "Look at the current point and move to the next highlight, if any. -If there is none below the point but there is a highlight in the -buffer, go back to the first one. - -After the point has moved to the next highlight, this command -lets you move further by re-entering only the last letter like -this example: - - C-n \] \] \] \] \] \(assuming this command is bound to C-n\) - -This is achieved by transient map with `set-transient-map'. - -If you have the same prefix for `org-marginalia-prev', you can combine it in -the sequence like so: - - C-n \] \] \] \[ \[" - (interactive) - (if (not org-marginalia-highlights) - (progn (message "No highlights present in this buffer.") nil) - (let ((p (org-marginalia-find-next-highlight))) - (if p (progn - (goto-char p) - ;; Setup the overriding keymap. - (unless overriding-terminal-local-map - (let ((prefix-keys (substring (this-single-command-keys) 0 -1)) - (map (cdr org-marginalia-mode-map))) - (when (< 0 (length prefix-keys)) - (mapc (lambda (k) (setq map (assq k map))) prefix-keys) - (setq map (cdr-safe map)) - (when (keymapp map) (set-transient-map map t))))) - t) - (message "Nothing done. No more visible highlights exist") nil)))) - -(defun org-marginalia-prev () - "Look at the current point, and move to the previous highlight, if any. -If there is none above the point, but there is a highlight in the -buffer, go back to the last one. - -After the point has moved to the previous highlight, this command -lets you move further by re-entering only the last letter like -this example: - - C-n \[ \[ \[ \[ \[ \(assuming this command is bound to C-n \[\) - -This is achieved by transient map with `set-transient-map'. - -If you have the same prefix for `org-marginalia-next', you can combine it in -the sequence like so: - - C-n \] \] \] \[ \[" - (interactive) - (if (not org-marginalia-highlights) - (progn (message "No highlights present in this buffer.") nil) - (let ((p (org-marginalia-find-prev-highlight))) - (if p (progn - (goto-char p) - ;; Setup the overriding keymap. - (unless overriding-terminal-local-map - (let ((prefix-keys (substring (this-single-command-keys) 0 -1)) - (map (cdr org-marginalia-mode-map))) - (when (< 0 (length prefix-keys)) - (mapc (lambda (k) (setq map (assq k map))) prefix-keys) - (setq map (cdr-safe map)) - (when (keymapp map) (set-transient-map map t))))) - t) - (message "Nothing done. No more visible highlights exist") nil)))) - -(defun org-marginalia-toggle () - "Toggle showing/hiding of highlighters in current buffer. -It only affects the display of the highlighters. Their locations -are still kept tracked; upon buffer-save the correct locations -are still recorded in the marginalia file." - (interactive) - (when-let ((highlights org-marginalia-highlights)) - ;; Check the first highlight in the buffer - ;; If it's hidden, all hidden. Show them. - ;; If not, all shown. Hide them. - (if-let* ((beg (overlay-start (nth 0 highlights))) - (hidden-p (get-char-property beg 'org-marginalia-hidden))) - (org-marginalia-show) - (org-marginalia-hide)) - t)) - -;;;; Functions - -;;;;; Private - -(defun org-marginalia-save-single-highlight (highlight title source-path orgid) - "Save a single HIGHLIGHT in the marginalia file with properties. -The marginalia file is specified by SOURCE-PATH. If headline with -the same ID already exists, update it based on the new highlight -position and highlighted text as TITLE. If it is a new highlight, -create a new headline at the end of the buffer. - -ORGID can be passed to this function. If user option -`org-marginalia-use-org-id' is non-nil, this function will create -a link back to the source via an Org-ID link instead of the -normal file link. - -When a new marginalia file is created and -`org-marginalia-use-org-id' is non-nil, this function adds ID -property to the file level. This is mainly to support Org-roam's -backlink feature for marginalia files." - (let* ((beg (overlay-start highlight)) - (end (overlay-end highlight)) - (id (overlay-get highlight 'org-marginalia-id)) - ;;`org-with-wide-buffer is a macro that should work for non-Org file' - (text (org-with-wide-buffer (buffer-substring-no-properties beg end)))) - ;; TODO Want to add a check if save is applicable here. - (with-current-buffer (find-file-noselect org-marginalia-notes-file-path) - ;; If it is a new empty marginalia file - (when (and (org-marginalia-empty-buffer-p) org-marginalia-use-org-id) - (org-id-get-create)) - (org-with-wide-buffer - (let ((file-headline (org-find-property - org-marginalia-prop-source-file source-path)) - (id-headline (org-find-property org-marginalia-prop-id id))) - (unless file-headline - ;; If file-headline does not exist, create one at the bottom - (goto-char (point-max)) - ;; Ensure to be in the beginning of line to add a new headline - (when (eolp) (open-line 1) (forward-line 1) (beginning-of-line)) - (insert (concat "* " title "\n")) - (org-set-property org-marginalia-prop-source-file source-path)) - (cond (id-headline - (goto-char id-headline) - ;; Update the existing headline and position properties - (org-edit-headline text) - (org-set-property org-marginalia-prop-source-beg - (number-to-string beg)) - (org-set-property org-marginalia-prop-source-end - (number-to-string end))) - (t ;; No headline with the ID property. Create one - (when-let ((p (org-find-property - org-marginalia-prop-source-file source-path))) - (goto-char p)) - (org-narrow-to-subtree) - (goto-char (point-max)) - ;; Ensure to be in the beginning of line to add a new headline - (when (eolp) (open-line 1) (forward-line 1) (beginning-of-line)) - ;; Create a headline - ;; Add a properties - (insert (concat "** " text "\n")) - (org-set-property org-marginalia-prop-id id) - (org-set-property org-marginalia-prop-source-beg - (number-to-string beg)) - (org-set-property org-marginalia-prop-source-end - (number-to-string end)) - (if (and org-marginalia-use-org-id orgid) - (insert (concat "[[id:" orgid "]" "[" title "]]")) - (insert (concat "[[file:" source-path "]" "[" title "]]"))))))) - (when (buffer-modified-p) (save-buffer) t)))) - -(defun org-marginalia-list-highlights-positions (&optional reverse) - "Return list of beg points of highlights in this buffer. -By default, the list is in ascending order. -If REVERSE is non-nil, return list in the descending order. - -It also checks if the position is visible or not. Return only -visible ones. - -If none, return nil." - (when org-marginalia-highlights - (let ((list org-marginalia-highlights)) - (setq list (mapcar - (lambda (h) - (let ((p (overlay-start h))) - ;; Checking if the p is visible or not - (if (or - (> p (point-max)) - (< p (point-min)) - ;; When the highlight is wihtin a visible folded - ;; area, this function returns 'outline - (org-invisible-p p)) - nil p))) - list)) - (setq list (remove nil list)) - (when list - (if reverse (reverse list) list))))) - -(defun org-marginalia-sort-highlights-list () - "Utility function to sort `org-marginalia-sort-highlights'. -It checks if there is any element exists for `org-marginalia-highlights'. -Instead of receiving it as an arg, it assumes its existence. It -also distructively updates `org-marginalia-highlights'. -It returns t when sorting is done." - (when org-marginalia-highlights - (setq org-marginalia-highlights - (seq-sort-by (lambda (ov) (overlay-start ov)) - #'< - org-marginalia-highlights)) - t)) - -(defun org-marginalia-find-next-highlight () - "Return the beg point of the next highlight. -Look through `org-marginalia-highlights' list." - (when-let ((points (org-marginalia-list-highlights-positions))) - ;; Find the first occurance of p > (point). If none, this means all the - ;; points occur before the current point. Take the first one. Assume - ;; `org-marginalia-highlights' is sorted in the ascending order (it is). - (seq-find (lambda (p) (> p (point))) points (nth 0 points)))) - -(defun org-marginalia-find-prev-highlight () - "Return the beg point of the previous highlight. -Look through `org-marginalia-highlights' list (in descending order)." - (when-let ((points (org-marginalia-list-highlights-positions 'reverse))) - ;; Find the first occurance of p < (point). If none, this means all the - ;; points occur before the current point. Take the first one. Assume - ;; `org-marginalia-highlights' is sorted in the descending order . - (seq-find (lambda (p) (< p (point))) points (nth 0 points)))) - -(defun org-marginalia-hide () - "Hide highlighters. -It will remove the font-lock-face of all the highlights, and add -'org-marginalia-hidden property with value 't. It does not check the current -hidden state, thus not interactive. Use `org-marginalia-toggle' -command to manually toggle the show/hide state." - (when-let ((highlights org-marginalia-highlights)) - (dolist (highlight highlights) - (overlay-put highlight 'face nil) - (overlay-put highlight 'org-marginalia-hidden t)) - t)) - -(defun org-marginalia-show () - "Show highlights. -It adds the font-lock-face to all the highlighted text regions. -It does not check the current hidden state, thus not interactive. -Use `org-marginalia-toggle' command to manually toggle the -show/hide state." - (when-let ((highlights org-marginalia-highlights)) - (dolist (highlight highlights) - (overlay-put highlight 'org-marginalia-hidden nil) - (overlay-put highlight 'face 'org-marginalia-highlighter)) - t)) - -(defun org-marginalia-remove-marginalia (id &optional delete-notes) - "Remove marginalia entry for the ID for the current buffer. -By default, it deletes only the properties of the entry keeping -the headline intact. You can pass DELETE-NOTES and delete the all -notes of the entry." - (with-current-buffer (find-file-noselect org-marginalia-notes-file-path) - (org-with-wide-buffer - (when-let ((id-headline (org-find-property org-marginalia-prop-id id))) - (goto-char id-headline) - (org-narrow-to-subtree) - (org-delete-property org-marginalia-prop-id) - (org-delete-property org-marginalia-prop-source-beg) - (org-delete-property org-marginalia-prop-source-end) - (when delete-notes - ;; TODO I would love to add the y-n prompt if there is any notes written - (delete-region (point-min)(point-max)) - (message "Deleted the marginal notes.")) - (when (buffer-modified-p) (save-buffer)))) - t)) - -(defun org-marginalia-housekeep () - "Housekeep the internal variable `org-marginalia-highlights'. -This is a private function; housekeep is automatically done on -save. - -Case 1. Both start and end of an overlay are identical - - This should not happen when you manually mark a text - region. A typical cause of this case is when you delete a - region that contains a highlight overlay. - -Case 2. The overlay points to no buffer - - This case happens when overlay is deleted by - `overlay-delete' but the variable not cleared." - (dolist (ov org-marginalia-highlights) - ;; Both start and end of an overlay are indentical; this should not happen - ;; when you manually mark a text region. A typical cause of this case is - ;; when you delete a region that contains a highlight overlay. - (when (and (overlay-buffer ov) - (= (overlay-start ov) (overlay-end ov))) - (org-marginalia-remove-marginalia (overlay-get ov 'org-marginalia-id)) - (delete-overlay ov)) - (unless (overlay-buffer ov) - (setq org-marginalia-highlights (delete ov org-marginalia-highlights)))) - t) - -(defun org-marginalia-empty-buffer-p () - "Return non-nil when the current buffer is empty." - (save-excursion - (goto-char (point-max)) - (= 1 (point)))) - -;;;; Footer - -(provide 'org-marginalia) - -;;; org-marginalia.el ends here - -;; Local Variables: -;; coding: utf-8 -;; fill-column: 80 -;; require-final-newline: t -;; sentence-end-double-space: nil -;; eval: (setq-local org-marginalia-notes-file-path "README.org") -;; End: diff --git a/org-remark-convert-legacy.el b/org-remark-convert-legacy.el new file mode 100644 index 0000000000..99463a543f --- /dev/null +++ b/org-remark-convert-legacy.el @@ -0,0 +1,101 @@ +;;; org-remark-convert-legacy.el --- Convert legacy Org-marginalia files to Org-remark -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 Free Software Foundation, Inc. + +;; Author: Noboru Ota <m...@nobiot.com> +;; URL: https://github.com/nobiot/org-remark +;; Last modified: 16 January 2022 +;; Created: 16 January 2022 +;; Package-Requires: ((emacs "27.1") (org "9.4")) +;; Keywords: org-mode, annotation, writing, note-taking, marginal notes + +;; 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: +;; This file is part of Org-remark and contains a feature that helps users of +;; Org-marginalia (former name of Org-remark) convert their legacy +;; marginalia.org files to those compatible with Org-remark. +;; +;; (require 'org-remark-convert-legacy-data) for `org-remark' to use +;; `org-remark-convert-legacy-data' function to automatically convert legacy +;; data on save and load. Alternatively, use the same function as an +;; interactive command on a marginalia.org buffer that contains legacy +;; Org-marginalia data. + +;;; Code: + +(require 'org) +(require 'org-remark) + +(defun org-remark-convert-legacy-data () + "Convert the legacy Org-marginalia properties to those for Org-remark. + +You can call this function interactively to convert the current +buffer. It also gets automatically triggered when you save or +load Org-remark marginal notes file if +`org-remark-convert-legacy' user option is non-nil. + +This function checks whether or not there is at least one legacy entry with +property \"marginalia-source-file\" in the current buffer. + +If one found, this function will: + +1. Create a backup copy with the filename \"<current-file-name>_archive\" +2. Convert all \"marginalia-*\" properties to \"org-remark-*\" equivalents + +- marginalia-source-file -> org-remark-file +- marginalia-id -> org-remark-id +- marginalia-source-beg -> org-remark-beg +- marginalia-source-end -> org-remark-end + +This assumes that all the \"marginalia-*\" properties were used +solely by Org-marginalia." + (interactive) + (org-with-wide-buffer + ;; Check that there is at least one legacy entry in the current buffer + (goto-char (point-min)) + (when (save-excursion (org-find-property "marginalia-source-file")) + ;; Do the process only when there is at least one entry + ;; Create a backup copy + (let ((filename (abbreviate-file-name + (concat (buffer-file-name) "_archive")))) + (write-region (point-min) (point-max) filename) + (message (format "org-remark: created backup file %s" filename))) + ;; Scan the whole marginal notes file + (while (not (org-next-visible-heading 1)) + (when-let (source-file (org-entry-get (point) "marginalia-source-file")) + (org-delete-property "marginalia-source-file") + (org-set-property org-remark-prop-source-file source-file)) + (when-let ((id (org-entry-get (point) "marginalia-id")) + (beg (string-to-number + (org-entry-get (point) + "marginalia-source-beg"))) + (end (string-to-number + (org-entry-get (point) + "marginalia-source-end")))) + (org-delete-property "marginalia-id") + (org-delete-property "marginalia-source-beg") + (org-delete-property "marginalia-source-end") + (org-set-property org-remark-prop-id id) + (org-remark-notes-set-properties beg end))) + (goto-char (point-min)) + (message (format "org-remark: Legacy \"marginalia-*\" properties updated for %s" + (abbreviate-file-name (buffer-file-name)))) + t))) + +(provide 'org-remark-convert-legacy) + +;;; org-remark-convert-legacy.el ends here diff --git a/org-remark-global-tracking.el b/org-remark-global-tracking.el new file mode 100644 index 0000000000..3974260cd0 --- /dev/null +++ b/org-remark-global-tracking.el @@ -0,0 +1,129 @@ +;;; org-remark-global-tracking.el --- Track files and auto-activate Org-remark -*- lexical-binding: t; -*- + +;; Copyright (C) 2021-2022 Free Software Foundation, Inc. + +;; Author: Noboru Ota <m...@nobiot.com> +;; URL: https://github.com/nobiot/org-remark +;; Created: 15 August 2021 +;; Last modified: 17 January 2022 +;; Package-Requires: ((emacs "27.1") (org "9.4")) +;; Keywords: org-mode, annotation, writing, note-taking, marginal notes + +;; 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: +;; This file is part of org-remark + +;;; Code: + +(declare-function org-remark-mode "org-remark") + +(defcustom org-remark-tracking-file + (abbreviate-file-name + (expand-file-name ".org-remark-tracking" user-emacs-directory)) + "Define file path to save the files `org-remark' tracks. +When `org-remark-global-tracking-mode' is active, opening a file +saved in `org-remark-tracking-file' automatically loads highlights." + :group 'org-remark + :type 'file) + +(defvar org-remark-files-tracked nil + "List of files being tracked by `org-remark-global-tracking-mode'.") + +;;;###autoload +(define-minor-mode org-remark-global-tracking-mode + "Track files saved in `org-remark-tracking-file'. +When opening any of them, automatically activates `org-remark-mode' +locally for the file opened." + :init-value nil + :lighter " ormk-auto" + :global t + :group 'org-remark + (cond + (org-remark-global-tracking-mode + ;; Activate + ;; Prioritise the new `org-remark-tracking-file' over the legacy one + (when-let (tracking-file (or (when (file-exists-p + org-remark-tracking-file) + org-remark-tracking-file) + (when (file-exists-p + (org-remark-legacy-tracking-file-get)) + (org-remark-legacy-tracking-file-get)))) + (org-remark-tracking-load tracking-file)) + ;; `org-remark-tracking-save' should be added to kill hook even when no + ;; tracking file existed before -- this would indicate first time use of + ;; tracking; the files tracked in the memory needs to persist in the file. + (add-hook 'find-file-hook #'org-remark-tracking-auto-on) + (add-hook 'kill-emacs-hook #'org-remark-tracking-save)) + (t + ;; Deactivate + (setq org-remark-files-tracked nil) + (remove-hook 'find-file-hook #'org-remark-tracking-auto-on) + (remove-hook 'kill-emacs-hook #'org-remark-tracking-save)))) + +;;;; Private Functions + +(defun org-remark-tracking-auto-on () + "Activate `org-remark-mode' when file is being tracked. +The files being tracked are loaded on to +`org-remark-files-tracked'. Refer to +`org-remark-tracking-load'." + (when (and org-remark-files-tracked + (member (abbreviate-file-name (buffer-file-name)) + org-remark-files-tracked)) + (unless (featurep 'org-remark) (require 'org-remark)) + (org-remark-mode +1))) + +(defun org-remark-tracking-load (tracking-file) + "Load files being tracked from TRACKING-FILE. +It has one filename each line. The filename is obtrained with +`abbreviated-file-names'. This function reloads the content of +the file regardless if it is already done in this Emacs session +or not." + (with-temp-buffer + (condition-case nil + (progn + (insert-file-contents tracking-file) + (setq org-remark-files-tracked + (split-string (buffer-string) "\n")))))) + +(defun org-remark-tracking-save () + "Save files being tracked in `org-remark-tracking-file'. +Files with marginal notes are tracked with variable +`org-remark-files-tracked'." + (interactive) + (when org-remark-files-tracked + ;; Save to the new Org-remark tracking file. No need to keep the old file any + ;; longer, ignore the legacy file path. + (with-temp-file org-remark-tracking-file + (insert (mapconcat 'identity org-remark-files-tracked "\n"))))) + +(defun org-remark-legacy-tracking-file-get () + "Return the path to the legacy tracking file. +This function is used to automate conversion from the legacy +Org-marginalia to the new Org-remark. For this purpose, this +function assumes the user has not customised the default tracking +file name \".org-marginalia-tracking\" placed their +`user-emacs-directory'. If personalized, it is reasonable to +expect the user is able to to also customize +`org-remark-tracking-file'." + (abbreviate-file-name (expand-file-name + ".org-marginalia-tracking" + user-emacs-directory))) + +(provide 'org-remark-global-tracking) + +;;; org-remark-global-tracking.el ends here diff --git a/org-remark.el b/org-remark.el new file mode 100644 index 0000000000..c819811209 --- /dev/null +++ b/org-remark.el @@ -0,0 +1,955 @@ +;;; org-remark.el --- Highlight & annotate text file -*- lexical-binding: t; -*- + +;; Copyright (C) 2020-2022 Free Software Foundation, Inc. + +;; Author: Noboru Ota <m...@nobiot.com> +;; URL: https://github.com/nobiot/org-remark +;; Version: 0.1.0 +;; Created: 22 December 2020 +;; Last modified: 17 January 2022 +;; Package-Requires: ((emacs "27.1") (org "9.4")) +;; Keywords: org-mode, annotation, writing, note-taking, marginal-notes + +;; 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: + +;; This package lets you highlight and annotate any text file with using Org +;; mode. Refer to README.org and docstring for detail. + +;;; Code: + +;;;; Requirements + +(require 'org) +(require 'org-id) +(require 'org-remark-global-tracking) +(declare-function org-remark-convert-legacy-data "org-remark-convert-legacy") + + +;;;; Customization + +(defgroup org-remark nil + "Highlight and annotate any text file with using Org mode." + :group 'org + :prefix "org-remark-" + :link '(url-link :tag "GitHub" "https://github.com/nobiot/org-remark")) + +(defface org-remark-highlighter + '((((class color) (min-colors 88) (background light)) + :underline "#aecf90" :background "#ecf7ed") + (((class color) (min-colors 88) (background dark)) + :underline "#00422a" :background "#001904") + (t + :inherit highlight)) + "Face for the default highlighter pen.") + +(defcustom org-remark-create-default-pen-set t + "When non-nil, Org-remark creates default pen set. +Set to nil if you prefer for it not to." + :type 'boolean) + +(defcustom org-remark-notes-file-path "marginalia.org" + "Define the file path to store the location of highlights and write annotations. +The default is one file per directory. Ensure that it is an Org +file." + :type 'file) + +(defcustom org-remark-notes-display-buffer-action + `((display-buffer-in-side-window) + (side . left) + (slot . 1) + (dedicated . t)) + "Define how Org-remark opens the notes buffer. +The default is to use a dedicated side-window on the left. It is +an action list for `display-buffer'. Refer to its documentation +for more detail and expected elements of the list." + :type display-buffer--action-custom-type) + +(defcustom org-remark-notes-buffer-name "*marginal notes*" + "Define the buffer name of the marginal notes. +`org-remark-open' creates an indirect clone buffer with this +name." + :type 'string) + +(defcustom org-remark-use-org-id nil + "Define if Org-remark use Org-ID to link back to the main note." + :type 'boolean) + + +;;;; Variables + +(defvar-local org-remark-loaded nil + "Indicate if highlights have been loaded onto current buffer.") + +(defvar-local org-remark-highlights '() + "Keep track of all the highlights in current buffer. +It is a local variable and is a list of overlays. Each overlay +represents a highlighted text region. + +On `save-buffer' each highlight will be save in the notes file at +`org-remark-notes-file-path'.") + +(defvar org-remark-last-notes-buffer nil + "Stores 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 nil) + +;; Const for the names of properties in Org Mode +(defconst org-remark-prop-id "org-remark-id") +(defconst org-remark-prop-source-file "org-remark-file") +(defconst org-remark-prop-source-beg "org-remark-beg") +(defconst org-remark-prop-source-end "org-remark-end") + + +;;;; Macros to create user-defined highlighter pen functions + +(defmacro 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'. + +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\"." + (if (not label) `(user-error "org-remark-create: Label is missing") + `(progn + (add-to-list 'org-remark-available-pens + (intern (format "org-remark-mark-%s" ,label))) + (defun ,(intern (format "org-remark-mark-%s" label)) + (beg end &optional id) + ,(format "Apply the following face to the region selected by BEG and END. + +%s + +Following overlay properties will be added to the highlighted +text region: + +%S + +When this function is used interactively, it will generate a new +ID, always assuming it is working on a new highlighted text +region, and Org-remark will start tracking the highlight's +location in the current buffer. + +When this function is called from Elisp, ID can be optionally +passed, indicating to Org-remark that it is an existing +highlight. In this case, no new ID gets generated." + (or face "`org-remark-highlighter'") properties) + (interactive (org-remark-region-or-word)) + (org-remark-single-highlight-mark + beg end id ,label ,face ,properties))))) + +;; Don't use category (symbol) as a property -- it's a special one of text +;; properties. If you use it, the value also need to be a symbol; otherwise, you +;; will get an error. You can use CATEGORY (symbol and all uppercase). +(when org-remark-create-default-pen-set + ;; Create default pen set. + ;; They are rather meant to be a starter pack and examples + ;; They can be overridden, or set `org-remark-create-default-pen-set' to nil + ;; so that Org-remark will not create them. + (org-remark-create "red-line" + '(:underline (:color "dark red" :style wave)) + '(CATEGORY "review" help-echo "Review this")) + (org-remark-create "yellow" + '(:underline "gold" :background "lemon chiffon") + '(CATEGORY "important"))) + + +;;;; Commands + +;;;###autoload +(define-minor-mode org-remark-mode + "Highlight and annotate any text file with using Org mode. +This is a local minor-mode. + +On activation, it loads your saved highlights from the notes file +and enables automatic saving of highlights thereafter. + +The automatic saving is achieved via function +`org-remark-save' added to `after-save-hook'. + +On deactivation, it removes all the overlays and stops tracking +the highlights in this buffer by setting variable +`org-remark-highlights' to nil. Be careful of behavior, if +you still wish to retain the locations of highlights. + +It is recommended to use `org-remark-toggle' if you wish to +temporarily hide highlights in the current buffer. It keeps +`org-remark-highlights' unchanged. + +While the tracking of highlights is stopped, +editing the buffer will likely result in mismatch between the +saved highlights' locations and the current buffer's text +content. + +Highlights tracked by variable `org-remark-highlights' cannot +persist when you kill the buffer or quit Emacs. When you +re-launch Emacs and visit the same file, ensure to turn on +`org-remark-mode' to load the highlights from the marginalia +file. `org-remark-global-tracking-mode' automates this. It is +recommended to turn it on as part of Emacs initialization. + +\\{org-remark-mode-map}" + :init-value nil + :lighter " ormk" + :global nil + :keymap (let ((map (make-sparse-keymap))) + map) + (cond + (org-remark-mode + ;; Activate + (org-remark-load) + (add-hook 'after-save-hook #'org-remark-save nil t) + (add-hook 'kill-buffer-hook #'org-remark-tracking-save nil t)) + (t + ;; Deactivate + (when org-remark-highlights + (dolist (highlight org-remark-highlights) + (delete-overlay highlight))) + (setq org-remark-highlights nil) + (setq org-remark-loaded nil) + (org-remark-tracking-save) + (remove-hook 'after-save-hook #'org-remark-save t) + (remove-hook 'kill-buffer-hook #'org-remark-tracking-save t)))) + +(add-to-list 'org-remark-available-pens #'org-remark-mark) +;;;###autoload +(defun org-remark-mark (beg end &optional id) + "Apply the FACE to the region selected by BEG and END. + +This function will apply face `org-remark-highlighter' to the selected region. + +When this function is used interactively, it will generate a new +ID, always assuming it is working on a new highlighted text +region, and Org-remark will start tracking the highlight's +location in the current buffer. + +A Org headline entry for the highlght will be created in the +marginal notes file specified by `org-remark-notes-file-path'. +If the file does not exist yet, it will be created. + +When this function is called from Elisp, ID can be optionally +passed, indicating to Org-remark that it is to load an existing +highlight. In this case, no new ID gets generated and the +highlight saved again, avoiding the unnecessary round-trip back +to the database." + (interactive (org-remark-region-or-word)) + ;; FIXME + ;; Adding "nil" is different to removing a prop + ;; This will do for now + (org-remark-single-highlight-mark beg end id nil nil + (list "org-remark-label" "nil"))) + +(defun org-remark-load () + "Visit `org-remark-notes-file' & load the saved highlights onto current buffer. +If there is no highlights or annotations for current buffer, +output a message in the echo. + +Highlights tracked locally by variable `org-remark-highlights' +cannot persist when you kill current buffer or quit Emacs. It is +recommended to set `org-remark-global-tracking-mode' in your +configuration. It automatically turns on `org-remark-mode', which +runs `org-remark-load' for current buffer when you open it. + +Otherwise, do not forget to turn on `org-remark-mode' manually to +load the highlights" + (interactive) + (unless org-remark-mode (org-remark-mode +1)) + (unless org-remark-loaded + ;; Loop highlights and add them to the current buffer + (dolist (highlight (org-remark-highlights-get)) + (let ((id (car highlight)) + (beg (caadr highlight)) + (end (cdadr highlight)) + (label (caddr highlight))) + (let ((fn (intern (concat "org-remark-mark-" label)))) + (unless (functionp fn) (setq fn #'org-remark-mark)) + (funcall fn beg end id)))) + (setq org-remark-loaded t)) + ;; Tracking + (org-remark-notes-track-file (buffer-file-name))) + +(defun org-remark-save () + "Save all the highlights tracked in current buffer to notes file. +Variable`org-remark-notes-file-path' defines the file path. + +This function is automatically called when you save the current +buffer via `after-save-hook'. + +When `org-remark-global-tracking-mode' is on, this function also +adds current buffer to variable `org-remark-files-tracked' so that +next time you visit this file, `org-remark-mode' can be +automatically turned on to load the highlights. + +`org-remark-highlights' is the local variable that tracks every highlight +in the current buffer. Each highlight is represented by an overlay." + (interactive) + (org-remark-housekeep) + (org-remark-highlights-sort) + (let ((path (buffer-file-name))) + (dolist (h org-remark-highlights) + (let ((beg (overlay-start h)) + (end (overlay-end h)) + (props (overlay-properties h))) + (org-remark-single-highlight-save path beg end props))) + ;; Tracking + (org-remark-notes-track-file path))) + +(defun org-remark-open (point &optional view-only) + "Open marginal notes file for highlight at POINT. +The marginal notes will be narrowed to the relevant headline to +show only the highlight at point. + +This function creates a cloned indirect buffer of the marginal +notes file \(`org-remark-notes-file-path'\). You can edit +marginal notes file as a normal Org file. Once you have done +editing, you can simply save and kill the buffer or keep it +around. + +The marginal notes file gets displayed by the action defined by +`org-remark-notes-display-buffer-action' (by default in a side +window in the left of the current frame), narrowed to the +relevant headline. + +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 cursour remaining in the +current buffer. + +This function ensures that there is only one cloned buffer for +notes file by tracking it." + (interactive "d\nP") + (when (buffer-live-p org-remark-last-notes-buffer) + (kill-buffer org-remark-last-notes-buffer)) + (when-let ((id (get-char-property point 'org-remark-id)) + (ibuf (make-indirect-buffer + (find-file-noselect org-remark-notes-file-path) + org-remark-notes-buffer-name 'clone))) + (setq org-remark-last-notes-buffer ibuf) + (with-current-buffer ibuf + (when-let (p (org-find-property org-remark-prop-id id)) + (widen)(goto-char p)(org-narrow-to-subtree))) + (display-buffer ibuf org-remark-notes-display-buffer-action) + (unless view-only (select-window (get-buffer-window ibuf))))) + +(defun org-remark-view (point) + "View marginal notes for highlight at POINT. +The marginal notes file gets displayed by the action defined by +`org-remark-notes-display-buffer-action' (by default in a side +window in the left of the current frame), narrowed to the +relevant headline. The cursor remains in the current buffer. + +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 +buffer, cycle back to the first one. + +After the point has moved to the next highlight, this command +lets you move further by re-entering only the last letter like +this example:v + + C-n \] \] \] \] \] \(assuming this command is bound to C-n \]\) + +This is achieved by transient map with `set-transient-map'. + +If you have the same prefix for `org-remark-prev', you can combine it in +the sequence like so: + + C-n \] \] \] \[ \[" + (interactive) + (org-remark-next-or-prev :next)) + +(defun org-remark-prev () + "Move to the previous highlight, if any. +If there is none above the point, but there is a highlight in the +buffer, cycle back to the last one. + +After the point has moved to the previous highlight, this command +lets you move further by re-entering only the last letter like +this example: + + C-n \[ \[ \[ \[ \[ \(assuming this command is bound to C-n \[\) + +This is achieved by transient map with `set-transient-map'. + +If you have the same prefix for `org-remark-next', you can combine it in +the sequence like so: + + C-n \] \] \] \[ \[" + (interactive) + (org-remark-next-or-prev)) + +(defun org-remark-view-next () + "Move the cursor to the next highlight and view its marginal notes." + (interactive) + (org-remark-next)(org-remark-view (point))) + +(defun org-remark-view-prev () + "Move the cursor to the previous highlight and view its marginal notes." + (interactive) + (org-remark-prev)(org-remark-view (point))) + +(defun org-remark-toggle () + "Toggle showing/hiding of highlights in current buffer. +This function only affects the display of the highlights. Their +locations are still kept tracked. On buffer-save the correct +locations are to be saved in the marginal notes file when the +highlights are hidden, thus it is recommended to use this +function, instead of `org-remark-mode', if you would just like to +hide the highlights." + (interactive) + (when-let ((highlights org-remark-highlights)) + ;; Check the first highlight in the buffer + ;; If it's hidden, all hidden. Show them. + ;; If not, all shown. Hide them. + (if-let* ((beg (overlay-start (nth 0 highlights))) + (hidden-p (get-char-property beg 'org-remark-hidden))) + (org-remark-show) + (org-remark-hide)) + t)) + +(defun org-remark-change (&optional pen) + "Change the highlight at point to PEN. +This function will show you a list of available pens to choose +from." + (interactive) + (when-let* ((ov (org-remark-overlay-find)) + (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))))) + (delete-overlay ov) + (funcall new-pen beg end id)))) + +(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." + (interactive "d\nP") + ;; TODO There may be multiple overlays + (when-let* ((id (get-char-property point 'org-remark-id))) + ;; Remove the highlight overlay and id + (dolist (ov (overlays-at (point))) + ;; Remove the element in the variable org-remark-highlights + (when (overlay-get ov 'org-remark-id) + (delete ov org-remark-highlights) + (delete-overlay ov))) + (org-remark-housekeep) + (org-remark-highlights-sort) + ;; Update the notes file accordingly + (org-remark-single-highlight-remove id delete) + t)) + + +;;;; Internal Functions + +(defun org-remark-next-or-prev (&optional next) + "Move cursor to the next or previous highlight if any. +NEXT must be either non-nil or nil. +When non-nil it's for the next; for nil, prev. + +This function is internal only and meant to be used by interctive +commands such as `org-remark-next' and `org-remark-prev'. + +Return t if the cursor has moved to next/prev. +Return nil if not after a message." + (if (not org-remark-highlights) + (progn (message "No highlights present in the buffer") nil) + (let ((p (if next (org-remark-find-next-highlight) + (org-remark-find-prev-highlight)))) + (if p (progn + (goto-char p) + ;; Setup the overriding keymap. + (unless overriding-terminal-local-map + (let ((prefix-keys (substring (this-single-command-keys) 0 -1)) + (map (cdr org-remark-mode-map))) + (when (< 0 (length prefix-keys)) + (mapc (lambda (k) (setq map (assq k map))) prefix-keys) + (setq map (cdr-safe map)) + (when (keymapp map) (set-transient-map map t))))) + t) + (message "No visible highlights present in the buffer") + nil)))) + +(defun org-remark-overlay-find () + "Return one org-remark overlay at point. +If there are more than one, return CAR of the list." + (let ((overlays (overlays-at (point))) + found) + (while overlays + (let ((overlay (car overlays))) + (if (overlay-get overlay 'org-remark-id) + (setq found (cons overlay found)))) + (setq overlays (cdr overlays))) + (car found))) + +(defun org-remark-single-highlight-mark + (beg end &optional id label face properties) + "Apply the FACE to the region selected by BEG and END. + +This function will apply FACE to the selected region. When it is +nil, this function will use the default face `org-remark-highlighter' + +This function will add LABEL and PROPERTIES as overlay +properties. PROPERTIES is a plist of pairs of a symbol and value. + +When this function is used interactively, it will generate a new +ID, always assuming it is working on a new highlighted text +region, and Org-remark will start tracking the highlight's +location in the current buffer. + +A Org headline entry for the highlght will be created in the +marginal notes file specified by `org-remark-notes-file-path'. +If the file does not exist yet, it will be created. + +When this function is called from Elisp, ID can be optionally +passed, indicating to Org-remark that it is to load an existing +highlight. In this case, no new ID gets generated and the +highlight saved again, avoiding the unnecessary round-trip back +to the database." + ;; BEG and END are not selected and in the interactive call + ;; not Elisp call + (let* ((load-only (when id t)) + ;; UUID is too long; does not have to be the full length + (id (if id id (substring (org-id-uuid) 0 8)))) + ;; Ensure to turn on the local minor mode + (unless org-remark-mode (org-remark-mode +1)) + ;; Add highlight to the text + (org-with-wide-buffer + (let ((ov (make-overlay beg end nil :front-advance))) + (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 any overlays. + (push ov org-remark-highlights) + (unless load-only + (org-remark-single-highlight-save (buffer-file-name) + beg end + (overlay-properties ov) + (org-remark-single-highlight-get-title))) + (deactivate-mark))) + (org-remark-housekeep) + (org-remark-highlights-sort))) + +(defun org-remark-single-highlight-get-title () + "Return the title of the current buffer. +Utility function to work with a single highlight overlay." + (or (cadr (assoc "TITLE" (org-collect-keywords '("TITLE")))) + (file-name-sans-extension + (file-name-nondirectory (buffer-file-name))))) + +(defun org-remark-single-highlight-get-org-id (point) + "Return Org-ID closest to POINT. +This function does this only when `org-remark-use-org-id' is +non-nil. Returns nil otherwise, or when no Org-ID is found." + (and org-remark-use-org-id + (org-entry-get point "ID" :inherit))) + +(defun org-remark-single-highlight-save (path beg end props &optional title) + "Save a single HIGHLIGHT in the marginal notes file. + +Return t. + +PATH specifies the source/main file with which the marginal notes +file is associated. + +BEG and END specify the range of the highlight being saved. It +is the highlight overlay's start and end. + +PROPS are the highlight overlay's properties. Not all the +properties will be added as headline properties. Refer to +`org-remark-notes-set-properties'. + +For the first highlight of the current buffer, this function will +create a new H1 headline for it at the bottom of the marginal +notes buffer with TITLE as its headline text. + +If it is a new highlight, this function will create a new H2 +headline with the highlighted text as its headline text at the +end of the H1 headline for the current buffer. + +If headline with the same ID already exists, update its position +and other \"org-remark-*\" properties (CATEGORY is the exception +and gets updated as well) from the highlight overlay. For +update, the headline text will be kept intact, because the user +might have changed it to their needs. + +This function will also add a normal file link as property +\"org-remark-lilnk\" of the H2 headline entry back to the current +buffer with serach option \"::line-number\". + +ORGID can be passed to this function. If user option +`org-remark-use-org-id' is non-nil, this function will add an +Org-ID link in the body text of the headline, linking back to the +source with using ORGID. + +When a new marginal notes file is to be created and +`org-remark-use-org-id' is non-nil, this function will also add +an Org-ID property to the file level. This can be helpful with +other packages such as Org-roam's backlink feature." + ;;`org-with-wide-buffer is a macro that should work for non-Org file' + (let* ((path (org-remark-source-path path)) + (id (plist-get props 'org-remark-id)) + (text (org-with-wide-buffer (buffer-substring-no-properties beg end))) + (orgid (org-remark-single-highlight-get-org-id beg)) + ;; FIXME current-line - it's not always at point + (line-num (org-current-line beg))) + ;; TODO Want to add a check if save is applicable here. + (with-current-buffer (find-file-noselect org-remark-notes-file-path) + ;; If it is a new empty marginalia file + (when (and (org-remark-empty-buffer-p) org-remark-use-org-id) + (org-id-get-create)) + (when (featurep 'org-remark-convert-legacy) (org-remark-convert-legacy-data)) + (org-with-wide-buffer + (let ((file-headline (or (org-find-property + org-remark-prop-source-file path) + (progn + ;; If file-headline does not exist, create one at the bottom + (goto-char (point-max)) + ;; Ensure to be in the beginning of line to add a new headline + (when (eolp) (open-line 1) (forward-line 1) (beginning-of-line)) + (insert (concat "* " title "\n")) + (org-set-property org-remark-prop-source-file path) + (org-up-heading-safe) (point)))) + (id-headline (org-find-property org-remark-prop-id id))) + ;; Add org-remark-link with updated line-num as a property + (plist-put props "org-remark-link" (concat + "[[file:" + path + (when line-num (format "::%d" line-num)) + "]]")) + (if id-headline + (progn + (goto-char id-headline) + ;; Update the existing headline and position properties + ;; Don't update the headline text when it already exists + ;; Let the user decide how to manage the headlines + ;; (org-edit-headline text) + ;; FIXME update the line-num in a normal link if any + (org-remark-notes-set-properties beg end props)) + ;; No headline with the marginal notes ID property. Create a new one + ;; at the end of the file's entry + (goto-char file-headline) + (org-narrow-to-subtree) + (goto-char (point-max)) + ;; Ensure to be in the beginning of line to add a new headline + (when (eolp) (open-line 1) (forward-line 1) (beginning-of-line)) + ;; Create a headline + ;; Add a properties + (insert (concat "** " text "\n")) + (org-remark-notes-set-properties beg end props) + (when (and orgid org-remark-use-org-id) + (insert (concat "[[id:" orgid "]" "[" title "]]")))))) + (when (buffer-modified-p) (save-buffer)) + t))) + +(defun org-remark-single-highlight-remove (id &optional delete) + "Remove the highlight entry for ID for current buffer. +By default, it deletes only the properties of the entry keeping +the headline intact. You can pass DELETE and delete the all +notes of the entry. + +Return t if an entry is removed or deleted." + (with-current-buffer (find-file-noselect org-remark-notes-file-path) + (org-with-wide-buffer + (when-let ((id-headline (org-find-property org-remark-prop-id id))) + (goto-char id-headline) + (org-narrow-to-subtree) + (dolist (prop (org-entry-properties)) + (when (string-prefix-p "org-remark-" (downcase (car prop))) + (org-delete-property (car prop)))) + ;; Backward compatible + (org-delete-property org-remark-prop-id) + (org-delete-property org-remark-prop-source-beg) + (org-delete-property org-remark-prop-source-end) + (when delete + ;; TODO I would love to add the y-n prompt if there is any notes written + (delete-region (point-min)(point-max)) + (message "Deleted the marginal notes entry")) + (when (buffer-modified-p) (save-buffer)))) + t)) + +(defun org-remark-notes-set-properties (beg end &optional props) + "Set properties for the headline in the notes file. +Return t. + +Minimal properties are: + +- org-remark-id :: ID +- org-remark-source-beg :: BEG +- org-remark-source-end :: END + +For PROPS, if the property name is CATEGORY \(case-sensitive\) or +prefixed with \"org-remark-\" set them to to headline's property +drawer." + (org-set-property org-remark-prop-source-beg + (number-to-string beg)) + (org-set-property org-remark-prop-source-end + (number-to-string end)) + (while props + (let ((p (pop props)) + (v (pop props))) + (when (symbolp p) (setq p (symbol-name p))) + (when (or (string-equal "CATEGORY" (upcase p)) + (and (> (length p) 11) + (string-equal "org-remark-" (downcase (substring p 0 11))))) + (org-set-property p v)))) + t) + +(defun org-remark-highlights-get () + "Return a list of highlights from `org-remark-notes-file-path'. +Each highlight is a list in the following structure: + (id (beg . end) label)" + (when-let ((notes-buf (find-file-noselect org-remark-notes-file-path)) + (source-path (org-remark-source-path (buffer-file-name)))) + ;; TODO check if there is any relevant notes for the current file + (let ((highlights)) + (with-current-buffer notes-buf + (when (featurep 'org-remark-convert-legacy) + (org-remark-convert-legacy-data)) + (org-with-wide-buffer + (let ((heading (org-find-property + org-remark-prop-source-file source-path))) + (if (not heading) + (message "No highlights or annotations found for %s." + source-path) + (goto-char heading) + ;; Narrow to only subtree for a single file. `org-find-property' + ;; ensures that it is the beginning of a headline + (org-narrow-to-subtree) + ;; It's important that the headline levels are fixed + ;; H1: File + ;; H2: Highlighted region (each one has a dedicated H2 subtree) + (while (not (org-next-visible-heading 1)) + (when-let ((id (org-entry-get (point) org-remark-prop-id)) + (beg (string-to-number + (org-entry-get (point) + org-remark-prop-source-beg))) + (end (string-to-number + (org-entry-get (point) + org-remark-prop-source-end)))) + (push (list id + (cons beg end) + (org-entry-get (point) "org-remark-label")) + highlights)))) + highlights)))))) + +(defun org-remark-highlights-get-positions (&optional reverse) + "Return list of the beggining point of all visible highlights in this buffer. +By default, the list is in ascending order. If REVERSE is +non-nil, return list in the descending order. + +This function also checks if the position is visible or not. +Return only visible ones. + +If none, return nil." + (when org-remark-highlights + (let ((list org-remark-highlights)) + (setq list (mapcar + (lambda (h) + (let ((p (overlay-start h))) + ;; Checking if the p is visible or not + (if (or + (> p (point-max)) + (< p (point-min)) + ;; When the highlight is within a visible folded + ;; area, this function returns 'outline + (org-invisible-p p)) + nil p))) + list)) + (setq list (remove nil list)) + (when list + (if reverse (reverse list) list))))) + +(defun org-remark-highlights-sort () + "Utility function to sort `org-remark-highlights'. +It checks if there is any element exists for `org-remark-highlights'. +Instead of receiving it as an arg, it assumes its existence. It +also destructively updates `org-remark-highlights'. +It returns t when sorting is done." + (when org-remark-highlights + (setq org-remark-highlights + (seq-sort-by (lambda (ov) (overlay-start ov)) + #'< + org-remark-highlights)) + t)) + +(defun org-remark-find-next-highlight () + "Return the beg point of the next highlight. +Look through `org-remark-highlights' list." + (when-let ((points (org-remark-highlights-get-positions))) + ;; Find the first occurrence of p > (point). If none, this means all the + ;; points occur before the current point. Take the first one. Assume + ;; `org-remark-highlights' is sorted in the ascending order (it is). + (seq-find (lambda (p) (> p (point))) points (nth 0 points)))) + +(defun org-remark-find-prev-highlight () + "Return the beg point of the previous highlight. +Look through `org-remark-highlights' list (in descending order)." + (when-let ((points (org-remark-highlights-get-positions 'reverse))) + ;; Find the first occurrence of p < (point). If none, this means all the + ;; points occur before the current point. Take the first one. Assume + ;; `org-remark-highlights' is sorted in the descending order . + (seq-find (lambda (p) (< p (point))) points (nth 0 points)))) + +(defun org-remark-hide () + "Hide highlights. +This function removes the font-lock-face of all the highlights, +and add org-remark-hidden property with value t. It does not +check the current hidden state, thus not interactive. Use +`org-remark-toggle' command to manually toggle the show/hide +state." + (when-let ((highlights org-remark-highlights)) + (dolist (highlight highlights) + (overlay-put highlight 'org-remark-face (overlay-get highlight 'face)) + (overlay-put highlight 'face nil) + (overlay-put highlight 'org-remark-hidden t)) + t)) + +(defun org-remark-show () + "Show highlights. +This function adds the font-lock-face to all the highlighted text +regions. It does not check the current hidden state, thus not +interactive. Use `org-remark-toggle' command to manually toggle +the show/hide state." + (when-let ((highlights org-remark-highlights)) + (dolist (highlight highlights) + (overlay-put highlight 'org-remark-hidden nil) + ;; TODO it does not work with new pens + (overlay-put highlight 'face (overlay-get highlight 'org-remark-face))) + t)) + +(defun org-remark-housekeep () + "Housekeep the internal variable `org-remark-highlights'. + +Return t. + +This is a private function; housekeep is automatically done on +mark, save, and remove -- before sort-highlights. + +Case 1. Both start and end of an overlay are identical + + This should not happen when you manually mark a text + region. A typical cause of this case is when you delete a + region that contains a highlight overlay. + +Case 2. The overlay points to no buffer + + This case happens when overlay is deleted by + `overlay-delete' but the variable not cleared." + (dolist (ov org-remark-highlights) + ;; Both start and end of an overlay are identical; this should not happen + ;; when you manually mark a text region. A typical cause of this case is + ;; when you delete a region that contains a highlight overlay. + (when (and (overlay-buffer ov) + (= (overlay-start ov) (overlay-end ov))) + (org-remark-single-highlight-remove (overlay-get ov 'org-remark-id)) + (delete-overlay ov)) + (unless (overlay-buffer ov) + (setq org-remark-highlights (delete ov org-remark-highlights)))) + t) + +(defun org-remark-source-path (path) + "Covert PATH either to absolute or relative for marginal notes files. +Returns the standardized path. Currently, it's only a place +holder and uses `abbreviate-file-name' to return an absolute +path." + ;; TODO + ;; A place holder for enhancemnet after the release of v1.0.0 + ;; Potentially support relative path. + ;; No capacity to test this properly at the moment. + ;; + ;; (if org-remark-notes-relative-directory + ;; (funcall org-remark-notes-path-function path org-remark-notes-relative-directory) + ;; (funcall org-remark-notes-path-function path))) + (abbreviate-file-name path)) + +(defun org-remark-notes-track-file (path) + "Add PATH to `org-remark-files-tracked' when relevant. +It works only when `org-remark-global-tracking-mode' is on. For +the global tracking purpose, the path must be an absolute path." + (when org-remark-global-tracking-mode + (add-to-list 'org-remark-files-tracked + (abbreviate-file-name path)))) + +(defun org-remark-empty-buffer-p () + "Return t when the current buffer is empty." + (when (= 0 (buffer-size)) t)) + +(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\" +key. The \"r\" key outputs an error when no mark is set. This +function extends the behavior and looks for the word at point" + (let ((beg (mark)) + (end (point)) + (word (bounds-of-thing-at-point 'word))) + ;; Use word's bounds when there is no active mark or one of beg/end is + ;; missing. The latter can happen when there is no mark is set yet. + (unless mark-active (setq beg (car word) end (cdr word))) + ;; Check beg end is required as the cursor may be on an empty point with no + ;; word under it. + (if (and beg end) + (list beg end) + (user-error "No region selected and the cursor is not on a word")))) + + +;;;; Footer + +(provide 'org-remark) + +;;; org-remark.el ends here + +;; Local Variables: +;; eval: (setq-local org-remark-notes-file-path "README.org") +;; End: