eh-org.el: org-super-agenda add :auto-eh-parent.
[emacs-helper.git] / eh-org.el
blob38ffe48a70ba8ac2324a19de9438286880df90c8
1 ;;; eh-org.el --- Tumashu's org-mode configuation -*- lexical-binding: t; -*-
3 ;; * Header
4 ;; Copyright (c) 2012-2016, Feng Shu
6 ;; Author: Feng Shu <tumashu@gmail.com>
7 ;; URL: https://github.com/tumashu/emacs-helper
8 ;; Version: 0.0.2
10 ;; This file is not part of GNU Emacs.
12 ;;; License:
14 ;; This program is free software; you can redistribute it and/or
15 ;; modify it under the terms of the GNU General Public License
16 ;; as published by the Free Software Foundation; either version 3
17 ;; of the License, or (at your option) any later version.
19 ;; This program is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs; see the file COPYING. If not, write to the
26 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
27 ;; Boston, MA 02110-1301, USA.
29 ;;; Commentary:
31 ;; * 简介 :README:
32 ;; 这个文件是tumashu个人专用的emacs配置文件,emacs中文用户可以参考。
34 ;;; Code:
36 ;; * 代码 :code:
38 ;; ** org
39 (require 'org)
40 (require 'org-attach)
41 (require 'org-archive)
43 (add-to-list 'auto-mode-alist '("\\.org$" . org-mode))
44 (add-to-list 'auto-mode-alist '("\\.org_archive$" . org-mode))
46 (defvar eh-org-directory
47 (expand-file-name
48 (or (cl-find-if #'file-exists-p
49 '("d:/org/"
50 "e:/org/"
51 "f:/org/"
52 "~/org/"
53 "~/storage/shared/org/"))
54 "~/org/")))
56 ;; 这个附件设置只适合我自己,千万别乱抄。
57 (setq org-attach-id-dir
58 (concat (file-name-as-directory
59 eh-org-directory)
60 "data/"))
62 (dolist (f '(org-open-file))
63 (advice-add f :around 'eh-find-file))
65 (setq org-todo-keywords
66 '((type "TODO(t)" "|" "DONE(d!)" "CANCELED(c!)")))
68 (setq org-tags-exclude-from-inheritance
69 '("proj"))
71 (setq org-tag-persistent-alist
72 '(("proj")
73 ("ref")
74 ("ATTACH")))
76 (setq org-stuck-projects
77 '("+proj/-DONE-CANCELED"
78 ("TODO")
79 nil ""))
81 (setq org-use-fast-tag-selection t)
82 (setq org-complete-tags-always-offer-all-agenda-tags t)
84 (defun eh-org-fast-tag-selection (current _inherited table &optional _todo-table)
85 (let* ((crm-separator "[ ]*[:,][ ]*")
86 (current-tags (cl-copy-list current))
87 (buf (current-buffer))
88 (n (length current-tags))
89 (max 5)
90 (prompt (if (> n 0)
91 (format "Tag (%s%s): "
92 (mapconcat #'identity
93 (cl-subseq current-tags 0 (min n max))
94 ", ")
95 (if (> n max)
96 " ..."
97 ""))
98 "Tag: "))
99 tab-tags tags)
101 (condition-case nil
102 (unless tab-tags
103 (setq tab-tags
104 (delq nil
105 (mapcar (lambda (x)
106 (let ((item (car-safe x)))
107 (and (stringp item)
108 (list item))))
109 (org--tag-add-to-alist
110 (with-current-buffer buf
111 (org-get-buffer-tags))
112 table))))))
114 (setq tags (completing-read-multiple
115 prompt (mapcar
116 (lambda (x)
117 (if (member (car x) current-tags)
118 (cons (propertize (car x) 'face '(:box t)) (cdr x))
120 tab-tags)))
122 (dolist (tg (delete-dups (remove "" tags)))
123 (when (string-match "\\S-" tg)
124 (if (member tg current-tags)
125 (setq current-tags (delete tg current-tags))
126 (push tg current-tags))))
127 (org-make-tag-string current-tags)))
129 (advice-add 'org-fast-tag-selection :override #'eh-org-fast-tag-selection)
131 (defun eh-org-end-of-id-line ()
132 (when (eq major-mode 'org-mode)
133 (org-back-to-heading t)
134 (org-id-get-create)
135 (search-forward ":ID:")
136 (end-of-line)
137 (org-fold-show-all '(drawers))))
139 (setq org-insert-heading-respect-content nil)
140 (setq org-log-done t)
141 (setq org-startup-indented nil)
142 (setq org-adapt-indentation 'headline-data)
143 (setq org-edit-src-content-indentation 0)
144 (setq org-id-link-to-org-use-id t)
145 (setq org-log-into-drawer t)
147 ;; org 文件显示内嵌图片的时候,首先缩放一下。
148 (setq org-image-actual-width t)
150 ;; 插入日期戳的命令不弹出日历表,太占地方。
151 (setq org-read-date-popup-calendar nil)
153 (defun eh-org-refile-agenda-files ()
154 (org-agenda-files t t))
156 (setq org-refile-targets
157 '((nil . (:maxlevel . 1))
158 (eh-org-refile-agenda-files . (:maxlevel . 1))))
160 (setq org-outline-path-complete-in-steps nil)
161 (setq org-refile-allow-creating-parent-nodes 'confirm)
162 (setq org-refile-use-outline-path 'file)
163 (setq org-refile-active-region-within-subtree t)
165 (defun eh-org-fill-paragraph ()
166 "Fill org paragraph"
167 (interactive)
168 (let ((fill-column 10000000))
169 (org-fill-paragraph)))
171 (defun eh-org-ctrl-c-ctrl-c (&optional arg)
172 "根据光标处内容,智能折行,比如,在表格中禁止折行。"
173 (interactive "P")
174 (let* ((context (org-element-context))
175 (type (org-element-type context)))
176 (pcase type
177 ((or `table `table-cell `table-row `item `plain-list)
178 (toggle-truncate-lines 1))
179 (_ (toggle-truncate-lines -1))))
180 (org-ctrl-c-ctrl-c arg))
182 (defun eh-org-smart-truncate-lines (&optional _arg)
183 (interactive)
184 (org-defkey org-mode-map "\C-c\C-c" 'eh-org-ctrl-c-ctrl-c))
186 (defun eh-org-visual-line-mode ()
187 (interactive)
188 (setq visual-line-fringe-indicators '(nil nil))
189 (visual-line-mode)
190 (if visual-line-mode
191 (setq word-wrap nil)))
193 (add-hook 'org-mode-hook 'eh-org-visual-line-mode)
194 (add-hook 'org-mode-hook 'eh-org-smart-truncate-lines)
196 (require 'autorevert)
197 (add-hook 'org-mode-hook #'turn-on-auto-revert-mode)
199 ;; (require 'org-protocol)
201 ;; ** org-export
202 (require 'ox-odt)
203 (require 'ox-org)
204 (require 'ox-ascii)
205 (require 'ox-md)
206 (require 'ox-html)
208 (setq org-export-default-language "zh-CN")
210 ;; org默认使用"_下标"来定义一个下标,使用"^上标"定义一个上标,
211 ;; 但这种方式在中文环境中与下划线冲突。
212 ;; 这里强制使用"_{下标}"来定义一个下标。"^{上标}"来定义一个上标。
213 (setq org-export-with-sub-superscripts '{})
214 (setq org-use-sub-superscripts '{})
216 ;;; ** export html
217 (setq org-html-coding-system 'utf-8)
218 (setq org-html-head-include-default-style t)
219 (setq org-html-head-include-scripts t)
220 (setq org-html-validation-link nil)
222 (defun eh-org-wash-text (text backend _info)
223 "导出 org file 时,删除中文之间不必要的空格。"
224 (when (org-export-derived-backend-p backend 'html)
225 (let ((regexp "[[:multibyte:]]")
226 (string text))
227 ;; org-mode 默认将一个换行符转换为空格,但中文不需要这个空格,删除。
228 (setq string
229 (replace-regexp-in-string
230 (format "\\(%s\\) *\n *\\(%s\\)" regexp regexp)
231 "\\1\\2" string))
232 ;; 删除粗体之后的空格
233 (dolist (str '("</b>" "</code>" "</del>" "</i>"))
234 (setq string
235 (replace-regexp-in-string
236 (format "\\(%s\\)\\(%s\\)[ ]+\\(%s\\)" regexp str regexp)
237 "\\1\\2\\3" string)))
238 ;; 删除粗体之前的空格
239 (dolist (str '("<b>" "<code>" "<del>" "<i>" "<span class=\"underline\">"))
240 (setq string
241 (replace-regexp-in-string
242 (format "\\(%s\\)[ ]+\\(%s\\)\\(%s\\)" regexp str regexp)
243 "\\1\\2\\3" string)))
244 string)))
246 (add-hook 'org-export-filter-headline-functions #'eh-org-wash-text)
247 (add-hook 'org-export-filter-paragraph-functions #'eh-org-wash-text)
249 ;; ** org-bable设置
250 (setq org-confirm-babel-evaluate nil)
251 (setq org-src-fontify-natively t)
253 (defun eh-org-show-babel-image ()
254 (when (not org-export-current-backend)
255 (org-display-inline-images)))
257 (add-hook 'org-babel-after-execute-hook #'eh-org-show-babel-image)
259 ;; *** org babel other modules
260 (require 'ob-org)
261 (require 'ob-emacs-lisp)
262 (require 'ob-python)
264 ;; ** org-archive
265 ;; 日常情况下,使用 ARCHIVE TAG 来隐藏已经完成的任务,安全又方便。
266 (setq org-archive-default-command 'org-archive-set-tag)
268 ;; ** org-attach
269 (setq org-attach-store-link-p 'attached)
270 (setq org-attach-sync-delete-empty-dir t)
272 (defun eh-org-attach-sync-all ()
273 (interactive)
274 (org-map-entries #'org-attach-sync)
275 (org-align-tags 'all))
277 (defun eh-org-attach-reveal ()
278 (interactive)
279 (let (marker)
280 (when (eq major-mode 'org-agenda-mode)
281 (setq marker (or (get-text-property (point) 'org-hd-marker)
282 (get-text-property (point) 'org-marker)))
283 (unless marker
284 (error "No task in current line")))
285 (save-excursion
286 (when marker
287 (set-buffer (marker-buffer marker))
288 (goto-char marker))
289 (org-back-to-heading t)
290 (call-interactively 'org-attach-reveal))))
292 (defun eh-org-attach-subtree ()
293 (interactive)
294 (when (yes-or-no-p "确定将 subtree 转移到 attach 目录中? ")
295 (org-back-to-heading t)
296 (let* ((case-fold-search nil)
297 (org-export-with-tags t)
298 (filename (expand-file-name
299 (concat
300 (org-element-property
301 :title (org-element-at-point))
303 (format-time-string "%Y%m%dT%H%M%S")
304 ".org")
305 (org-attach-dir t))))
306 (org-export-to-file 'org filename nil t)
307 (org-end-of-meta-data)
308 (delete-region (point) (org-end-of-subtree t)))))
310 ;; ** org-capture
311 (require 'org-capture)
312 (global-set-key (kbd "C-c c") 'org-capture)
314 (setq org-capture-templates
315 (let ((file (concat (file-name-as-directory eh-org-directory) "projects.org")))
316 `(("n" "Note" entry (file ,file)
317 "* %?
318 :PROPERTIES:
319 :created: %U
320 :END:
322 %i")
323 ("s" "Schedule" entry (file+headline ,file "待整理")
324 "* TODO %?
325 SCHEDULED: %t
326 :PROPERTIES:
327 :created: %U
328 :END:
330 %i")
331 ("d" "Deadline" entry (file+headline ,file "待整理")
332 "* TODO %?
333 DEADLINE: %t
334 :PROPERTIES:
335 :created: %U
336 :END:
338 %i"))))
340 (defun eh-org-capture-note ()
341 (interactive)
342 (org-capture nil "n"))
344 (defun eh-org-capture-schedule ()
345 (interactive)
346 (org-capture nil "s"))
348 (defun eh-org-capture-refresh-agenda (&rest _)
349 (when (eq major-mode 'org-agenda-mode)
350 (eh-org-agenda-redo-all)))
352 (advice-add 'org-capture-finalize :after #'eh-org-capture-refresh-agenda)
353 (advice-add 'org-capture-refile :after #'eh-org-capture-refresh-agenda)
355 ;; ** org-agenda
356 (require 'org-agenda)
358 (global-set-key (kbd "C-c a") 'org-agenda)
359 (define-key org-agenda-mode-map (kbd "SPC") 'org-agenda-switch-to)
360 (define-key org-agenda-mode-map (kbd "i") 'org-agenda-switch-to)
361 (define-key org-agenda-mode-map (kbd "g") 'eh-org-agenda-redo-all)
362 (define-key org-agenda-mode-map (kbd "A") 'org-agenda-archive-default-with-confirmation)
364 (defun eh-org-agenda-kill ()
365 (interactive)
366 (call-interactively #'org-agenda-kill)
367 (eh-org-agenda-redo-all))
369 (define-key org-agenda-mode-map (kbd "C-k") #'eh-org-agenda-kill)
370 (define-key org-agenda-mode-map (kbd "k") #'eh-org-agenda-kill)
371 (define-key org-agenda-mode-map (kbd "c") #'eh-org-capture-schedule)
372 (define-key org-agenda-mode-map (kbd "C") #'eh-org-capture-note)
374 (define-key org-agenda-mode-map (kbd "h") 'ignore)
375 (define-key org-agenda-mode-map (kbd "y") 'ignore)
376 (define-key org-agenda-mode-map (kbd "a") 'ignore)
378 ;; 取消下面关于 archive 的快捷键,容易误操作
379 (org-defkey org-agenda-mode-map "\C-c\C-x\C-a" 'ignore)
380 (org-defkey org-agenda-mode-map "\C-c\C-xa" 'ignore)
381 (org-defkey org-agenda-mode-map "\C-c\C-xA" 'ignore)
382 (org-defkey org-agenda-mode-map "\C-c\C-x\C-s" 'ignore)
383 (org-defkey org-agenda-mode-map "$" 'ignore)
385 ;; 加快 agenda 启动速度
386 (setq org-agenda-dim-blocked-tasks t)
387 (setq org-agenda-inhibit-startup t)
389 ;; 我更习惯类似 google 的搜索方式。
390 (setq org-agenda-search-view-always-boolean t)
391 (setq org-agenda-search-view-force-full-words nil)
393 (add-to-list 'org-agenda-files eh-org-directory t)
394 (add-to-list 'org-agenda-files (concat (file-name-as-directory eh-org-directory) "orgzly") t)
396 (when (file-writable-p "~")
397 (make-directory (concat (file-name-as-directory eh-org-directory) "orgzly") t))
399 (defun eh-revert-org-buffers ()
400 "Refreshes all opened org buffers."
401 (interactive)
402 (dolist (buf (buffer-list))
403 (with-current-buffer buf
404 (when (and (buffer-file-name)
405 (string-match-p "org$" (buffer-file-name))
406 (file-exists-p (buffer-file-name))
407 (not (buffer-modified-p)))
408 (revert-buffer t t t) )))
409 (message "Refreshed all opened org files."))
411 (defun eh-org-agenda-redo-all (&optional arg)
412 (interactive "P")
413 (eh-revert-org-buffers)
414 (funcall-interactively #'org-agenda-redo-all arg)
415 (message (substitute-command-keys
416 "刷新完成,记得按快捷键 '\\[org-save-all-org-buffers]' 来保存更改。")))
418 (setq org-agenda-span 'day)
419 (setq org-agenda-window-setup 'current-window)
420 (setq org-agenda-include-diary nil)
422 (setq org-agenda-todo-ignore-scheduled t)
423 (setq org-agenda-todo-ignore-deadlines t)
425 (setq org-agenda-todo-list-sublevels t)
426 (setq org-agenda-todo-ignore-scheduled t)
428 (setq org-agenda-breadcrumbs-separator " ~> ")
430 (setq org-agenda-prefix-format
431 '((agenda . " %i %-14:c%?-12t% s")
432 (todo . " %i %-14:c")
433 (tags . " %i %-14:c")
434 (search . " %i %-14:c")))
436 (setq org-agenda-format-date 'eh-org-agenda-format-date-aligned)
438 (defun eh-org-agenda-format-date-aligned (date)
439 (require 'cal-iso)
440 (let* ((dayname (calendar-day-name date))
441 (day (cadr date))
442 (day-of-week (calendar-day-of-week date))
443 (month (car date))
444 (year (nth 2 date))
445 (iso-week (org-days-to-iso-week
446 (calendar-absolute-from-gregorian date)))
447 (cn-date (calendar-chinese-from-absolute
448 (calendar-absolute-from-gregorian date)))
449 (cn-month (cl-caddr cn-date))
450 (cn-day (cl-cadddr cn-date))
451 (cn-month-name
452 ["正月" "二月" "三月" "四月" "五月" "六月"
453 "七月" "八月" "九月" "十月" "冬月" "腊月"])
454 (cn-day-name
455 ["初一" "初二" "初三" "初四" "初五" "初六" "初七" "初八" "初九" "初十"
456 "十一" "十二" "十三" "十四" "十五" "十六" "十七" "十八" "十九" "二十"
457 "廿一" "廿二" "廿三" "廿四" "廿五" "廿六" "廿七" "廿八" "廿九" "三十"
458 "卅一" "卅二" "卅三" "卅四" "卅五" "卅六" "卅七" "卅八" "卅九" "卅十"])
459 (extra (format "(%s%s%s%s%s)"
460 (if (or (eq org-agenda-current-span 'day)
461 (= day-of-week 1)
462 (= cn-day 1))
463 (aref cn-month-name (1- (floor cn-month)))
465 (if (or (= day-of-week 1)
466 (= cn-day 1))
467 (if (integerp cn-month) "" "[闰]")
469 (aref cn-day-name (1- cn-day))
470 (if (or (= day-of-week 1)
471 (eq org-agenda-current-span 'day))
472 (let ((holiday (mapconcat #'identity (calendar-check-holidays date) ", ")))
473 (if (> (length holiday) 0)
474 (concat ", " holiday)
475 ""))
477 (if (or (= day-of-week 1)
478 (eq org-agenda-current-span 'day))
479 (format ", 第%02d周" iso-week)
480 ""))))
481 (format "%04d-%02d-%02d %s %s"
482 year month day dayname extra)))
484 (defun eh-org-agenda-jump-to-first-item ()
485 ;; 用 (goto-char (point-min)) 不管用,我估计是切换 tab 的缘故。
486 (let ((window (get-buffer-window org-agenda-buffer)))
487 (when (windowp window)
488 (set-window-point window (point-min))
489 (org-agenda-next-item 1))))
491 (add-hook 'org-agenda-finalize-hook #'eh-org-agenda-jump-to-first-item 100)
493 ;; Org super agenda
494 (require 'org-super-agenda)
496 (org-super-agenda--def-auto-group eh-parent "their parent heading"
497 :key-form (org-super-agenda--when-with-marker-buffer (org-super-agenda--get-marker item)
498 (when (org-up-heading-safe)
499 (concat (org-entry-get nil "ITEM")
500 (if (string-suffix-p ".org_archive" (buffer-name))
501 "(归档)"
502 "")))))
504 (setq org-super-agenda-unmatched-name "未分组")
505 (setq org-super-agenda-groups
506 '((:name "Today" :time-grid t)
507 (:name "待整理" :tag "待整理")
508 (;; :auto-parent 和 :auto-outline-path 无法很好的处理一级
509 ;; headline, 这里首先将一级 headline 归类。
510 :name "(ROOT)"
511 :pred (lambda (item)
512 (org-super-agenda--when-with-marker-buffer
513 (org-super-agenda--get-marker item)
514 (equal (org-current-level) 1))))
515 (:auto-eh-parent t)))
517 (org-super-agenda-mode 1)
519 (cl-pushnew
520 '("a" "[org-super-agenda] Agenda for current week or day." agenda ""
521 ((org-agenda-remove-tags t)
522 (org-super-agenda-header-separator "\n")
523 (org-super-agenda-final-group-separator "\n")))
524 org-agenda-custom-commands)
526 ;; org ql
527 (require 'org-ql)
529 ;; 公文和协议
530 (defun eh-org-update-all-headlines ()
531 (interactive)
532 (org-map-entries #'eh-org-update-headline))
534 (defun eh-org-update-headline ()
535 (interactive)
536 (eh-org-update-gongwen-headline)
537 (eh-org-update-xieyi-headline))
539 (defun eh-org-update-gongwen-headline ()
540 (interactive)
541 (when (and (eq major-mode 'org-mode)
542 (equal (org-entry-get (point) "CATEGORY")
543 "党政机关公文"))
544 (let* ((title (org-entry-get (point) "标题"))
545 (daizi (or (org-entry-get (point) "发文机关代字") ""))
546 (year (or (org-entry-get (point) "年份") ""))
547 (num (or (org-entry-get (point) "发文顺序号") ""))
548 (data (or (org-entry-get (point) "成文日期") ""))
549 (organization (org-entry-get (point) "发文机关标志"))
550 (organization (if (= (length organization) 0)
551 "未知发文机关"
552 organization))
553 (zihao1 (org-entry-get (point) "发文字号"))
554 (zihao (cond ((and (> (length daizi) 0)
555 (> (length year) 0)
556 (> (length num) 0))
557 (format "%s〔%s〕%s号" daizi year num))
558 ;; 以前记录的一些公文信息,只记录字号,没有记录代字,年份和
559 ;; 顺序号。
560 ((> (length zihao1) 0)
561 (replace-regexp-in-string
562 (regexp-quote "]") "〕"
563 (replace-regexp-in-string
564 (regexp-quote "[") "〔"
565 zihao1)))
566 ((and (= (length daizi) 0)
567 (> (length data) 0))
568 (format "%s %s" organization data))
569 (t organization)))
570 (prefix "[公文]"))
571 (when (> (length title) 0)
572 (when (and title (string-match-p "《" title))
573 (setq title
574 (replace-regexp-in-string
575 "《" "〈"
576 (replace-regexp-in-string "》" "〉" title))))
577 (org-id-get-create)
578 (org-edit-headline
579 (format "%s《%s》(%s)" prefix title zihao))
580 (when (string-match-p "〔" zihao)
581 (org-set-property "发文字号" zihao))))))
583 (defun eh-org-update-xieyi-headline ()
584 (interactive)
585 (when (and (eq major-mode 'org-mode)
586 (equal (org-entry-get (point) "CATEGORY")
587 "协议或合同"))
588 (let* ((project (org-entry-get (point) "项目或服务名称"))
589 (weituo (or (org-entry-get (point) "委托方") ""))
590 (chengjie (or (org-entry-get (point) "承接方") ""))
591 (jiakuan (or (org-entry-get (point) "价款(元)") ""))
592 (date (org-entry-get (point) "签署日期"))
593 (prefix "[协议]"))
594 (when (> (length project) 0)
595 (org-id-get-create)
596 (org-edit-headline
597 (format "%s《%s》(%s %s %s %s)" prefix project weituo chengjie jiakuan date))))))
599 (add-hook 'write-file-functions #'eh-org-update-headline 100)
600 (add-hook 'org-capture-prepare-finalize-hook #'eh-org-update-headline 100)
602 ;; * Footer
603 (provide 'eh-org)
605 ;; Local Variables:
606 ;; coding: utf-8-unix
607 ;; End:
609 ;;; eh-org.el ends here