org-noter-core.el 101 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228
  1. ;;; org-noter-core.el --- Core functions of Org-noter -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2017-2018 Gonçalo Santos
  3. ;; Author: Gonçalo Santos (aka. weirdNox@GitHub)
  4. ;; Homepage: https://github.com/weirdNox/org-noter
  5. ;; Keywords: lisp pdf interleave annotate external sync notes documents org-mode
  6. ;; Package-Requires: ((emacs "24.4") (cl-lib "0.6") (org "9.0"))
  7. ;; Version: 1.4.1
  8. ;; This file is not part of GNU Emacs.
  9. ;; This program is free software; you can redistribute it and/or modify
  10. ;; it under the terms of the GNU General Public License as published by
  11. ;; the Free Software Foundation, either version 3 of the License, or
  12. ;; (at your option) any later version.
  13. ;; This program is distributed in the hope that it will be useful,
  14. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;; GNU General Public License for more details.
  17. ;; You should have received a copy of the GNU General Public License
  18. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. ;;; Commentary:
  20. ;; The idea is to let you create notes that are kept in sync when you scroll through the
  21. ;; document, but that are external to it - the notes themselves live in an Org-mode file. As
  22. ;; such, this leverages the power of Org-mode (the notes may have outlines, latex fragments,
  23. ;; babel, etc...) while acting like notes that are made /in/ the document.
  24. ;; Also, I must thank Sebastian for the original idea and inspiration!
  25. ;; Link to the original Interleave package:
  26. ;; https://github.com/rudolfochrist/interleave
  27. ;;; Code:
  28. (require 'org)
  29. (require 'org-element)
  30. (require 'cl-lib)
  31. (require 'pdf-tools)
  32. (declare-function doc-view-goto-page "doc-view")
  33. (declare-function image-display-size "image-mode")
  34. (declare-function image-get-display-property "image-mode")
  35. (declare-function image-mode-window-get "image-mode")
  36. (declare-function image-scroll-up "image-mode")
  37. (declare-function nov-render-document "ext:nov")
  38. (declare-function org-attach-dir "org-attach")
  39. (declare-function org-attach-file-list "org-attach")
  40. (declare-function pdf-info-getannots "ext:pdf-info")
  41. (declare-function pdf-info-gettext "ext:pdf-info")
  42. (declare-function pdf-info-outline "ext:pdf-info")
  43. (declare-function pdf-info-pagelinks "ext:pdf-info")
  44. ;; (declare-function pdf-util-tooltip-arrow "ext:pdf-util")
  45. (declare-function pdf-view-active-region "ext:pdf-view")
  46. (declare-function pdf-view-active-region-p "ext:pdf-view")
  47. (declare-function pdf-view-active-region-text "ext:pdf-view")
  48. (declare-function pdf-view-goto-page "ext:pdf-view")
  49. (declare-function pdf-view-mode "ext:pdf-view")
  50. (defvar nov-documents-index)
  51. (defvar nov-file-name)
  52. ;; --------------------------------------------------------------------------------
  53. ;;; User variables
  54. (defgroup org-noter nil
  55. "A synchronized, external annotator"
  56. :group 'convenience
  57. :version "25.3.1")
  58. (defcustom org-noter-supported-modes '(doc-view-mode pdf-view-mode nov-mode djvu-read-mode)
  59. "Major modes that are supported by org-noter."
  60. :group 'org-noter
  61. :type '(repeat symbol))
  62. (defcustom org-noter-property-doc-file "NOTER_DOCUMENT"
  63. "Name of the property that specifies the document."
  64. :group 'org-noter
  65. :type 'string)
  66. (defcustom org-noter-property-note-location "NOTER_PAGE"
  67. "Name of the property that specifies the location of the current note.
  68. The default value is still NOTER_PAGE for backwards compatibility."
  69. :group 'org-noter
  70. :type 'string)
  71. (defcustom org-noter-default-heading-title "Notes for page $p$"
  72. "The default title for headings created with `org-noter-insert-note'.
  73. $p$ is replaced with the number of the page or chapter you are in
  74. at the moment."
  75. :group 'org-noter
  76. :type 'string)
  77. (defcustom org-noter-notes-window-behavior '(start scroll)
  78. "This setting specifies in what situations the notes window should be created.
  79. When the list contains:
  80. - `start', the window will be created when starting a `org-noter' session.
  81. - `scroll', it will be created when you go to a location with an associated note.
  82. - `only-prev', it will be created when you go to a location without notes, but that
  83. has previous notes that are shown."
  84. :group 'org-noter
  85. :type '(set (const :tag "Session start" start)
  86. (const :tag "Scroll to location with notes" scroll)
  87. (const :tag "Scroll to location with previous notes only" only-prev)))
  88. (defcustom org-noter-notes-window-location 'horizontal-split
  89. "Whether the notes should appear in the main frame (horizontal or vertical split) or in a separate frame.
  90. Note that this will only have effect on session startup if `start'
  91. is member of `org-noter-notes-window-behavior' (which see)."
  92. :group 'org-noter
  93. :type '(choice (const :tag "Horizontal" horizontal-split)
  94. (const :tag "Vertical" vertical-split)
  95. (const :tag "Other frame" other-frame)))
  96. (define-obsolete-variable-alias 'org-noter-doc-split-percentage 'org-noter-doc-split-fraction "1.2.0")
  97. (defcustom org-noter-doc-split-fraction '(0.5 . 0.5)
  98. "Fraction of the frame that the document window will occupy when split.
  99. This is a cons of the type (HORIZONTAL-FRACTION . VERTICAL-FRACTION)."
  100. :group 'org-noter
  101. :type '(cons (number :tag "Horizontal fraction") (number :tag "Vertical fraction")))
  102. (defcustom org-noter-auto-save-last-location nil
  103. "When non-nil, save the last visited location automatically; when starting a new session, go to that location."
  104. :group 'org-noter
  105. :type 'boolean)
  106. (defcustom org-noter-prefer-root-as-file-level nil
  107. "When non-nil, org-noter will always try to return the file-level property drawer
  108. even when there are headings.
  109. With the default value nil, org-noter will always use the first heading as root when
  110. there is at least one heading."
  111. :group 'org-noter
  112. :type 'boolean)
  113. (defcustom org-noter-hide-other t
  114. "When non-nil, hide all headings not related to the command used.
  115. For example, when scrolling to pages with notes, collapse all the
  116. notes that are not annotating the current page."
  117. :group 'org-noter
  118. :type 'boolean)
  119. (defcustom org-noter-always-create-frame t
  120. "When non-nil, org-noter will always create a new frame for the session.
  121. When nil, it will use the selected frame if it does not belong to any other session."
  122. :group 'org-noter
  123. :type 'boolean)
  124. (defcustom org-noter-disable-narrowing nil
  125. "Disable narrowing in notes/org buffer."
  126. :group 'org-noter
  127. :type 'boolean)
  128. (defcustom org-noter-use-indirect-buffer t
  129. "When non-nil, org-noter will create an indirect buffer of the calling
  130. org file as a note buffer of the session.
  131. When nil, it will use the real buffer."
  132. :group 'org-noter
  133. :type 'boolean)
  134. (defcustom org-noter-swap-window nil
  135. "By default `org-noter' will make a session by setting the buffer of the selected window
  136. to the document buffer then split with the window of the notes buffer on the right.
  137. If this variable is non-nil, the buffers of the two windows will be the other way around."
  138. :group 'org-noter
  139. :type 'boolean)
  140. (defcustom org-noter-suggest-from-attachments t
  141. "When non-nil, org-noter will suggest files from the attachments
  142. when creating a session, if the document is missing."
  143. :group 'org-noter
  144. :type 'boolean)
  145. (defcustom org-noter-separate-notes-from-heading nil
  146. "When non-nil, add an empty line between each note's heading and content."
  147. :group 'org-noter
  148. :type 'boolean)
  149. (defcustom org-noter-insert-selected-text-inside-note t
  150. "When non-nil, it will automatically append the selected text into an existing note."
  151. :group 'org-noter
  152. :type 'boolean)
  153. (defcustom org-noter-closest-tipping-point 0.3
  154. "Defines when to show the closest previous note.
  155. Let x be (this value)*100. The following schematic represents the
  156. view (eg. a page of a PDF):
  157. +----+
  158. | | -> If there are notes in here, the closest previous note is not shown
  159. +----+--> Tipping point, at x% of the view
  160. | | -> When _all_ notes are in here, below the tipping point, the closest
  161. | | previous note will be shown.
  162. +----+
  163. When this value is negative, disable this feature.
  164. This setting may be overridden in a document with the function
  165. `org-noter-set-closest-tipping-point', which see."
  166. :group 'org-noter
  167. :type 'number)
  168. (defcustom org-noter-default-notes-file-names '("Notes.org")
  169. "List of possible names for the default notes file, in increasing order of priority."
  170. :group 'org-noter
  171. :type '(repeat string))
  172. (defcustom org-noter-notes-search-path '("~/Documents")
  173. "List of paths to check (non recursively) when searching for a notes file."
  174. :group 'org-noter
  175. :type '(repeat string))
  176. (defcustom org-noter-arrow-delay 0.2
  177. "Number of seconds from when the command was invoked until the tooltip arrow appears.
  178. When set to a negative number, the arrow tooltip is disabled.
  179. This is needed in order to keep Emacs from hanging when doing many syncs."
  180. :group 'org-noter
  181. :type 'number)
  182. (defcustom org-noter-doc-property-in-notes nil
  183. "If non-nil, every new note will have the document property too.
  184. This makes moving notes out of the root heading easier."
  185. :group 'org-noter
  186. :type 'boolean)
  187. (defcustom org-noter-insert-note-no-questions nil
  188. "When non-nil, `org-noter-insert-note' won't ask for a title and will always insert a new note.
  189. The title used will be the default one."
  190. :group 'org-noter
  191. :type 'boolean)
  192. (defcustom org-noter-kill-frame-at-session-end t
  193. "If non-nil, `org-noter-kill-session' will delete the frame if others exist on the current display.'"
  194. :group 'org-noter
  195. :type 'boolean)
  196. (defcustom org-noter-insert-heading-hook nil
  197. "Hook being run after inserting a new heading."
  198. :group 'org-noter
  199. :type 'hook)
  200. (defcustom org-noter-find-additional-notes-functions nil
  201. "Functions that when given a document file path as argument, give out
  202. an org note file path.
  203. The functions in this list must accept 1 argument, a file name.
  204. The argument will be given by `org-noter'.
  205. The return value must be a path to an org file. No matter if it's
  206. an absolute or relative path, the file name will be expanded to
  207. each directory set in `org-noter-notes-search-path' to test if it exists.
  208. If it exists, it will be listed as a candidate that `org-noter' will have
  209. the user select to use as the note file of the document."
  210. :group 'org-noter
  211. :type 'hook)
  212. (defface org-noter-no-notes-exist-face
  213. '((t
  214. :foreground "chocolate"
  215. :weight bold))
  216. "Face for modeline note count, when 0."
  217. :group 'org-noter)
  218. (defface org-noter-notes-exist-face
  219. '((t
  220. :foreground "SpringGreen"
  221. :weight bold))
  222. "Face for modeline note count, when not 0."
  223. :group 'org-noter)
  224. ;; --------------------------------------------------------------------------------
  225. ;;; Integration with other packages
  226. (defcustom org-noter--get-location-property-hook nil
  227. "The list of functions that will return the note location of an org element.
  228. These functions must accept one argument, an org element.
  229. These functions is used by `org-noter--parse-location-property' and
  230. `org-noter--check-location-property' when they can't find the note location
  231. of the org element given to them, that org element will be passed to
  232. the functions in this list."
  233. :group 'org-noter
  234. :type 'hook)
  235. (defcustom org-noter--get-containing-element-hook '(org-noter--get-containing-heading
  236. org-noter--get-containing-property-drawer)
  237. "The list of functions that will be called by
  238. `org-noter--get-containing-element' to get the org element of the note
  239. at point."
  240. :group 'org-noter
  241. :type 'hook)
  242. (defcustom org-noter-parse-document-property-hook nil
  243. "The list of functions that return a file name for the value of
  244. the property `org-noter-property-doc-file'
  245. This is used by `org-noter--get-or-read-document-property' and
  246. `org-noter--doc-file-property'.
  247. This is added for integration with other packages.
  248. For example, the module `org-noter-citar' adds the function
  249. `org-noter-citar-find-document-from-refs' to this list which when
  250. the property \"NOTER_DOCUMENT\" (the default value of
  251. `org-noter-property-doc-file') of an org file passed to it is a
  252. citation key, it will return the path to the note file associated
  253. with the citation key and that path will be used for other
  254. operations instead of the real value of the property."
  255. :group 'org-noter
  256. :type 'hook)
  257. (defcustom org-noter-get-buffer-file-name-hook '(org-noter-get-buffer-file-name-nov)
  258. "Functions that when passed a major mode, will return the current buffer file name.
  259. This is used by the `org-noter' command to determine the file name when
  260. user calls `org-noter' on a document buffer.
  261. For example, `nov-mode', a renderer for EPUB documents uses a unique variable
  262. called `nov-file-name' to store the file name of its document while the other
  263. major modes uses the `buffer-file-name' variable."
  264. :group 'org-noter
  265. :type 'hook)
  266. (defcustom org-noter-set-up-document-hook nil
  267. "TODO"
  268. :group 'org-noter
  269. :type 'hook)
  270. (defcustom org-noter-get-selected-text-hook nil
  271. "TODO"
  272. :group 'org-noter
  273. :type 'hook)
  274. (defcustom org-noter--check-location-property-hook nil
  275. "TODO"
  276. :group 'org-noter
  277. :type 'hook)
  278. (defcustom org-noter--parse-location-property-hook nil
  279. "TODO"
  280. :group 'org-noter
  281. :type 'hook)
  282. (defcustom org-noter--pretty-print-location-hook nil
  283. "TODO"
  284. :group 'org-noter
  285. :type 'hook)
  286. (defcustom org-noter--convert-to-location-cons-hook nil
  287. "TODO"
  288. :group 'org-noter
  289. :type 'hook)
  290. (defcustom org-noter--doc-goto-location-hook nil
  291. "TODO"
  292. :group 'org-noter
  293. :type 'hook)
  294. (defcustom org-noter--note-after-tipping-point-hook nil
  295. "TODO"
  296. :group 'org-noter
  297. :type 'hook)
  298. (defcustom org-noter--relative-position-to-view-hook nil
  299. "TODO"
  300. :group 'org-noter
  301. :type 'hook)
  302. (defcustom org-noter--get-precise-info-hook nil
  303. "TODO"
  304. :group 'org-noter
  305. :type 'hook)
  306. (defcustom org-noter--get-current-view-hook nil
  307. "TODO"
  308. :group 'org-noter
  309. :type 'hook)
  310. (defcustom org-noter--doc-approx-location-hook nil
  311. "TODO"
  312. :group 'org-noter
  313. :type 'hook)
  314. (defcustom org-noter-create-skeleton-functions nil
  315. "Function that inserts a tree of headlines according to the outline of the document.
  316. The functions will be given a major mode of the document and must
  317. return a non-nil value when the outline is created.
  318. Used by `org-noter-create-skeleton'."
  319. :group 'org-noter
  320. :type 'hook)
  321. (defcustom org-noter-open-document-functions nil
  322. "Functions that gives a buffer when passed with a document property.
  323. Used by `org-noter--create-session' when creating a new session."
  324. :group 'org-noter
  325. :type 'hook)
  326. ;; --------------------------------------------------------------------------------
  327. ;;; Private variables or constants
  328. (cl-defstruct org-noter--session
  329. id frame doc-buffer notes-buffer ast modified-tick doc-mode display-name notes-file-path property-text
  330. level num-notes-in-view window-behavior window-location doc-split-fraction auto-save-last-location
  331. hide-other closest-tipping-point)
  332. (defvar org-noter--sessions nil
  333. "List of `org-noter' sessions.")
  334. (defvar-local org-noter--session nil
  335. "Session associated with the current buffer.")
  336. (defvar org-noter--inhibit-location-change-handler nil
  337. "Prevent location change from updating point in notes.")
  338. (defvar org-noter--start-location-override nil
  339. "Used to open the session from the document in the right page.")
  340. (defvar-local org-noter--nov-timer nil
  341. "Timer for synchronizing notes after scrolling.")
  342. (defvar org-noter--arrow-location nil
  343. "A vector [TIMER WINDOW TOP LEFT] that shows where the arrow should appear, when idling.")
  344. (defvar org-noter--completing-read-keymap (make-sparse-keymap)
  345. "A `completing-read' keymap that let's the user insert spaces.")
  346. (set-keymap-parent org-noter--completing-read-keymap minibuffer-local-completion-map)
  347. (define-key org-noter--completing-read-keymap (kbd "SPC") 'self-insert-command)
  348. (defconst org-noter--property-behavior "NOTER_NOTES_BEHAVIOR"
  349. "Property for overriding global `org-noter-notes-window-behavior'.")
  350. (defconst org-noter--property-location "NOTER_NOTES_LOCATION"
  351. "Property for overriding global `org-noter-notes-window-location'.")
  352. (defconst org-noter--property-doc-split-fraction "NOTER_DOCUMENT_SPLIT_FRACTION"
  353. "Property for overriding global `org-noter-doc-split-fraction'.")
  354. (defconst org-noter--property-auto-save-last-location "NOTER_AUTO_SAVE_LAST_LOCATION"
  355. "Property for overriding global `org-noter-auto-save-last-location'.")
  356. (defconst org-noter--property-hide-other "NOTER_HIDE_OTHER"
  357. "Property for overriding global `org-noter-hide-other'.")
  358. (defconst org-noter--property-closest-tipping-point "NOTER_CLOSEST_TIPPING_POINT"
  359. "Property for overriding global `org-noter-closest-tipping-point'.")
  360. (defconst org-noter--note-search-element-type '(headline)
  361. "List of elements that should be searched for notes.")
  362. (defconst org-noter--id-text-property 'org-noter-session-id
  363. "Text property used to mark the headings with open sessions.")
  364. (defvar org-noter--url-regexp
  365. (concat
  366. "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|"
  367. "nntp\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)"
  368. "\\(//[-a-z0-9_.]+:[0-9]*\\)?"
  369. (let ((chars "-a-z0-9_=#$@~%&*+\\/[:word:]")
  370. (punct "!?:;.,"))
  371. (concat
  372. "\\(?:"
  373. ;; Match paired parentheses, e.g. in Wikipedia URLs:
  374. ;; http://thread.gmane.org/47B4E3B2.3050402@gmail.com
  375. "[" chars punct "]+" "(" "[" chars punct "]+" ")"
  376. "\\(?:" "[" chars punct "]+" "[" chars "]" "\\)?"
  377. "\\|"
  378. "[" chars punct "]+" "[" chars "]"
  379. "\\)"))
  380. "\\)")
  381. "Regular expression that matches URLs.")
  382. ;; --------------------------------------------------------------------------------
  383. ;;; Utility functions
  384. (defun org-noter--no-heading-p ()
  385. "Return nil if the current buffer has atleast one heading.
  386. Otherwise return the maximum value for point."
  387. (save-excursion
  388. (and (org-before-first-heading-p) (org-next-visible-heading 1))))
  389. (defun org-noter--get-new-id ()
  390. (catch 'break
  391. (while t
  392. (let ((id (random most-positive-fixnum)))
  393. (unless (cl-loop for session in org-noter--sessions
  394. when (= (org-noter--session-id session) id) return t)
  395. (throw 'break id))))))
  396. (defmacro org-noter--property-or-default (name)
  397. (let ((function-name (intern (concat "org-noter--" (symbol-name name) "-property")))
  398. (variable (intern (concat "org-noter-" (symbol-name name)))))
  399. `(let ((prop-value (,function-name ast)))
  400. (cond ((eq prop-value 'disable) nil)
  401. (prop-value)
  402. (t ,variable)))))
  403. (defun org-noter-parse-link (s)
  404. (pcase (with-temp-buffer
  405. (let ((org-inhibit-startup nil))
  406. (insert s)
  407. (org-mode)
  408. (goto-char (point-min))
  409. (org-element-link-parser)))
  410. (`nil nil)
  411. (link link)))
  412. (defun org-noter--create-session (ast document-property-value notes-file-path)
  413. (let* ((raw-value-not-empty (> (length (org-element-property :raw-value ast)) 0))
  414. (link-p (or (string-match-p org-bracket-link-regexp document-property-value)
  415. (string-match-p org-noter--url-regexp document-property-value)))
  416. (display-name (if raw-value-not-empty
  417. (org-element-property :raw-value ast)
  418. (if link-p
  419. document-property-value
  420. (file-name-nondirectory document-property-value))))
  421. (frame-name (format "Emacs Org-noter - %s" display-name))
  422. (document (or (run-hook-with-args-until-success 'org-noter-open-document-functions document-property-value)
  423. (if link-p
  424. (progn (org-link-open-from-string document-property-value)
  425. (current-buffer))
  426. (find-file-noselect document-property-value))))
  427. (document-major-mode (if (or link-p (eq document (current-buffer)))
  428. document-property-value
  429. (buffer-local-value 'major-mode document)))
  430. (document-buffer-name
  431. (generate-new-buffer-name (concat (unless raw-value-not-empty "Org-noter: ") display-name)))
  432. (document-buffer document)
  433. (notes-buffer
  434. (progn (when (and org-window-config-before-follow-link link-p)
  435. (set-window-configuration org-window-config-before-follow-link))
  436. (if org-noter-use-indirect-buffer
  437. (make-indirect-buffer
  438. (or (buffer-base-buffer)
  439. (current-buffer))
  440. (generate-new-buffer-name (concat "Notes of " display-name)) t)
  441. (current-buffer))))
  442. (single (eq (or (buffer-base-buffer document-buffer)
  443. document-buffer)
  444. (or (buffer-base-buffer notes-buffer)
  445. notes-buffer)))
  446. (session
  447. (make-org-noter--session
  448. :id (org-noter--get-new-id)
  449. :display-name display-name
  450. :frame
  451. (if (or org-noter-always-create-frame
  452. (catch 'has-session
  453. (dolist (test-session org-noter--sessions)
  454. (when (eq (org-noter--session-frame test-session) (selected-frame))
  455. (throw 'has-session t)))))
  456. (make-frame `((name . ,frame-name) (fullscreen . maximized)))
  457. (set-frame-parameter nil 'name frame-name)
  458. (selected-frame))
  459. :doc-mode document-major-mode
  460. :property-text document-property-value
  461. :notes-file-path notes-file-path
  462. :doc-buffer document-buffer
  463. :notes-buffer notes-buffer
  464. :level (or (org-element-property :level ast) 0)
  465. :window-behavior (org-noter--property-or-default notes-window-behavior)
  466. :window-location (org-noter--property-or-default notes-window-location)
  467. :doc-split-fraction (org-noter--property-or-default doc-split-fraction)
  468. :auto-save-last-location (org-noter--property-or-default auto-save-last-location)
  469. :hide-other (org-noter--property-or-default hide-other)
  470. :closest-tipping-point (org-noter--property-or-default closest-tipping-point)
  471. :modified-tick -1))
  472. (target-location org-noter--start-location-override)
  473. (starting-point (point)))
  474. (add-hook 'delete-frame-functions 'org-noter--handle-delete-frame)
  475. (push session org-noter--sessions)
  476. (with-current-buffer document-buffer
  477. (or (run-hook-with-args-until-success 'org-noter-set-up-document-hook document-major-mode)
  478. (run-hook-with-args-until-success 'org-noter-set-up-document-hook document-property-value)
  479. (error "This document handler is not supported :/"))
  480. (org-noter-doc-mode 1)
  481. (setq org-noter--session session)
  482. (add-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer nil t))
  483. (with-current-buffer notes-buffer
  484. (org-noter-notes-mode 1)
  485. ;; NOTE(nox): This is needed because a session created in an indirect buffer would use the point of
  486. ;; the base buffer (as this buffer is indirect to the base!)
  487. (goto-char starting-point)
  488. (setq buffer-file-name notes-file-path
  489. org-noter--session session
  490. fringe-indicator-alist '((truncation . nil)))
  491. (add-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer nil t)
  492. (add-hook 'window-scroll-functions 'org-noter--set-notes-scroll nil t)
  493. (org-noter--set-text-properties (org-noter--parse-root (vector notes-buffer document-property-value))
  494. (org-noter--session-id session))
  495. (unless target-location
  496. (setq target-location (org-noter--parse-location-property (org-noter--get-containing-element t)))))
  497. ;; NOTE(nox): This timer is for preventing reflowing too soon.
  498. (unless single
  499. (run-with-idle-timer
  500. 0.05 nil
  501. (lambda ()
  502. ;; NOTE(ahmed-shariff): setup-window run here to avoid crash when notes buffer not setup in time
  503. (org-noter--setup-windows session)
  504. (with-current-buffer document-buffer
  505. (let ((org-noter--inhibit-location-change-handler t))
  506. (when target-location (org-noter--doc-goto-location target-location)))
  507. (org-noter--doc-location-change-handler)))))))
  508. (defun org-noter--valid-session (session)
  509. (when session
  510. (if (and (frame-live-p (org-noter--session-frame session))
  511. (buffer-live-p (org-noter--session-doc-buffer session))
  512. (buffer-live-p (org-noter--session-notes-buffer session)))
  513. t
  514. (org-noter-kill-session session)
  515. nil)))
  516. (defmacro org-noter--with-valid-session (&rest body)
  517. (declare (debug (body)))
  518. `(let ((session org-noter--session))
  519. (when (org-noter--valid-session session)
  520. (progn ,@body))))
  521. (defun org-noter--handle-kill-buffer ()
  522. (org-noter--with-valid-session
  523. (let ((buffer (current-buffer))
  524. (notes-buffer (org-noter--session-notes-buffer session))
  525. (doc-buffer (org-noter--session-doc-buffer session)))
  526. ;; NOTE(nox): This needs to be checked in order to prevent session killing because of
  527. ;; temporary buffers with the same local variables
  528. (when (or (eq buffer notes-buffer)
  529. (eq buffer doc-buffer))
  530. (org-noter-kill-session session)))))
  531. (defun org-noter--handle-delete-frame (frame)
  532. (dolist (session org-noter--sessions)
  533. (when (eq (org-noter--session-frame session) frame)
  534. (org-noter-kill-session session))))
  535. (defun org-noter--parse-root (&optional info)
  536. "Parse and return the root AST.
  537. When used, the INFO argument may be an org-noter session or a vector [NotesBuffer PropertyText].
  538. If nil, the session used will be `org-noter--session'."
  539. (let* ((arg-is-session (org-noter--session-p info))
  540. (session (or (and arg-is-session info) org-noter--session))
  541. root-pos ast)
  542. (cond
  543. ((and (not arg-is-session) (vectorp info))
  544. ;; NOTE(nox): Use arguments to find heading, by trying to find the outermost parent heading with
  545. ;; the specified property
  546. (let ((notes-buffer (aref info 0))
  547. (wanted-prop (aref info 1)))
  548. (unless (and (buffer-live-p notes-buffer) (or (stringp wanted-prop)
  549. (eq 'link (org-element-type wanted-prop)))
  550. (eq (buffer-local-value 'major-mode notes-buffer) 'org-mode))
  551. (error "Error parsing root with invalid arguments"))
  552. (with-current-buffer notes-buffer
  553. (org-with-wide-buffer
  554. (catch 'break
  555. (while t
  556. (let ((document-property (or (org-entry-get nil org-noter-property-doc-file t)
  557. (cadar (org-collect-keywords (list org-noter-property-doc-file))))))
  558. (when (string= (or (run-hook-with-args-until-success 'org-noter-parse-document-property-hook document-property)
  559. document-property)
  560. wanted-prop)
  561. (setq root-pos (copy-marker (if (and org-noter-prefer-root-as-file-level
  562. (save-excursion
  563. (goto-char (point-min))
  564. (eq 'property-drawer (org-element-type (org-element-at-point)))))
  565. (point-min)
  566. (point))))))
  567. (unless (org-up-heading-safe) (throw 'break t))))))))
  568. ((org-noter--valid-session session)
  569. ;; NOTE(nox): Use session to find heading
  570. (or (and (= (buffer-chars-modified-tick (org-noter--session-notes-buffer session))
  571. (org-noter--session-modified-tick session))
  572. (setq ast (org-noter--session-ast session))) ; NOTE(nox): Cached version!
  573. ;; NOTE(nox): Find session id text property
  574. (with-current-buffer (org-noter--session-notes-buffer session)
  575. (org-with-wide-buffer
  576. (let ((pos (text-property-any (point-min) (point-max) org-noter--id-text-property
  577. (org-noter--session-id session))))
  578. (when pos (setq root-pos (copy-marker pos)))))))))
  579. (unless ast
  580. (unless root-pos (if (or org-noter-prefer-root-as-file-level (org-noter--no-heading-p))
  581. (setq root-pos (copy-marker (point-min)))
  582. (org-next-visible-heading 1)
  583. (setq root-pos (copy-marker (point)))))
  584. (with-current-buffer (marker-buffer root-pos)
  585. (org-with-point-at (marker-position root-pos)
  586. (org-back-to-heading-or-point-min t)
  587. (if (org-at-heading-p)
  588. (org-narrow-to-subtree)
  589. (org-hide-drawer-toggle 'force))
  590. (setq ast (car (org-element-contents (org-element-parse-buffer 'greater-element))))
  591. (when (and (not (vectorp info)) (org-noter--valid-session session))
  592. (setf (org-noter--session-ast session) ast
  593. (org-noter--session-modified-tick session) (buffer-chars-modified-tick))))))
  594. ast))
  595. (defun org-noter--get-properties-end (ast &optional force-trim)
  596. (when ast
  597. (let* ((contents (org-element-contents ast))
  598. (section (org-element-map contents 'section 'identity nil t 'headline))
  599. (properties (or (org-element-map section 'property-drawer 'identity nil t)
  600. (org-element-map contents 'property-drawer 'identity nil t)))
  601. properties-end)
  602. (if (not properties)
  603. (org-element-property :contents-begin ast)
  604. (setq properties-end (org-element-property :end properties))
  605. (when (or force-trim
  606. (= (org-element-property :end section) properties-end))
  607. (while (not (eq (char-before properties-end) ?:))
  608. (setq properties-end (1- properties-end))))
  609. properties-end))))
  610. (defun org-noter--set-text-properties (ast id)
  611. (org-with-wide-buffer
  612. (when ast
  613. (let* ((level (or (org-element-property :level ast) 0))
  614. (begin (org-element-property :begin ast))
  615. (title-begin (+ 1 level begin))
  616. (contents-begin (org-element-property :contents-begin ast))
  617. (properties-end (org-noter--get-properties-end ast t))
  618. (inhibit-read-only t)
  619. (modified (buffer-modified-p)))
  620. (if (= level 0)
  621. (when properties-end
  622. (add-text-properties contents-begin properties-end
  623. `(read-only t rear-nonsticky t ,org-noter--id-text-property ,id))
  624. (set-buffer-modified-p modified))
  625. (add-text-properties (max 1 (1- begin)) begin '(read-only t))
  626. (add-text-properties begin (1- title-begin) `(read-only t front-sticky t ,org-noter--id-text-property ,id))
  627. (add-text-properties (1- title-begin) title-begin '(read-only t rear-nonsticky t))
  628. ;; (add-text-properties (1- contents-begin) (1- properties-end) '(read-only t))
  629. (when properties-end
  630. (add-text-properties (1- properties-end) properties-end
  631. '(read-only t rear-nonsticky t)))
  632. (set-buffer-modified-p modified))))))
  633. (defun org-noter--unset-text-properties (ast)
  634. (when ast
  635. (org-with-wide-buffer
  636. (let* ((begin (org-element-property :begin ast))
  637. (end (org-noter--get-properties-end ast t))
  638. (inhibit-read-only t)
  639. (modified (buffer-modified-p)))
  640. (when end
  641. (remove-list-of-text-properties (max 1 (1- begin)) end
  642. `(read-only front-sticky rear-nonsticky ,org-noter--id-text-property))
  643. (set-buffer-modified-p modified))))))
  644. (defun org-noter--set-notes-scroll (window &rest ignored)
  645. (when window
  646. (with-selected-window window
  647. (org-noter--with-valid-session
  648. (let* ((level (org-noter--session-level session))
  649. (goal (* (1- level) 2))
  650. (current-scroll (window-hscroll)))
  651. (when (and (bound-and-true-p org-indent-mode) (< current-scroll goal))
  652. (scroll-right current-scroll)
  653. (scroll-left goal t)))))))
  654. (defun org-noter--insert-heading (level title &optional newlines-number location)
  655. "Insert a new heading at LEVEL with TITLE.
  656. The point will be at the start of the contents, after any
  657. properties, by a margin of NEWLINES-NUMBER."
  658. (setq newlines-number (or newlines-number 1))
  659. (org-insert-heading nil t)
  660. (let* ((initial-level (org-element-property :level (org-element-at-point)))
  661. (changer (if (> level initial-level) 'org-do-demote 'org-do-promote))
  662. (number-of-times (abs (- level initial-level))))
  663. (dotimes (_ number-of-times) (funcall changer))
  664. (insert (org-trim (replace-regexp-in-string "\n" " " title)))
  665. (org-end-of-subtree)
  666. (unless (bolp) (insert "\n"))
  667. (org-N-empty-lines-before-current (1- newlines-number))
  668. (when location
  669. (org-entry-put nil org-noter-property-note-location (org-noter--pretty-print-location location))
  670. (when org-noter-doc-property-in-notes
  671. (org-noter--with-valid-session
  672. (org-entry-put nil org-noter-property-doc-file (org-noter--session-property-text session))
  673. (org-entry-put nil org-noter--property-auto-save-last-location "nil"))))
  674. (run-hooks 'org-noter-insert-heading-hook)))
  675. (defun org-noter--narrow-to-root (ast)
  676. (when ast
  677. (save-excursion
  678. (goto-char (org-element-property :contents-begin ast))
  679. (org-show-entry)
  680. (when (org-at-heading-p) (org-narrow-to-subtree))
  681. (org-cycle-hide-drawers 'all))))
  682. (defun org-noter--get-doc-window ()
  683. (org-noter--with-valid-session
  684. (or (get-buffer-window (org-noter--session-doc-buffer session)
  685. (org-noter--session-frame session))
  686. (org-noter--setup-windows org-noter--session)
  687. (get-buffer-window (org-noter--session-doc-buffer session)
  688. (org-noter--session-frame session)))))
  689. (defun org-noter--get-notes-window (&optional type)
  690. (org-noter--with-valid-session
  691. (let ((notes-buffer (org-noter--session-notes-buffer session))
  692. (window-location (org-noter--session-window-location session))
  693. (window-behavior (org-noter--session-window-behavior session))
  694. notes-window)
  695. (or (get-buffer-window notes-buffer t)
  696. (when (or (eq type 'force) (memq type window-behavior))
  697. (if (eq window-location 'other-frame)
  698. (let ((restore-frame (selected-frame)))
  699. (switch-to-buffer-other-frame notes-buffer)
  700. (setq notes-window (get-buffer-window notes-buffer t))
  701. (x-focus-frame restore-frame)
  702. (raise-frame (window-frame notes-window)))
  703. (with-selected-window (org-noter--get-doc-window)
  704. (let ((horizontal (eq window-location 'horizontal-split)))
  705. (setq
  706. notes-window
  707. (if (window-combined-p nil horizontal)
  708. ;; NOTE(nox): Reuse already existent window
  709. (let ((sibling-window (or (window-next-sibling) (window-prev-sibling))))
  710. (or (window-top-child sibling-window) (window-left-child sibling-window)
  711. sibling-window))
  712. (if horizontal
  713. (split-window-right (ceiling (* (car (org-noter--session-doc-split-fraction session))
  714. (window-total-width))))
  715. (split-window-below (ceiling (* (cadr (org-noter--session-doc-split-fraction session))
  716. (window-total-height)))))))))
  717. (set-window-buffer notes-window notes-buffer))
  718. notes-window)))))
  719. (defun org-noter--setup-windows (session)
  720. "Setup windows when starting session, respecting user configuration."
  721. (when (org-noter--valid-session session)
  722. (with-selected-frame (org-noter--session-frame session)
  723. (delete-other-windows)
  724. (let* ((doc-buffer (org-noter--session-doc-buffer session))
  725. (doc-window (selected-window))
  726. (notes-buffer (org-noter--session-notes-buffer session))
  727. (window-location (org-noter--session-window-location session))
  728. notes-window)
  729. (set-window-buffer doc-window doc-buffer)
  730. (with-current-buffer notes-buffer
  731. (unless org-noter-disable-narrowing
  732. (org-noter--narrow-to-root (org-noter--parse-root session)))
  733. (setq notes-window (org-noter--get-notes-window 'start))
  734. (org-noter--set-notes-scroll notes-window))
  735. (when org-noter-swap-window
  736. (cl-labels ((swap-windows (window1 window2)
  737. "Swap the buffers of WINDOW1 and WINDOW2."
  738. (let ((buffer1 (window-buffer window1))
  739. (buffer2 (window-buffer window2)))
  740. (set-window-buffer window1 buffer2)
  741. (set-window-buffer window2 buffer1)
  742. (select-window window2))))
  743. (let ((frame (window-frame notes-window)))
  744. (when (and (frame-live-p frame)
  745. (not (eq frame (selected-frame))))
  746. (select-frame-set-input-focus (window-frame notes-window)))
  747. (when (and (window-live-p notes-window)
  748. (not (eq notes-window doc-window)))
  749. (swap-windows notes-window doc-window))))
  750. (if (eq window-location 'horizontal-split)
  751. (enlarge-window (- (ceiling (* (- 1 (car (org-noter--session-doc-split-fraction session)))
  752. (frame-width)))
  753. (window-total-width)) t)
  754. (enlarge-window (- (ceiling (* (- 1 (cadr (org-noter--session-doc-split-fraction session)))
  755. (frame-height)))
  756. (window-total-height)))))
  757. (if org-noter-swap-window
  758. ;; the variable NOTES-WINDOW here is really
  759. ;; the document window since the two got swapped
  760. (set-window-dedicated-p notes-window t)
  761. ;; It's not swapped so set it normally
  762. (set-window-dedicated-p doc-window t))))))
  763. (defmacro org-noter--with-selected-notes-window (error-str &rest body)
  764. (declare (debug ([&optional stringp] body)))
  765. (let ((with-error (stringp error-str)))
  766. `(org-noter--with-valid-session
  767. (let ((notes-window (org-noter--get-notes-window)))
  768. (if notes-window
  769. (with-selected-window notes-window
  770. ,(if with-error
  771. `(progn ,@body)
  772. (if body
  773. `(progn ,error-str ,@body)
  774. `(progn ,error-str))))
  775. ,(when with-error `(user-error "%s" ,error-str)))))))
  776. (defun org-noter--notes-window-behavior-property (ast)
  777. (let ((property (org-element-property (intern (concat ":" org-noter--property-behavior)) ast))
  778. value)
  779. (when (and (stringp property) (> (length property) 0))
  780. (setq value (car (read-from-string property)))
  781. (when (listp value) value))))
  782. (defun org-noter--notes-window-location-property (ast)
  783. (let ((property (org-element-property (intern (concat ":" org-noter--property-location)) ast))
  784. value)
  785. (when (and (stringp property) (> (length property) 0))
  786. (setq value (intern property))
  787. (when (memq value '(horizontal-split vertical-split other-frame)) value))))
  788. (defun org-noter--doc-split-fraction-property (ast)
  789. (let ((property (org-element-property (intern (concat ":" org-noter--property-doc-split-fraction)) ast))
  790. value)
  791. (when (and (stringp property) (> (length property) 0))
  792. (setq value (car (read-from-string property)))
  793. (when (consp value) value))))
  794. (defun org-noter--auto-save-last-location-property (ast)
  795. (let ((property (org-element-property (intern (concat ":" org-noter--property-auto-save-last-location)) ast)))
  796. (when (and (stringp property) (> (length property) 0))
  797. (if (intern property) t 'disable))))
  798. (defun org-noter--hide-other-property (ast)
  799. (let ((property (org-element-property (intern (concat ":" org-noter--property-hide-other)) ast)))
  800. (when (and (stringp property) (> (length property) 0))
  801. (if (intern property) t 'disable))))
  802. (defun org-noter--closest-tipping-point-property (ast)
  803. (let ((property (org-element-property (intern (concat ":" org-noter--property-closest-tipping-point)) ast)))
  804. (when (and (stringp property) (> (length property) 0))
  805. (ignore-errors (string-to-number property)))))
  806. (defun org-noter--doc-approx-location (&optional precise-info force-new-ref)
  807. "TODO"
  808. (let ((window (if (org-noter--valid-session org-noter--session)
  809. (org-noter--get-doc-window)
  810. (selected-window))))
  811. (cl-assert window)
  812. (with-selected-window window
  813. (or (run-hook-with-args-until-success
  814. 'org-noter--doc-approx-location-hook major-mode precise-info force-new-ref)
  815. (error "Unknown document type %s" major-mode)))))
  816. (defun org-noter--location-change-advice (&rest _)
  817. (org-noter--with-valid-session (org-noter--doc-location-change-handler)))
  818. (defun org-noter--nov-scroll-handler (&rest _)
  819. (when org-noter--nov-timer (cancel-timer org-noter--nov-timer))
  820. (unless org-noter--inhibit-location-change-handler
  821. (setq org-noter--nov-timer (run-with-timer 0.25 nil 'org-noter--doc-location-change-handler))))
  822. (defsubst org-noter--doc-file-property (headline)
  823. (let ((doc-prop (or (org-element-property (intern (concat ":" org-noter-property-doc-file)) headline)
  824. (cadar (org-collect-keywords (list org-noter-property-doc-file)))
  825. (org-entry-get nil org-noter-property-doc-file t))))
  826. (or (run-hook-with-args-until-success 'org-noter-parse-document-property-hook doc-prop)
  827. doc-prop)))
  828. (defun org-noter--check-location-property (arg)
  829. (let ((property (if (stringp arg) arg
  830. (or (org-element-property
  831. (intern (concat ":" org-noter-property-note-location)) arg)
  832. (run-hook-with-args-until-success
  833. 'org-noter--get-location-property-hook arg)))))
  834. (when (and (stringp property) (> (length property) 0))
  835. (or (run-hook-with-args-until-success 'org-noter--check-location-property-hook property)
  836. (let ((value (car (read-from-string property))))
  837. (or (and (consp value) (integerp (car value)) (numberp (cdr value)))
  838. (and (consp value) (integerp (car value)) (integerp (cadr value)) (integerp (cddr value)))
  839. (integerp value)))))))
  840. (defun org-noter--parse-location-property (arg)
  841. (let ((property (if (stringp arg) arg
  842. (or (org-element-property
  843. (intern (concat ":" org-noter-property-note-location)) arg)
  844. (run-hook-with-args-until-success
  845. 'org-noter--get-location-property-hook arg)))))
  846. (when (and (stringp property) (> (length property) 0))
  847. (or (run-hook-with-args-until-success 'org-noter--parse-location-property-hook property)
  848. (let ((value (car (read-from-string property))))
  849. (cond ((and (consp value) (integerp (car value)) (numberp (cdr value))) value)
  850. ((and (consp value) (integerp (car value)) (consp (cdr value)) (numberp (cadr value)) (numberp (cddr value))) value)
  851. ((integerp value) (cons value 0))))))))
  852. (defun org-noter--pretty-print-location (location)
  853. (org-noter--with-valid-session
  854. (run-hook-with-args-until-success
  855. 'org-noter--pretty-print-location-hook location)))
  856. ;; TODO: Documentation
  857. (defun org-noter--get-containing-element (&optional include-root)
  858. (run-hook-with-args-until-success 'org-noter--get-containing-element-hook include-root))
  859. (defun org-noter--get-containing-heading (&optional include-root)
  860. "Get smallest containing heading that encloses the point and has location property.
  861. If the point isn't inside any heading with location property, return the outer heading.
  862. When INCLUDE-ROOT is non-nil, the root heading is also eligible to be returned."
  863. (org-noter--with-valid-session
  864. (org-with-wide-buffer
  865. (unless (org-before-first-heading-p)
  866. (org-back-to-heading t)
  867. (let (previous)
  868. (catch 'break
  869. (while t
  870. (let ((prop (org-noter--check-location-property (org-entry-get nil org-noter-property-note-location)))
  871. (at-root (equal (org-noter--session-id session)
  872. (get-text-property (point) org-noter--id-text-property)))
  873. (heading (org-element-at-point)))
  874. (when (and prop (or include-root (not at-root)))
  875. (throw 'break heading))
  876. (when (or at-root (not (org-up-heading-safe)))
  877. (throw 'break (if include-root heading previous)))
  878. (setq previous heading)))))))))
  879. (defun org-noter--get-containing-property-drawer (&optional include-root)
  880. "Get smallest containing heading that encloses the point and has location property.
  881. If the point isn't inside any heading with location property, return the outer heading.
  882. When INCLUDE-ROOT is non-nil, the root heading is also eligible to be returned."
  883. (org-noter--with-valid-session
  884. (org-with-point-at (point-min)
  885. (when (org-before-first-heading-p)
  886. (let ((prop (org-entry-get nil org-noter-property-note-location))
  887. (at-root (equal (org-noter--session-id session)
  888. (get-text-property (point) org-noter--id-text-property))))
  889. (when (and (org-noter--check-location-property prop) (or include-root (not at-root)))
  890. prop))))))
  891. (defun org-noter--doc-get-page-slice ()
  892. "Return (slice-top . slice-height)."
  893. (let* ((slice (or (image-mode-window-get 'slice) '(0 0 1 1)))
  894. (slice-left (float (nth 0 slice)))
  895. (slice-top (float (nth 1 slice)))
  896. (slice-width (float (nth 2 slice)))
  897. (slice-height (float (nth 3 slice))))
  898. (when (or (> slice-top 1)
  899. (> slice-height 1))
  900. (let ((height (cdr (image-size (image-mode-window-get 'image) t))))
  901. (setq slice-top (/ slice-top height)
  902. slice-height (/ slice-height height))))
  903. (when (or (> slice-width 1)
  904. (> slice-left 1))
  905. (let ((width (car (image-size (image-mode-window-get 'image) t))))
  906. (setq slice-width (/ slice-width height)
  907. slice-left (/ slice-left height))))
  908. (list slice-top slice-height slice-left slice-width)))
  909. (defun org-noter--conv-page-scroll-percentage (vscroll &optional hscroll)
  910. (let* ((slice (org-noter--doc-get-page-slice))
  911. (display-size (image-display-size (image-get-display-property)))
  912. (display-percentage-height (/ vscroll (cdr display-size)))
  913. (hpercentage (max 0 (min 1 (+ (nth 0 slice) (* (nth 1 slice) display-percentage-height))))))
  914. (if hscroll
  915. (cons hpercentage (max 0 (min 1 (+ (nth 2 slice) (* (nth 3 slice) (/ vscroll (car display-size)))))))
  916. (cons hpercentage 0))))
  917. (defun org-noter--conv-page-percentage-scroll (percentage)
  918. (let* ((slice (org-noter--doc-get-page-slice))
  919. (display-height (cdr (image-display-size (image-get-display-property))))
  920. (display-percentage (min 1 (max 0 (/ (- percentage (nth 0 slice)) (nth 1 slice)))))
  921. (scroll (max 0 (floor (* display-percentage display-height)))))
  922. scroll))
  923. (defun org-noter--get-precise-info ()
  924. (org-noter--with-valid-session
  925. (let ((window (org-noter--get-doc-window))
  926. (mode (org-noter--session-doc-mode session)))
  927. (with-selected-window window
  928. (run-hook-with-args-until-success 'org-noter--get-precise-info-hook mode)))))
  929. (defun org-noter--show-arrow ()
  930. (when (and org-noter--arrow-location
  931. (window-live-p (aref org-noter--arrow-location 1)))
  932. (with-selected-window (aref org-noter--arrow-location 1)
  933. ;; From `pdf-util-tooltip-arrow'.
  934. (pdf-util-assert-pdf-window)
  935. (let* (x-gtk-use-system-tooltips
  936. (image-top (if (floatp (aref org-noter--arrow-location 2))
  937. (round (* (aref org-noter--arrow-location 2)
  938. (cdr (pdf-view-image-size))))))
  939. (image-left (if (floatp (aref org-noter--arrow-location 3))
  940. (round (* (aref org-noter--arrow-location 3) (car (pdf-view-image-size))))))
  941. (dx (or image-left
  942. (+ (or (car (window-margins)) 0)
  943. (car (window-fringes)))))
  944. (dy (or image-top 0))
  945. (pos (list dx dy dx (+ dy (* 2 (frame-char-height)))))
  946. (vscroll (pdf-util-required-vscroll pos))
  947. (tooltip-frame-parameters
  948. `((border-width . 0)
  949. (internal-border-width . 0)
  950. ,@tooltip-frame-parameters))
  951. (tooltip-hide-delay 3))
  952. (when vscroll
  953. (image-set-window-vscroll vscroll))
  954. (setq dy (max 0 (- dy
  955. (cdr (pdf-view-image-offset))
  956. (window-vscroll nil t)
  957. (frame-char-height))))
  958. (when (overlay-get (pdf-view-current-overlay) 'before-string)
  959. (let* ((e (window-inside-pixel-edges))
  960. (xw (pdf-util-with-edges (e) e-width)))
  961. (cl-incf dx (/ (- xw (car (pdf-view-image-size t))) 2))))
  962. (pdf-util-tooltip-in-window
  963. (propertize
  964. " " 'display (propertize
  965. "\u2192" ;; right arrow
  966. 'display '(height 2)
  967. 'face `(:foreground
  968. "orange red"
  969. :background
  970. ,(if (bound-and-true-p pdf-view-midnight-minor-mode)
  971. (cdr pdf-view-midnight-colors)
  972. "white"))))
  973. dx dy))
  974. (setq org-noter--arrow-location nil))))
  975. (defun org-noter--get-location-top (location)
  976. "Get the top coordinate given a LOCATION of form (page top . left) or (page . top)."
  977. (if (listp (cdr location))
  978. (cadr location)
  979. (cdr location)))
  980. (defun org-noter--get-location-page (location)
  981. "Get the page number given a LOCATION of form (page top . left) or (page . top)."
  982. (car location))
  983. (defun org-noter--get-location-left (location)
  984. "Get the left coordinate given a LOCATION of form (page top . left) or (page . top). If later form of vector is passed return 0."
  985. (if (listp (cdr location))
  986. (if (listp (cddr location))
  987. (caddr location)
  988. (cddr location))
  989. 0))
  990. (defun org-noter--doc-goto-location (location)
  991. "Go to location specified by LOCATION."
  992. (org-noter--with-valid-session
  993. (let ((window (org-noter--get-doc-window))
  994. (mode (org-noter--session-doc-mode session)))
  995. (with-selected-window window
  996. (run-hook-with-args-until-success 'org-noter--doc-goto-location-hook mode location)
  997. (redisplay)))))
  998. (defun org-noter--compare-location-cons (comp l1 l2)
  999. "Compare L1 and L2, which are location cons.
  1000. See `org-noter--compare-locations'"
  1001. (cl-assert (and (consp l1) (consp l2)))
  1002. (cond ((eq comp '=)
  1003. (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1004. (= (org-noter--get-location-top l1) (org-noter--get-location-top l2))
  1005. (= (org-noter--get-location-left l1) (org-noter--get-location-left l2))))
  1006. ((eq comp '<)
  1007. (or (< (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1008. (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1009. (< (org-noter--get-location-top l1) (org-noter--get-location-top l2)))
  1010. (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1011. (= (org-noter--get-location-top l1) (org-noter--get-location-top l2))
  1012. (< (org-noter--get-location-left l1) (org-noter--get-location-left l2)))))
  1013. ((eq comp '<=)
  1014. (or (< (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1015. (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1016. (<= (org-noter--get-location-top l1) (org-noter--get-location-top l2)))
  1017. (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1018. (= (org-noter--get-location-top l1) (org-noter--get-location-top l2))
  1019. (<= (org-noter--get-location-left l1) (org-noter--get-location-left l2)))))
  1020. ((eq comp '>)
  1021. (or (> (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1022. (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1023. (> (org-noter--get-location-top l1) (org-noter--get-location-top l2)))
  1024. (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1025. (= (org-noter--get-location-top l1) (org-noter--get-location-top l2))
  1026. (> (org-noter--get-location-left l1) (org-noter--get-location-left l2)))))
  1027. ((eq comp '>=)
  1028. (or (> (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1029. (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1030. (>= (org-noter--get-location-top l1) (org-noter--get-location-top l2)))
  1031. (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1032. (= (org-noter--get-location-top l1) (org-noter--get-location-top l2))
  1033. (>= (org-noter--get-location-left l1) (org-noter--get-location-left l2)))))
  1034. ((eq comp '>f)
  1035. (or (> (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1036. (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1037. (< (org-noter--get-location-top l1) (org-noter--get-location-top l2)))
  1038. (and (= (org-noter--get-location-page l1) (org-noter--get-location-page l2))
  1039. (= (org-noter--get-location-top l1) (org-noter--get-location-top l2))
  1040. (< (org-noter--get-location-left l1) (org-noter--get-location-left l2)))))
  1041. (t (error "Comparison operator %s not known" comp))))
  1042. (defun org-noter--compare-locations (comp l1 l2)
  1043. "Compare L1 and L2.
  1044. When COMP is '<, '<=, '>, or '>=, it works as expected.
  1045. When COMP is '>f, it will return t when L1 is a page greater than
  1046. L2 or, when in the same page, if L1 is the _f_irst of the two."
  1047. (cond ((not l1) nil)
  1048. ((not l2) t)
  1049. (t
  1050. (setq l1 (or (run-hook-with-args-until-success 'org-noter--convert-to-location-cons-hook l1) l1)
  1051. l2 (or (run-hook-with-args-until-success 'org-noter--convert-to-location-cons-hook l2) l2))
  1052. (if (numberp (cdr l2))
  1053. (org-noter--compare-location-cons comp l1 l2)
  1054. (org-noter--compare-location-cons comp l1 (cons (car l2) (cadr l2)))))))
  1055. (defun org-noter--show-note-entry (session note)
  1056. "This will show the note entry and its children.
  1057. Every direct subheading _until_ the first heading that doesn't
  1058. belong to the same view (ie. until a heading with location or
  1059. document property) will be opened."
  1060. (save-excursion
  1061. (goto-char (org-element-property :contents-begin note))
  1062. (org-show-set-visibility t)
  1063. (org-element-map (org-element-contents note) 'headline
  1064. (lambda (headline)
  1065. (let ((doc-file (org-noter--doc-file-property headline)))
  1066. (if (or (and doc-file (not (string= doc-file (org-noter--session-property-text session))))
  1067. (org-noter--check-location-property headline))
  1068. t
  1069. (goto-char (org-element-property :begin headline))
  1070. (org-show-entry)
  1071. (org-show-children)
  1072. nil)))
  1073. nil t org-element-all-elements)))
  1074. (defun org-noter--focus-notes-region (view-info)
  1075. (org-noter--with-selected-notes-window
  1076. (if (org-noter--session-hide-other session)
  1077. (save-excursion
  1078. (goto-char (org-element-property :begin (org-noter--parse-root)))
  1079. (unless (org-before-first-heading-p)
  1080. (outline-hide-subtree)))
  1081. (org-cycle-hide-drawers 'all))
  1082. (let* ((notes-cons (org-noter--view-info-notes view-info))
  1083. (regions (or (org-noter--view-info-regions view-info)
  1084. (org-noter--view-info-prev-regions view-info)))
  1085. (point-before (point))
  1086. target-region
  1087. point-inside-target-region)
  1088. (cond
  1089. (notes-cons
  1090. (dolist (note-cons notes-cons) (org-noter--show-note-entry session (car note-cons)))
  1091. (setq target-region (or (catch 'result (dolist (region regions)
  1092. (when (and (>= point-before (car region))
  1093. (or (save-restriction (goto-char (cdr region)) (eobp))
  1094. (< point-before (cdr region))))
  1095. (setq point-inside-target-region t)
  1096. (throw 'result region))))
  1097. (car regions)))
  1098. (let ((begin (car target-region)) (end (cdr target-region)) num-lines
  1099. (target-char (if point-inside-target-region
  1100. point-before
  1101. (org-noter--get-properties-end (caar notes-cons))))
  1102. (window-start (window-start)) (window-end (window-end nil t)))
  1103. (setq num-lines (count-screen-lines begin end))
  1104. (cond
  1105. ((> num-lines (window-height))
  1106. (goto-char begin)
  1107. (recenter 0))
  1108. ((< begin window-start)
  1109. (goto-char begin)
  1110. (recenter 0))
  1111. ((> end window-end)
  1112. (goto-char end)
  1113. (recenter -2)))
  1114. (goto-char target-char)))
  1115. (t (org-noter--show-note-entry session (org-noter--parse-root)))))
  1116. (org-cycle-show-empty-lines t)))
  1117. (defun org-noter--get-current-view ()
  1118. "Return a vector with the current view information."
  1119. (org-noter--with-valid-session
  1120. (let ((mode (org-noter--session-doc-mode session)))
  1121. (with-selected-window (org-noter--get-doc-window)
  1122. (or (run-hook-with-args-until-success 'org-noter--get-current-view-hook mode)
  1123. (error "Unknown document type"))))))
  1124. (defun org-noter--note-after-tipping-point (point location view)
  1125. ;; NOTE(nox): This __assumes__ the note is inside the view!
  1126. (let (hook-result)
  1127. (cond
  1128. ((setq hook-result (run-hook-with-args-until-success 'org-noter--note-after-tipping-point-hook
  1129. point location view))
  1130. (cdr hook-result))
  1131. ((eq (aref view 0) 'paged)
  1132. (> (org-noter--get-location-top location) point))
  1133. ((eq (aref view 0) 'nov)
  1134. (> (org-noter--get-location-top location) (+ (* point (- (cdr (aref view 2)) (cdr (aref view 1))))
  1135. (cdr (aref view 1))))))))
  1136. (defun org-noter--relative-position-to-view (location view)
  1137. (cond
  1138. ((run-hook-with-args-until-success 'org-noter--relative-position-to-view-hook location view))
  1139. ((eq (aref view 0) 'paged)
  1140. (let ((note-page (org-noter--get-location-page location))
  1141. (view-page (aref view 1)))
  1142. (cond ((< note-page view-page) 'before)
  1143. ((= note-page view-page) 'inside)
  1144. (t 'after))))
  1145. ((eq (aref view 0) 'nov)
  1146. (let ((view-top (aref view 1))
  1147. (view-bot (aref view 2)))
  1148. (cond ((org-noter--compare-locations '< location view-top) 'before)
  1149. ((org-noter--compare-locations '<= location view-bot) 'inside)
  1150. (t 'after))))))
  1151. (defmacro org-noter--view-region-finish (info &optional terminating-headline)
  1152. `(when ,info
  1153. ,(if terminating-headline
  1154. `(push (cons (aref ,info 1) (min (aref ,info 2) (org-element-property :begin ,terminating-headline)))
  1155. (gv-deref (aref ,info 0)))
  1156. `(push (cons (aref ,info 1) (aref ,info 2)) (gv-deref (aref ,info 0))))
  1157. (setq ,info nil)))
  1158. (defmacro org-noter--view-region-add (info list-name headline)
  1159. `(progn
  1160. (when (and ,info (not (eq (aref ,info 3) ',list-name)))
  1161. (org-noter--view-region-finish ,info ,headline))
  1162. (if ,info
  1163. (setf (aref ,info 2) (max (aref ,info 2) (org-element-property :end ,headline)))
  1164. (setq ,info (vector (gv-ref ,list-name)
  1165. (org-element-property :begin ,headline) (org-element-property :end ,headline)
  1166. ',list-name)))))
  1167. ;; NOTE(nox): notes is a list of (HEADING . HEADING-TO-INSERT-TEXT-BEFORE):
  1168. ;; - HEADING is the root heading of the note
  1169. ;; - SHOULD-ADD-SPACE indicates if there should be extra spacing when inserting text to the note (ie. the
  1170. ;; note has contents)
  1171. (cl-defstruct org-noter--view-info notes regions prev-regions reference-for-insertion)
  1172. (defun org-noter--get-view-info (view &optional new-location)
  1173. "Return VIEW related information.
  1174. When optional NEW-LOCATION is provided, it will be used to find
  1175. the best heading to serve as a reference to create the new one
  1176. relative to."
  1177. (when view
  1178. (org-noter--with-valid-session
  1179. (let ((contents (if (= 0 (org-noter--session-level session))
  1180. (org-element-contents
  1181. (org-element-property :parent (org-noter--parse-root)))
  1182. (org-element-contents (org-noter--parse-root))))
  1183. (preamble t)
  1184. notes-in-view regions-in-view
  1185. reference-for-insertion reference-location
  1186. (all-after-tipping-point t)
  1187. (closest-tipping-point (and (>= (org-noter--session-closest-tipping-point session) 0)
  1188. (org-noter--session-closest-tipping-point session)))
  1189. closest-notes closest-notes-regions closest-notes-location
  1190. ignore-until-level
  1191. current-region-info) ;; NOTE(nox): [REGIONS-LIST-PTR START MAX-END REGIONS-LIST-NAME]
  1192. (with-current-buffer (or (buffer-base-buffer (org-noter--session-notes-buffer session))
  1193. (org-noter--session-notes-buffer session))
  1194. (org-element-map contents org-noter--note-search-element-type
  1195. (lambda (element)
  1196. (let ((doc-file (org-noter--doc-file-property element))
  1197. (location (org-noter--parse-location-property element)))
  1198. (when (and ignore-until-level (<= (org-element-property :level element) ignore-until-level))
  1199. (setq ignore-until-level nil))
  1200. (cond
  1201. (ignore-until-level) ;; NOTE(nox): This heading is ignored, do nothing
  1202. ((and doc-file (not (string= doc-file (org-noter--session-property-text session))))
  1203. (org-noter--view-region-finish current-region-info element)
  1204. (setq ignore-until-level (org-element-property :level element))
  1205. (when (and preamble new-location
  1206. (or (not reference-for-insertion)
  1207. (>= (org-element-property :begin element)
  1208. (org-element-property :end (cdr reference-for-insertion)))))
  1209. (setq reference-for-insertion (cons 'after element))))
  1210. (location
  1211. (let ((relative-position (org-noter--relative-position-to-view location view)))
  1212. (cond
  1213. ((eq relative-position 'inside)
  1214. (push (cons element nil) notes-in-view)
  1215. (org-noter--view-region-add current-region-info regions-in-view element)
  1216. (setq all-after-tipping-point
  1217. (and all-after-tipping-point (org-noter--note-after-tipping-point
  1218. closest-tipping-point location view))))
  1219. (t
  1220. (when current-region-info
  1221. (let ((note-cons-to-change (cond ((eq (aref current-region-info 3) 'regions-in-view)
  1222. (car notes-in-view))
  1223. ((eq (aref current-region-info 3) 'closest-notes-regions)
  1224. (car closest-notes)))))
  1225. (when (< (org-element-property :begin element)
  1226. (org-element-property :end (car note-cons-to-change)))
  1227. (setcdr note-cons-to-change element))))
  1228. (let ((eligible-for-before (and closest-tipping-point all-after-tipping-point
  1229. (eq relative-position 'before))))
  1230. (cond ((and eligible-for-before
  1231. (org-noter--compare-locations '> location closest-notes-location))
  1232. (setq closest-notes (list (cons element nil))
  1233. closest-notes-location location
  1234. current-region-info nil
  1235. closest-notes-regions nil)
  1236. (org-noter--view-region-add current-region-info closest-notes-regions element))
  1237. ((and eligible-for-before (equal location closest-notes-location))
  1238. (push (cons element nil) closest-notes)
  1239. (org-noter--view-region-add current-region-info closest-notes-regions element))
  1240. (t (org-noter--view-region-finish current-region-info element)))))))
  1241. (when new-location
  1242. (setq preamble nil)
  1243. (cond ((and (org-noter--compare-locations '<= location new-location)
  1244. (or (eq (car reference-for-insertion) 'before)
  1245. (org-noter--compare-locations '>= location reference-location)))
  1246. (setq reference-for-insertion (cons 'after element)
  1247. reference-location location))
  1248. ((and (eq (car reference-for-insertion) 'after)
  1249. (< (org-element-property :begin element)
  1250. (org-element-property :end (cdr reference-for-insertion)))
  1251. (org-noter--compare-locations '>= location new-location))
  1252. (setq reference-for-insertion (cons 'before element)
  1253. reference-location location)))))
  1254. (t
  1255. (when (and preamble new-location
  1256. (or (not reference-for-insertion)
  1257. (>= (org-element-property :begin element)
  1258. (org-element-property :end (cdr reference-for-insertion)))))
  1259. (setq reference-for-insertion (cons 'after element)))))))
  1260. nil nil (delete 'headline (append org-element-all-elements nil))))
  1261. (org-noter--view-region-finish current-region-info)
  1262. (setf (org-noter--session-num-notes-in-view session) (length notes-in-view))
  1263. (when all-after-tipping-point (setq notes-in-view (append closest-notes notes-in-view)))
  1264. (make-org-noter--view-info
  1265. :notes (nreverse notes-in-view)
  1266. :regions (nreverse regions-in-view)
  1267. :prev-regions (nreverse closest-notes-regions)
  1268. :reference-for-insertion reference-for-insertion)))))
  1269. (defun org-noter--make-view-info-for-single-note (session headline)
  1270. (let ((not-belonging-element
  1271. (org-element-map (org-element-contents headline) 'headline
  1272. (lambda (headline)
  1273. (let ((doc-file (org-noter--doc-file-property headline)))
  1274. (and (or (and doc-file (not (string= doc-file (org-noter--session-property-text session))))
  1275. (org-noter--check-location-property headline))
  1276. headline)))
  1277. nil t)))
  1278. (make-org-noter--view-info
  1279. ;; NOTE(nox): The cdr is only used when inserting, doesn't matter here
  1280. :notes (list (cons headline nil))
  1281. :regions (list (cons (org-element-property :begin headline)
  1282. (or (and not-belonging-element (org-element-property :begin not-belonging-element))
  1283. (org-element-property :end headline)))))))
  1284. (defun org-noter--doc-location-change-handler ()
  1285. (org-noter--with-valid-session
  1286. (let ((view-info (org-noter--get-view-info (org-noter--get-current-view))))
  1287. (force-mode-line-update t)
  1288. (unless org-noter--inhibit-location-change-handler
  1289. (org-noter--get-notes-window (cond ((org-noter--view-info-regions view-info) 'scroll)
  1290. ((org-noter--view-info-prev-regions view-info) 'only-prev)))
  1291. (org-noter--focus-notes-region view-info)))
  1292. (when (org-noter--session-auto-save-last-location session) (org-noter-set-start-location))))
  1293. (defun org-noter--mode-line-text ()
  1294. (org-noter--with-valid-session
  1295. (let* ((number-of-notes (or (org-noter--session-num-notes-in-view session) 0)))
  1296. (cond ((= number-of-notes 0) (propertize " 0 notes " 'face 'org-noter-no-notes-exist-face))
  1297. ((= number-of-notes 1) (propertize " 1 note " 'face 'org-noter-notes-exist-face))
  1298. (t (propertize (format " %d notes " number-of-notes) 'face 'org-noter-notes-exist-face))))))
  1299. ;; NOTE(nox): From machc/pdf-tools-org
  1300. (defun org-noter--pdf-tools-edges-to-region (edges)
  1301. "Get 4-entry region (LEFT TOP RIGHT BOTTOM) from several EDGES."
  1302. (when edges
  1303. (let ((left0 (nth 0 (car edges)))
  1304. (top0 (nth 1 (car edges)))
  1305. (bottom0 (nth 3 (car edges)))
  1306. (top1 (nth 1 (car (last edges))))
  1307. (right1 (nth 2 (car (last edges))))
  1308. (bottom1 (nth 3 (car (last edges)))))
  1309. (list left0
  1310. (+ top0 (/ (- bottom0 top0) 3))
  1311. right1
  1312. (- bottom1 (/ (- bottom1 top1) 3))))))
  1313. (defun org-noter--check-if-document-is-annotated-on-file (document-path notes-path)
  1314. ;; NOTE(nox): In order to insert the correct file contents
  1315. (let ((buffer (find-buffer-visiting notes-path)))
  1316. (when buffer (with-current-buffer buffer (save-buffer)))
  1317. (with-temp-buffer
  1318. (insert-file-contents notes-path)
  1319. (catch 'break
  1320. (while (re-search-forward (org-re-property org-noter-property-doc-file) nil t)
  1321. (when (file-equal-p (or (expand-file-name (match-string 3) (file-name-directory notes-path))
  1322. (cadar (org-collect-keywords '(org-noter-property-doc-file))))
  1323. document-path)
  1324. ;; NOTE(nox): This notes file has the document we want!
  1325. (throw 'break t)))))))
  1326. (defsubst org-noter--check-doc-prop (doc-prop)
  1327. (and doc-prop (or (string-match-p org-link-bracket-re doc-prop)
  1328. (string-match-p org-noter--url-regexp doc-prop)
  1329. (and (not (file-directory-p doc-prop)) (file-readable-p doc-prop)))))
  1330. (defun org-noter--get-or-read-document-property (inherit-prop &optional force-new)
  1331. (let ((doc-prop (and (not force-new) (or (org-entry-get nil org-noter-property-doc-file inherit-prop)
  1332. (cadar (org-collect-keywords (list org-noter-property-doc-file)))))))
  1333. (setq doc-prop (or (run-hook-with-args-until-success 'org-noter-parse-document-property-hook doc-prop)
  1334. doc-prop))
  1335. (unless (org-noter--check-doc-prop doc-prop)
  1336. (setq doc-prop nil)
  1337. (when org-noter-suggest-from-attachments
  1338. (require 'org-attach)
  1339. (let* ((attach-dir (org-attach-dir))
  1340. (attach-list (and attach-dir (org-attach-file-list attach-dir))))
  1341. (when (and attach-list (y-or-n-p "Do you want to annotate an attached file?"))
  1342. (setq doc-prop (completing-read "File to annotate: " attach-list nil t))
  1343. (when doc-prop (setq doc-prop (file-relative-name (expand-file-name doc-prop attach-dir)))))))
  1344. (unless (org-noter--check-doc-prop doc-prop)
  1345. (setq doc-prop (expand-file-name
  1346. (read-file-name
  1347. (cond
  1348. ((null doc-prop) "No document property found. Please specify a document path: ")
  1349. ((file-directory-p doc-prop)
  1350. (format "Document property (\"%s\") is a directory. Please specify a document file: "
  1351. doc-prop))
  1352. ((not (file-readable-p doc-prop))
  1353. (format "The file specified by the document property \"%s\" is unreadable. Please specify a new document: "
  1354. doc-prop)))
  1355. nil nil t)))
  1356. (when (or (file-directory-p doc-prop) (not (file-readable-p doc-prop)))
  1357. (user-error "Invalid file path"))
  1358. (when (y-or-n-p "Do you want a relative file name? ")
  1359. (setq doc-prop (file-relative-name doc-prop))))
  1360. (org-entry-put nil org-noter-property-doc-file doc-prop))
  1361. doc-prop))
  1362. (defun org-noter--other-frames (&optional this-frame)
  1363. "Returns non-`nil' when there is at least another frame"
  1364. (setq this-frame (or this-frame (selected-frame)))
  1365. (catch 'other-frame
  1366. (dolist (frame (visible-frame-list))
  1367. (unless (or (eq this-frame frame)
  1368. (frame-parent frame)
  1369. (frame-parameter frame 'delete-before))
  1370. (throw 'other-frame frame)))))
  1371. ;; --------------------------------------------------------------------------------
  1372. ;;; User commands
  1373. (defun org-noter-set-start-location (&optional arg)
  1374. "When opening a session with this document, go to the current location.
  1375. With a prefix ARG, remove start location."
  1376. (interactive "P")
  1377. (org-noter--with-valid-session
  1378. (let ((inhibit-read-only t)
  1379. (ast (org-noter--parse-root))
  1380. (location (org-noter--doc-approx-location
  1381. (when (called-interactively-p 'any) 'interactive))))
  1382. (with-current-buffer (org-noter--session-notes-buffer session)
  1383. (org-with-wide-buffer
  1384. (goto-char (org-element-property :begin ast))
  1385. (if arg
  1386. (org-entry-delete nil org-noter-property-note-location)
  1387. (org-entry-put nil org-noter-property-note-location
  1388. (org-noter--pretty-print-location location))))))))
  1389. (defun org-noter-set-auto-save-last-location (arg)
  1390. "This toggles saving the last visited location for this document.
  1391. With a prefix ARG, delete the current setting and use the default."
  1392. (interactive "P")
  1393. (org-noter--with-valid-session
  1394. (let ((inhibit-read-only t)
  1395. (ast (org-noter--parse-root))
  1396. (new-setting (if arg
  1397. org-noter-auto-save-last-location
  1398. (not (org-noter--session-auto-save-last-location session)))))
  1399. (setf (org-noter--session-auto-save-last-location session)
  1400. new-setting)
  1401. (with-current-buffer (org-noter--session-notes-buffer session)
  1402. (org-with-wide-buffer
  1403. (goto-char (org-element-property :begin ast))
  1404. (if arg
  1405. (org-entry-delete nil org-noter--property-auto-save-last-location)
  1406. (org-entry-put nil org-noter--property-auto-save-last-location (format "%s" new-setting)))
  1407. (unless new-setting (org-entry-delete nil org-noter-property-note-location)))))))
  1408. (defun org-noter-set-hide-other (arg)
  1409. "This toggles hiding other headings for the current session.
  1410. - With a prefix \\[universal-argument], set the current setting permanently for this document.
  1411. - With a prefix \\[universal-argument] \\[universal-argument], remove the setting and use the default."
  1412. (interactive "P")
  1413. (org-noter--with-valid-session
  1414. (let* ((inhibit-read-only t)
  1415. (ast (org-noter--parse-root))
  1416. (persistent
  1417. (cond ((equal arg '(4)) 'write)
  1418. ((equal arg '(16)) 'remove)))
  1419. (new-setting
  1420. (cond ((eq persistent 'write) (org-noter--session-hide-other session))
  1421. ((eq persistent 'remove) org-noter-hide-other)
  1422. ('other-cases (not (org-noter--session-hide-other session))))))
  1423. (setf (org-noter--session-hide-other session) new-setting)
  1424. (when persistent
  1425. (with-current-buffer (org-noter--session-notes-buffer session)
  1426. (org-with-wide-buffer
  1427. (goto-char (org-element-property :begin ast))
  1428. (if (eq persistent 'write)
  1429. (org-entry-put nil org-noter--property-hide-other (format "%s" new-setting))
  1430. (org-entry-delete nil org-noter--property-hide-other))))))))
  1431. (defun org-noter-set-closest-tipping-point (arg)
  1432. "This sets the closest note tipping point (see `org-noter-closest-tipping-point')
  1433. - With a prefix \\[universal-argument], set it permanently for this document.
  1434. - With a prefix \\[universal-argument] \\[universal-argument], remove the setting and use the default."
  1435. (interactive "P")
  1436. (org-noter--with-valid-session
  1437. (let* ((ast (org-noter--parse-root))
  1438. (inhibit-read-only t)
  1439. (persistent (cond ((equal arg '(4)) 'write)
  1440. ((equal arg '(16)) 'remove)))
  1441. (new-setting (if (eq persistent 'remove)
  1442. org-noter-closest-tipping-point
  1443. (read-number "New tipping point: " (org-noter--session-closest-tipping-point session)))))
  1444. (setf (org-noter--session-closest-tipping-point session) new-setting)
  1445. (when persistent
  1446. (with-current-buffer (org-noter--session-notes-buffer session)
  1447. (org-with-wide-buffer
  1448. (goto-char (org-element-property :begin ast))
  1449. (if (eq persistent 'write)
  1450. (org-entry-put nil org-noter--property-closest-tipping-point (format "%f" new-setting))
  1451. (org-entry-delete nil org-noter--property-closest-tipping-point))))))))
  1452. (defun org-noter-set-notes-window-behavior (arg)
  1453. "Set the notes window behaviour for the current session.
  1454. With a prefix ARG, it becomes persistent for that document.
  1455. See `org-noter-notes-window-behavior' for more information."
  1456. (interactive "P")
  1457. (org-noter--with-valid-session
  1458. (let* ((inhibit-read-only t)
  1459. (ast (org-noter--parse-root))
  1460. (possible-behaviors (list '("Default" . default)
  1461. '("On start" . start)
  1462. '("On scroll" . scroll)
  1463. '("On scroll to location that only has previous notes" . only-prev)
  1464. '("Never" . never)))
  1465. chosen-behaviors)
  1466. (while (> (length possible-behaviors) 1)
  1467. (let ((chosen-pair (assoc (completing-read "Behavior: " possible-behaviors nil t) possible-behaviors)))
  1468. (cond ((eq (cdr chosen-pair) 'default) (setq possible-behaviors nil))
  1469. ((eq (cdr chosen-pair) 'never) (setq chosen-behaviors (list 'never)
  1470. possible-behaviors nil))
  1471. ((eq (cdr chosen-pair) 'done) (setq possible-behaviors nil))
  1472. (t (push (cdr chosen-pair) chosen-behaviors)
  1473. (setq possible-behaviors (delq chosen-pair possible-behaviors))
  1474. (when (= (length chosen-behaviors) 1)
  1475. (setq possible-behaviors (delq (rassq 'default possible-behaviors) possible-behaviors)
  1476. possible-behaviors (delq (rassq 'never possible-behaviors) possible-behaviors))
  1477. (push (cons "Done" 'done) possible-behaviors))))))
  1478. (setf (org-noter--session-window-behavior session)
  1479. (or chosen-behaviors org-noter-notes-window-behavior))
  1480. (when arg
  1481. (with-current-buffer (org-noter--session-notes-buffer session)
  1482. (org-with-wide-buffer
  1483. (goto-char (org-element-property :begin ast))
  1484. (if chosen-behaviors
  1485. (org-entry-put nil org-noter--property-behavior (format "%s" chosen-behaviors))
  1486. (org-entry-delete nil org-noter--property-behavior))))))))
  1487. (defun org-noter-set-notes-window-location (arg)
  1488. "Set the notes window default location for the current session.
  1489. With a prefix ARG, it becomes persistent for that document.
  1490. See `org-noter-notes-window-behavior' for more information."
  1491. (interactive "P")
  1492. (org-noter--with-valid-session
  1493. (let* ((inhibit-read-only t)
  1494. (ast (org-noter--parse-root))
  1495. (location-possibilities
  1496. '(("Default" . nil)
  1497. ("Horizontal split" . horizontal-split)
  1498. ("Vertical split" . vertical-split)
  1499. ("Other frame" . other-frame)))
  1500. (location
  1501. (cdr (assoc (completing-read "Location: " location-possibilities nil t)
  1502. location-possibilities)))
  1503. (notes-buffer (org-noter--session-notes-buffer session)))
  1504. (setf (org-noter--session-window-location session)
  1505. (or location org-noter-notes-window-location))
  1506. (let (exists)
  1507. (dolist (window (get-buffer-window-list notes-buffer nil t))
  1508. (setq exists t)
  1509. (with-selected-frame (window-frame window)
  1510. (if (= (count-windows) 1)
  1511. (delete-frame)
  1512. (delete-window window))))
  1513. (when exists (org-noter--get-notes-window 'force)))
  1514. (when arg
  1515. (with-current-buffer notes-buffer
  1516. (org-with-wide-buffer
  1517. (goto-char (org-element-property :begin ast))
  1518. (if location
  1519. (org-entry-put nil org-noter--property-location
  1520. (format "%s" location))
  1521. (org-entry-delete nil org-noter--property-location))))))))
  1522. (defun org-noter-set-doc-split-fraction (arg)
  1523. "Set the fraction of the frame that the document window will occupy when split.
  1524. - With a prefix \\[universal-argument], set it permanently for this document.
  1525. - With a prefix \\[universal-argument] \\[universal-argument], remove the setting and use the default."
  1526. (interactive "P")
  1527. (org-noter--with-valid-session
  1528. (let* ((ast (org-noter--parse-root))
  1529. (inhibit-read-only t)
  1530. (persistent (cond ((equal arg '(4)) 'write)
  1531. ((equal arg '(16)) 'remove)))
  1532. (current-setting (org-noter--session-doc-split-fraction session))
  1533. (new-setting
  1534. (if (eq persistent 'remove)
  1535. org-noter-doc-split-fraction
  1536. (cons (read-number "Horizontal fraction: " (car current-setting))
  1537. (read-number "Vertical fraction: " (cdr current-setting))))))
  1538. (setf (org-noter--session-doc-split-fraction session) new-setting)
  1539. (when (org-noter--get-notes-window)
  1540. (with-current-buffer (org-noter--session-doc-buffer session)
  1541. (delete-other-windows)
  1542. (org-noter--get-notes-window 'force)))
  1543. (when persistent
  1544. (with-current-buffer (org-noter--session-notes-buffer session)
  1545. (org-with-wide-buffer
  1546. (goto-char (org-element-property :begin ast))
  1547. (if (eq persistent 'write)
  1548. (org-entry-put nil org-noter--property-doc-split-fraction (format "%s" new-setting))
  1549. (org-entry-delete nil org-noter--property-doc-split-fraction))))))))
  1550. (defun org-noter-kill-session (&optional session)
  1551. "Kill an `org-noter' session.
  1552. When called interactively, if there is no prefix argument and the
  1553. buffer has an annotation session, it will kill it; else, it will
  1554. show a list of open `org-noter' sessions, asking for which to
  1555. kill.
  1556. When called from elisp code, you have to pass in the SESSION you
  1557. want to kill."
  1558. (interactive "P")
  1559. (when (and (called-interactively-p 'any) (> (length org-noter--sessions) 0))
  1560. ;; NOTE(nox): `session' is representing a prefix argument
  1561. (if (and org-noter--session (not session))
  1562. (setq session org-noter--session)
  1563. (setq session nil)
  1564. (let (collection default doc-display-name notes-file-name display)
  1565. (dolist (session org-noter--sessions)
  1566. (setq doc-display-name (org-noter--session-display-name session)
  1567. notes-file-name (file-name-nondirectory
  1568. (org-noter--session-notes-file-path session))
  1569. display (concat doc-display-name " - " notes-file-name))
  1570. (when (eq session org-noter--session) (setq default display))
  1571. (push (cons display session) collection))
  1572. (setq session (cdr (assoc (completing-read "Which session? " collection nil t
  1573. nil nil default)
  1574. collection))))))
  1575. (when (and session (memq session org-noter--sessions))
  1576. (setq org-noter--sessions (delq session org-noter--sessions))
  1577. (when (eq (length org-noter--sessions) 0)
  1578. (remove-hook 'delete-frame-functions 'org-noter--handle-delete-frame)
  1579. (advice-remove 'doc-view-goto-page 'org-noter--location-change-advice)
  1580. (advice-remove 'nov-render-document 'org-noter--nov-scroll-handler))
  1581. (let* ((ast (org-noter--parse-root session))
  1582. (frame (org-noter--session-frame session))
  1583. (notes-buffer (org-noter--session-notes-buffer session))
  1584. (base-buffer (buffer-base-buffer notes-buffer))
  1585. (notes-modified (buffer-modified-p base-buffer))
  1586. (doc-buffer (org-noter--session-doc-buffer session)))
  1587. (dolist (window (get-buffer-window-list notes-buffer nil t))
  1588. (with-selected-frame (window-frame window)
  1589. (if (= (count-windows) 1)
  1590. (when (org-noter--other-frames) (delete-frame))
  1591. (delete-window window))))
  1592. (with-current-buffer notes-buffer
  1593. (remove-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer t)
  1594. (restore-buffer-modified-p nil))
  1595. (unless org-noter-use-indirect-buffer
  1596. (kill-buffer notes-buffer))
  1597. (when base-buffer
  1598. (with-current-buffer base-buffer
  1599. (org-noter--unset-text-properties ast)
  1600. (set-buffer-modified-p notes-modified)))
  1601. (with-current-buffer doc-buffer
  1602. (remove-hook 'kill-buffer-hook 'org-noter--handle-kill-buffer t))
  1603. (kill-buffer doc-buffer)
  1604. (when (frame-live-p frame)
  1605. (if (and (org-noter--other-frames) org-noter-kill-frame-at-session-end)
  1606. (delete-frame frame)
  1607. (progn
  1608. (delete-other-windows)
  1609. (set-frame-parameter nil 'name nil)))))))
  1610. (defun org-noter-create-skeleton ()
  1611. "Create notes skeleton based on the outline of the document."
  1612. (interactive)
  1613. (org-noter--with-valid-session
  1614. (or (run-hook-with-args-until-success 'org-noter-create-skeleton-functions
  1615. (org-noter--session-doc-mode session))
  1616. (user-error "This command is not supported for %s"
  1617. (org-noter--session-doc-mode session)))))
  1618. (defun org-noter-insert-note (&optional precise-info note-title)
  1619. "Insert note associated with the current location.
  1620. This command will prompt for a title of the note and then insert
  1621. it in the notes buffer. When the input is empty, a title based on
  1622. `org-noter-default-heading-title' will be generated.
  1623. If there are other notes related to the current location, the
  1624. prompt will also suggest them. Depending on the value of the
  1625. variable `org-noter-closest-tipping-point', it may also
  1626. suggest the closest previous note.
  1627. PRECISE-INFO makes the new note associated with a more
  1628. specific location (see `org-noter-insert-precise-note' for more
  1629. info).
  1630. When you insert into an existing note and have text selected on
  1631. the document buffer, the variable `org-noter-insert-selected-text-inside-note'
  1632. defines if the text should be inserted inside the note."
  1633. (interactive)
  1634. (org-noter--with-valid-session
  1635. (let* ((ast (org-noter--parse-root)) (contents (org-element-contents ast))
  1636. (window (org-noter--get-notes-window 'force))
  1637. (selected-text
  1638. (run-hook-with-args-until-success
  1639. 'org-noter-get-selected-text-hook
  1640. (org-noter--session-doc-mode session)))
  1641. force-new
  1642. (location (org-noter--doc-approx-location (or precise-info 'interactive) (gv-ref force-new)))
  1643. (view-info (org-noter--get-view-info (org-noter--get-current-view) location)))
  1644. (let ((inhibit-quit t))
  1645. (with-local-quit
  1646. (select-frame-set-input-focus (window-frame window))
  1647. (select-window window)
  1648. ;; IMPORTANT(nox): Need to be careful changing the next part, it is a bit
  1649. ;; complicated to get it right...
  1650. (let ((point (point))
  1651. (minibuffer-local-completion-map org-noter--completing-read-keymap)
  1652. collection default default-begin title selection quote-p
  1653. (empty-lines-number (if org-noter-separate-notes-from-heading 2 1)))
  1654. (cond
  1655. ;; NOTE(nox): Both precise and without questions will create new notes
  1656. ((or precise-info force-new)
  1657. (setq quote-p (with-temp-buffer
  1658. (insert (or selected-text ""))
  1659. (> (how-many "\n" (point-min)) 2)))
  1660. (setq default (and selected-text
  1661. (replace-regexp-in-string "\n" " " selected-text))))
  1662. (org-noter-insert-note-no-questions)
  1663. (t
  1664. (dolist (note-cons (org-noter--view-info-notes view-info))
  1665. (let ((display (org-element-property :raw-value (car note-cons)))
  1666. (begin (org-element-property :begin (car note-cons))))
  1667. (push (cons display note-cons) collection)
  1668. (when (and (>= point begin) (> begin (or default-begin 0)))
  1669. (setq default display
  1670. default-begin begin))))))
  1671. (setq collection (nreverse collection)
  1672. title (if (or org-noter-insert-note-no-questions note-title)
  1673. (or default note-title)
  1674. (completing-read "Note: " collection nil nil nil nil default))
  1675. selection (unless org-noter-insert-note-no-questions (cdr (assoc title collection))))
  1676. (if selection
  1677. ;; NOTE(nox): Inserting on an existing note
  1678. (let* ((note (car selection))
  1679. (insert-before-element (cdr selection))
  1680. (has-content
  1681. (eq (org-element-map (org-element-contents note) org-element-all-elements
  1682. (lambda (element)
  1683. (if (org-noter--check-location-property element)
  1684. 'stop
  1685. (not (memq (org-element-type element) '(section property-drawer)))))
  1686. nil t)
  1687. t)))
  1688. (when has-content (setq empty-lines-number 2))
  1689. (if insert-before-element
  1690. (goto-char (org-element-property :begin insert-before-element))
  1691. (goto-char (org-element-property :end note)))
  1692. (if (org-at-heading-p)
  1693. (progn
  1694. (org-N-empty-lines-before-current empty-lines-number)
  1695. (forward-line -1))
  1696. (unless (bolp) (insert "\n"))
  1697. (org-N-empty-lines-before-current (1- empty-lines-number)))
  1698. (when (and org-noter-insert-selected-text-inside-note selected-text) (insert selected-text)))
  1699. ;; NOTE(nox): Inserting a new note
  1700. (let ((reference-element-cons (org-noter--view-info-reference-for-insertion view-info))
  1701. level)
  1702. (when (or quote-p (zerop (length title)))
  1703. (setq title (replace-regexp-in-string (regexp-quote "$p$")
  1704. (org-noter--pretty-print-location location)
  1705. org-noter-default-heading-title)))
  1706. (if reference-element-cons
  1707. (progn
  1708. (cond
  1709. ((eq (car reference-element-cons) 'before)
  1710. (goto-char (org-element-property :begin (cdr reference-element-cons))))
  1711. ((eq (car reference-element-cons) 'after)
  1712. (goto-char (org-element-property :end (cdr reference-element-cons)))))
  1713. ;; NOTE(nox): This is here to make the automatic "should insert blank" work better.
  1714. (when (org-at-heading-p) (backward-char))
  1715. (setq level (org-element-property :level (cdr reference-element-cons))))
  1716. (goto-char (or (org-element-map contents 'section
  1717. (lambda (section) (org-element-property :end section))
  1718. nil t org-element-all-elements)
  1719. (point-max))))
  1720. (setq level (1+ (or (org-element-property :level ast) 0)))
  1721. ;; NOTE(nox): This is needed to insert in the right place
  1722. (unless (org-noter--no-heading-p) (outline-show-entry))
  1723. (org-noter--insert-heading level title empty-lines-number location)
  1724. (when quote-p
  1725. (save-excursion
  1726. (insert "#+BEGIN_QUOTE\n" selected-text "\n#+END_QUOTE")))
  1727. (when (org-noter--session-hide-other session) (org-overview))
  1728. (setf (org-noter--session-num-notes-in-view session)
  1729. (1+ (org-noter--session-num-notes-in-view session)))))
  1730. (org-show-set-visibility t)
  1731. (org-cycle-hide-drawers 'all)
  1732. (org-cycle-show-empty-lines t)))
  1733. (when quit-flag
  1734. ;; NOTE(nox): If this runs, it means the user quitted while creating a note, so
  1735. ;; revert to the previous window.
  1736. (select-frame-set-input-focus (org-noter--session-frame session))
  1737. (select-window (get-buffer-window (org-noter--session-doc-buffer session))))))))
  1738. (defun org-noter-insert-precise-note (&optional toggle-no-questions)
  1739. "Insert note associated with a specific location.
  1740. This will ask you to click where you want to scroll to when you
  1741. sync the document to this note. You should click on the top of
  1742. that part. Will always create a new note.
  1743. When text is selected, it will automatically choose the top of
  1744. the selected text as the location and the text itself as the
  1745. title of the note (you may change it anyway!).
  1746. See `org-noter-insert-note' docstring for more."
  1747. (interactive "P")
  1748. (org-noter--with-valid-session
  1749. (let ((org-noter-insert-note-no-questions (if toggle-no-questions
  1750. (not org-noter-insert-note-no-questions)
  1751. org-noter-insert-note-no-questions)))
  1752. (org-noter-insert-note (org-noter--get-precise-info)))))
  1753. (defun org-noter-insert-note-toggle-no-questions ()
  1754. "Insert note associated with the current location.
  1755. This is like `org-noter-insert-note', except it will toggle `org-noter-insert-note-no-questions'"
  1756. (interactive)
  1757. (org-noter--with-valid-session
  1758. (let ((org-noter-insert-note-no-questions (not org-noter-insert-note-no-questions)))
  1759. (org-noter-insert-note))))
  1760. (defmacro org-noter--map-ignore-headings-with-doc-file (contents match-first &rest body)
  1761. `(let (ignore-until-level)
  1762. (org-element-map ,contents 'headline
  1763. (lambda (headline)
  1764. (let ((doc-file (org-noter--doc-file-property headline))
  1765. (location (org-noter--parse-location-property headline)))
  1766. (when (and ignore-until-level (<= (org-element-property :level headline) ignore-until-level))
  1767. (setq ignore-until-level nil))
  1768. (cond
  1769. (ignore-until-level nil) ;; NOTE(nox): This heading is ignored, do nothing
  1770. ((and doc-file (not (string= doc-file (org-noter--session-property-text session))))
  1771. (setq ignore-until-level (org-element-property :level headline)) nil)
  1772. (t ,@body))))
  1773. nil ,match-first org-noter--note-search-no-recurse)))
  1774. (defun org-noter-sync-prev-page-or-chapter ()
  1775. "Show previous page or chapter that has notes, in relation to the current page or chapter.
  1776. This will force the notes window to popup."
  1777. (interactive)
  1778. (org-noter--with-valid-session
  1779. (let ((this-location (org-noter--doc-approx-location 0))
  1780. (contents (org-element-contents (org-noter--parse-root)))
  1781. target-location)
  1782. (org-noter--get-notes-window 'force)
  1783. (org-noter--map-ignore-headings-with-doc-file
  1784. contents nil
  1785. (when (and (org-noter--compare-locations '< location this-location)
  1786. (org-noter--compare-locations '>f location target-location))
  1787. (setq target-location location)))
  1788. (org-noter--get-notes-window 'force)
  1789. (select-window (org-noter--get-doc-window))
  1790. (if target-location
  1791. (org-noter--doc-goto-location target-location)
  1792. (user-error "There are no more previous pages or chapters with notes")))))
  1793. (defun org-noter-sync-current-page-or-chapter ()
  1794. "Show current page or chapter notes.
  1795. This will force the notes window to popup."
  1796. (interactive)
  1797. (org-noter--with-valid-session
  1798. (let ((window (org-noter--get-notes-window 'force)))
  1799. (select-frame-set-input-focus (window-frame window))
  1800. (select-window window)
  1801. (org-noter--doc-location-change-handler))))
  1802. (defun org-noter-sync-next-page-or-chapter ()
  1803. "Show next page or chapter that has notes, in relation to the current page or chapter.
  1804. This will force the notes window to popup."
  1805. (interactive)
  1806. (org-noter--with-valid-session
  1807. (let ((this-location (org-noter--doc-approx-location most-positive-fixnum))
  1808. (contents (org-element-contents (org-noter--parse-root)))
  1809. target-location)
  1810. (org-noter--map-ignore-headings-with-doc-file
  1811. contents nil
  1812. (when (and (org-noter--compare-locations '> location this-location)
  1813. (org-noter--compare-locations '< location target-location))
  1814. (setq target-location location)))
  1815. (org-noter--get-notes-window 'force)
  1816. (select-window (org-noter--get-doc-window))
  1817. (if target-location
  1818. (org-noter--doc-goto-location target-location)
  1819. (user-error "There are no more following pages or chapters with notes")))))
  1820. (defun org-noter-sync-prev-note ()
  1821. "Go to the location of the previous note, in relation to where the point is.
  1822. As such, it will only work when the notes window exists."
  1823. (interactive)
  1824. (org-noter--with-selected-notes-window
  1825. "No notes window exists"
  1826. (let ((org-noter--inhibit-location-change-handler t)
  1827. (contents (org-element-contents (org-noter--parse-root)))
  1828. (current-begin (org-element-property :begin (org-noter--get-containing-element)))
  1829. previous)
  1830. (when current-begin
  1831. (org-noter--map-ignore-headings-with-doc-file
  1832. contents t
  1833. (when location
  1834. (if (= current-begin (org-element-property :begin headline))
  1835. t
  1836. (setq previous headline)
  1837. nil))))
  1838. (if previous
  1839. (progn
  1840. ;; NOTE(nox): This needs to be manual so we can focus the correct note
  1841. (org-noter--doc-goto-location (org-noter--parse-location-property previous))
  1842. (org-noter--focus-notes-region (org-noter--make-view-info-for-single-note session previous)))
  1843. (user-error "There is no previous note"))))
  1844. (select-window (org-noter--get-doc-window)))
  1845. (defun org-noter-sync-current-note ()
  1846. "Go the location of the selected note, in relation to where the point is.
  1847. As such, it will only work when the notes window exists."
  1848. (interactive)
  1849. (org-noter--with-selected-notes-window
  1850. "No notes window exists"
  1851. (if (string= (or (org-noter--get-or-read-document-property t)
  1852. (cadar (org-collect-keywords (list org-noter-property-doc-file))))
  1853. (org-noter--session-property-text session))
  1854. (let ((location (org-noter--parse-location-property (org-noter--get-containing-element))))
  1855. (if location
  1856. (org-noter--doc-goto-location location)
  1857. (user-error "No note selected")))
  1858. (user-error "You are inside a different document")))
  1859. (let ((window (org-noter--get-doc-window)))
  1860. (select-frame-set-input-focus (window-frame window))
  1861. (select-window window)))
  1862. (defun org-noter-sync-next-note ()
  1863. "Go to the location of the next note, in relation to where the point is.
  1864. As such, it will only work when the notes window exists."
  1865. (interactive)
  1866. (org-noter--with-selected-notes-window
  1867. "No notes window exists"
  1868. (let ((org-noter--inhibit-location-change-handler t)
  1869. (contents (org-element-contents (org-noter--parse-root)))
  1870. next)
  1871. (org-noter--map-ignore-headings-with-doc-file
  1872. contents t
  1873. (when (and location (< (point) (org-element-property :begin headline)))
  1874. (setq next headline)))
  1875. (if next
  1876. (progn
  1877. (org-noter--doc-goto-location (org-noter--parse-location-property next))
  1878. (org-noter--focus-notes-region (org-noter--make-view-info-for-single-note session next)))
  1879. (user-error "There is no next note"))))
  1880. (select-window (org-noter--get-doc-window)))
  1881. (define-minor-mode org-noter-doc-mode
  1882. "Minor mode for the document buffer.
  1883. Keymap:
  1884. \\{org-noter-doc-mode-map}"
  1885. :keymap `((,(kbd "i") . org-noter-insert-note)
  1886. (,(kbd "C-i") . org-noter-insert-note-toggle-no-questions)
  1887. (,(kbd "M-i") . org-noter-insert-precise-note)
  1888. (,(kbd "q") . org-noter-kill-session)
  1889. (,(kbd "M-p") . org-noter-sync-prev-page-or-chapter)
  1890. (,(kbd "M-.") . org-noter-sync-current-page-or-chapter)
  1891. (,(kbd "M-n") . org-noter-sync-next-page-or-chapter)
  1892. (,(kbd "C-M-p") . org-noter-sync-prev-note)
  1893. (,(kbd "C-M-.") . org-noter-sync-current-note)
  1894. (,(kbd "C-M-n") . org-noter-sync-next-note))
  1895. (let ((mode-line-segment '(:eval (org-noter--mode-line-text))))
  1896. (if org-noter-doc-mode
  1897. (if (symbolp (car-safe mode-line-format))
  1898. (setq mode-line-format (list mode-line-segment mode-line-format))
  1899. (push mode-line-segment mode-line-format))
  1900. (setq mode-line-format (delete mode-line-segment mode-line-format)))))
  1901. (define-minor-mode org-noter-notes-mode
  1902. "Minor mode for the notes buffer.
  1903. Keymap:
  1904. \\{org-noter-notes-mode-map}"
  1905. :keymap `((,(kbd "M-p") . org-noter-sync-prev-page-or-chapter)
  1906. (,(kbd "M-.") . org-noter-sync-current-page-or-chapter)
  1907. (,(kbd "M-n") . org-noter-sync-next-page-or-chapter)
  1908. (,(kbd "C-M-p") . org-noter-sync-prev-note)
  1909. (,(kbd "C-M-.") . org-noter-sync-current-note)
  1910. (,(kbd "C-M-n") . org-noter-sync-next-note))
  1911. (if org-noter-doc-mode
  1912. (org-noter-doc-mode -1)))
  1913. (provide 'org-noter-core)
  1914. ;;; org-noter-core.el ends here