org-mode 系列——org-mode 日程管理

在 org-mode 中,我们使用 capture 来创建任务或日程,但是,捕获的结果放在 org 文件里,并不容易管理。而 org-agenda 能够将分散在多个 .org 文件中的待办事项、计划任务和日程安排收集起来,以一个统一的、高度可定制的视图清晰地呈现给用户。

1 自定义函数

(defun my-diary-chinese-anniversary (lunar-month lunar-day &optional year mark)
  "干支纪年法,第一轮开始于公元前2637年。LUNAR-MONTH: 农历月;
LUNAR-DAY: 农历日; YEAR: 年; MARK: 高亮标记.  According to legend, the
Chinese calendar developed during the third millennium BC. It is said
to have been invented by the first legendary ruler, Huang Di or the
Yellow Emperor, who reigned, by tradition, c.2698-2599 BC. The fourth
legendary ruler, Emperor Yao, added the intercalary month. The 60-year
\"stem-branch\" cycle was first used to mark years during the first
century BC. Tradition fixes the first year of the first cycle (the
epoch) at 2637 BC. Thus the cycle beginning in 1984 is the 78th. Other
opinions fix the first year at 2697 BC (while Huangdi was still
immature), by which count we are now in cycle 79. "
  (if year
      (let* ((d-date (diary-make-date lunar-month lunar-day year))
             (a-date (calendar-absolute-from-gregorian d-date))
             (c-date (calendar-chinese-from-absolute a-date))
             (cycle (car c-date))
             (yy (cadr c-date))
             (y (+ (* 100 cycle) yy)))
        (diary-chinese-anniversary lunar-month lunar-day y mark))
    (diary-chinese-anniversary lunar-month lunar-day year mark)))


(defun diary-countdown (m1 d1 y1 n)
  "Reminder during the previous n days to the date.
Order of parameters is M1, D1, Y1, N if
`european-calendar-style' is nil, and D1, M1, Y1, N otherwise."
  (diary-remind '(diary-date m1 d1 y1)
                (let (value) (dotimes (number n value) (setq value (cons number value))))))


(defun org-days-to-relative-week (days-now)
  "定义另一套week number体系用于显示每学期的周数.
DAYS-NOW are days from absolute gregorian"
  (let* ((school-terms
          `((,(calendar-absolute-from-gregorian '(8 30 2021))
             . ,(calendar-absolute-from-gregorian '(1 16 2022)))
            (,(calendar-absolute-from-gregorian '(2 28 2022))
             . ,(calendar-absolute-from-gregorian '(7 6 2022)))
            (,(calendar-absolute-from-gregorian '(8 29 2022))
             . ,(calendar-absolute-from-gregorian '(1 6 2023)))
            (,(calendar-absolute-from-gregorian '(2 13 2023))
             . ,(calendar-absolute-from-gregorian '(6 30 2023)))
            (,(calendar-absolute-from-gregorian '(8 28 2023))
             . ,(calendar-absolute-from-gregorian '(1 12 2024)))
            (,(calendar-absolute-from-gregorian '(2 26 2024))
             . ,(calendar-absolute-from-gregorian '(7 5 2024)))
            (,(calendar-absolute-from-gregorian '(9 2 2024))
             . ,(calendar-absolute-from-gregorian '(1 11 2025)))
            (,(calendar-absolute-from-gregorian '(2 24 2025))
             . ,(calendar-absolute-from-gregorian '(7 5 2025)))
            (,(calendar-absolute-from-gregorian '(9 1 2025))
             . ,(calendar-absolute-from-gregorian '(1 10 2026)))
            (,(calendar-absolute-from-gregorian '(3 2 2026))
             . ,(calendar-absolute-from-gregorian '(7 11 2026)))))
         (x (seq-filter (lambda (p) (<= (car p) days-now (cdr p)))
                        school-terms)))
    (if x
        (1+ (/ (- days-now (caar x)) 7))
      x)))


(defconst cal-china-x-day-name
  ["初一" "初二" "初三" "初四" "初五" "初六" "初七" "初八" "初九" "初十"
   "十一" "十二" "十三" "十四" "十五" "十六" "十七" "十八" "十九"  "廿"
   "廿一" "廿二" "廿三" "廿四" "廿五" "廿六" "廿七" "廿八" "廿九" "三十"
   "卅一" "卅二" "卅三" "卅四" "卅五" "卅六" "卅七" "卅八" "卅九" "卅十"])
;;; 显示定制的周数.Week-agenda (w15) 这一行是不适合进行改造的,它可能是由 w15-w16这样的字符串构成。它可能是由 w15-w16这样的字符串构成,所以不符合改造要求;Monday 11 April 2022 W15:这一行是适合改造的,它只出现在每周第一天,即使在显示多个星期时也不会有问题。
(advice-add 'org-agenda-format-date-aligned
            :around
            (lambda (orig-fun date)
              (let* ((day-of-week (calendar-day-of-week date))
                     (custom-week (org-days-to-relative-week
                                   (calendar-absolute-from-gregorian date)))
                     (cn-date (calendar-chinese-from-absolute
                               (calendar-absolute-from-gregorian date)))
                     (cn-month (caddr cn-date))
                     (cn-day (cadddr cn-date))
                     (cn-month-string (concat (if (integerp cn-month) "" "闰")
                                              (aref calendar-chinese-month-name-array 
                                                    (1- (floor cn-month))))) 
                     (cn-day-string (aref cal-china-x-day-name 
                                          (1- cn-day))) 
                     (orig-str (funcall orig-fun date)))
                (if (and custom-week (= 1 day-of-week))
                    (format "%s | 第%02d周 农历%s%s" orig-str custom-week cn-month-string cn-day-string)
                  (format "%s 农历%s%s" orig-str cn-month-string cn-day-string)))))

2 agenda 文件位置

告诉 Agenda 在哪里找文件,agenda 会从这些文件中搜索任务:

(defconst org-agenda-files (list org-agenda-dir))

3 显示设置

如果任务已完成,则不再显示:

(org-agenda-skip-scheduled-if-done t) ;不显示已完成的scheduled
(org-agenda-skip-deadline-if-done t) ;不显示已完成的deadline

因为我使用 org 管理任务和日程,所以 org agenda 的使用频率非常高,对 org agenda 的配置进行更改,将 org-agenda-window-setup 设置为 'reorganize-frame ,即将窗口重新划分成两个,一个显示当前窗口的 buffer,另一个显示 agenda;然后将 org-agenda-restore-windows-after-quit 的值设置为 t ,即按 q 离开 agenda 时恢复原来的窗口布局:

(org-agenda-window-setup 'reorganize-frame)
(org-agenda-restore-windows-after-quit t)

设置日历默认地点:

(calendar-latitude 30.5)
(calendar-longitude 114.3)
(calendar-location-name "Wuhan, China")

当一个 item 下有多个时间戳时,全部时间戳都有效:

(org-agenda-skip-additional-timestamps-same-entry nil)

需要加载 solar,是一个用于计算和显示天文信息的功能集合,如:日出日落时间,月相等:

(require 'solar)

4 设置任务/日程提醒

先定义一个显示提醒消息的函数:

(defun appt-disp-window-and-notification (min-to-appt current-time appt-msg)
  (let ((title (format "%s分钟内有新的任务" min-to-appt)))
    (notifications-notify :timeout (* appt-display-interval 60000) ;一直持续到下一次提醒
                          :title title
                          :body appt-msg
                          )
    (appt-disp-window min-to-appt current-time appt-msg)) ;同时也调用原有的提醒函数
  )

使用 appt 包完成消息提醒:

(use-package appt
  ;; org-agenda-to-appt函数可以把org-agenda中的预约信息同步给appt
  :hook ((after-init . (lambda ()
                         (appt-activate t)
                         ;; 每15分钟同步一次appt,并且现在就开始同步
                         (run-at-time nil 900 'org-agenda-to-appt)))
         (org-capture-after-finalize . org-agenda-to-appt))
  :custom
  (appt-display-format 'window);提醒出现的方式
  (appt-disp-window-function #'appt-disp-window-and-notification)
  (appt-message-warning-time 15)     ;在到期前15分钟提醒
  (appt-display-duration 30);提醒持续时间(秒)
  (appt-display-interval 5)       ;间隔5分钟提醒一次
  (appt-audible t)  ;声音提醒
  (appt-display-mode-line t);在状态栏显示时间(分钟)
  )

5 将提醒消息发送到操作系统

notifications 包可以将提醒消息发送到操作系统,linux 系统就可以在系统消息栏显示相应的消息。

(use-package notifications
  :ensure t)

6 主要的 agenda 视图

日程表视图是 org agenda 的主要视图,设置全局快捷键,方便调用 org-agenda-mode:

("C-c a" . org-agenda)

TODO 视图是 org agenda 的另一种主要视图,进入 TODO 视图用 org-agenda(C-c a t)。

7 org agenda 中常用操作

  • 修改后刷新: g
  • 移动光标: npL (将当前条目放在窗口正中)。
  • 翻页: forg-agenda-later ), borg-agenda-earlier ), jo0rg-agenda-goto-date )直接跳转到某个日期, .org-agenda-goto-today )跳到今天
  • 删除当前日程及其下属日程: C-k (org-agenda-kill)
  • 在另一个窗口中打开条目的所在位置: SPCorg-agenda-show-and-scroll-up ), TABorg-agenda-goto )还会将光标移动过去, RETorg-agenda-switch-to )还会把当前窗口给关掉, Forg-agenda-follow-mode )会切换“跟随”状态,使两个窗口的显示同步。
  • v 系列命令:按下 v 后,会弹出一个菜单,可以切换显示时间段,可以切换归档显示,也可以切换日志显示。

8 日历窗口操作

  • 日历相关:c(org-agenda-goto-calendar)在日历窗口和日程窗口间切换
  • i (org-agenda-diary-entry)系列命令:插入 diary
  • Morg-agenda-phases-of-moon )显示月蚀, Sorg-agenda-sunrise-sunset )显示日出日落(需要设置 calendar-latitude,calendar-longitude,calendar-location-name 等变量)
  • p 系列命令:用于显示某个日历, g o 会显示所有公历外的其他日历
  • g 系列命令:用于日期跳转
  • 移动命令: C-f, C-b, C-n, C-p, M-}calendar-forward-month ), M-{calendar-backward-month ), C-x ]calendar-forward-year ), C-x [calendar-backward-year )。
  • 移动命令: C-a, C-e (week); M-a, M-e (month); M-<, M-> (year)。
  • 卷动日历: C-x <, C-x >, C-v, M-v

9 番茄工作法

番茄工作法强调专注力,结合 org-mode 强大的任务处理能力,可以实现高效的时间管理。通常来说,一个番茄钟通常为 25 分钟的工作时间 + 5 分钟的休息时间,但这并不是绝对的。

9.1 基础配置

首先,建议进行一些基础配置,让时钟操作更便捷:

(use-package org-clock
  :ensure nil
  :custom
  (org-clock-rounding-minutes 25)       ;设置默认时钟报告时长为一个番茄时间(25分钟)
  (org-clock-in-switch-to-state "DOING") ;当时钟进入一个任务时,自动将任务状态改为 "DOING"
  (org-log-note-clock-out t)            ;在clock-out的时候提示输入记录文字
  )

9.2 基本工作流程

  1. 浏览任务列表,决定首先要完成哪个任务,将其状态从 TODO 改为 NEXT,这体现了“规划”,明确下一步任务。
  2. C-c C-x C-iorg-clock-in ),任务状态会自动变为 DOING,并且 org-mode 开始计时。
  3. 在接下来的 25 分钟内,专注于这个任务。
  4. 25 分钟后,按 C-c C-x C-oorg-clock-out )结束时钟,org-mode 会记录下起止时间和时间长度: [2024-01-15 周一 10:00]--[2024-01-15 周一 10:25] => 0:25
  5. 休息 5 分钟,按 C-c C-x C-xorg-clock-in-last )继续,或者选择一个新的 TODO 项,开始下一个番茄钟。

如果误操作打开一个计时,可以按 C-c C-x C-qorg-clock-cancel )取消计时。

如果中途被打断,可以按 C-c C-x C-jorg-clock-goto )跳转到正在计时的项目。

如果是在 org-agenda 界面,可以按 Iorg-clock-in ), Oorg-clock-out ), Jorg-agenda-clock-goto )。

9.3 生成时钟报告

可以查看在一天、一周或一个任务上花费了多少个番茄钟。

9.3.1 创建时钟表

在 .org 文件末尾,创建一个时钟表:

* 时钟报告
#+BEGIN: clocktable :maxlevel 3 :scope file :block today
#+END:

常用的时间范围包括:

  • :scope file :当前文件
  • :block today :今天
  • :block thisweek :本周
  • :scope subtree :仅当前任务及其子任务

9.3.2 更新报告

将光标放在时钟表 #+BEGIN: 行,按 C-c C-c 生成报告。报告会显示每个任务在指定时间范围内花费的总时间。

9.3.3 管理任务的时间预估

首先,按 C-c C-x eorg-set-effort )为任务设置 effort 属性,即预估任务所需时间(如 1:30 表示 1 个半小时)。

当任务正在计时的时候,如果估计时间发生变化,可以按 C-c C-x eorg-clock-modify-effort-estimate )修改努力量估算。修改的目的不是使实际用时与预估用时相符,而是修改因各种原因导致的预估错误。

通过自定义函数,可以在实际时间超过估算时自动提醒。

在时钟表添加 :effort t ,显示努力量与实际对比:

* 时钟报告
#+BEGIN: clocktable :maxlevel 3 :scope file :block today :effort t
#+CAPTION: 时钟汇总(含努力量估算)
#+END:

通过努力量估算,可以优化未来的时间估算能力,提高个人时间管理精度。

9.4 使用 org-pomodoro 包

org-pomodoro 不仅管理时钟,还管理休息和长休息时间,并提供通知,是在 org-mode 中应用番茄工作法的更好选择。

(use-package org-pomodoro
  :ensure t
  :config
  (setq org-pomodoro-length 25
        org-pomodoro-short-break-length 5
        org-pomodoro-long-break-length 15
        org-pomodoro-keep-killed-pomodoro-time t ;即使中断也记录时间
        ))

org-pomodoro 可以自动处理时钟的启动和停止,并与操作系统集成(在 macOS 上显示通知,在 Linux 上可以使用 libnotify 等)。

10 子弹笔记法

子弹笔记,是一种集笔记、任务列表、日记于一体的,高度可定制化的,以纸笔为核心的个人组织管理系统。它由纽约设计师赖德·卡罗尔创造,旨在帮助人们追踪“过去”,管理“现在”,并规划“未来”。它的核心魅力在于 极简高效灵活 。它不使用复杂的App或软件,而是回归最本质的纸和笔,通过一套简单的符号系统(“子弹”)来快速捕捉和组织信息。

子弹笔记有 3 种类型:任务、事件和笔记。

  • 任务 :需要做的事情。
  • 事件 :日程安排,有明确的日期和时间。
  • 笔记 :信息、事实、想法或灵感。

子弹笔记的基本流程:记录 -> 处理 -> 整合 -> 迁移 -> 反思。前述 org-mode 的配置基本体现了子弹笔记的思想:

  • 快速捕获( org-capture ):对应着子弹笔记随时记录的理念。
  • 关键字:Org-mode 的 TODO, DONE, SCHEDULED, DEADLINE 等关键字,就是电子化的“子弹符号”。
  • 模块化:Org-mode 项目文件( task.org ),对应着子弹笔记的每日记录、月度记录和未来记录等模块,在 org-mode 中,这几种记录不需要分开。 笔记文件( memo.org )则用于记录一些暂时不知道怎么处理或暂时没时间处理的信息。 journal.org 可用于对 agenda 的补充,记录一天中具体做了什么事,尤其是那些计划外或零散的活动,或者捕获稍纵即逝的灵感。
  • org-agenda 视图:对应子弹笔记的迁移。回顾每日记录、月度记录,检查未完成任务,都可以通过这个 agenda 视图来实现。

11 文件同步

定义全局快捷键:

("C-<f4>" . sync-with-jianguo)

如果是 linux 系统,则在 init 之后进行同步。

(when (eq system-type 'gnu/linux)
  (add-hook 'after-init-hook #'sync-with-jianguo))

定义同步函数,将本地日程文件与 webdav 同步:

;; 同步功能辅助函数
(defun get-file-modification-time (file)
  "安全地获取文件的修改时间。"
  (if (file-exists-p file)
      (file-attribute-modification-time (file-attributes file))
    '(0 0 0 0)))

(defun load-sync-info (sync-file)
  "加载同步信息文件。"
  (if (file-readable-p sync-file)
      (with-temp-buffer
        (insert-file-contents sync-file)
        (read (buffer-string)))
    '()))

(defun save-sync-info (sync-file to-sync)
  "保存同步信息到文件。"
  (with-temp-file sync-file 
    (print to-sync (current-buffer))))

(defun get-org-files (dir)
  "获取目录中所有的org和kdbx文件。"
  (seq-filter (lambda (f)
                (and (string-match "^.*\\.\\(?:org\\(?:_archive\\)?\\|kdbx\\)$" f)
                     (file-regular-p (expand-file-name f dir))))
              (directory-files dir)))

(defun calculate-sync-time (t-l t-r synced fn)
  "计算同步时间。"
  (let ((t-s (cadr (assoc fn synced))))
    (if t-s
        t-s
      (time-convert
       (time-add t-l
                 (floor (/ (float-time (time-subtract t-r t-l)) 2)))
       'list))))

(defun update-agenda-file (synced loc-dir rmt-dir fn)
  "同步单个议程文件。"
  (let* ((loc (expand-file-name fn loc-dir))
         (rmt (expand-file-name fn rmt-dir))
         (t-l (get-file-modification-time loc))
         (t-r (get-file-modification-time rmt))
         (t-s (calculate-sync-time t-l t-r synced fn))
         result)

    (setq result
          (cond
           ;; 双方都修改了文件
           ((and (time-less-p t-s t-r) (time-less-p t-s t-l))
            (error (format "%s: Both local and remote file have been changed since last sync!" fn)))
           ;; 本地文件较新
           ((time-less-p t-s t-l)
            (copy-file loc rmt t t)
            (list fn (get-file-modification-time rmt)))
           ;; 远程文件较新
           ((time-less-p t-s t-r)
            (copy-file rmt loc t t)
            (list fn (get-file-modification-time loc)))
           ;; 无需同步
           (t (list fn t-s))))

    ;; 调试信息
    (when (get-buffer "*sync-debug*")
      (with-current-buffer "*sync-debug*"
        (goto-char (point-max))
        (insert (format "%s: local=%s remote=%s sync=%s result=%s\n"
                        fn t-l t-r t-s result))))

    ;; 如果文件在缓冲区中打开,重新加载
    (when (get-buffer fn)
      (with-current-buffer fn 
        (revert-buffer nil t)))

    result))

(defun sync-with-jianguo ()
  "用于日历文件与坚果上的同步。"
  (interactive)
  (message "Start sync...")

  (condition-case err
      (with-timeout (5 nil)
        (let* ((loc-dir org-agenda-dir)
               (rmt-dir webdav-directory)
               (sync-file (expand-file-name "sync-info-jgy.el" org-directory))
               (all-files (delete-dups 
                           (append (get-org-files loc-dir)
                                   (get-org-files rmt-dir))))
               (synced-info (load-sync-info sync-file))
               to-sync)

          ;; 确保同步文件存在
          (unless (file-exists-p sync-file)
            (save-sync-info sync-file '()))

          (setq to-sync
                (mapcar (lambda (fn)
                          (update-agenda-file synced-info loc-dir rmt-dir fn))
                        all-files))

          (save-sync-info sync-file to-sync)
          (message "Sync succeeded. Processed %d files." (length all-files))))

    (error (message "Sync error: %s" err))
    (timeout (message "Sync timeout after 5 seconds"))))

12 附:最终配置文件

;;; init-org-agenda.el --- init for org-agenda mode. -*- lexical-binding: nil -*-
;;; Commentary:
;;; Code:

(defun my-diary-chinese-anniversary (lunar-month lunar-day &optional year mark)
  "干支纪年法,第一轮开始于公元前2637年。LUNAR-MONTH: 农历月;
LUNAR-DAY: 农历日; YEAR: 年; MARK: 高亮标记.  According to legend, the
Chinese calendar developed during the third millennium BC. It is said
to have been invented by the first legendary ruler, Huang Di or the
Yellow Emperor, who reigned, by tradition, c.2698-2599 BC. The fourth
legendary ruler, Emperor Yao, added the intercalary month. The 60-year
\"stem-branch\" cycle was first used to mark years during the first
century BC. Tradition fixes the first year of the first cycle (the
epoch) at 2637 BC. Thus the cycle beginning in 1984 is the 78th. Other
opinions fix the first year at 2697 BC (while Huangdi was still
immature), by which count we are now in cycle 79. "
  (if year
      (let* ((d-date (diary-make-date lunar-month lunar-day year))
             (a-date (calendar-absolute-from-gregorian d-date))
             (c-date (calendar-chinese-from-absolute a-date))
             (cycle (car c-date))
             (yy (cadr c-date))
             (y (+ (* 100 cycle) yy)))
        (diary-chinese-anniversary lunar-month lunar-day y mark))
    (diary-chinese-anniversary lunar-month lunar-day year mark)))


(defun diary-countdown (m1 d1 y1 n)
  "Reminder during the previous n days to the date.
Order of parameters is M1, D1, Y1, N if
`european-calendar-style' is nil, and D1, M1, Y1, N otherwise."
  (diary-remind '(diary-date m1 d1 y1)
                (let (value) (dotimes (number n value) (setq value (cons number value))))))


(defun org-days-to-relative-week (days-now)
  "定义另一套week number体系用于显示每学期的周数.
DAYS-NOW are days from absolute gregorian"
  (let* ((school-terms
          `((,(calendar-absolute-from-gregorian '(8 30 2021))
             . ,(calendar-absolute-from-gregorian '(1 16 2022)))
            (,(calendar-absolute-from-gregorian '(2 28 2022))
             . ,(calendar-absolute-from-gregorian '(7 6 2022)))
            (,(calendar-absolute-from-gregorian '(8 29 2022))
             . ,(calendar-absolute-from-gregorian '(1 6 2023)))
            (,(calendar-absolute-from-gregorian '(2 13 2023))
             . ,(calendar-absolute-from-gregorian '(6 30 2023)))
            (,(calendar-absolute-from-gregorian '(8 28 2023))
             . ,(calendar-absolute-from-gregorian '(1 12 2024)))
            (,(calendar-absolute-from-gregorian '(2 26 2024))
             . ,(calendar-absolute-from-gregorian '(7 5 2024)))
            (,(calendar-absolute-from-gregorian '(9 2 2024))
             . ,(calendar-absolute-from-gregorian '(1 11 2025)))
            (,(calendar-absolute-from-gregorian '(2 24 2025))
             . ,(calendar-absolute-from-gregorian '(7 5 2025)))
            (,(calendar-absolute-from-gregorian '(9 1 2025))
             . ,(calendar-absolute-from-gregorian '(1 10 2026)))
            (,(calendar-absolute-from-gregorian '(3 2 2026))
             . ,(calendar-absolute-from-gregorian '(7 11 2026)))))
         (x (seq-filter (lambda (p) (<= (car p) days-now (cdr p)))
                        school-terms)))
    (if x
        (1+ (/ (- days-now (caar x)) 7))
      x)))


(defconst cal-china-x-day-name
  ["初一" "初二" "初三" "初四" "初五" "初六" "初七" "初八" "初九" "初十"
   "十一" "十二" "十三" "十四" "十五" "十六" "十七" "十八" "十九"  "廿"
   "廿一" "廿二" "廿三" "廿四" "廿五" "廿六" "廿七" "廿八" "廿九" "三十"
   "卅一" "卅二" "卅三" "卅四" "卅五" "卅六" "卅七" "卅八" "卅九" "卅十"])
;;; 显示定制的周数.Week-agenda (w15) 这一行是不适合进行改造的,它可能是由 w15-w16这样的字符串构成。它可能是由 w15-w16这样的字符串构成,所以不符合改造要求;Monday 11 April 2022 W15:这一行是适合改造的,它只出现在每周第一天,即使在显示多个星期时也不会有问题。
(advice-add 'org-agenda-format-date-aligned
            :around
            (lambda (orig-fun date)
              (let* ((day-of-week (calendar-day-of-week date))
                     (custom-week (org-days-to-relative-week
                                   (calendar-absolute-from-gregorian date)))
                     (cn-date (calendar-chinese-from-absolute
                               (calendar-absolute-from-gregorian date)))
                     (cn-month (caddr cn-date))
                     (cn-day (cadddr cn-date))
                     (cn-month-string (concat (if (integerp cn-month) "" "闰")
                                              (aref calendar-chinese-month-name-array 
                                                    (1- (floor cn-month))))) 
                     (cn-day-string (aref cal-china-x-day-name 
                                          (1- cn-day))) 
                     (orig-str (funcall orig-fun date)))
                (if (and custom-week (= 1 day-of-week))
                    (format "%s | 第%02d周 农历%s%s" orig-str custom-week cn-month-string cn-day-string)
                  (format "%s 农历%s%s" orig-str cn-month-string cn-day-string)))))

;; 同步功能辅助函数
(defun get-file-modification-time (file)
  "安全地获取文件的修改时间。"
  (if (file-exists-p file)
      (file-attribute-modification-time (file-attributes file))
    '(0 0 0 0)))

(defun load-sync-info (sync-file)
  "加载同步信息文件。"
  (if (file-readable-p sync-file)
      (with-temp-buffer
        (insert-file-contents sync-file)
        (read (buffer-string)))
    '()))

(defun save-sync-info (sync-file to-sync)
  "保存同步信息到文件。"
  (with-temp-file sync-file 
    (print to-sync (current-buffer))))

(defun get-org-files (dir)
  "获取目录中所有的org和kdbx文件。"
  (seq-filter (lambda (f)
                (and (string-match "^.*\\.\\(?:org\\(?:_archive\\)?\\|kdbx\\)$" f)
                     (file-regular-p (expand-file-name f dir))))
              (directory-files dir)))

(defun calculate-sync-time (t-l t-r synced fn)
  "计算同步时间。"
  (let ((t-s (cadr (assoc fn synced))))
    (if t-s
        t-s
      (time-convert
       (time-add t-l
                 (floor (/ (float-time (time-subtract t-r t-l)) 2)))
       'list))))

(defun update-agenda-file (synced loc-dir rmt-dir fn)
  "同步单个议程文件。"
  (let* ((loc (expand-file-name fn loc-dir))
         (rmt (expand-file-name fn rmt-dir))
         (t-l (get-file-modification-time loc))
         (t-r (get-file-modification-time rmt))
         (t-s (calculate-sync-time t-l t-r synced fn))
         result)

    (setq result
          (cond
           ;; 双方都修改了文件
           ((and (time-less-p t-s t-r) (time-less-p t-s t-l))
            (error (format "%s: Both local and remote file have been changed since last sync!" fn)))
           ;; 本地文件较新
           ((time-less-p t-s t-l)
            (copy-file loc rmt t t)
            (list fn (get-file-modification-time rmt)))
           ;; 远程文件较新
           ((time-less-p t-s t-r)
            (copy-file rmt loc t t)
            (list fn (get-file-modification-time loc)))
           ;; 无需同步
           (t (list fn t-s))))

    ;; 调试信息
    (when (get-buffer "*sync-debug*")
      (with-current-buffer "*sync-debug*"
        (goto-char (point-max))
        (insert (format "%s: local=%s remote=%s sync=%s result=%s\n"
                        fn t-l t-r t-s result))))

    ;; 如果文件在缓冲区中打开,重新加载
    (when (get-buffer fn)
      (with-current-buffer fn 
        (revert-buffer nil t)))

    result))

(defun sync-with-jianguo ()
  "用于日历文件与坚果上的同步。"
  (interactive)
  (message "Start sync...")

  (condition-case err
      (with-timeout (5 nil)
        (let* ((loc-dir org-agenda-dir)
               (rmt-dir webdav-directory)
               (sync-file (expand-file-name "sync-info-jgy.el" org-directory))
               (all-files (delete-dups 
                           (append (get-org-files loc-dir)
                                   (get-org-files rmt-dir))))
               (synced-info (load-sync-info sync-file))
               to-sync)

          ;; 确保同步文件存在
          (unless (file-exists-p sync-file)
            (save-sync-info sync-file '()))

          (setq to-sync
                (mapcar (lambda (fn)
                          (update-agenda-file synced-info loc-dir rmt-dir fn))
                        all-files))

          (save-sync-info sync-file to-sync)
          (message "Sync succeeded. Processed %d files." (length all-files))))

    (error (message "Sync error: %s" err))
    (timeout (message "Sync timeout after 5 seconds"))))


(use-package org-agenda
  :bind
  (
   ("C-c a" . org-agenda)
   ("C-<f4>" . sync-with-jianguo)
   )
  :custom
  (org-agenda-skip-scheduled-if-done t) ;不显示已完成的scheduled
  (org-agenda-skip-deadline-if-done t) ;不显示已完成的deadline
  (org-agenda-window-setup 'reorganize-frame)
  (org-agenda-restore-windows-after-quit t)
  (calendar-latitude 30.5)
  (calendar-longitude 114.3)
  (calendar-location-name "Wuhan, China")
  (org-agenda-skip-additional-timestamps-same-entry nil)
  :preface
  (defconst org-agenda-files (list org-agenda-dir))
  :config
  (require 'solar)
  )

(use-package org-clock
  :ensure nil
  :custom
  (org-clock-rounding-minutes 25)       ;设置默认时钟报告时长为一个番茄时间(25分钟)
  (org-clock-in-switch-to-state "DOING") ;当时钟进入一个任务时,自动将任务状态改为 "DOING"
  (org-log-note-clock-out t)            ;在clock-out的时候提示输入记录文字
  )

(defun appt-disp-window-and-notification (min-to-appt current-time appt-msg)
  (let ((title (format "%s分钟内有新的任务" min-to-appt)))
    (notifications-notify :timeout (* appt-display-interval 60000) ;一直持续到下一次提醒
                          :title title
                          :body appt-msg
                          )
    (appt-disp-window min-to-appt current-time appt-msg)) ;同时也调用原有的提醒函数
  )

(use-package appt
  ;; org-agenda-to-appt函数可以把org-agenda中的预约信息同步给appt
  :hook ((after-init . (lambda ()
                         (appt-activate t)
                         ;; 每15分钟同步一次appt,并且现在就开始同步
                         (run-at-time nil 900 'org-agenda-to-appt)))
         (org-capture-after-finalize . org-agenda-to-appt))
  :custom
  (appt-display-format 'window);提醒出现的方式
  (appt-disp-window-function #'appt-disp-window-and-notification)
  (appt-message-warning-time 15)     ;在到期前15分钟提醒
  (appt-display-duration 30);提醒持续时间(秒)
  (appt-display-interval 5)       ;间隔5分钟提醒一次
  (appt-audible t)  ;声音提醒
  (appt-display-mode-line t);在状态栏显示时间(分钟)
  )

(use-package notifications
  :ensure t)

(when (eq system-type 'gnu/linux)
  (add-hook 'after-init-hook #'sync-with-jianguo))

(provide 'init-org-agenda)
;;; init-org-agenda.el ends here
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值