Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1886 lines
76 KiB

  1. ;;; org-journal.el --- a simple org-mode based journaling mode -*- lexical-binding: t; -*-
  2. ;; Author: Bastian Bechtold
  3. ;; Christian Schwarzgruber
  4. ;; URL: http://github.com/bastibe/org-journal
  5. ;; Package-Version: 2.1.2
  6. ;; Package-Commit: c26e73a017963f6638044f1f63354c453f2db54a
  7. ;; Version: 2.1.2
  8. ;; Package-Requires: ((emacs "25.1") (org "9.1"))
  9. ;;; Commentary:
  10. ;; Adapted from http://www.emacswiki.org/PersonalDiary
  11. ;; Functions to maintain a simple personal diary / journal in Emacs.
  12. ;; Feel free to use, modify and improve the code! - mtvoid, bastibe
  13. ;; This file is also available from marmalade as
  14. ;; http://marmalade-repo.org/packages/journal. After installing, add
  15. ;; the line (require 'org-journal) to your .emacs or init.el to activate
  16. ;; it. You also need to specify the directory where your journal files
  17. ;; will be saved. You can do this by setting the variable journal-dir
  18. ;; (remember to add a trailing slash). journal-dir is also a
  19. ;; customizable variable. The default value for journal-dir is
  20. ;; ~/Documents/journal/.
  21. ;;
  22. ;; Inside the journal directory, a separate file is created for each
  23. ;; day with a journal entry, with a file name in the format YYYYMMDD
  24. ;; (this is customizable). Each journal entry is an org-mode file that
  25. ;; begins with a date entry on the top, followed by entries for a
  26. ;; different times. Any subsequent entries on the same day are written
  27. ;; in the same file, with their own timestamp. You can customize the
  28. ;; date and time formats (or remove them entirely). To start writing a
  29. ;; journal entry, press "C-c C-j". You can also open the current day's
  30. ;; entry without adding a new entry with "C-u C-c C-j".
  31. ;;
  32. ;; You can browse through existing journal entries on disk via the
  33. ;; calendar. All dates for which an entry is present are highlighted.
  34. ;; Pressing "j" will open it up for viewing. Pressing "C-j" will open
  35. ;; it for viewing, but not switch to it. Pressing "[" or "]" will
  36. ;; select the date with the previous or next journal entry,
  37. ;; respectively. Pressing "i j" will create a new entry for the chosen
  38. ;; date.
  39. ;;
  40. ;; TODO items from the previous day will carry over to the current
  41. ;; day. This is customizable through org-journal-carryover-items.
  42. ;;
  43. ;; Quick summary:
  44. ;; To create a new journal entry for the current time and day: C-c C-j
  45. ;; To open today's journal without creating a new entry: C-u C-c C-j
  46. ;; In calendar view: j m to mark entries in calendar
  47. ;; j r to view an entry in a new buffer
  48. ;; j d to view an entry but not switch to it
  49. ;; j n to add a new entry
  50. ;; j s w to search all entries of the current week
  51. ;; j s m to search all entries of the current month
  52. ;; j s y to search all entries of the current year
  53. ;; j s f to search all entries of all time
  54. ;; j s F to search all entries in the future
  55. ;; [ to go to previous entry
  56. ;; ] to go to next entry
  57. ;; When viewing a journal entry: C-c C-b to view previous entry
  58. ;; C-c C-f to view next entry
  59. ;;; Code:
  60. (require 'cal-iso)
  61. (require 'epa)
  62. (require 'org)
  63. (require 'org-crypt)
  64. (require 'seq)
  65. (require 'subr-x)
  66. ;; Silent byte-compiler
  67. (defvar view-exit-action)
  68. (declare-function org-collect-keywords "org")
  69. (when (version< org-version "9.2")
  70. (defalias 'org-set-tags-to 'org-set-tags))
  71. (unless (fboundp 'org--tag-add-to-alist)
  72. ;; This function can be removed once emacs-26 es required or de-facto standard.
  73. (defun org-tag-add-to-alist (alist1 alist2)
  74. "Append ALIST1 elements to ALIST2 if they are not there yet.
  75. From branch \"emacs-26\", added for compatibility.
  76. "
  77. (cond
  78. ((null alist2) alist1)
  79. ((null alist1) alist2)
  80. (t (let ((alist2-cars (mapcar (lambda (x) (car-safe x)) alist2))
  81. to-add)
  82. (dolist (i alist1)
  83. (unless (member (car-safe i) alist2-cars)
  84. (push i to-add)))
  85. (append to-add alist2)))))
  86. (defalias 'org--tag-add-to-alist 'org-tag-add-to-alist))
  87. ;;; Customizable variables
  88. (defgroup org-journal nil
  89. "Settings for the personal journal"
  90. :group 'org
  91. :group 'org-journal)
  92. (defface org-journal-highlight
  93. '((t (:foreground "#ff1493")))
  94. "Face for highlighting org-journal buffers.")
  95. (defun org-journal-highlight (str)
  96. "Highlight STR in current-buffer"
  97. (goto-char (point-min))
  98. (while (search-forward str nil t)
  99. (put-text-property (match-beginning 0) (match-end 0) 'font-lock-face 'org-journal-highlight)))
  100. (defface org-journal-calendar-entry-face
  101. '((t (:foreground "#aa0000" :slant italic)))
  102. "Face for highlighting org-journal entries in M-x calendar.")
  103. (defface org-journal-calendar-scheduled-face
  104. '((t (:foreground "#600000" :slant italic)))
  105. "Face for highlighting future org-journal entries in M-x calendar.")
  106. (defcustom org-journal-file-type 'daily
  107. "What type of journal file to create.
  108. When switching from daily to weekly, monthly, yearly, or from weekly,
  109. monthly, yearly to daily, you need to invalidate the cache. This has currently
  110. to be done manually by calling `org-journal-invalidate-cache'."
  111. :type '(choice
  112. (const :tag "Daily" daily)
  113. (const :tag "Weekly" weekly)
  114. (const :tag "Monthly" monthly)
  115. (const :tag "Yearly" yearly)))
  116. (defcustom org-journal-start-on-weekday 1
  117. "When `org-journal-file-type' is set to 'weekly, start the week on this day.
  118. 1 for Monday, ..., and 7 for Sunday."
  119. :type '(choice
  120. (const :tag "Monday" 1)
  121. (const :tag "Tuesday" 2)
  122. (const :tag "Wednesday" 3)
  123. (const :tag "Thursday" 4)
  124. (const :tag "Friday" 5)
  125. (const :tag "Saturday" 6)
  126. (const :tag "Sunday" 7)))
  127. (defcustom org-journal-dir "~/Documents/journal/"
  128. "Directory containing journal entries."
  129. :type 'directory
  130. :risky t)
  131. (defcustom org-journal-file-format "%Y%m%d"
  132. "Format string for journal file names (Default \"YYYYMMDD\").
  133. This pattern MUST include `%Y', `%m' and `%d' when `org-journal-file-type' is
  134. `daily' or `weekly'. When `org-journal-file-type' is `monthly' this pattern
  135. MUST at least include `%Y' and `%m', and at least `%Y' when
  136. `org-journalf-file-type' is `yearly'.
  137. Currently supported placeholders are:
  138. %Y is the year as decimal number, including the century.
  139. %m is the month as a decimal number (range 01 to 12).
  140. %d is the day as a decimal number (range 01 to 31).
  141. %V is the ISO 8601 week number as a decimal number (range 01 to 53).
  142. %a is the locales abbreviated name of the day of week, %A the full name.
  143. %b is the locale's abbreviated name of the month, %B the full name.
  144. %F is the ISO 8601 date format (equivalent to \"%Y-%m-%d\")."
  145. :type 'string)
  146. (defcustom org-journal-date-format "%A, %x"
  147. "Format string for date entries.
  148. By default \"WEEKDAY, DATE\", where DATE is what Emacs thinks is an
  149. appropriate way to format days in your language.
  150. If the value is a function, the function will be evaluated and the return
  151. value will be inserted."
  152. :type '(choice
  153. (string :tag "String")
  154. (function :tag "Function")))
  155. (defcustom org-journal-search-result-date-format "%A, %x"
  156. "Date format string for search result.
  157. By default \"WEEKDAY, DATE\", where DATE is what Emacs thinks is an
  158. appropriate way to format days in your language."
  159. :type 'string)
  160. (defcustom org-journal-date-prefix "* "
  161. "Prefix for `org-journal-date-format'.
  162. The default prefix creates an `org-mode' heading. This default
  163. should not be changed for weekly, monthly or yearly journal
  164. files. An alternative for daily journal files could be
  165. \"#+title: \" creating a title rather than a heading. To create
  166. a \"#+title: \" for weekly, monthly or yearly (but also daily)
  167. journal files, customize `org-journal-file-header' instead."
  168. :type 'string)
  169. (defcustom org-journal-time-format "%R "
  170. "Format string for time entries.
  171. By default HH:MM. Set it to a blank string if you want to disable timestamps."
  172. :type 'string)
  173. (defcustom org-journal-time-format-post-midnight ""
  174. "When non-blank, a separate time format string for after midnight.
  175. When the current time is before the hour set by `org-extend-today-until'."
  176. :type 'string)
  177. (defcustom org-journal-time-prefix "** "
  178. "String that is put before every time entry in a journal file.
  179. By default, this is an org-mode sub-heading."
  180. :type 'string)
  181. (defcustom org-journal-hide-entries-p t
  182. "If true all but the current entry will be hidden when creating a new one."
  183. :type 'boolean)
  184. (defcustom org-journal-enable-encryption nil
  185. "Add `org-crypt-tag-matcher' tag for encrypted entries when non-nil.
  186. Whenever a user saves/opens these journal entries, Emacs asks a user
  187. passphrase to encrypt/decrypt it."
  188. :type 'boolean)
  189. (defcustom org-journal-encrypt-journal nil
  190. "If non-nil, encrypt journal files using gpg.
  191. The journal files will have the file extension \".gpg\"."
  192. :type 'boolean)
  193. (defcustom org-journal-encrypt-on 'before-save-hook
  194. "Hook on which to encrypt entries.
  195. It can be set to other hooks like `kill-buffer-hook'."
  196. :type 'function)
  197. (defcustom org-journal-enable-agenda-integration nil
  198. "Add current and future org-journal files to `org-agenda-files' when non-nil."
  199. :type 'boolean)
  200. (defcustom org-journal-find-file 'find-file-other-window
  201. "The function to use when opening an entry.
  202. Set this to `find-file' if you don't want org-journal to split your window."
  203. :type 'function)
  204. (defcustom org-journal-carryover-items "TODO=\"TODO\""
  205. "Carry over items that match these criteria.
  206. See agenda tags view match description for the format of this."
  207. :type 'string)
  208. (defcustom org-journal-skip-carryover-drawers nil
  209. "By default, we carry over all the drawers associated with the items.
  210. This option can be used to skip certain drawers being carried over.
  211. The drawers listed here will be wiped completely, when the item gets carried
  212. over."
  213. :type 'list)
  214. (defcustom org-journal-handle-old-carryover 'org-journal-delete-old-carryover
  215. "The function to handle the carryover entries in the previous journal.
  216. This function takes one argument, which is a list of the carryover entries
  217. in the journal of previous day.
  218. The list is in form of ((START_POINT (END_POINT . \"TEXT\")) ...);
  219. and in ascending order of START_POINT."
  220. :type 'function)
  221. (defcustom org-journal-carryover-delete-empty-journal 'never
  222. "Delete empty journal entry/file after carryover.
  223. Default is to `never' delete an empty journal entry/file. Other options
  224. are `always', i.e. don't prompt, just delete or `ask'"
  225. :type '(choice
  226. (const :tag "never" never)
  227. (const :tag "always" always)
  228. (const :tag "ask" ask)))
  229. (defcustom org-journal-search-results-order-by :asc
  230. "Journal entry search order.
  231. Search gets sorted by date either ascending :asc, or descending :desc."
  232. :type 'symbol)
  233. (defcustom org-journal-tag-alist nil
  234. "Default tags for use in Org-Journal mode.
  235. This is analogous to `org-tag-alist', and uses the same format.
  236. If nil, then `org-tag-alist' is used instead.
  237. This can also be overridden on a file-local level by using a #+TAGS: keyword."
  238. :type (get 'org-tag-alist 'custom-type))
  239. (defcustom org-journal-tag-persistent-alist nil
  240. "Persistent tags for use in Org-Journal mode.
  241. This is analogous to `org-tag-persistent-alist', and uses the same
  242. format. If nil, the default, then `org-tag-persistent-alist' is used
  243. instead. These tags cannot be overridden with a #+TAGS: keyword, but
  244. they can be disabled per-file by adding the line #+STARTUP: noptag
  245. anywhere in your file."
  246. :type (get 'org-tag-persistent-alist 'custom-type))
  247. (defcustom org-journal-search-forward-fn 'search-forward
  248. "The function used by `org-journal-search`.
  249. Other possible value is e.g. `re-search-forward'."
  250. :type 'function)
  251. (defcustom org-journal-follow-mode nil
  252. "If `t', follow journal entry in calendar."
  253. :type 'boolean)
  254. (defcustom org-journal-enable-cache nil
  255. "If `t', journal entry dates will be cached for faster calendar operations."
  256. :type 'boolean)
  257. (defcustom org-journal-file-header ""
  258. "A string which should be inserted at the top of a new journal file.
  259. The string will be passed to `format-time-string' along with the time
  260. of the new journal entry.
  261. The value can also be a function expecting a time value."
  262. :type '(choice
  263. (string :tag "String")
  264. (function :tag "Function")))
  265. (defcustom org-journal-created-property-timestamp-format "%Y%m%d"
  266. "The created property timestamp format-string.
  267. We must be able to reconstruct the timestamp from year,
  268. month and day.
  269. Currently supported placeholders are:
  270. %Y is the year as decimal number, including the century.
  271. %m is the month as a decimal number (range 01 to 12).
  272. %d is the day as a decimal number (range 01 to 31).
  273. %V is the ISO 8601 week number as a decimal number (range 01 to 53).
  274. %a is the locales abbreviated name of the day of week, %A the full name.
  275. %b is the locale's abbreviated name of the month, %B the full name.
  276. %F is the ISO 8601 date format (equivalent to \"%Y-%m-%d\").
  277. You must call `org-journal-convert-created-property-timestamps' afterwards,
  278. if you have existing journal entries."
  279. :type 'string)
  280. (defcustom org-journal-prefix-key "C-c C-"
  281. "The default prefix key inside `org-journal-mode'.
  282. This variable needs to set before `org-journal' gets loaded.
  283. When this variable is set to an empty string or `nil' no bindings will
  284. be made.
  285. This prefix key is used for:
  286. - `org-journal-next-entry' (key \"f\")
  287. - `org-journal-previous-entry' (key \"b\")
  288. - `org-journal-new-entry' (key \"j\")
  289. - `org-journal-search' (key \"s\")"
  290. :type 'string)
  291. (defvar org-journal-after-entry-create-hook nil
  292. "Hook called after journal entry creation.")
  293. (defvar org-journal-after-header-create-hook nil
  294. "Hook called after journal header creation.
  295. The header is the string described by `org-journal-date-format'.
  296. This runs once per date, before `org-journal-after-entry-create-hook'.")
  297. (defvar org-journal--search-buffer "*Org-journal search*")
  298. ;;;###autoload
  299. (add-hook 'calendar-today-visible-hook 'org-journal-mark-entries)
  300. ;;;###autoload
  301. (add-hook 'calendar-today-invisible-hook 'org-journal-mark-entries)
  302. ;; Journal mode definition
  303. ;;;###autoload
  304. (define-derived-mode org-journal-mode org-mode
  305. "Journal"
  306. "Mode for writing or viewing entries written in the journal."
  307. (turn-on-visual-line-mode)
  308. (add-hook 'after-save-hook 'org-journal-after-save-hook nil t)
  309. (when (or org-journal-tag-alist org-journal-tag-persistent-alist)
  310. (org-journal--set-current-tag-alist))
  311. (run-mode-hooks))
  312. ;;;###autoload
  313. (define-obsolete-function-alias 'org-journal-open-next-entry 'org-journal-next-entry "2.1.0")
  314. ;;;###autoload
  315. (define-obsolete-function-alias 'org-journal-open-previous-entry 'org-journal-previous-entry "2.1.0")
  316. ;; Key bindings
  317. (when (and (stringp org-journal-prefix-key) (not (string-empty-p org-journal-prefix-key)))
  318. (let ((command-table '(("f" . org-journal-next-entry)
  319. ("b" . org-journal-previous-entry)
  320. ("j" . org-journal-new-entry)
  321. ("s" . org-journal-search)))
  322. (key-func (if (string-prefix-p "\\" org-journal-prefix-key)
  323. #'concat
  324. (lambda (prefix key) (kbd (concat prefix "" key))))))
  325. (cl-loop for (key . command) in command-table
  326. do (define-key org-journal-mode-map (funcall key-func org-journal-prefix-key key) command))))
  327. (eval-after-load "calendar"
  328. '(progn
  329. (define-key calendar-mode-map (kbd "j m") 'org-journal-mark-entries)
  330. (define-key calendar-mode-map (kbd "j r") 'org-journal-read-entry)
  331. (define-key calendar-mode-map (kbd "j d") 'org-journal-display-entry)
  332. (define-key calendar-mode-map "]" 'org-journal-next-entry)
  333. (define-key calendar-mode-map "[" 'org-journal-previous-entry)
  334. (define-key calendar-mode-map (kbd "j n") 'org-journal-new-date-entry)
  335. (define-key calendar-mode-map (kbd "j s f") 'org-journal-search-forever)
  336. (define-key calendar-mode-map (kbd "j s F") 'org-journal-search-future)
  337. (define-key calendar-mode-map (kbd "j s w") 'org-journal-search-calendar-week)
  338. (define-key calendar-mode-map (kbd "j s m") 'org-journal-search-calendar-month)
  339. (define-key calendar-mode-map (kbd "j s y") 'org-journal-search-calendar-year)))
  340. (global-set-key (kbd "C-c C-j") 'org-journal-new-entry)
  341. (defmacro org-journal--with-journal (file &rest body)
  342. "Opens JOURNAL-FILE in fundamental mode, or switches to the buffer which is visiting JOURNAL-FILE.
  343. Returns the last value from BODY. If the buffer didn't exist before it will be deposed."
  344. ;; Use find-file... instead of view-file... since
  345. ;; view-file does not respect auto-mode-alist
  346. (declare (indent 1))
  347. `(let* ((buffer-exists (get-buffer (file-name-nondirectory ,file)))
  348. (buf (if buffer-exists buffer-exists
  349. (generate-new-buffer (file-name-nondirectory ,file))))
  350. result)
  351. (with-current-buffer buf
  352. (unless buffer-exists
  353. (insert-file-contents ,file))
  354. (setq result (progn ,@body)))
  355. (unless buffer-exists
  356. (kill-buffer buf))
  357. result))
  358. (def-edebug-spec org-journal--with-journal (form body))
  359. (defun org-journal-after-save-hook ()
  360. "Update agenda files and dates."
  361. (org-journal--update-org-agenda-files)
  362. (org-journal--dates-puthash)
  363. (org-journal--serialize))
  364. (defun org-journal-is-journal ()
  365. "Determine if file is a journal file."
  366. (and (buffer-file-name)
  367. (string-match (org-journal--dir-and-file-format->pattern) (buffer-file-name))))
  368. ;; Open files in `org-journal-mode' if `org-journal-is-journal' returns true.
  369. (add-to-list 'magic-mode-alist '(org-journal-is-journal . org-journal-mode))
  370. (defun org-journal--dir-and-file-format->pattern ()
  371. "Return the current journal file pattern"
  372. (concat (file-name-as-directory (file-truename org-journal-dir))
  373. (org-journal--format->regex org-journal-file-format)
  374. "\\(\\.gpg\\)?\\'"))
  375. (defvar org-journal--format-rx-alist
  376. '(("%[aAbB]" . "\\\\(?4:[a-zA-Z]\\\\{3,\\\\}\\\\)")
  377. ("%d" . "\\\\(?3:[0-9]\\\\{2\\\\}\\\\)")
  378. ("%m" . "\\\\(?2:[0-9]\\\\{2\\\\}\\\\)")
  379. ("%Y" . "\\\\(?1:[0-9]\\\\{4\\\\}\\\\)")
  380. ("%V" . "[0-9]\\\\{2\\\\}")))
  381. (defun org-journal--format->regex (format)
  382. (setq format (replace-regexp-in-string "%F" "%Y-%m-%d" format))
  383. (cl-loop
  384. initially (setq format (regexp-quote format))
  385. for x in org-journal--format-rx-alist
  386. do (setq format (replace-regexp-in-string (car x) (cdr x) format))
  387. finally return format))
  388. (defvar org-journal--created-re "^ *:CREATED: +.*$" "Regex to find created property.")
  389. (defun org-journal--search-forward-created (date &optional bound noerror count)
  390. "Search for CREATED tag with date."
  391. (re-search-forward
  392. (format-time-string
  393. (concat "[ \t]*:CREATED:[ \t]+"
  394. (regexp-quote org-journal-created-property-timestamp-format)
  395. "[ \t]*$")
  396. (org-journal--calendar-date->time date))
  397. bound noerror count))
  398. (defsubst org-journal--daily-p ()
  399. "Returns t if `org-journal-file-type' is set to `'daily'."
  400. (eq org-journal-file-type 'daily))
  401. (defun org-journal--is-date-prefix-org-heading-p ()
  402. "Returns t if `org-journal-date-prefix' starts with \"* \"."
  403. (eq 0 (string-match "^\* " org-journal-date-prefix)))
  404. ;;;###autoload
  405. (defun org-journal-convert-created-property-timestamps (old-format)
  406. "Convert CREATED property timestamps to `org-journal-created-property-timestamp-format'."
  407. (interactive "sEnter old format: ")
  408. (if (org-journal--daily-p)
  409. (message "Nothing to do, org-journal-file-type is 'daily")
  410. (dolist (file (org-journal--list-files))
  411. (let* ((inhibit-read-only)
  412. (buffer (get-buffer (file-name-nondirectory file)))
  413. (buffer-modefied (when buffer (buffer-modified-p buffer))))
  414. (with-current-buffer (if buffer buffer (find-file-noselect file))
  415. (goto-char (point-min))
  416. (ignore-errors
  417. (dolist (date (reverse (let ((org-journal-created-property-timestamp-format old-format))
  418. (org-journal--file->calendar-dates file))))
  419. (unless (let ((org-journal-created-property-timestamp-format old-format))
  420. (org-journal--search-forward-created date nil t))
  421. (error "Didn't find journal entry in file (%s), date was (%s) " file date))
  422. (org-set-property "CREATED" (format-time-string
  423. org-journal-created-property-timestamp-format
  424. (org-journal--calendar-date->time date)))))
  425. (unless buffer-modefied (save-buffer))
  426. (unless buffer (kill-buffer)))))))
  427. (defun org-journal--convert-time-to-file-type-time (&optional time)
  428. "Converts TIME to the file type format date.
  429. If `org-journal-file-type' is 'weekly, the TIME will be rounded to
  430. the first date of the week.
  431. If `org-journal-file-type' is 'monthly, the TIME will be rounded to
  432. the first date of the month.
  433. If `org-journal-file-type' is 'yearly, the TIME will be rounded to
  434. the first date of the year."
  435. (or time (setq time (current-time)))
  436. (pcase org-journal-file-type
  437. ;; Do nothing for daily
  438. (`daily time)
  439. ;; Round to the monday of the current week, e.g. 20181231 is the first week of 2019
  440. (`weekly
  441. (let* ((absolute-monday
  442. (calendar-iso-to-absolute
  443. (mapcar 'string-to-number
  444. (split-string (format-time-string "%V 1 %G" time) " "))))
  445. (absolute-now
  446. (calendar-absolute-from-gregorian
  447. (mapcar 'string-to-number
  448. (split-string (format-time-string "%m %d %Y" time) " "))))
  449. (target-date
  450. (+ absolute-monday
  451. (- org-journal-start-on-weekday 1)))
  452. (date
  453. (calendar-gregorian-from-absolute
  454. (if (> target-date absolute-now)
  455. (- target-date 7)
  456. target-date))))
  457. (org-journal--calendar-date->time date)))
  458. ;; Round to the first day of the month, e.g. 20190301
  459. (`monthly
  460. (org-journal--calendar-date->time
  461. (mapcar 'string-to-number (split-string (format-time-string "%m 1 %Y" time) " "))))
  462. ;; Round to the first day of the year, e.g. 20190101
  463. (`yearly
  464. (org-journal--calendar-date->time
  465. (mapcar 'string-to-number (split-string (format-time-string "1 1 %Y" time) " "))))))
  466. (defun org-journal--get-entry-path (&optional time)
  467. "Return the path to an entry matching TIME, if no TIME is given, uses the current time."
  468. (let ((file (file-truename
  469. (expand-file-name
  470. (format-time-string org-journal-file-format
  471. (org-journal--convert-time-to-file-type-time time))
  472. org-journal-dir))))
  473. (when (and org-journal-encrypt-journal (not (file-exists-p file)))
  474. (setq file (concat file ".gpg")))
  475. file))
  476. (defun org-journal--create-journal-dir ()
  477. "Create the `org-journal-dir'."
  478. (unless (file-exists-p org-journal-dir)
  479. (if (yes-or-no-p (format
  480. "Journal directory %s doesn't exists. Create it? "
  481. (file-truename org-journal-dir)))
  482. (make-directory (file-truename org-journal-dir) t)
  483. (user-error "A journal directory is necessary to use org-journal"))))
  484. (defun org-journal--sanity-checks ()
  485. "Do some sanity checks."
  486. (unless (symbolp org-journal-file-type)
  487. (user-error
  488. "The value of `org-journal-file-type' must be symbol, not a %s"
  489. (type-of org-journal-file-type))))
  490. (defun org-journal--set-current-tag-alist ()
  491. "Set `org-current-tag-alist' for the current journal file.
  492. This allows the use of `org-journal-tag-alist' and
  493. `org-journal-tag-persistent-alist', which when non-nil override
  494. `org-tag-alist' and `org-journal-tag-persistent-alist' respectively."
  495. (setq org-current-tag-alist ; this var is always buffer-local
  496. (org--tag-add-to-alist
  497. (or org-journal-tag-persistent-alist org-tag-persistent-alist)
  498. ;; TODO: Remove this once org 9.3.7 is required
  499. ;; `org--setup-collect-keywords' was removed between version 9.3.6 and 9.3.7,
  500. ;; and is now called `org-collect-keywords', which has a different signature.
  501. (let* ((alist (if (fboundp 'org--setup-collect-keywords)
  502. (org--setup-collect-keywords
  503. (org-make-options-regexp
  504. '("FILETAGS" "TAGS" "SETUPFILE")))
  505. (org-collect-keywords '("FILETAGS" "TAGS"))))
  506. (tags (cdr (assq 'tags alist))))
  507. (if (and alist tags)
  508. (org-tag-string-to-alist tags)
  509. (or org-journal-tag-alist org-tag-alist))))))
  510. (defun org-journal--calendar-date-compare (date1 date2)
  511. "Return t if DATE1 is before DATE2, nil otherwise."
  512. (< (calendar-absolute-from-gregorian date1)
  513. (calendar-absolute-from-gregorian date2)))
  514. (defun org-journal--insert-header (time)
  515. "Insert `org-journal-file-header'."
  516. (when (and (or (functionp org-journal-file-header)
  517. (and (stringp org-journal-file-header)
  518. (not (string-empty-p org-journal-file-header))))
  519. (= (buffer-size) 0))
  520. (insert (if (functionp org-journal-file-header)
  521. (funcall org-journal-file-header time)
  522. (format-time-string org-journal-file-header time)))
  523. (save-excursion
  524. (when (re-search-backward "^#\\+" nil t)
  525. (org-ctrl-c-ctrl-c)))))
  526. (defun org-journal--insert-entry-header (time)
  527. "Create new journal entry if there isn't one."
  528. (let ((entry-header
  529. (if (functionp org-journal-date-format)
  530. (funcall org-journal-date-format time)
  531. (when (string-empty-p org-journal-date-format)
  532. (user-error "org-journal-date-format is empty, this won't work"))
  533. (concat org-journal-date-prefix
  534. (format-time-string org-journal-date-format time)))))
  535. (goto-char (point-min))
  536. (unless (if (org-journal--daily-p)
  537. (or (search-forward entry-header nil t) (and (goto-char (point-max)) nil))
  538. (cl-loop
  539. with date = (decode-time time)
  540. with file-dates = (sort (org-journal--file->calendar-dates (buffer-file-name))
  541. (lambda (a b)
  542. (org-journal--calendar-date-compare b a)))
  543. with entry
  544. initially (setq date (list (nth 4 date) (nth 3 date) (nth 5 date)))
  545. unless file-dates ;; New entry at bof
  546. do
  547. (unless (re-search-forward (concat "^\\(" org-outline-regexp "\\)") nil t)
  548. (goto-char (point-max)))
  549. (if (org-at-heading-p)
  550. (progn
  551. (beginning-of-line)
  552. (insert "\n")
  553. (forward-line -1))
  554. (forward-line -1)
  555. (end-of-line))
  556. and return nil
  557. while file-dates
  558. do
  559. (setq entry (car file-dates)
  560. file-dates (cdr file-dates))
  561. if (or (org-journal--calendar-date-compare entry date) (equal entry date))
  562. do
  563. (org-journal--search-forward-created entry)
  564. (when (org-journal--calendar-date-compare entry date) ;; New entry at eof, or somewhere in-between
  565. (org-end-of-subtree))
  566. and return (equal entry date))) ;; If an entry exists don't create a header
  567. (when (looking-back "[^\t ]" (point-at-bol))
  568. (insert "\n"))
  569. (insert entry-header)
  570. ;; Create CREATED property for weekly, monthly, and yearly journal entries
  571. (unless (org-journal--daily-p)
  572. (org-set-property "CREATED"
  573. (format-time-string
  574. org-journal-created-property-timestamp-format time)))
  575. (when org-journal-enable-encryption
  576. (unless (member org-crypt-tag-matcher (org-get-tags))
  577. (org-set-tags org-crypt-tag-matcher)))
  578. (run-hooks 'org-journal-after-header-create-hook))))
  579. (defun org-journal--insert-entry (time org-extend-today-until-active-p)
  580. "Insert a new entry."
  581. (unless (eq (current-column) 0) (insert "\n"))
  582. (let* ((day-discrepancy (- (time-to-days (current-time)) (time-to-days time)))
  583. (timestamp (cond
  584. ;; “time” is today, use normal timestamp format
  585. ((= day-discrepancy 0)
  586. (format-time-string org-journal-time-format))
  587. ;; “time” is yesterday with org-extend-today-until,
  588. ;; use different timestamp format if available
  589. ((and (= day-discrepancy 1) org-extend-today-until-active-p)
  590. (if (not (string-equal org-journal-time-format-post-midnight ""))
  591. (format-time-string org-journal-time-format-post-midnight)
  592. (format-time-string org-journal-time-format)))
  593. ;; “time” is on some other day, use blank timestamp
  594. (t ""))))
  595. (insert org-journal-time-prefix timestamp))
  596. (run-hooks 'org-journal-after-entry-create-hook))
  597. ;;;###autoload
  598. (defun org-journal-new-entry (prefix &optional time)
  599. "Open today's journal file and start a new entry.
  600. With a PREFIX arg, open the today's file, create a heading if it doesn't exist yet,
  601. but do not create a new entry.
  602. If given a TIME, create an entry for the time's day. If no TIME was given,
  603. use the current time (which is interpreted as belonging to yesterday if
  604. smaller than `org-extend-today-until`).
  605. Whenever a journal entry is created the `org-journal-after-entry-create-hook'
  606. hook is run."
  607. (interactive "P")
  608. (org-journal--sanity-checks)
  609. (org-journal--create-journal-dir)
  610. ;; If time is before org-extend-today-until, interpret it as
  611. ;; part of the previous day:
  612. (let* ((now (decode-time nil))
  613. (org-extend-today-until-active-p (and (not time) (< (nth 2 now) org-extend-today-until)))
  614. (entry-path (org-journal--get-entry-path time))
  615. (should-add-entry-p (not prefix)))
  616. (when org-extend-today-until-active-p
  617. (setq time (encode-time (nth 0 now)
  618. (nth 1 now)
  619. (nth 2 now)
  620. (1- (nth 3 now))
  621. (nth 4 now)
  622. (nth 5 now)
  623. (nth 8 now))))
  624. ;; Open journal file
  625. (unless (string= entry-path (buffer-file-name))
  626. (funcall org-journal-find-file entry-path))
  627. ;; Insure `view-mode' is not active
  628. (view-mode -1)
  629. (org-journal--insert-header time)
  630. (org-journal--insert-entry-header time)
  631. (org-journal--decrypt)
  632. ;; Move TODOs from previous day to new entry
  633. (when (and org-journal-carryover-items
  634. (not (string-blank-p org-journal-carryover-items))
  635. (string= entry-path (org-journal--get-entry-path (current-time))))
  636. (org-journal--carryover))
  637. (if (org-journal--is-date-prefix-org-heading-p)
  638. (outline-end-of-subtree)
  639. (goto-char (point-max)))
  640. (when should-add-entry-p
  641. (org-journal--insert-entry time org-extend-today-until-active-p))
  642. (if (and org-journal-hide-entries-p (org-journal--time-entry-level))
  643. (outline-hide-sublevels (org-journal--time-entry-level))
  644. (save-excursion (org-journal--finalize-view)))
  645. (when should-add-entry-p
  646. (outline-show-entry))))
  647. (defvar org-journal--kill-buffer nil
  648. "Will be set to the `t' if `org-journal--open-entry' is visiting a
  649. buffer not open already, otherwise `nil'.")
  650. (defun org-journal--empty-journal-p (prev-buffer)
  651. (let (entry)
  652. (with-current-buffer prev-buffer (save-buffer))
  653. (save-excursion
  654. (org-journal--open-entry t t)
  655. (setq entry (if (org-journal--is-date-prefix-org-heading-p)
  656. (org-get-entry)
  657. (buffer-substring-no-properties (point) (point-max)))))
  658. (with-temp-buffer
  659. (insert entry)
  660. (goto-char (point-min))
  661. (let (start end)
  662. ;; Delete scheduled timestamps
  663. (while (re-search-forward (concat " *\\(CLOSED\\|DEADLINE\\|SCHEDULED\\): *" org-ts-regexp-both) nil t)
  664. (kill-region (match-beginning 0) (match-end 0)))
  665. ;; Delete drawers
  666. (while (re-search-forward org-drawer-regexp nil t)
  667. (setq start (match-beginning 0))
  668. (re-search-forward org-drawer-regexp nil t)
  669. (setq end (match-end 0))
  670. (kill-region start end)))
  671. (string-empty-p (org-trim (buffer-string))))))
  672. (defun org-journal--remove-drawer ()
  673. "Removes the drawer configured via `org-journal-skip-carryover-drawers'"
  674. (save-excursion
  675. (save-restriction
  676. (unless (org-journal--daily-p)
  677. (org-narrow-to-subtree))
  678. (goto-char (point-min))
  679. (mapc 'delete-matching-lines (mapcar
  680. (lambda (x)
  681. (format ".*%s:[\\n[:ascii:]]+?:END:$" x))
  682. org-journal-skip-carryover-drawers)))))
  683. (defun org-journal--carryover-delete-empty-journal (prev-buffer)
  684. "Check if the previous entry/file is empty after we carried over the
  685. items, and delete or not delete the empty entry/file based on
  686. `org-journal-carryover-delete-empty-journal'."
  687. (when (and (org-journal--empty-journal-p prev-buffer)
  688. (or (and (eq org-journal-carryover-delete-empty-journal 'ask)
  689. (y-or-n-p "Delete empty journal entry/file?"))
  690. (eq org-journal-carryover-delete-empty-journal 'always)))
  691. (let ((inhibit-message t))
  692. ;; Check if the file doesn't contain any other entry, by comparing the
  693. ;; new filename with the previous entry filename and the next entry filename.
  694. (if (and (save-excursion
  695. (org-journal--open-entry t t)
  696. (or (not (org-journal--open-entry t t))
  697. (not (eq (current-buffer) prev-buffer))))
  698. (not (eq (current-buffer) prev-buffer)))
  699. (progn
  700. (delete-file (buffer-file-name prev-buffer))
  701. (kill-buffer prev-buffer)
  702. (org-journal--list-dates))
  703. (save-excursion
  704. (org-journal--open-entry t t)
  705. (kill-region (point) (progn (outline-end-of-subtree) (point)))
  706. (save-buffer))))))
  707. (defun org-journal-delete-old-carryover (old_entries)
  708. "Delete all carryover entries from the previous day's journal.
  709. If the parent heading has no more content, delete it as well."
  710. (mapc (lambda (x)
  711. (unless (save-excursion
  712. (goto-char (1- (cadr x)))
  713. (org-goto-first-child))
  714. (kill-region (car x) (cadr x))))
  715. (reverse old_entries)))
  716. (defun org-journal-carryover-items (text entries prev-buffer)
  717. "Carryover items.
  718. Will insert `entries', and run `org-journal-handle-old-carryover' function
  719. to process the carryover entries in `prev-buffer'."
  720. (when entries
  721. (if (org-journal--is-date-prefix-org-heading-p)
  722. (progn
  723. (while (org-up-heading-safe))
  724. (outline-end-of-subtree))
  725. (goto-char (point-max)))
  726. ;; Insure `view-mode' is not active
  727. (view-mode -1)
  728. (unless (eq (current-column) 0) (insert "\n"))
  729. (insert text)
  730. (save-excursion
  731. (if (org-journal--daily-p)
  732. (goto-char (point-min))
  733. (while (org-up-heading-safe)))
  734. (unless (null org-journal-skip-carryover-drawers)
  735. (org-journal--remove-drawer))
  736. (save-excursion
  737. (while (re-search-forward "<\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\( [a-z]+\\)?\\)>" nil t)
  738. (unless (save-excursion
  739. (goto-char (point-at-bol))
  740. (re-search-forward "\\<\\(SCHEDULED\\|DEADLINE\\):" (point-at-eol) t))
  741. (replace-match
  742. (format-time-string "%Y-%m-%d %a"
  743. (org-journal--calendar-date->time
  744. (save-match-data
  745. (if (org-journal--daily-p)
  746. (org-journal--file-name->calendar-date (buffer-file-name))
  747. (save-excursion
  748. (while (org-up-heading-safe))
  749. (org-journal--entry-date->calendar-date))))))
  750. nil nil nil 1)))))
  751. (outline-end-of-subtree)
  752. ;; Process carryover entries in the previous day's journal
  753. (with-current-buffer prev-buffer
  754. (funcall org-journal-handle-old-carryover entries))))
  755. (defun org-journal--carryover ()
  756. "Moves all items matching `org-journal-carryover-items' from the
  757. previous day's file to the current file."
  758. (interactive)
  759. (let* ((org-journal-find-file 'find-file)
  760. (mapper (lambda ()
  761. (let ((headings (org-journal--carryover-item-with-parents)))
  762. ;; Since the next subtree now starts at point,
  763. ;; continue mapping from before that, to include it
  764. ;; in the search
  765. (setq org-map-continue-from (point))
  766. headings)))
  767. carryover-paths prev-buffer)
  768. ;; Get carryover paths
  769. (save-excursion
  770. (save-restriction
  771. (when (org-journal--open-entry t t)
  772. (setq prev-buffer (current-buffer))
  773. (unless (org-journal--daily-p)
  774. (org-narrow-to-subtree))
  775. (setq carryover-paths (org-map-entries mapper org-journal-carryover-items)))))
  776. (when (and prev-buffer carryover-paths)
  777. (let (cleared-carryover-paths text)
  778. ;; Construct the text to carryover, and remove any duplicate elements from carryover-paths
  779. (cl-loop
  780. for paths in carryover-paths
  781. with prev-paths
  782. do (cl-loop
  783. for path in paths
  784. with cleared-paths
  785. count t into counter
  786. do (when (or (not (and prev-paths (nth counter prev-paths)))
  787. (> (car path) (car (nth counter prev-paths))))
  788. (setq text (concat text (cddr path)))
  789. (if cleared-paths
  790. (setcdr (last cleared-paths) (list path))
  791. (setq cleared-paths (list path))))
  792. finally (if cleared-carryover-paths
  793. (setcdr (last cleared-carryover-paths) cleared-paths)
  794. (setq cleared-carryover-paths cleared-paths))
  795. (setq prev-paths paths)))
  796. (org-journal-carryover-items text cleared-carryover-paths prev-buffer))
  797. (org-journal--carryover-delete-empty-journal prev-buffer))
  798. (when org-journal--kill-buffer
  799. (mapc 'kill-buffer org-journal--kill-buffer)
  800. (setq org-journal--kill-buffer nil))))
  801. (defun org-journal--carryover-item-with-parents ()
  802. "Return carryover item inclusive the parents.
  803. The parents ... The carryover item
  804. ;; ((START END . \"TEXT\") ... (START END . \"TEXT\"))
  805. "
  806. (let (start end text carryover-item-with-parents)
  807. (save-excursion
  808. (while (> (org-outline-level) (org-journal--time-entry-level))
  809. (org-up-heading-safe)
  810. (setq start (point)
  811. end (save-excursion (outline-next-heading) (point))
  812. text (buffer-substring-no-properties start end))
  813. (push (cons start (cons end text)) carryover-item-with-parents)))
  814. (setq start (point-at-bol)
  815. end (progn (outline-end-of-subtree) (outline-next-heading) (point))
  816. text (buffer-substring-no-properties start end))
  817. (setq carryover-item-with-parents (append carryover-item-with-parents (list (cons start (cons end text)))))))
  818. (defun org-journal--time-entry-level ()
  819. "Return the headline level of time entries based on the number
  820. of leading asterisks in `org-journal-time-prefix'.
  821. Return nil when it's impossible to figure out the level."
  822. (when (string-match "\\(^\*+\\)" org-journal-time-prefix)
  823. (length (match-string 1 org-journal-time-prefix))))
  824. (defun org-journal--calendar-date->time (date)
  825. "Convert a date as returned from the calendar (MONTH DAY YEAR) to a time."
  826. (encode-time 0 0 0 (nth 1 date) (nth 0 date) (nth 2 date)))
  827. (defun org-journal--file-name->calendar-date (file-name)
  828. "Convert an org-journal file name to a calendar date.
  829. Month and Day capture group default to 1."
  830. (let ((file-pattern (org-journal--dir-and-file-format->pattern))
  831. (day 1)
  832. (month 1)
  833. year
  834. (file (file-truename file-name)))
  835. (setq year (string-to-number
  836. (replace-regexp-in-string file-pattern "\\1" file)))
  837. (when (= year 0)
  838. (user-error "Failed to extract year from file: %s" file))
  839. (if (and (not (integerp (string-match "\(\?2:" file-pattern)))
  840. (member org-journal-file-type '(daily weekly monthly)))
  841. (user-error "Failed to extract month from file: %s" file)
  842. (setq month (string-to-number
  843. (replace-regexp-in-string file-pattern "\\2" file))))
  844. (if (and (not (integerp (string-match "\(\?3:" file-pattern)))
  845. (member org-journal-file-type '(daily weekly)))
  846. (user-error "Failed to extract day from file: %s" file)
  847. (setq day (string-to-number
  848. (replace-regexp-in-string file-pattern "\\3" file))))
  849. (list month day year)))
  850. (defun org-journal--entry-date->calendar-date ()
  851. "Return journal calendar-date from current buffer.
  852. This is the counterpart of `org-journal--file-name->calendar-date' for
  853. 'weekly, 'monthly and 'yearly journal files."
  854. (let ((re (org-journal--format->regex org-journal-created-property-timestamp-format))
  855. date)
  856. (setq date (org-entry-get (point) "CREATED"))
  857. (unless (ignore-errors (string-match re date))
  858. (user-error "Created property timestamp format \"%s\" doesn't match CREATED property value (%s) from entry at line: %s" org-journal-created-property-timestamp-format date (what-line)))
  859. (list (string-to-number (match-string 2 date)) ;; Month
  860. (string-to-number (match-string 3 date)) ;; Day
  861. (string-to-number (match-string 1 date))))) ;; Year
  862. (defun org-journal--file->calendar-dates (file)
  863. "Return journal dates from FILE."
  864. (org-journal--with-journal
  865. file
  866. (let (dates)
  867. (save-excursion
  868. (goto-char (point-min))
  869. (while (re-search-forward org-journal--created-re nil t)
  870. (when (= (save-excursion (org-back-to-heading) (org-outline-level)) 1)
  871. (push (org-journal--entry-date->calendar-date) dates)))
  872. dates))))
  873. ;;;###autoload
  874. (defun org-journal-new-date-entry (prefix &optional event)
  875. "Open the journal for the date indicated by point and start a new entry.
  876. If the date is not today, it won't be given a time heading. With one prefix (C-u),
  877. don't add a new heading.
  878. If the date is in the future, create a schedule entry, unless two universal prefix
  879. arguments (C-u C-u) are given. In that case insert just the heading."
  880. (interactive
  881. (list current-prefix-arg last-nonmenu-event))
  882. (let* ((time (or (ignore-errors (org-journal--calendar-date->time (calendar-cursor-to-date t event)))
  883. (org-time-string-to-time (org-read-date nil nil nil "Date:")))))
  884. (if (time-less-p time (current-time))
  885. (org-journal-new-entry prefix time)
  886. (org-journal-new-scheduled-entry prefix time))))
  887. ;;;###autoload
  888. (defun org-journal-new-scheduled-entry (prefix &optional scheduled-time)
  889. "Create a new entry in the future with an active timestamp.
  890. With non-nil prefix argument create a regular entry instead of a TODO entry."
  891. (interactive "P")
  892. (let ((time (or scheduled-time (org-time-string-to-time (org-read-date nil nil nil "Date:"))))
  893. org-journal-carryover-items)
  894. (when (time-less-p time (current-time))
  895. (user-error "Scheduled time needs to be in the future"))
  896. (org-journal-new-entry nil time)
  897. (unless prefix
  898. (insert "TODO "))
  899. (save-excursion
  900. (insert "\n")
  901. (org-insert-time-stamp time))))
  902. ;;;###autoload
  903. (defun org-journal-reschedule-scheduled-entry (&optional time)
  904. "Reschedule an entry in the future."
  905. (interactive "P")
  906. (or time (setq time (org-time-string-to-time (org-read-date nil nil nil "Data:"))))
  907. (when (time-less-p time (current-time))
  908. (user-error "Scheduled time needs to be in the future"))
  909. (save-excursion
  910. (save-restriction
  911. (org-back-to-heading)
  912. (org-narrow-to-subtree)
  913. (if (re-search-forward org-ts-regexp (line-end-position 2) t)
  914. (replace-match "")
  915. (org-end-of-subtree)
  916. (insert "\n"))
  917. (org-insert-time-stamp time)
  918. (org-cut-subtree))
  919. (let (org-journal-carryover-items)
  920. (org-save-outline-visibility t
  921. (org-journal-new-entry t time)
  922. (when (looking-back "[^\t ]" (point-at-bol) t)
  923. (insert "\n"))
  924. (org-yank)))))
  925. (defun org-journal--goto-entry (date)
  926. "Goto DATE entry in current journal file."
  927. (widen)
  928. (goto-char (point-min))
  929. (if (org-journal--daily-p)
  930. (outline-next-visible-heading 1)
  931. (org-journal--search-forward-created date))
  932. (org-journal--finalize-view))
  933. (defun org-journal-sort-dates (dates calendar-date prev)
  934. "Sorts DATES to determine the order of journal entries.
  935. Can be advised/replaced by a user."
  936. (unless (member calendar-date dates)
  937. (setq dates (copy-tree dates))
  938. (cl-loop
  939. for date in dates
  940. while (org-journal--calendar-date-compare date calendar-date)
  941. count t into cnt
  942. finally (if (> cnt 0)
  943. ;; Insert new date into list
  944. (setcdr (nthcdr (1- cnt) dates) (cons calendar-date (nthcdr cnt dates)))
  945. ;; Insert new date at front
  946. (setq dates (cons calendar-date dates)))))
  947. ;; Reverse list for previous search.
  948. (if prev (reverse dates) dates))
  949. (defun org-journal--open-entry (&optional prev no-select)
  950. "Open journal entry.
  951. If PREV is non-nil, open previous entry instead of next.
  952. If NO-SELECT is non-nil, open it, but don't show it."
  953. (let* ((calendar-date (if (org-journal--daily-p)
  954. (org-journal--file-name->calendar-date (file-truename (buffer-file-name)))
  955. (while (org-up-heading-safe))
  956. (org-journal--entry-date->calendar-date)))
  957. (view-mode-p view-mode)
  958. (dates (org-journal-sort-dates (org-journal--list-dates) calendar-date prev)))
  959. (while (and dates (car dates)
  960. (or (if prev
  961. (org-journal--calendar-date-compare calendar-date (car dates))
  962. (org-journal--calendar-date-compare (car dates) calendar-date))
  963. (calendar-date-equal (car dates) calendar-date)))
  964. (setq dates (cdr dates)))
  965. (if (and dates (car dates))
  966. (let ((filename (org-journal--get-entry-path
  967. (org-journal--calendar-date->time (car dates)))))
  968. (if (get-file-buffer filename)
  969. (progn
  970. (if no-select
  971. (set-buffer (get-file-buffer filename))
  972. (switch-to-buffer (get-file-buffer filename)))
  973. (setq org-journal--kill-buffer nil))
  974. (push (if no-select
  975. (set-buffer (find-file-noselect filename))
  976. (find-file filename))
  977. org-journal--kill-buffer))
  978. (org-journal--goto-entry (car dates))
  979. (view-mode (if view-mode-p 1 -1))
  980. t)
  981. nil)))
  982. ;;;###autoload
  983. (defun org-journal-open-current-journal-file ()
  984. "Open the current journal file"
  985. (interactive)
  986. (let ((org-journal-file (org-journal--get-entry-path)))
  987. (if (file-exists-p org-journal-file)
  988. (progn
  989. (funcall org-journal-find-file org-journal-file)
  990. (unless (org-journal--daily-p)
  991. (let ((last-entry-date (car (org-journal--file->calendar-dates org-journal-file))))
  992. (when last-entry-date
  993. (org-journal--goto-entry last-entry-date)))))
  994. (message "Journal file %s not found" org-journal-file))))
  995. (defun org-journal--list-files ()
  996. "Returns a list of all files in the journal directory."
  997. (org-journal--create-journal-dir)
  998. ;; grab the file list. We can’t use directory-files-recursively’s
  999. ;; regexp facility to filter it, because that only checks the
  1000. ;; regexp against the base filenames, and we need to check it
  1001. ;; against filenames relative to org-journal-dir.
  1002. (let ((file-list (directory-files-recursively
  1003. (file-truename (expand-file-name
  1004. (file-name-as-directory org-journal-dir))) "\.*"))
  1005. (predicate (lambda (file-path)
  1006. (and (string-match-p (org-journal--dir-and-file-format->pattern) file-path)
  1007. (or org-journal-encrypt-journal
  1008. (not (string-match-p "\.gpg$" file-path)))))))
  1009. (seq-filter predicate file-list)))
  1010. (defconst org-journal--cache-file
  1011. (expand-file-name "org-journal.cache" user-emacs-directory)
  1012. "Cache file for `org-journal--dates'.")
  1013. (defvar org-journal--dates (make-hash-table :test 'equal)
  1014. "Hash table for journal dates.
  1015. The key is a journal date entry, and the value of the key is of the form
  1016. \(FILENAME \(FILE MODIFICATION TIME\)\).")
  1017. ;;;###autoload
  1018. (defun org-journal-invalidate-cache ()
  1019. "Clear `org-journal--dates' hash table, and the cache file."
  1020. (interactive)
  1021. (clrhash org-journal--dates)
  1022. (when org-journal-enable-cache
  1023. (org-journal--serialize)))
  1024. (defun org-journal--file-modification-time (file)
  1025. (nth 5 (file-attributes file)))
  1026. (defun org-journal--dates-puthash (&optional file)
  1027. (or file (setq file (buffer-file-name)))
  1028. (let ((mtime (org-journal--file-modification-time file)))
  1029. (if (org-journal--daily-p)
  1030. (puthash (org-journal--file-name->calendar-date file) (list file mtime) org-journal--dates)
  1031. ;; Remove any key where (car value) equals FILE
  1032. (cl-loop for key being the hash-keys of org-journal--dates
  1033. when (string-equal (car (gethash key org-journal--dates)) file)
  1034. do (remhash key org-journal--dates))
  1035. (dolist (date (org-journal--file->calendar-dates file))
  1036. (puthash date (list file mtime) org-journal--dates)))))
  1037. (defun org-journal--serialize ()
  1038. "Write hashmap to file."
  1039. (when org-journal-enable-cache
  1040. (unless (file-directory-p (file-name-directory org-journal--cache-file))
  1041. (make-directory (file-name-directory org-journal--cache-file) t))
  1042. (if (file-writable-p org-journal--cache-file)
  1043. (with-temp-file org-journal--cache-file
  1044. (let (print-length)
  1045. (insert (prin1-to-string org-journal--dates))))
  1046. (error "%s is not writable" org-journal--cache-file)))
  1047. (org-journal--sort-dates))
  1048. (defun org-journal--deserialize ()
  1049. "Read hashmap from file."
  1050. (when org-journal-enable-cache
  1051. (with-demoted-errors
  1052. "Error during file deserialization: %S"
  1053. (when (file-exists-p org-journal--cache-file)
  1054. (with-temp-buffer
  1055. (insert-file-contents org-journal--cache-file)
  1056. (setq org-journal--dates (read (buffer-substring (point-at-bol) (point-at-eol))))))))
  1057. (org-journal--sort-dates))
  1058. (defvar org-journal--sorted-dates nil)
  1059. (defun org-journal--sort-dates ()
  1060. "Flatten and sort dates, and assign the result to `org-journal-flatten-dates'."
  1061. (setq org-journal--sorted-dates (sort (hash-table-keys org-journal--dates) 'org-journal--calendar-date-compare)))
  1062. (defun org-journal--list-dates ()
  1063. "Return all journal dates.
  1064. The list ((month day year) ...) contains calendar dates, and is sorted
  1065. from oldest to newest."
  1066. (let ((files (org-journal--list-files))
  1067. reparse-files serialize-p
  1068. rem-keys)
  1069. (when (hash-table-empty-p org-journal--dates)
  1070. (org-journal--deserialize)
  1071. (when (hash-table-empty-p org-journal--dates)
  1072. (dolist (file files)
  1073. (org-journal--dates-puthash file))
  1074. (setq serialize-p t)))
  1075. ;; Verify modification time is unchanged, if we have already data.
  1076. (unless serialize-p
  1077. (cl-loop
  1078. with (value files-in-hash file)
  1079. for key being the hash-keys of org-journal--dates
  1080. always (setq value (gethash key org-journal--dates)
  1081. file (car value))
  1082. do
  1083. (unless (member (car value) files)
  1084. (unless (member key rem-keys)
  1085. (push key rem-keys)))
  1086. (unless (member file files-in-hash)
  1087. (push file files-in-hash)
  1088. (unless (equal (cadr value) (org-journal--file-modification-time file))
  1089. (when (and (member file files) (not (member file reparse-files)))
  1090. (push file reparse-files))))
  1091. finally (dolist (file files) ;; Are there any new files
  1092. (unless (member file files-in-hash)
  1093. (push file reparse-files)))))
  1094. (when rem-keys
  1095. (dolist (k rem-keys)
  1096. (remhash k org-journal--dates))
  1097. (setq serialize-p t))
  1098. (when reparse-files
  1099. (dolist (f reparse-files)
  1100. (org-journal--dates-puthash f))
  1101. (setq serialize-p t))
  1102. (when serialize-p
  1103. (org-journal--serialize))
  1104. org-journal--sorted-dates))
  1105. ;;;###autoload
  1106. (defun org-journal-mark-entries ()
  1107. "Mark days in the calendar for which a journal entry is present."
  1108. (interactive)
  1109. (when (file-exists-p org-journal-dir)
  1110. (let ((current-time (current-time)))
  1111. (dolist (journal-entry (org-journal--list-dates))
  1112. (if (calendar-date-is-visible-p journal-entry)
  1113. (if (time-less-p (org-journal--calendar-date->time journal-entry)
  1114. current-time)
  1115. (calendar-mark-visible-date journal-entry 'org-journal-calendar-entry-face)
  1116. (calendar-mark-visible-date journal-entry 'org-journal-calendar-scheduled-face)))))))
  1117. ;;;###autoload
  1118. (defun org-journal-read-entry (_arg &optional event)
  1119. "Open journal entry for selected date for viewing."
  1120. (interactive
  1121. (list current-prefix-arg last-nonmenu-event))
  1122. (let* ((time (org-journal--calendar-date->time
  1123. (calendar-cursor-to-date t event))))
  1124. (org-journal-read-or-display-entry time nil)))
  1125. ;;;###autoload
  1126. (defun org-journal-display-entry (_arg &optional event)
  1127. "Display journal entry for selected date in another window."
  1128. (interactive
  1129. (list current-prefix-arg last-nonmenu-event))
  1130. (let* ((time (org-journal--calendar-date->time
  1131. (calendar-cursor-to-date t event))))
  1132. (org-journal-read-or-display-entry time t)))
  1133. (defun org-journal--finalize-view ()
  1134. "Finalize visability of entry."
  1135. (org-journal--decrypt)
  1136. (if (org-journal--is-date-prefix-org-heading-p)
  1137. (progn
  1138. (org-up-heading-safe)
  1139. (org-back-to-heading)
  1140. (outline-hide-other)
  1141. (outline-show-subtree))
  1142. (outline-show-all)))
  1143. ;;;###autoload
  1144. (defun org-journal-read-or-display-entry (time &optional noselect)
  1145. "Read an entry for the TIME and either select the new window when NOSELECT
  1146. is nil or avoid switching when NOSELECT is non-nil."
  1147. (let* ((org-journal-file (org-journal--get-entry-path time))
  1148. (buf-exists (get-file-buffer org-journal-file))
  1149. buf point)
  1150. (if (and (when (file-exists-p org-journal-file)
  1151. (setq buf (find-file-noselect org-journal-file)))
  1152. ;; If daily continue with than clause of if condition
  1153. (or (org-journal--daily-p)
  1154. ;; Search for journal entry
  1155. (with-current-buffer buf
  1156. (save-mark-and-excursion
  1157. (goto-char (point-min))
  1158. (setq time (decode-time time))
  1159. (setq point (org-journal--search-forward-created
  1160. (list (nth 4 time) (nth 3 time) (nth 5 time))
  1161. nil t))))))
  1162. (progn
  1163. ;; Use `find-file-noselect' instead of `view-file' as it does not respect `auto-mode-alist'
  1164. (with-current-buffer buf
  1165. ;; Open file in view-mode if not opened already.
  1166. (unless buf-exists
  1167. (view-mode)
  1168. (setq view-exit-action 'kill-buffer))
  1169. (set (make-local-variable 'org-hide-emphasis-markers) t)
  1170. (if (org-journal--daily-p)
  1171. (when (org-journal--is-date-prefix-org-heading-p)
  1172. (goto-char (point-min))
  1173. (re-search-forward (concat org-journal-date-prefix
  1174. (if (functionp org-journal-date-format)
  1175. (funcall org-journal-date-format time)
  1176. (format-time-string org-journal-date-format time)))))
  1177. (goto-char point))
  1178. (org-journal--finalize-view)
  1179. (setq point (point)))
  1180. (if noselect
  1181. (display-buffer buf t)
  1182. (funcall org-journal-find-file org-journal-file))
  1183. (set-window-point (get-buffer-window (get-file-buffer org-journal-file)) point)
  1184. buf)
  1185. (message "No journal entry for this date."))))
  1186. (defun org-journal--next-entry (&optional prev)
  1187. "Go to next entry.
  1188. If prev is non-nil open previous entry instead of next."
  1189. (unless (cond
  1190. ((eq major-mode 'calendar-mode)
  1191. (let ((dates (if prev
  1192. (reverse (org-journal--list-dates))
  1193. (org-journal--list-dates))))
  1194. (while (and dates
  1195. (not (if prev
  1196. (org-journal--calendar-date-compare (car dates) (calendar-cursor-to-date))
  1197. (org-journal--calendar-date-compare (calendar-cursor-to-date) (car dates)))))
  1198. (setq dates (cdr dates)))
  1199. (when dates
  1200. (calendar-goto-date (car dates))
  1201. (when org-journal-follow-mode
  1202. (org-journal-display-entry nil)))))
  1203. ((eq major-mode 'org-journal-mode)
  1204. (org-journal--open-entry prev))
  1205. (t
  1206. (user-error
  1207. (concat "org-journal-" (if prev "previous" "next")
  1208. "-entry called outside calendar/org-journal mode"))))
  1209. (message (concat "No journal entry " (if prev "before" "after") " this one"))))
  1210. ;;;###autoload
  1211. (defun org-journal-next-entry ()
  1212. "Go to the next journal entry."
  1213. (interactive)
  1214. (org-journal--next-entry))
  1215. ;;;###autoload
  1216. (defun org-journal-previous-entry ()
  1217. "Go to the previous journal entry."
  1218. (interactive)
  1219. (org-journal--next-entry t))
  1220. ;;; Journal search facilities
  1221. ;;;###autoload
  1222. (defun org-journal-search (str &optional period-name)
  1223. "Search for a string in the journal files.
  1224. See `org-read-date' for information on ways to specify dates.
  1225. If a prefix argument is given, search all dates."
  1226. (interactive
  1227. (list (read-string "Enter a string to search for: " nil 'org-journal-search-history)))
  1228. (let* ((period-pair (org-journal--read-period (if current-prefix-arg 'forever period-name)))
  1229. (start (org-journal--calendar-date->time (car period-pair)))
  1230. (end (org-journal--calendar-date->time (cdr period-pair))))
  1231. ;; Including period-start in search
  1232. (setcar (cdr start) (1- (cadr start)))
  1233. ;; Including period-end in search
  1234. (setcar (cdr end) (1+ (cadr end)))
  1235. (org-journal--search-by-string str start end)))
  1236. (defvar org-journal-search-history nil)
  1237. ;;;###autoload
  1238. (defun org-journal-search-calendar-week (str)
  1239. "Search for a string within a current calendar-mode week entries."
  1240. (interactive
  1241. (list
  1242. (read-string "Enter a string to search for: " nil 'org-journal-search-history)))
  1243. (org-journal-search str 'week))
  1244. ;;;###autoload
  1245. (defun org-journal-search-calendar-month (str)
  1246. "Search for a string within a current calendar-mode month entries."
  1247. (interactive
  1248. (list
  1249. (read-string "Enter a string to search for: " nil 'org-journal-search-history)))
  1250. (org-journal-search str 'month))
  1251. ;;;###autoload
  1252. (defun org-journal-search-calendar-year (str)
  1253. "Search for a string within a current calendar-mode year entries."
  1254. (interactive
  1255. (list
  1256. (read-string "Enter a string to search for: " nil 'org-journal-search-history)))
  1257. (org-journal-search str 'year))
  1258. ;;;###autoload
  1259. (defun org-journal-search-forever (str)
  1260. "Search for a string within all entries."
  1261. (interactive
  1262. (list
  1263. (read-string "Enter a string to search for: " nil 'org-journal-search-history)))
  1264. (org-journal-search str 'forever))
  1265. ;;;###autoload
  1266. (defun org-journal-search-future (str)
  1267. "Search for a string within all future entries."
  1268. (interactive
  1269. (list
  1270. (read-string "Enter a string to search for: " nil 'org-journal-search-history)))
  1271. (org-journal-search str 'future))
  1272. ;;;###autoload
  1273. (defun org-journal-search-future-scheduled ()
  1274. "Search for TODOs within all future entries."
  1275. (interactive)
  1276. (org-journal-search "TODO" 'future))
  1277. ;; This macro is needed for many of the following functions.
  1278. (defmacro org-journal--with-find-file (file &rest body)
  1279. "Executes BODY in FILE. Use this to insert text into FILE.
  1280. The buffer is disposed after the macro exits (unless it already
  1281. existed before)."
  1282. (declare (indent 1))
  1283. `(save-excursion
  1284. (let ((current-buffer (current-buffer))
  1285. (buffer-exists (get-buffer (file-name-nondirectory ,file)))
  1286. (result nil))
  1287. (if buffer-exists
  1288. (switch-to-buffer buffer-exists)
  1289. (find-file ,file))
  1290. (setq result (progn ,@body))
  1291. (basic-save-buffer)
  1292. (unless buffer-exists
  1293. (kill-buffer))
  1294. (switch-to-buffer current-buffer)
  1295. result)))
  1296. (def-edebug-spec org-journal--with-find-file (form body))
  1297. (defun org-journal--update-org-agenda-files ()
  1298. "Adds the current and future journal files to `org-agenda-files' containing TODOs,
  1299. and cleans out past org-journal files."
  1300. (when org-journal-enable-agenda-integration
  1301. (let ((not-org-journal-agenda-files
  1302. (seq-filter
  1303. (lambda (fname)
  1304. (not (string-match (org-journal--dir-and-file-format->pattern) fname)))
  1305. (org-agenda-files)))
  1306. (org-journal-agenda-files
  1307. (let* ((future (org-journal--read-period 'future))
  1308. (beg (car future))
  1309. (end (cdr future)))
  1310. (setcar (cdr beg) (1- (cadr beg))) ;; Include today; required for `org-journal--search-build-file-list'
  1311. (when (< (nth 2 (decode-time (current-time))) org-extend-today-until)
  1312. (setq beg (decode-time (apply #'encode-time `(0 59 -1 ,(nth 1 beg) ,(nth 0 beg) ,(nth 2 beg))))
  1313. beg (list (nth 4 beg) (nth 3 beg) (nth 5 beg))))
  1314. (org-journal--search-build-file-list
  1315. (org-journal--calendar-date->time beg)
  1316. (org-journal--calendar-date->time end)))))
  1317. (org-store-new-agenda-file-list (append not-org-journal-agenda-files
  1318. org-journal-agenda-files)))))
  1319. (defvar org-journal--schedule-buffer-name "*Org-journal schedule*")
  1320. (defun org-journal-schedule-view ()
  1321. "Opens a new window with all scheduled journal entries.
  1322. Think of this as a faster, less fancy version of your `org-agenda'."
  1323. (interactive)
  1324. (when (get-buffer org-journal--schedule-buffer-name)
  1325. (kill-buffer org-journal--schedule-buffer-name))
  1326. (with-current-buffer (get-buffer-create org-journal--schedule-buffer-name)
  1327. (org-mode)
  1328. (insert "#+TITLE: Org-Journal Schedule\n\n")
  1329. (goto-char (point-max)))
  1330. (cl-loop
  1331. with copy-mapper = (lambda ()
  1332. (let ((subtree (org-journal--carryover-item-with-parents)))
  1333. ;; since the next subtree now starts at point,
  1334. ;; continue mapping from before that, to include it
  1335. ;; in the search
  1336. (backward-char)
  1337. (setq org-map-continue-from (point))
  1338. subtree))
  1339. with (content-to-copy journal-buffers)
  1340. with today = (current-time)
  1341. for date in (org-journal--list-dates)
  1342. always (setq date (org-journal--calendar-date->time date))
  1343. when (time-less-p today date)
  1344. do
  1345. (cl-pushnew (org-journal-read-or-display-entry date) journal-buffers)
  1346. (with-current-buffer org-journal--schedule-buffer-name
  1347. (if (functionp org-journal-date-format)
  1348. (insert (funcall org-journal-date-format date))
  1349. (insert org-journal-date-prefix
  1350. (format-time-string org-journal-date-format date)
  1351. "\n")))
  1352. (save-restriction
  1353. (org-narrow-to-subtree)
  1354. (setq content-to-copy (org-map-entries
  1355. copy-mapper
  1356. "+TIMESTAMP>=\"<now>\"|+SCHEDULED>=\"<now>\"")))
  1357. (when content-to-copy
  1358. (with-current-buffer org-journal--schedule-buffer-name
  1359. (insert (mapconcat (lambda (item) (cddar item)) content-to-copy "")
  1360. "\n")))
  1361. finally
  1362. (mapc (lambda (b)
  1363. (with-current-buffer b
  1364. (when view-mode
  1365. (kill-buffer))))
  1366. journal-buffers))
  1367. (with-current-buffer org-journal--schedule-buffer-name
  1368. (set-buffer-modified-p nil)
  1369. (view-mode t)
  1370. (goto-char (point-min)))
  1371. (switch-to-buffer org-journal--schedule-buffer-name))
  1372. (defun org-journal--read-period (period-name)
  1373. "Return read period.
  1374. If the PERIOD-NAME is nil, then ask the user for period start/end.
  1375. If PERIOD-NAME is 'forever, set the period from the beginning of time
  1376. to eternity. If PERIOD-NAME is a symbol equal to 'week, 'month or 'year
  1377. then use current week, month or year from the calendar, accordingly."
  1378. (cond
  1379. ;; no period-name? ask the user for input
  1380. ((not period-name)
  1381. (let* ((org-read-date-prefer-future nil)
  1382. (absolute-start (time-to-days (org-read-date nil t nil "Enter the search start")))
  1383. (absolute-end (time-to-days (org-read-date nil t nil "Enter the search end")))
  1384. (start (calendar-gregorian-from-absolute absolute-start))
  1385. (end (calendar-gregorian-from-absolute absolute-end)))
  1386. (cons start end)))
  1387. ;; eternity start/end
  1388. ((eq period-name 'forever)
  1389. (cons (list 1 1 1971)
  1390. (list 12 31 2030)))
  1391. ;; future start/end
  1392. ((eq period-name 'future)
  1393. (let ((date (decode-time (current-time))))
  1394. (cons (list (nth 4 date) (nth 3 date) (nth 5 date))
  1395. (list 12 31 2030))))
  1396. ;; extract a year start/end using the calendar curson
  1397. ((and (eq period-name 'year) (eq major-mode 'calendar-mode))
  1398. (calendar-cursor-to-nearest-date)
  1399. (let* ((date (calendar-cursor-to-date))
  1400. (year (calendar-extract-year date))
  1401. (jan-first (list 1 1 year))
  1402. (dec-31 (list 12 31 year)))
  1403. (cons jan-first
  1404. dec-31)))
  1405. ;; month start/end
  1406. ((and (eq period-name 'month) (eq major-mode 'calendar-mode))
  1407. (calendar-cursor-to-nearest-date)
  1408. (let* ((date (calendar-cursor-to-date))
  1409. (year (calendar-extract-year date))
  1410. (month (calendar-extract-month date))
  1411. (last-day (calendar-last-day-of-month month year)))
  1412. (cons (list month 1 year)
  1413. (list month last-day year))))
  1414. ;; week start/end
  1415. ((and (eq period-name 'week) (eq major-mode 'calendar-mode))
  1416. (calendar-cursor-to-nearest-date)
  1417. (let* ((date (calendar-cursor-to-date))
  1418. (absoluteday (calendar-absolute-from-gregorian date))
  1419. (weekday (calendar-day-of-week date))
  1420. (zerobased-weekday (- weekday calendar-week-start-day))
  1421. (absolute-start (- absoluteday zerobased-weekday))
  1422. (absolute-end (+ absoluteday (- 7 zerobased-weekday)))
  1423. (start (calendar-gregorian-from-absolute absolute-start))
  1424. (end (calendar-gregorian-from-absolute absolute-end)))
  1425. (cons start end)))
  1426. (t (user-error "Wrong period-name given or not in the calendar mode"))))
  1427. (defun org-journal--search-by-string (str &optional period-start period-end)
  1428. "Search for a string within a given time interval.
  1429. If STR is empty, search for all entries using `org-journal-time-prefix'."
  1430. (when (time-less-p period-end period-start)
  1431. (user-error "Period end cannot be before the start"))
  1432. (let* ((search-str (if (string= "" str) org-journal-time-prefix str))
  1433. (files (org-journal--search-build-file-list period-start period-end))
  1434. (results (org-journal--search-do-search search-str files))
  1435. (buf (get-buffer-create org-journal--search-buffer))
  1436. (inhibit-read-only t))
  1437. (unless (get-buffer-window buf 0)
  1438. (switch-to-buffer buf))
  1439. (with-current-buffer buf
  1440. (org-journal-search-mode)
  1441. (erase-buffer)
  1442. (org-journal--search-print-results str results period-start period-end)
  1443. (goto-char (point-min))
  1444. (forward-button 1)
  1445. (button-activate (button-at (point))))))
  1446. (defun org-journal--search-build-file-list (period-start period-end)
  1447. "Build a list of journal files within a given time interval."
  1448. (unless (and period-start period-end ;; Check for null values
  1449. (car period-start) (cdr period-start)
  1450. (car period-end) (cdr period-end))
  1451. (user-error "Time `%s' and/or `%s' are not valid" period-start period-end))
  1452. (let (result filetime)
  1453. (dolist (file (org-journal--list-files))
  1454. (setq filetime (org-journal--calendar-date->time
  1455. (org-journal--file-name->calendar-date file)))
  1456. (when (and
  1457. (time-less-p
  1458. period-start
  1459. ;; Convert to period-start boundary.
  1460. (pcase org-journal-file-type
  1461. ;; For daily, filetime is period-start boundary.
  1462. (`daily filetime)
  1463. ;; For weekly, filetime +6 days is period-start boundary.
  1464. (`weekly
  1465. (let* ((time (decode-time filetime))
  1466. (day (+ 6 (nth 3 time))) ;; End of week
  1467. (month (nth 4 time))
  1468. (year (nth 5 time))
  1469. (last-day-of-month (calendar-last-day-of-month month year)))
  1470. (when (> day last-day-of-month)
  1471. (setq day (- day last-day-of-month))
  1472. (when (= month 12)
  1473. (setq month 0)
  1474. (setq year (1+ year)))
  1475. (setq month (1+ month)))
  1476. (org-journal--calendar-date->time (list month day year))))
  1477. ;; For monthly, end of month is period-start boundary.
  1478. (`monthly
  1479. (let* ((time (decode-time filetime))
  1480. (month (nth 4 time))
  1481. (year (nth 5 time))
  1482. (day (calendar-last-day-of-month month year)))
  1483. (org-journal--calendar-date->time (list month day year))))
  1484. ;; For yearly, end of year is period-start boundary.
  1485. (`yearly
  1486. (org-journal--calendar-date->time (list 12 31 (nth 5 (decode-time filetime)))))))
  1487. (time-less-p filetime period-end))
  1488. (push file result)))
  1489. result))
  1490. (defun org-journal--search-do-search (str files)
  1491. "Search for a string within a list of files, return match pairs (PATH . LINENUM)."
  1492. (let (results result)
  1493. (dolist (fname (reverse files))
  1494. (setq result (org-journal--with-journal
  1495. fname
  1496. (when org-journal-enable-encryption
  1497. (goto-char (point-min))
  1498. (while (search-forward ":crypt:" nil t)
  1499. (org-decrypt-entry)))
  1500. (goto-char (point-min))
  1501. (while (funcall org-journal-search-forward-fn str nil t)
  1502. (push
  1503. (list
  1504. (let ((date
  1505. (if (org-journal--daily-p)
  1506. (org-journal--file-name->calendar-date fname)
  1507. (save-excursion
  1508. (when (re-search-backward org-journal--created-re nil t)
  1509. (when (= (save-excursion (org-back-to-heading) (org-outline-level)) 1)
  1510. (org-journal--entry-date->calendar-date)))))))
  1511. (when date
  1512. (org-journal--calendar-date->time date)))
  1513. (- (point) (length str))
  1514. (buffer-substring-no-properties
  1515. (line-beginning-position)
  1516. (line-end-position)))
  1517. result))
  1518. result))
  1519. (when result
  1520. (mapc (lambda (res) (push res results)) result)))
  1521. (cond
  1522. ((eql org-journal-search-results-order-by :desc) results)
  1523. (t (reverse results)))))
  1524. (defun org-journal--search-format-date (time)
  1525. "Format TIME according to `org-journal-search-result-date-format'."
  1526. (format-time-string org-journal-search-result-date-format time))
  1527. (defun org-journal--search-next ()
  1528. (interactive)
  1529. (forward-button 1 t)
  1530. (button-activate (button-at (point))))
  1531. (defun org-journal--search-prev ()
  1532. (interactive)
  1533. (backward-button 1 t)
  1534. (button-activate (button-at (point))))
  1535. (defvar org-journal-search-mode-map nil
  1536. "Keymap for *Org-journal search* buffers.")
  1537. (unless org-journal-search-mode-map
  1538. (setq org-journal-search-mode-map
  1539. (let ((map (make-sparse-keymap)))
  1540. (define-key map "q" 'kill-this-buffer)
  1541. (define-key map (kbd "<tab>") 'org-journal--search-next)
  1542. (define-key map (kbd "<backtab>") 'org-journal--search-prev)
  1543. (define-key map "n" 'org-journal--search-next)
  1544. (define-key map "p" 'org-journal--search-prev)
  1545. map)))
  1546. (fset 'org-journal-search-mode-map org-journal-search-mode-map)
  1547. (define-derived-mode org-journal-search-mode special-mode
  1548. "org-journal-search"
  1549. "Major mode for displaying org-journal search results.
  1550. \\{org-journal-search-mode-map}."
  1551. (use-local-map org-journal-search-mode-map)
  1552. (setq truncate-lines t
  1553. buffer-undo-list t)
  1554. (hl-line-mode 1))
  1555. (defun org-journal--search-print-results (str results period-start period-end)
  1556. "Print search results using text buttons."
  1557. (let ((label-start (org-journal--search-format-date period-start))
  1558. (label-end (org-journal--search-format-date period-end)))
  1559. (insert (concat "Search results for \"" str "\" between "
  1560. label-start " and " label-end
  1561. ": \n\n")))
  1562. (let (point fullstr time label)
  1563. (dolist (res results)
  1564. (setq time (nth 0 res)
  1565. point (nth 1 res)
  1566. fullstr (nth 2 res)
  1567. label (and time (org-journal--search-format-date time)))
  1568. ;; Filter out entries not within period-start/end for weekly/monthly/yearly journal files.
  1569. (when (or (org-journal--daily-p)
  1570. (and time
  1571. (time-less-p period-start time)
  1572. (time-less-p time period-end)))
  1573. (insert-text-button label
  1574. 'action 'org-journal--search-follow-link-action
  1575. 'org-journal-link (cons point time))
  1576. (insert "\t" fullstr "\n"))))
  1577. (org-journal-highlight str))
  1578. (defun org-journal--search-follow-link-action (button)
  1579. "Follow the link using info saved in button properties."
  1580. (let* ((target (button-get button 'org-journal-link))
  1581. (point (car target))
  1582. (time (cdr target))
  1583. (buf (org-journal-read-or-display-entry time t)))
  1584. (set-window-point (get-buffer-window buf) point)))
  1585. (defun org-journal-re-encrypt-journals (recipient)
  1586. "Re-encrypt journal files."
  1587. (interactive (list (epa-select-keys (epg-make-context epa-protocol)
  1588. "Select new recipient for encryption.
  1589. Only one recipient is supported. ")))
  1590. (unless recipient
  1591. (user-error "You need to specify exactly one recipient"))
  1592. (unless org-journal-encrypt-journal
  1593. (user-error "org-journal encryption not enabled"))
  1594. (cl-loop
  1595. with buf
  1596. with kill-buffer
  1597. for journal in (org-journal--list-files)
  1598. do
  1599. (setq buf (get-file-buffer journal)
  1600. kill-buffer nil)
  1601. (when (and buf
  1602. (buffer-modified-p buf)
  1603. (y-or-n-p (format "Journal \"%s\" modified, save before re-encryption?"
  1604. (file-name-nondirectory journal))))
  1605. (save-buffer buf))
  1606. (unless buf
  1607. (setq kill-buffer t
  1608. buf (find-file-noselect journal)))
  1609. (with-current-buffer buf
  1610. (let ((epa-file-encrypt-to (epg-sub-key-id (car (epg-key-sub-key-list (car recipient))))))
  1611. (set-buffer-modified-p t)
  1612. (save-buffer)
  1613. (when kill-buffer
  1614. (kill-buffer))))))
  1615. (defun org-journal--decrypt ()
  1616. "Decrypt journal entry at point."
  1617. (when org-journal-enable-encryption
  1618. (let ((buffer-read-only nil))
  1619. (org-decrypt-entries))))
  1620. (defun org-journal-encryption-hook ()
  1621. "The function added to the hook specified by `org-journal-encrypt-on'."
  1622. (when org-journal-enable-encryption
  1623. (org-encrypt-entries)
  1624. (unless (equal org-journal-encrypt-on
  1625. 'before-save-hook)
  1626. (save-buffer))))
  1627. ;; Setup encryption by default
  1628. ;;;###autoload
  1629. (add-hook 'org-journal-mode-hook
  1630. (lambda () (add-hook org-journal-encrypt-on
  1631. 'org-journal-encryption-hook
  1632. nil t)))
  1633. (provide 'org-journal)
  1634. ;;; org-journal.el ends here