journal.el 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. ;;; journal.el --- Working with journal (diary) entries -*- lexical-binding: t -*-
  2. ;; Copyright © 2012–2015, 2018 Alex Kost
  3. ;; Author: Alex Kost <alezost@gmail.com>
  4. ;; Created: 15 Nov 2012
  5. ;; Version: 0.1
  6. ;; URL: https://github.com/alezost/journal.el
  7. ;; Keywords: convenience
  8. ;; This program is free software; you can redistribute it and/or modify
  9. ;; it under the terms of the GNU General Public License as published by
  10. ;; the Free Software Foundation, either version 3 of the License, or
  11. ;; (at your option) any later version.
  12. ;;
  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. ;;
  18. ;; You should have received a copy of the GNU General Public License
  19. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. ;;; Commentary:
  21. ;; This file provides some miscellaneous functionality for creating and
  22. ;; managing org-mode based diary entries.
  23. ;;
  24. ;; "diary" would be a more suitable name for this package, but there is
  25. ;; an in-built Emacs package with that name already, so this one is
  26. ;; named "journal".
  27. ;; This package is highly specialized to suit my own needs so I think
  28. ;; it's unlikely that it may be used as it is by anyone else. But
  29. ;; anyway, here are my "diary needs":
  30. ;;
  31. ;; 1. At first, from time to time I make some quick notes in a special
  32. ;; file (`journal-notes-file'), for which I have an org template:
  33. ;;
  34. ;; (setq org-capture-templates
  35. ;; '(("n" "notes" entry (file org-default-notes-file)
  36. ;; "* %T\n %?\n")))
  37. ;;
  38. ;; 2. When enough notes were accumulated there, I use
  39. ;; `journal-create-entry' command and then I try to expand all that
  40. ;; stuff into something readable.
  41. ;;
  42. ;; 3. My journal files have names "2004", "2005", … and are placed in
  43. ;; `journal-directory'. The structure of those org files is the
  44. ;; following (it is created automatically):
  45. ;;
  46. ;; * 2014
  47. ;; ** 2014-01 January
  48. ;; *** 2014-01-02 Thursday
  49. ;; ...
  50. ;; ** 2014-12 December
  51. ;; *** 2014-12-31 Wednesday
  52. ;; :PROPERTIES:
  53. ;; :ID: abb90b74-4fbd-4830-a9c6-35b841998213
  54. ;; :described: <2014-12-30 Tue>--<2014-12-31 Wed>
  55. ;; :created: <2014-12-31 Wed 20:08>--<2014-12-31 Wed 21:20>
  56. ;; :END:
  57. ;; **** :guix:emacs:
  58. ;; Yesterday I added 2 more files ...
  59. ;;
  60. ;; 4. My `journal-template-file' has the following contents:
  61. ;;
  62. ;; -*- mode: org; -*-
  63. ;; #+STARTUP: showeverything
  64. ;;
  65. ;; 5. Also my journal entries are full of links to other entries. I use
  66. ;; a usual pair of org commands (`org-store-link' and
  67. ;; `org-insert-link') for that. This file
  68. ;; contains some code to add "journal" links, which look this:
  69. ;;
  70. ;; [[journal:85192c5f-aa3c-4dab-89db-f752f8076911::record][17.12.2014]]
  71. ;;
  72. ;; 6. And finally I can quickly search all my diary files using
  73. ;; `journal-search-by-date' and `journal-search-by-re' commands.
  74. ;;
  75. ;; ∞. Oof, I think that's all more or less.
  76. ;; This package optionally depends on "date-at-point" package
  77. ;; <https://gitlab.com/alezost-emacs/date-at-point>.
  78. ;; This package has nothing to do with
  79. ;; <https://github.com/bastibe/org-journal>. It is a totally different
  80. ;; project.
  81. ;;; Code:
  82. (require 'org-datetree)
  83. (require 'org-id)
  84. (require 'grep)
  85. (require 'date-at-point nil t)
  86. (defgroup journal nil
  87. "Org based journal/diary."
  88. :group 'convenience
  89. :group 'org)
  90. (defcustom journal-directory "~/Documents/journal"
  91. "Directory with journal files."
  92. :type 'directory
  93. :group 'journal)
  94. (defcustom journal-notes-file org-default-notes-file
  95. "File with temporary notes for journal."
  96. :type 'file
  97. :group 'journal)
  98. (defcustom journal-template-file nil
  99. "If non-nil, used as a template for journal files."
  100. :type '(choice (const :tag "No" nil)
  101. file)
  102. :group 'journal)
  103. (defcustom journal-open-block "|"
  104. "String for openning an embedded block."
  105. :type 'string
  106. :group 'journal)
  107. (defcustom journal-close-block "|"
  108. "String for closing an embedded block."
  109. :type 'string
  110. :group 'journal)
  111. (defcustom journal-block-date-separator " — "
  112. "String to insert before a date in an embedded block."
  113. :type 'string
  114. :group 'journal)
  115. (defcustom journal-late-time-seconds (* 3 60 60)
  116. "Number of seconds after midnight that counted as a previous date.
  117. For instance, if this variable is set to 3 hours (default), when
  118. you make a journal entry at '2:30 AM 12 Jul', it will belong to
  119. 11 Jul."
  120. :type 'integer
  121. :group 'journal)
  122. (defvar journal-entry-heading-regexp "^\\*\\*\\* +\\(.*\\)$"
  123. "Regular expression matching journal entry heading.")
  124. (defvar journal-entry-heading-time-format "%Y-%m-%d %A"
  125. "Format time string of a journal entry heading.")
  126. (defvar journal-file-name-regexp "\\`[0-9]\\{4\\}\\'"
  127. "Regular expression matching journal file name.")
  128. (defvar journal-files-pattern "20*"
  129. "Shell pattern matching journal files.")
  130. (defvar journal-current-file nil
  131. "Last used journal file.
  132. If non-nil, used in `journal-position-windows'.")
  133. (defvar journal-described-property-name "described")
  134. (defvar journal-created-property-name "created")
  135. (defvar journal-converted-property-name "converted")
  136. (defun journal-get-time-stamp (time &optional with-hm)
  137. "Return org time stamp string from TIME (iso or system format).
  138. WITH-HM means use the stamp format that includes the time of the day."
  139. (let ((fmt (funcall (if with-hm #'cdr #'car)
  140. org-time-stamp-formats)))
  141. (and (stringp time)
  142. (setq time (org-read-date nil t time)))
  143. (format-time-string fmt time)))
  144. (defun journal-get-time-from-stamp (org-time &optional end-time-p force)
  145. "Return time value from org time stamp or range ORG-TIME.
  146. Use the start part of the time range if END-TIME-P is nil.
  147. If ORG-TIME is a single time stamp and END-TIME-P is non-nil,
  148. return nil; with FORCE return ORG-TIME time value. "
  149. (or (string-match org-tsr-regexp org-time)
  150. (error "Wrong org time stamp/range"))
  151. (let ((ts (if (string-match "---?" org-time)
  152. (if end-time-p
  153. (substring org-time (match-end 0))
  154. (substring org-time 0 (match-beginning 0)))
  155. (and (or force (not end-time-p))
  156. org-time))))
  157. (and ts
  158. (apply #'encode-time
  159. (org-parse-time-string ts)))))
  160. (defun journal-start-time ()
  161. "Return start time for a new journal entry.
  162. Returning value is a time value defined from the first time stamp
  163. from `journal-notes-file'. Return nil if no time stamps found."
  164. (let ((buf (find-file-noselect journal-notes-file)))
  165. (with-current-buffer buf
  166. (save-excursion
  167. (goto-char (point-min))
  168. (when (re-search-forward org-ts-regexp nil t)
  169. (let ((ts (buffer-substring-no-properties
  170. (match-beginning 0) (match-end 0))))
  171. (apply #'encode-time
  172. (org-parse-time-string ts))))))))
  173. (defun journal-date (time)
  174. "Return time value of TIME shifted by `journal-late-time-seconds'."
  175. (time-add time
  176. (seconds-to-time (- journal-late-time-seconds))))
  177. (defun journal-date-list (time)
  178. "Return (MONTH DAY YEAR) list from time value TIME.
  179. Such a list is used in `org-datetree-find-date-create'.
  180. See also `calendar-gregorian-from-absolute'."
  181. (let ((date (butlast (nthcdr 3 (decode-time time)) 3)))
  182. (list (nth 1 date)
  183. (car date)
  184. (nth 2 date))))
  185. (defun journal-insert-empty-entry (date-list)
  186. "Insert new DATE-LIST day entry in the current buffer.
  187. DATE-LIST is a list (month day year).
  188. This function is the same as `org-datetree-find-date-create', but
  189. it creates a new entry even if an entry with this date already
  190. exists."
  191. (org-datetree-find-date-create date-list)
  192. ;; Duplicate current heading if the day entry exists.
  193. (when (save-excursion
  194. (re-search-forward org-property-start-re
  195. (save-excursion
  196. (org-forward-heading-same-level 1)
  197. (point))
  198. 'no-error))
  199. ;; Scroll through all entries with a specified described day to add
  200. ;; the new entry after the last one.
  201. (let ((heading (buffer-substring-no-properties
  202. (point) (line-end-position))))
  203. (org-forward-heading-same-level 1)
  204. (while (string= heading
  205. (buffer-substring-no-properties
  206. (point) (line-end-position)))
  207. (org-forward-heading-same-level 1))
  208. (save-excursion (insert heading "\n")))))
  209. (defun journal-insert-entry (start-date end-date created-time
  210. &optional buffer)
  211. "Add journal entry to a buffer BUFFER.
  212. If BUFFER is nil, use current buffer.
  213. START-DATE, END-DATE, CREATED-TIME should be time values."
  214. (let ((start-date-list (journal-date-list start-date))
  215. (end-date-list (journal-date-list end-date)))
  216. (with-current-buffer (or buffer (current-buffer))
  217. (journal-insert-empty-entry end-date-list)
  218. (org-id-get-create)
  219. (org-set-property journal-described-property-name
  220. ;; Compare days, not microseconds.
  221. (if (equal start-date-list end-date-list)
  222. (journal-get-time-stamp start-date)
  223. (concat (journal-get-time-stamp start-date)
  224. "--"
  225. (journal-get-time-stamp end-date))))
  226. (org-set-property journal-created-property-name
  227. (journal-get-time-stamp created-time t))
  228. ;; Show properties, add subheading.
  229. (outline-show-entry)
  230. (re-search-forward org-property-end-re nil t)
  231. (insert "\n ")
  232. (journal-insert-subheading))))
  233. ;;;###autoload
  234. (defun journal-create-entry (start-date end-date)
  235. "Create journal entry for describing a date range.
  236. START-DATE and END-DATE should be time values.
  237. The entry is created in a file with START-DATE's year name in
  238. `journal-directory'.
  239. Interactively, prompt for a described range."
  240. (interactive
  241. (save-excursion
  242. (delete-other-windows)
  243. (find-file journal-notes-file)
  244. (list (org-read-date nil t nil "Start date" (journal-date
  245. (journal-start-time)))
  246. (org-read-date nil t nil "End date" (current-time)))))
  247. (let ((file (expand-file-name (format-time-string "%Y" end-date)
  248. journal-directory)))
  249. (find-file file)
  250. (when (and (not (file-exists-p file))
  251. (file-exists-p journal-template-file))
  252. (insert-file-contents journal-template-file)
  253. (save-buffer))
  254. (setq journal-current-file file)
  255. (or (eq major-mode 'org-mode) (org-mode))
  256. (journal-position-windows (current-buffer))
  257. (journal-insert-entry start-date end-date (current-time))))
  258. ;;;###autoload
  259. (defun journal-position-windows (&optional buffer)
  260. "Position notes buffer and journal BUFFER into 2 windows.
  261. Notes buffer is a buffer visiting `journal-notes-file'.
  262. If BUFFER is nil, use a buffer with `journal-current-file'."
  263. (interactive)
  264. (find-file journal-notes-file)
  265. (delete-other-windows)
  266. (split-window-sensibly)
  267. (other-window 1)
  268. (if buffer
  269. (switch-to-buffer buffer)
  270. (find-file journal-current-file)))
  271. ;;;###autoload
  272. (defun journal-insert-subheading ()
  273. "Insert subentry heading before the current line and prompt for tags."
  274. (interactive)
  275. (save-excursion
  276. (end-of-line 0)
  277. (unless (outline-on-heading-p)
  278. (newline)
  279. (insert "**** "))
  280. (org-set-tags)))
  281. ;;;###autoload
  282. (defun journal-insert-block ()
  283. "Insert block at point."
  284. (interactive)
  285. (insert journal-open-block)
  286. (save-excursion
  287. (insert journal-block-date-separator)
  288. (org-insert-time-stamp (current-time) 't)
  289. (insert journal-close-block)))
  290. (defun journal-back-to-entry-heading ()
  291. "Move to the beginning of previous entry (3rd level) heading."
  292. (interactive)
  293. (if (re-search-backward journal-entry-heading-regexp nil t)
  294. (beginning-of-line)
  295. (error "Before first entry heading")))
  296. (defun journal-at-heading-p ()
  297. "Return non-nil, if the current line is an entry heading."
  298. (save-excursion
  299. (beginning-of-line)
  300. (looking-at journal-entry-heading-regexp)))
  301. (defmacro journal-with-current-entry (&rest body)
  302. "Evaluate BODY with point placed on a current entry heading."
  303. `(save-excursion
  304. ;; Move to an entry heading. If already in a heading, don't move
  305. ;; to the previous one.
  306. (end-of-line)
  307. (journal-back-to-entry-heading)
  308. ,@body))
  309. (put 'journal-with-current-entry 'lisp-indent-function 0)
  310. (defun journal-id ()
  311. "Return ID property of the current journal entry."
  312. (journal-with-current-entry
  313. (or (org-id-get)
  314. (error "No ID property"))))
  315. (defun journal-file-p (file)
  316. "Return non-nil if FILE is a journal file."
  317. (let ((journal-dir (file-name-as-directory
  318. (expand-file-name journal-directory)))
  319. (file-dir (file-name-directory
  320. (expand-file-name file)))
  321. (file-name (file-name-nondirectory file)))
  322. (and (string= journal-dir file-dir)
  323. (string-match-p journal-file-name-regexp
  324. file-name))))
  325. (defun journal-buffer-p (&optional buffer)
  326. "Return non-nil if file in BUFFER is a journal file.
  327. If BUFFER is nil, check the current buffer."
  328. (let ((file (buffer-file-name buffer)))
  329. (and file (journal-file-p file))))
  330. (defun journal-get-entry-property (property)
  331. "Get PROPERTY of the current entry."
  332. (save-excursion
  333. (journal-back-to-entry-heading)
  334. (org-entry-get nil property)))
  335. (defun journal-set-entry-property (property value)
  336. "Set PROPERTY of the current entry to VALUE."
  337. (save-excursion
  338. (journal-back-to-entry-heading)
  339. (org-set-property property value)))
  340. ;;; Modifying time properties
  341. (defun journal-change-start-time (range-or-stamp &optional time with-hm)
  342. "Change the start value of time RANGE-OR-STAMP."
  343. (let ((beg (or time
  344. (org-read-date with-hm t nil nil
  345. (journal-get-time-from-stamp
  346. range-or-stamp))))
  347. (end (journal-get-time-from-stamp range-or-stamp t)))
  348. (if end
  349. (concat (journal-get-time-stamp beg with-hm) "--"
  350. (journal-get-time-stamp end with-hm))
  351. (journal-get-time-stamp beg with-hm))))
  352. (defun journal-change-end-time (range-or-stamp &optional time with-hm)
  353. "Change the end value of time RANGE-OR-STAMP."
  354. (let ((beg (journal-get-time-from-stamp range-or-stamp))
  355. (end (or time
  356. (org-read-date with-hm t nil nil
  357. (journal-get-time-from-stamp
  358. range-or-stamp t)))))
  359. (concat (journal-get-time-stamp beg with-hm) "--"
  360. (journal-get-time-stamp end with-hm))))
  361. (defun journal-change-time-property (function property &optional time with-hm)
  362. "Change time of PROPERTY in the current entry using FUNCTION.
  363. TIME is time value. If it is nil, prompt for it.
  364. WITH-HM means use the stamp format that includes the time of the day.
  365. If PROPERTY does not exist, create it.
  366. FUNCTION is called with PROPERTY value, TIME and WITH-HM as arguments."
  367. (let* ((old (journal-get-entry-property property))
  368. (new (if old
  369. (funcall function old time with-hm)
  370. (journal-get-time-stamp (or time
  371. (org-read-date with-hm t))
  372. with-hm))))
  373. (journal-set-entry-property property new)))
  374. (defun journal-change-start-time-property (property &optional time with-hm)
  375. "Change start time of PROPERTY in the current entry with TIME.
  376. See `journal-change-time-property' for details."
  377. (journal-change-time-property #'journal-change-start-time
  378. property time with-hm))
  379. (defun journal-change-end-time-property (property &optional time with-hm)
  380. "Change end time of PROPERTY in the current entry with TIME.
  381. See `journal-change-time-property' for details."
  382. (journal-change-time-property #'journal-change-end-time
  383. property time with-hm))
  384. (defun journal-change-created-property ()
  385. "Modify \"created\" property in the current entry with the current time.
  386. See `journal-change-time-property' for details."
  387. (interactive)
  388. (journal-change-end-time-property
  389. journal-created-property-name (current-time) t))
  390. (defun journal-change-converted-property ()
  391. "Modify \"converted\" property in the current entry with the current time.
  392. See `journal-change-time-property' for details."
  393. (interactive)
  394. (journal-change-end-time-property
  395. journal-converted-property-name (current-time) t))
  396. (defun journal-change-described-property (&optional arg)
  397. "Modify \"described\" property in the current entry.
  398. See `journal-change-time-property' for details.
  399. If ARG is nil, change the start date of the range.
  400. If ARG is \\[universal-argument], change the end date of the range.
  401. If ARG is \\[universal-argument] \\[universal-argument], create a single time stamp."
  402. (interactive "P")
  403. (cond ((null arg)
  404. (journal-change-start-time-property
  405. journal-described-property-name))
  406. ((equal arg '(4))
  407. (journal-change-end-time-property
  408. journal-described-property-name))
  409. ((equal arg '(16))
  410. (journal-set-entry-property
  411. journal-described-property-name
  412. (journal-get-time-stamp (org-read-date nil t))))
  413. (t
  414. (user-error "Wrong prefix argument")))
  415. (when arg
  416. (let ((time (journal-get-time-from-stamp
  417. (journal-get-entry-property
  418. journal-described-property-name)
  419. t t)))
  420. (save-excursion
  421. (re-search-backward journal-entry-heading-regexp nil t)
  422. (delete-region (match-beginning 1)
  423. (progn (end-of-line) (point)))
  424. (insert (format-time-string
  425. journal-entry-heading-time-format time))))))
  426. ;;; Searching entries
  427. ;;;###autoload
  428. (defun journal-search-by-re (regexp)
  429. "Search journal files for REGEXP."
  430. (interactive
  431. (progn
  432. ;; This thing exists in every command from "grep.el" because it
  433. ;; wouldn't work otherwise.
  434. (grep-compute-defaults)
  435. (list (read-regexp "Search for regexp: "))))
  436. (lgrep regexp journal-files-pattern journal-directory))
  437. ;;;###autoload
  438. (defun journal-grep ()
  439. "Search journal with grep.
  440. Prompt for a searching regexp.
  441. With \\[universal-argument] prompt for a whole grep command."
  442. (interactive)
  443. (if current-prefix-arg
  444. (let* ((cmd (grep-expand-template grep-template ""
  445. journal-files-pattern))
  446. (cmd (read-from-minibuffer "grep command: " cmd
  447. nil nil 'grep-history))
  448. (default-directory (file-name-as-directory
  449. journal-directory)))
  450. (grep cmd))
  451. (call-interactively #'journal-search-by-re)))
  452. ;;;###autoload
  453. (defun journal-search-by-date (date)
  454. "Search for an entry containing DATE in a described property.
  455. DATE is a time value."
  456. (interactive
  457. (list (org-read-date nil t (thing-at-point 'date))))
  458. (let ((file (expand-file-name (format-time-string "%Y" date)
  459. journal-directory)))
  460. (or (file-regular-p file)
  461. (error "File '%s' doesn't exist" file))
  462. (push-mark)
  463. (find-file file)
  464. (goto-char (point-min))
  465. (let ((date-str (format-time-string
  466. journal-entry-heading-time-format date))
  467. (seconds (float-time date)))
  468. (while (and (re-search-forward journal-entry-heading-regexp nil t)
  469. (string< (match-string 1) date-str)))
  470. (let* ((prop (org-entry-get nil journal-described-property-name))
  471. (beg-seconds (float-time (journal-get-time-from-stamp prop)))
  472. (end-seconds (float-time (journal-get-time-from-stamp prop t))))
  473. (or (and (>= seconds beg-seconds)
  474. (<= seconds end-seconds))
  475. (error "No entries with this date"))))))
  476. ;;; Org links to journal entries
  477. (defvar journal-link-regexp
  478. ;; Originates from `org-bracket-link-regexp'.
  479. "\\[\\[journal:\\([^][]+\\)\\]\\(\\[\\([^][]+\\)\\]\\)\\]"
  480. "Regular expression matching a journal link.")
  481. (defvar journal-link-date-format "%d.%m.%Y"
  482. "Format string of a date used as a description for a journal link.")
  483. (defun journal-link-date ()
  484. "Return described or created date of the current journal entry."
  485. (journal-with-current-entry
  486. (format-time-string
  487. journal-link-date-format
  488. (journal-get-time-from-stamp
  489. (or (org-entry-get nil journal-described-property-name)
  490. (org-entry-get nil journal-created-property-name))
  491. t t))))
  492. (defun journal-link-search-part ()
  493. "Return search part for a journal link.
  494. Search part is:
  495. nil if point is in an entry heading;
  496. selected region if region is active;
  497. current word otherwise."
  498. (unless (journal-at-heading-p)
  499. (let ((text (if (use-region-p)
  500. (buffer-substring-no-properties
  501. (region-beginning) (region-end))
  502. (thing-at-point 'word))))
  503. (regexp-quote text))))
  504. (defun journal-link ()
  505. "Return journal link to the current location."
  506. (let ((id (journal-id))
  507. (search (journal-link-search-part)))
  508. (concat "journal:" id
  509. (and search (concat "::" search)))))
  510. (defun journal-store-link ()
  511. "Store journal link to the current entry."
  512. (when (journal-buffer-p)
  513. (org-store-link-props
  514. :type "journal"
  515. :link (journal-link)
  516. :description (journal-link-date))))
  517. (defun journal-find-link (id &optional regexp)
  518. "Visit journal file containing an entry with ID.
  519. If REGEXP is non-nil, search for it in the found entry."
  520. (let* ((find-list (org-id-find id))
  521. (file (car find-list))
  522. (pos (cdr find-list)))
  523. (org-mark-ring-push)
  524. (find-file file)
  525. (goto-char pos)
  526. (when regexp
  527. (let ((end-of-entry
  528. (save-excursion
  529. (end-of-line)
  530. (and (re-search-forward "^\\*\\{1,3\\} " nil t)
  531. (match-beginning 0))))
  532. foundp found-pos)
  533. (save-excursion
  534. (while (and (setq foundp (re-search-forward
  535. regexp end-of-entry t))
  536. (setq found-pos (match-beginning 0))
  537. ;; Ignore matches if they are placed inside
  538. ;; journal links. `org-in-regexp' modifies match
  539. ;; data so set FOUND-POS before calling it.
  540. (org-in-regexp journal-link-regexp))))
  541. (if foundp
  542. (goto-char found-pos)
  543. (message "Regexp '%s' not found." regexp))))))
  544. (defun journal-open-link (link)
  545. "Follow journal LINK."
  546. (let ((id link) search)
  547. (when (string-match "::\\(.+\\)\\'" link)
  548. (setq search (match-string 1 link)
  549. id (substring link 0 (match-beginning 0))))
  550. (journal-find-link id search)))
  551. (org-link-set-parameters
  552. "journal"
  553. :follow #'journal-open-link
  554. :store #'journal-store-link)
  555. (provide 'journal)
  556. ;;; journal.el ends here