todo 和 文档还有项目的规划是在同一个语言体系(系统)下;个人很多todo 都是是在写规划或者文章时,所生产.过往的GTD 工具碰到的最难受的问题就是总是需要手工关联任务和文章
本文档作为 org 文件 提供,您可以在 Emacs 中加载它并通过C-c C-v C-t
(org-babel-tangle) 导出 custom-post.el,文件中包含本文档中所有 emacs 配置的 elisp 代码,可以将它放到你的Emacs配置目录中
准备工作:
- 手动下载一些 package
git clone [email protected]:kanglmf/emacs-chinese-word-segmentation.git ~/.emacs.d/site-lisp/chinese-word-segmentation/ #chinese-word-segmentation
注意事项
- TODO 关键字尽量不要使用中文, 因为
org-tag-view
无法支持中文 - TODO 关键字尽量不要使用
+/-
之类的符号,会导致org-tag-view
的快捷搜索功能(/TODO关键字
的搜索方式)无法搜索,只能使用TODO = "TODO关键字"
的搜索方式,也许转义能使用,但是我没找到转义字符时哪个
(setq org-todo-keywords
(quote ((sequence "TODO(t)" "IMMED(i)" "NEXT(n)" "|" "DONE(d@/!)")
(sequence "WAITING(w@/!)" "HANGUP(h@/!)" "|" "CANCELLED(c@/!)")))
org-use-fast-todo-selection t
org-treat-S-cursor-todo-selection-as-state-change nil
org-todo-state-tags-triggers (quote (("CANCELLED" ("CANCELLED" . t))
("WAITING" ("WAITING" . t))
("HANGUP" ("WAITING") ("HANGUP" . t))
(done ("WAITING") ("HANGUP"))
("TODO" ("WAITING") ("CANCELLED") ("HANGUP") ("IMMED"))
("NEXT" ("WAITING") ("CANCELLED") ("HANGUP") ("IMMED") )
("DONE" ("WAITING") ("CANCELLED") ("HANGUP") ("IMMED") )))
)
(setq org-modern-todo-faces
(quote (("TODO" :foreground (face-attribute 'font-lock-warning-face :foreground) :weight bold)
("NEXT" :foreground (face-attribute 'font-lock-function-name-face :foreground) :weight bold)
("DONE" :foreground (face-attribute 'font-lock-comment-face :foreground) :weight bold)
("CANCELLED" :foreground (face-attribute 'shadow :foreground)))))
;; 设置 stuck project ,参考:https://oomake.com/question/2338872
(setq org-stuck-projects
'("TODO={.+}/-DONE" nil nil "SCHEDULED:\\|DEADLINE:"))
;; (add-hook 'org-mode-hook
;; (lambda ()
;; (local-set-key (kbd "C-c i") 'org-capture-at-point)))
(defun org-capture-to-year-month-week-worklog (file)
"确保 Org 文件 FILE 中存在当前月份和周的标题结构.
此函数用于帮助管理工作日志,在指定的 Org 文件中创建
当前月份和周的标题。如果当前月份(如 '* sep.')或当前周
(如 '** W38 工作记录')的标题不存在,则会自动创建。
在创建周标题时,还会添加一个 Org 属性 \"Create\",记录
当前时间戳。
参数:
FILE -- 要管理的 Org 文件路径。
函数行为:
- 创建月份标题,格式为 '* <月份>.', 例如 '* sep.'。
- 创建周标题,格式为 '** W<周数> 工作记录', 例如 '** W38 工作记录'。
- 如果创建了周标题,会同时添加 Org 属性 \"Create\",值为当前日期,
格式为 '[YYYY-MM-DD]'。
示例:
(org-capture-to-year-month-week-worklog \"~/worklog.org\")"
(let* ((month (downcase (format-time-string "%b"))) ; 当前月份的英文缩写,例如 "sep"
(week (format-time-string "%V")) ; 当前年的第几周
(headline-month (format "* %s." month)) ; 月份 headline
(headline-week (format "** W%s 工作记录" week))) ; 周数 headline
(with-current-buffer (find-file-noselect file)
(goto-char (point-min))
(unless (search-forward headline-month nil t)
;; 如果月份 headline 不存在,则创建
(goto-char (point-max))
(insert (concat "\n" headline-month "\n")))
(unless (search-forward headline-week nil t)
;; 如果周数 headline 不存在,则在月份 headline 下创建
(goto-char (point-max))
(insert (concat headline-week " [0/0] \n"))
;; 设置属性 "Create" 为当前时间
(org-set-property "Create" (format-time-string "[%Y-%m-%d]"))
))
;; (list 'file+headline file (concat month " " week)) ; 返回 org-capture 的目标位置
))
(push '("w" "PLAN: 每周工作记录"
item (file+function "./worklog/2024.org" (lambda () (org-capture-to-year-month-week-worklog (concat org-directory "/worklog/2024.org"))))
"\n\n - %?" :tree-type week)
org-capture-templates)
- org-sidebar
官网
;;从 官网复制过来改的https://github.com/alphapapa/org-sidebar/blob/master/org-sidebar.el#L325 (defun my/org-sidebar-import (source-buffer) "Import TODO." (let ((display-buffer (generate-new-buffer (format "org-sidebar<%s>" (buffer-name source-buffer)))) (title (concat "Import items in: " (buffer-name source-buffer)))) (with-current-buffer display-buffer (setf org-sidebar-source-buffer source-buffer)) (save-window-excursion ;; `org-ql-search' displays the buffer, but we don't want to do that here. (org-ql-search source-buffer '(and (priority > "B") (not (done))) :narrow t :sort 'date :super-groups '((:auto-planning)) :buffer display-buffer :title title)) display-buffer))
- 不再使用 capture, 保留是为了有配置样例
(setq org-capture-templates `((;; 依据福格行为模型创建习惯:写每日计划 "l" "PLAN: 每日计划" entry (,(if emacs/>=27p 'file+olp+datetree 'file+datetree) ,(concat org-directory "/worklog/inbox.org")) "* TODO 计划 \nSCHEDULED:%U\n:PROPERTIES:\n:Create: %U\n:END:\n - [ ] %?" :tree-type week) (;; 有计划时间的叫计划 "s" "Task 有计划时间的TODO " entry (,(if emacs/>=27p 'file+olp+datetree 'file+datetree) ,(concat org-directory "/worklog/inbox.org")) "* TODO \%^{任务标题} \n:PROPERTIES:\n:Create: %U\n:END:\n%^{来源||来源:%a}\n%?" :tree-type week) ("r" "Notes" entry (,(if emacs/>=27p 'file+olp+datetree 'file+datetree) ,(concat org-directory "/worklog/inbox.org")) "* %^{标题} :NOTE:%^g \n :PROPERTIES:\n:Create: %u\n:END: \n%a\n " :tree-type week :jump-to-captured t) ("h" "Headline 任意地方插入带 :Create: 的 headline" entry (,(if emacs/>=27p 'file+olp+datetree 'file+datetree) ,(concat org-directory "/worklog/inbox.org")) "* %^{标题} \n :PROPERTIES:\n:Create: %u\n:END: \n%?\n " :jump-to-captured t) ("g" "预留的组") ("gi" "Idea" entry (file ,(concat org-directory "/idea.org")) "* %^{Title} %?\n%U\n%a\n") ("gb" "Book" entry (,(if emacs/>=27p 'file+olp+datetree 'file+datetree) ,(concat org-directory "/reading-notes.org")) "* Topic: %^{Description} %^g %? Added: %U") ("gp" "Create Project 保留原因:template 创建文件" plain (file ref/create-org-file ) "#+STARTUP: content \n\n* %^{项目名称}\n %? " :jump-to-captured) ("t" "工具组") ("tw" "文档书写工具" entry (file ,(concat org-directory "/idea.org")) "* %^{Title} %?\n%U\n%a\n") ))
org-mode 的使用流程主要用于 GTD+个人 KB (第二知识库)
agenda 设置会使用到到插件 org-ql,org-super-agenda,org-ql是 通过查询语句找到符合条件的TODO,org-super-agenda 是给传入的TODO 列表进行分类 ;; 从 org-mode xx 复制过来的
(defvar bh/hide-scheduled-and-waiting-next-tasks t)
(defun bh/find-project-task ()
"Move point to the parent (project) task if any"
(save-restriction
(widen)
(let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point))))
(while (org-up-heading-safe)
(when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
(setq parent-task (point))))
(goto-char parent-task)
parent-task)))
(defun bh/is-project-p ()
"Any task with a todo keyword subtask"
(save-restriction
(widen)
(let ((has-subtask)
(subtree-end (save-excursion (org-end-of-subtree t)))
(is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
(save-excursion
(forward-line 1)
(while (and (not has-subtask)
(< (point) subtree-end)
(re-search-forward "^\*+ " subtree-end t))
(when (member (org-get-todo-state) org-todo-keywords-1)
(setq has-subtask t))))
(and is-a-task has-subtask))))
(defun bh/is-project-subtree-p ()
"Any task with a todo keyword that is in a project subtree.
Callers of this function already widen the buffer view."
(let ((task (save-excursion (org-back-to-heading 'invisible-ok)
(point))))
(save-excursion
(bh/find-project-task)
(if (equal (point) task)
nil
t))))
(defun bh/is-task-p ()
"Any task with a todo keyword and no subtask"
(save-restriction
(widen)
(let ((has-subtask)
(subtree-end (save-excursion (org-end-of-subtree t)))
(is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
(save-excursion
(forward-line 1)
(while (and (not has-subtask)
(< (point) subtree-end)
(re-search-forward "^\*+ " subtree-end t))
(when (member (org-get-todo-state) org-todo-keywords-1)
(setq has-subtask t))))
(and is-a-task (not has-subtask)))))
(defun bh/is-subproject-p ()
"Any task which is a subtask of another project"
(let ((is-subproject)
(is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
(save-excursion
(while (and (not is-subproject) (org-up-heading-safe))
(when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
(setq is-subproject t))))
(and is-a-task is-subproject)))
(defun bh/list-sublevels-for-projects-indented ()
"Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
This is normally used by skipping functions where this variable is already local to the agenda."
(if (marker-buffer org-agenda-restrict-begin)
(setq org-tags-match-list-sublevels 'indented)
(setq org-tags-match-list-sublevels nil))
nil)
(defun bh/list-sublevels-for-projects ()
"Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
This is normally used by skipping functions where this variable is already local to the agenda."
(if (marker-buffer org-agenda-restrict-begin)
(setq org-tags-match-list-sublevels t)
(setq org-tags-match-list-sublevels nil))
nil)
(defvar bh/hide-scheduled-and-waiting-next-tasks t)
(defun bh/toggle-next-task-display ()
(interactive)
(setq bh/hide-scheduled-and-waiting-next-tasks (not bh/hide-scheduled-and-waiting-next-tasks))
(when (equal major-mode 'org-agenda-mode)
(org-agenda-redo))
(message "%s WAITING and SCHEDULED NEXT Tasks" (if bh/hide-scheduled-and-waiting-next-tasks "Hide" "Show")))
(defun bh/skip-stuck-projects ()
"Skip trees that are stuck projects"
(save-restriction
(widen)
(let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(if (bh/is-project-p)
(let* ((subtree-end (save-excursion (org-end-of-subtree t)))
(has-next ))
(save-excursion
(forward-line 1)
(while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
(unless (member "WAITING" (org-get-tags-at))
(setq has-next t))))
(if has-next
nil
next-headline)) ; a stuck project, has subtasks but no next task
nil))))
(defun bh/skip-non-stuck-projects ()
"Skip trees that are not stuck projects"
;; (bh/list-sublevels-for-projects-indented)
(save-restriction
(widen)
(let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(if (bh/is-project-p)
(let* ((subtree-end (save-excursion (org-end-of-subtree t)))
(has-next ))
(save-excursion
(forward-line 1)
(while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
(unless (member "WAITING" (org-get-tags-at))
(setq has-next t))))
(if has-next
next-headline
nil)) ; a stuck project, has subtasks but no next task
next-headline))))
(defun bh/skip-non-projects ()
"Skip trees that are not projects"
;; (bh/list-sublevels-for-projects-indented)
(if (save-excursion (bh/skip-non-stuck-projects))
(save-restriction
(widen)
(let ((subtree-end (save-excursion (org-end-of-subtree t))))
(cond
((bh/is-project-p)
nil)
((and (bh/is-project-subtree-p) (not (bh/is-task-p)))
nil)
(t
subtree-end))))
(save-excursion (org-end-of-subtree t))))
(defun bh/skip-non-tasks ()
"Show non-project tasks.
Skip project and sub-project tasks, habits, and project related tasks."
(save-restriction
(widen)
(let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(cond
((bh/is-task-p)
nil)
(t
next-headline)))))
(defun bh/skip-project-tasks ()
"Show non-project tasks.
Skip project and sub-project tasks, habits, and project related tasks."
(save-restriction
(widen)
(let* ((subtree-end (save-excursion (org-end-of-subtree t))))
(cond
((bh/is-project-p)
subtree-end)
;; ((org-is-habit-p)
;; subtree-end)
((bh/is-project-subtree-p)
subtree-end)
(t
nil)))))
(defun bh/skip-non-project-tasks ()
"Show project tasks.
Skip project and sub-project tasks, habits, and loose non-project tasks."
(save-restriction
(widen)
(let* ((subtree-end (save-excursion (org-end-of-subtree t)))
(next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(cond
((bh/is-project-p)
(let* ((has-next ))
(save-excursion
(forward-line 1)
(while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
(unless (member "WAITING" (org-get-tags-at))
(setq has-next t))))
(if has-next
next-headline
subtree-end))
)
;; ((org-is-habit-p)
;; subtree-end)
((and (bh/is-project-subtree-p)
(member (org-get-todo-state) (list "NEXT")))
subtree-end)
((not (bh/is-project-subtree-p))
subtree-end)
(t
nil)))))
(defun bh/skip-non-archivable-tasks ()
"Skip trees that are not available for archiving"
(save-restriction
(widen)
;; Consider only tasks with done todo headings as archivable candidates
(let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))
(subtree-end (save-excursion (org-end-of-subtree t))))
(if (member (org-get-todo-state) org-todo-keywords-1)
(if (member (org-get-todo-state) org-done-keywords)
(let* ((daynr (string-to-number (format-time-string "%d" (current-time))))
(a-month-ago (* 60 60 24 (+ daynr 1)))
(last-month (format-time-string "%Y-%m-" (time-subtract (current-time) (seconds-to-time a-month-ago))))
(this-month (format-time-string "%Y-%m-" (current-time)))
(subtree-is-current (save-excursion
(forward-line 1)
(and (< (point) subtree-end)
(re-search-forward (concat last-month "\\|" this-month) subtree-end t)))))
(if subtree-is-current
subtree-end ; Has a date in this month or last month, skip it
nil)) ; available to archive
(or subtree-end (point-max)))
next-headline))))
;; Do not dim blocked tasks
(setq org-agenda-dim-blocked-tasks nil)
;; Compact the block agenda view
(setq org-agenda-compact-blocks t)
(defun bh/org-auto-exclude-function (tag)
"Automatic task exclusion in the agenda with / RET"
(and (cond
((string= tag "hold")
t)
((string= tag "test")
t)
((string= tag "#life")
t))
(concat "-" tag)))
(setq org-agenda-auto-exclude-function 'bh/org-auto-exclude-function)
;;;;;;;;;;;;;;;;;;;;;;;;;;; Agenda configuration
;; recursively add org file
(setq org-agenda-files (directory-files-recursively centaur-org-directory "^[^\\.][^#].*\\.org$"))
(setq org-agenda-files (append org-agenda-files '("~/.emacs.d/custom-post.org")))
;; Custom agenda command definitions
(setq org-agenda-custom-commands
(quote (("n" "Notes" tags "NOTE+CATEGORY=\"inbox\"-TODO=\"DONE\"-TODO=\"CANCELLED\""
((org-agenda-overriding-header "Notes")
(org-tags-match-list-sublevels t)))
("g" "Agenda"
((agenda "" nil)
(tags "REFILE"
((org-agenda-overriding-header "Tasks to Refile")
(org-tags-match-list-sublevels nil)))
(tags-todo "-CANCELLED/!"
((org-agenda-overriding-header "Stuck Projects")
(org-agenda-skip-function 'bh/skip-non-stuck-projects)
(org-agenda-sorting-strategy
'(category-keep))))
(tags-todo "-HANGUP-CANCELLED/!"
((org-agenda-overriding-header "Projects")
(org-agenda-skip-function 'bh/skip-non-projects)
(org-tags-match-list-sublevels 'indented)
(org-agenda-sorting-strategy
'(category-keep))))
(tags-todo "-CANCELLED/!NEXT"
((org-agenda-overriding-header (concat "Project Next Tasks"
(if bh/hide-scheduled-and-waiting-next-tasks
""
" (including WAITING and SCHEDULED tasks)")))
(org-agenda-skip-function 'bh/skip-projects-and-habits-and-single-tasks)
(org-tags-match-list-sublevels t)
(org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
(org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
(org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
(org-agenda-auto-exclude-function 'bh/org-auto-exclude-function)
(org-agenda-sorting-strategy
'(todo-state-down effort-up category-keep))))
(tags-todo "-REFILE-CANCELLED-WAITING-HANGUP/!"
((org-agenda-overriding-header (concat "Project Subtasks"
(if bh/hide-scheduled-and-waiting-next-tasks
""
" (including WAITING and SCHEDULED tasks)")))
(org-agenda-skip-function 'bh/skip-non-project-tasks)
(org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
(org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
(org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
(org-agenda-sorting-strategy
'(category-keep))))
(tags-todo "-REFILE-CANCELLED-WAITING-HANGUP/!+TODO"
((org-agenda-overriding-header (concat "Standalone Tasks"
(if bh/hide-scheduled-and-waiting-next-tasks
""
" (including WAITING and SCHEDULED tasks)")))
(org-agenda-skip-function 'bh/skip-project-tasks)
(org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
(org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)
(org-agenda-todo-ignore-with-date bh/hide-scheduled-and-waiting-next-tasks)
(org-agenda-sorting-strategy
'(category-keep))))
(tags-todo "-CANCELLED+WAITING|HANGUP/!"
((org-agenda-overriding-header (concat "Waiting and Postponed Tasks"
(if bh/hide-scheduled-and-waiting-next-tasks
""
" (including WAITING and SCHEDULED tasks)")))
(org-agenda-skip-function 'bh/skip-non-tasks)
(org-tags-match-list-sublevels nil)
(org-agenda-todo-ignore-scheduled bh/hide-scheduled-and-waiting-next-tasks)
(org-agenda-todo-ignore-deadlines bh/hide-scheduled-and-waiting-next-tasks)))
(tags "-REFILE/"
((org-agenda-overriding-header "Tasks to Archive")
(org-agenda-skip-function 'bh/skip-non-archivable-tasks)
(org-tags-match-list-sublevels nil))))
nil))))
(use-package org-super-agenda
:ensure t ;Auto-install the package from Melpa (optional)
:after (org)
:config
(org-super-agenda-mode)
)
(setq org-agenda-custom-commands
'(("c" "TODO 列表"
((tags-todo "-TODO=\"DONE\"-TODO=\"CANCELLED\"-test"
((org-agenda-overriding-header "All TODOs"))))
((org-super-agenda-groups
'((:name "MQ Tasks"
:tag "MQ")
(:name "Immediately Tasks"
:todo "IMMED")
(:name "Overtime"
:deadline past)
(:name "High Priority Tasks"
:priority "A")
(:name "Other Tasks"
:discard (:tag "MQ"))))))))
(setq org-tag-alist '((:startgroup)
("#work" . ?w) ("#life" . ?h)
(:endgroup )
;; work
("项目" . nil) ("故障" . nil) ("产品" . nil)
;;GTD
("记录" . nil)
("REVIEW" . ?r);; 回顾,验收前期结果
("Retro" . nil) ;; 回顾,专注流程和持续流程改进
("元数据" . ?m)
("NOTE" . ?n)
("PRIVATE" . ?p) ;; 需要隐藏的信息
;; 任务标志
("MQ" . nil)
))
;; 不希望子节点继承的的列表
(setq org-tags-exclude-from-inheritance (quote("日常" "MQ" )))
(setq org-complete-tags-always-offer-all-agenda-tags t)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; config keyboard shortcuts
;;;;;;;;;;;;;;;;;;;;;;;;;;;; roam configs
(use-package org-roam
:ensure t
:after org
:custom
(org-roam-directory (file-truename centaur-org-directory ))
:bind (("C-c n l" . org-roam-buffer-toggle)
("C-c n f" . org-roam-node-find)
("C-c n g" . org-roam-graph)
("C-c n i" . org-roam-node-insert)
("C-c n c" . org-roam-capture)
;; Dailies
("C-c n j" . org-roam-dailies-capture-today))
:config
;; If you're using a vertical completion framework, you might want a more informative completion interface
(setq org-roam-node-display-template (concat "${title:*} " (propertize "${tags:10}" 'face 'org-tag)))
(org-roam-db-autosync-mode)
;; If using org-roam-protocol
(require 'org-roam-protocol))
(use-package org-roam-ui
:ensure t
:after org-roam
:config
(setq org-roam-ui-sync-theme t
org-roam-ui-follow t
org-roam-ui-update-on-save t
org-roam-ui-open-on-start t)
)
(setq org-roam-capture-templates
'(
(;; 依据福格行为模型创建习惯:写每日计划
"l" "PLAN: 每日计划" entry (,(if emacs/>=27p 'file+olp+datetree 'file+datetree)
,(concat org-directory "worklog/inbox.org"))
"* TODO 计划 \nSCHEDULED:%U\n:PROPERTIES:\n:Create: %U\n:END:\n - [ ] %?" :tree-type week)
("1" "Person" entry "\n\n* ${title}\n :PROPERTIES: \n :ID: %(org-id-uuid)\n :company: %^{公司}\n:END:\n%?"
:target (file+head "person/_index.org"
"* ${title}"))
("2" "Project" entry "* ${title}\n :PROPERTIES: \n :ID: %(org-id-uuid)\n :项目经理: %^{项目经理} \n :区域: %^{区域}\n :END: \n %?"
:target (file+olp "project/项目信息.org" ("Projects")))
("p" "new post" plain (function org-roam-capture--get-point)
"%?"
:file-name "${slug}/_index"
:head "#+title: ${title}\n#+date: %<%Y-%m-%d>\n\n#+roam_alias:${title}\n\n#+hugo_section: posts/${slug}\n#+hugo_base_dir: ../.QL.\n\n%?")
))
;; 配置 dired
;; 当在 dired 中删除文件时,需要同步更新 org-agenda-files
(defun update-org-agenda-files-after-delete (file-to-delete &rest args)
"Update `org-agenda-files` after a file has been deleted."
(let ((abbreviated-file (abbreviate-file-name file-to-delete)))
(when (member abbreviated-file org-agenda-files)
(setq org-agenda-files (delete abbreviated-file org-agenda-files))
(message "Removed '%s' from org-agenda-files" abbreviated-file))))
(advice-add 'dired-delete-file :after #'update-org-agenda-files-after-delete)
(put 'dired-find-alternate-file 'disabled nil)
(with-eval-after-load 'dired
;; 在 dired 列表中,忽略obsidian 和 organice 的文件
(setq dired-omit-files
(concat dired-omit-files
"\\|^.obsidian*\\|\\.organice-bak$"))
;; dire 初始化的配置中, 会隐藏当前目录和上级目录,导致目录跳转不方便,因此放出来
(setq dired-omit-files(replace-regexp-in-string
"\\\\`\\[\\.\\]\\[\\.\\]\\?\\\\'\\\\\|" "" dired-omit-files))
;; 修改 dired 展示样式 https://oremacs.com/2015/01/13/dired-options/
;; (setq dired-listing-switches "-laGh1v")
;; 使用系统默认程序打开 xlsx
(add-to-list 'dired-guess-shell-alist-user '("\\.xlsx\\'" "open") t)
)
(org-add-link-type
"tag" 'endless/follow-tag-link)
(defun endless/follow-tag-link (tag)
"Display a list of TODO headlines with tag TAG.
With prefix argument, also display headlines without a TODO keyword."
(org-tags-view (null current-prefix-arg) tag))
orgmode 自带的查询方式:Matching tags and properties, org-mode 作为结构化的文本格式,每个 headline 拥有很多属性,因此查询条件也很丰富。但是 org-mode 自身提供的查询方式语法复杂,改为使用 org-ql
(use-package org-ql
:ensure t
:after org
:config
(setq org-ql-ask-unsafe-queries nil))
(use-package helm-org-ql
:after (org org-ql)
:ensure t)
(defun ref/org-ql-sort-by-create-desc (a b)
"Sort headline by :CREATE: property, headlines without :CREATE: property are sorted last, and newer dates come first."
(let ((a-create (org-element-property :CREATE a))
(b-create (org-element-property :CREATE b)))
(cond ((and a-create b-create)
(string> a-create b-create))
((and (not a-create) b-create)
nil)
((and a-create (not b-create))
t)
((and (not a-create) (not b-create))
nil))))
- State “DONE” from “NEXT” [2021-02-09 Tue 11:52]
完成:能 refile ,并在refile中能挑选出所有我需要的target
将 Headline 移到另一个 headline 中,可以是本地的headline 也可以是其它文件的headline
- Manual
- Organizing Notes With Refile
- The main thing you can configure about Refile is where the target list comes from and how it is presented.
- 默认配置中,refile 仅列出 Org Buferr 中的文件的一级 Heading
- outline形式列出所有的 Heading
(setq org-refile-targets '((org-agenda-files :maxlevel . 5))
org-refile-use-outline-path 'file
org-outline-path-complete-in-steps nil
org-refile-allow-creating-parent-nodes 'confirm)
使用 org-mode 维护代码的好处是,树状结构人读比较清晰,文档齐全且跟代码同时更新
- 如何编辑(因为 org-mode lsp 不是具体的编程语言)
在代码块执行 org-edit-special(
C-c '
)
存储的需要考虑的内容
- 如果 org file 可能移动,因此不能使用相对路径
- 需要 ox-hugo 能支持的方式,否则生成的 blog 无法展示图片
目的:建立一个 static 的附件目录,并保障 org-directory 下不论哪一级的 org 文件引用 static 中的文件时都是以 org-directory 为相对路径,这样做的好处是,不论 org 文件本身怎么移动,只要是引用的 static/ 下的文件,是一直能找到的 通过两个函数实现以上目的
org-link-file-path-type
是控制着 orgmode 存储 url 的行为,通过ref/org-static-file-link
存储时的是 org-directory 的相对路径,- org-open-at-point-functions 是控制着 orgmode 打开 url 的行为,通过
ref/org-custom-open-file
控制打开 ./static/ 下的文件时是找的 org-directory 的下的文件
(defun ref/org-static-file-link (path)
"如果 PATH 的绝对路径位于 `org-directory/static/` 下,返回从 static 以及之后的部分;否则返回原始 PATH。"
(let* ((org-static-dir (expand-file-name (file-name-as-directory (concat (file-name-as-directory org-directory) "static"))))
(absolute-path (expand-file-name path)))
(print (format "statis %s is abs % sub? %s" org-static-dir absolute-path (string-prefix-p org-static-dir absolute-path) ) )
;;(print org-static-dir)
;; (print absolute-path)
(if (string-prefix-p org-static-dir absolute-path)
;; 如果 absolute-path 位于 org-static-dir 下,则返回从 "static" 开始的部分
(concat "./static/" (substring absolute-path (length org-static-dir)))
;; 否则返回原始的 path
path)))
(setq org-link-file-path-type 'ref/org-static-file-link)
(defun ref/org-custom-open-file ()
"Open a relative file path by converting it based on org-directory."
;; 获取当前光标下的链接元素
(let* ((element (org-element-context))
(link-type (org-element-property :type element))
(path (org-element-property :path element)))
;; 确保这是一个 file 类型的链接
(when (and (eq (org-element-type element) 'link)
(string-equal link-type "file")
(not (file-name-absolute-p path))
(string-match-p "/static/" path)) ; 检查是否为相对路径
;; 转换相对路径到 org-directory
(let ((new-path (expand-file-name path
org-directory)))
;; 如果新路径存在,打开文件并返回 t 阻止其他的打开操作
(message "ref/org-custom-open-file: change url(%s) to (%s) " path new-path)
(org-open-file new-path)
t
)))) ; 返回 t 阻止其他 handlers
;; 将自定义函数添加到 org-open-at-point-functions 钩子中
(add-hook 'org-open-at-point-functions #'ref/org-custom-open-file)
- org-transclusion
类似于飞书文档的块应用
(use-package org-transclusion :ensure t :init (with-eval-after-load 'pyim ;; 在 transclusion 范围内默认切换英文输入 (add-to-list 'pyim-english-input-switch-functions 'org-transclusion-within-transclusion-p) ) )
(defun org-capture-at-point ()
"Insert an org capture template at point."
(interactive)
(org-capture 0 "h"))
(defun gtd-today-plan ()
"GTD-Flow: Today's plan."
(interactive)
(org-capture 1 "l"))
(define-key org-mode-map (kbd "M-g i") #'org-capture-at-point)
(define-key org-mode-map (kbd "M-g p") #'gtd-today-plan)
- snippets 配置
(push (concat org-directory "/.config/snippets") yas-snippet-dirs ) (yas-reload-all)
- 我的org文件目录除了存放TODO ,还存放知识,因此不能每次回顾所有的文件,而必须明确的列出那些是必须回顾的项目
方案:
所有的项目放在project/下,每次去扫目录下文件,完成的项目archieve: 文件太多- 为所有的项目标记 :项目: TAG ,并且有项目自身的状态流程 标签: :项目: 状态流程: 售前(PreSale)/交付中(InDelivery)/被阻塞[HOLD(h@/!)]/尾期(End)/终止(Terminated)/结束[DONE(@/!)]
(add-to-list 'org-todo-keywords '(sequence "PreSale(s)" "InDelivery(j)" "HANGUP(h@/!)" "End(e@/!)" "|" "Terminated(@/!)" "DONE(@/!)"))
(setq org-todo-state-tags-triggers (append '(("PreSale" ("项目" . t) ("WAITING") ("CANCELLED") ("HANGUP") )
("Terminated" ("项目" . t) )
("InDelivery" ("项目" . t) ("WAITING") ("CANCELLED") ("HANGUP")))
org-todo-state-tags-triggers))
(setq org-tags-column -50)
;; visual alignment for Org Mode, Markdown and table.el tables on GUI Emacs.
;; ref: https://emacs-china.org/t/org-mode/13248
(use-package ox-hugo
:ensure t ;Auto-install the package from Melpa (optional)
:after (org ox))
(setq org-id-extra-files (directory-files-recursively org-roam-directory "\.org$"))
(with-eval-after-load 'org
(defun org-link-evernote-export-link (link desc format)
"Create export version of LINK and DESC to FORMAT."
(let ((link (concat "evernote:" link)))
(cond
((eq format 'html)
(format "<a href=\"%s\">%s</a>" link desc))
((eq format 'latex)
(format "\\href{%s}{%s}" link desc))
(t ;`ascii', `md', `hugo', etc.
(format "[%s](%s)" desc link)))))
(org-link-set-parameters "evernote" :export #'org-link-evernote-export-link))
- 简易版本 symbol 跳转和高亮
高亮定义(也可以是单词),并可在当前 buffer 中跳转,symbol-overlay
;; 本部分在init-highlight 中定义,因此 tangle=no ("M-i" . symbol-overlay-put) ("M-n" . symbol-overlay-jump-next) ("M-p" . symbol-overlay-jump-prev) ("M-N" . symbol-overlay-switch-forward) ("M-P" . symbol-overlay-switch-backward) ("M-C" . symbol-overlay-remove-all) ([M-f3] . symbol-overlay-remove-all)
speed-command: 当在 headline 的行首时,可以使用 speed 快捷指令。 只定义了常用的几个,1-5是参考 ithougt 的快捷键配置,用处是展示当前 level 下的第 N 级 headline
;; 使用 org-speed-commands
(custom-set-variables
'(org-speed-commands
'(("Outline Navigation")
("n" org-speed-move-safe 'org-next-visible-heading)
("p" org-speed-move-safe 'org-previous-visible-heading)
("f" org-speed-move-safe 'org-forward-heading-same-level)
("b" org-speed-move-safe 'org-backward-heading-same-level)
("F" . org-next-block)
("B" . org-previous-block)
("u" org-speed-move-safe 'outline-up-heading)
("j" . org-goto)
("g" org-refile
'(4))
("Outline Visibility")
("c" . org-cycle)
("C" . org-shifttab)
(" " . org-display-outline-path)
("s" . org-toggle-narrow-to-subtree)
("k" . org-cut-subtree)
("=" . org-columns)
("Meta Data Editing")
("t" . org-todo)
("," org-priority)
("0" org-priority-up)
("Show headline level")
("1" progn
(org-content (+ 0 (org-outline-level)))
)
("2" progn
(org-content (+ 1 (org-outline-level)))
)
("3" progn
(org-content (+ 2 (org-outline-level)))
)
("4" progn
(org-content (+ 3 (org-outline-level)))
)
("5" progn
(org-content (+ 4 (org-outline-level)))
)
("Outline Structure Editing")
("U" . org-metaup)
("D" . org-metadown)
("r" . org-metaright)
("l" . org-metaleft)
("R" . org-shiftmetaright)
("L" . org-shiftmetaleft)
("i" progn
(forward-char 1)
(call-interactively 'org-insert-heading-respect-content))
("w" . org-refile)
("a" . org-archive-subtree-default-with-confirmation)
("@" . org-mark-subtree)
("#" . org-toggle-comment)
("Agenda Views etc")
("v" . org-agenda)
("/" . org-sparse-tree)
("Misc")
("o" . org-open-at-point)
("?" . org-speed-command-help)
("<" org-agenda-set-restriction-lock 'subtree)
(">" org-agenda-remove-restriction-lock)))
'(org-use-speed-commands t)
)
(setq org-export-preserve-breaks 't) ; 在 orgmode 中不再需要写多个回车即可使导出的 HTML 中出现换行。
(setq org-html-checkbox-type 'html) ; 导出 checkbox 时,同步渲染为 HTML 复选框元素
(use-package org-re-reveal
:ensure t
:after org
:config
(setq org-re-reveal-root(concat "file://" (expand-file-name "~/.emacs.d/deps/reveal.js"))))
- Glossary 配置
(add-to-list 'load-path "~/emacs.d/site-lisp/org-glossary") (with-eval-after-load 'org (require 'org-glossary) (setq org-glossary-collection-root (concat org-directory "/Glossary/")))
- org-count-words
(add-to-list 'load-path "~/emacs.d/site-lisp/org-count-word")
(with-eval-after-load 'org
(require 'org-count-words)
)
(use-package mini-frame
:custom (
(mini-frame-show-parameters '((top . 0.2)
(width . 0.8)
(left . 0.5)
(left-fringe . 4)
(right-fringe . 4)
(height . 15)))
)
:hook (after-init . mini-frame-mode))
- 快速选中(并复制):选中行,选中list,选中引号内的内容,选中括号内的内容,甚至是选中当前buffer的文件名。如果使用的是easy-kill的功能,选中时,就会复制到剪贴板等等。
- 快速选中
er/expand-region
(C-=
) : 选中后可以使用+-0
快速扩大或者所有选取
同类型的插件有:things-edit 增加 easy-kill 配置:
(use-package easy-kill-extras
:ensure t
:config
(add-to-list 'easy-kill-alist '(?\' squoted-string "") t)
;; 选中 '' 中的内容
(add-to-list 'easy-kill-alist '(?\" dquoted-string "") t)
)
org-super-links-quick-insert-inline-link
能快速搜索 headline ,并在光标当前位置插入链接,同时如果原 headline 没有 ID 还会自动为原 headline 生成 ID 。
变更内容:原 package 会在被引用的 headline 中生成一条 backlink,简单修复方式:注释掉 org-super-links.el#L345 和 346 行 快捷键注释调的原因: 未设置 C-c s 的 key-prefix,所以快捷键注册失败
(use-package org-super-links
:load-path "~/.emacs.d/site-lisp/org-super-links"
:after (org org-id) ;; 参考 README, 如果不使用 org-id, org-super-link 插入的 link 不是引用的id 而是 file:headline
:commands (org-super-links-quick-insert-inline-link)
:config
(setq org-super-links-related-into-drawer nil
org-super-links-link-prefix 'org-super-links-link-prefix-timestamp
org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)
)
- 将完成的 TODO Headline 上加上删除线,参考
- 垂直分屏 默认垂直分屏,要换成水平分屏,参考: https://cloud.tencent.com/developer/ask/105836,
- 用 org-cycle 控制 plain-list org-cycle-include-plain-lists 控制 org-cycle 命令对纯文本列表的处理方式: integrate:将纯文本列表与其他列表一同处理,跟随标题的展开/折叠状态。 t:将纯文本列表视为独立的部分,不受标题的展开/折叠影响。 nil:将纯文本列表视为独立的部分,不受标题的展开/折叠影响,并且不会自动展开。
;; 新建buffer时水平分割
(setq split-width-threshold 1 )
;; 在Headline 上加删除线
(setq org-fontify-done-headline t)
;; headline 的对齐线
(with-eval-after-load 'org
(require 'org-bars)
(add-hook 'org-mode-hook #'org-bars-mode))
;;在 org-mode 折叠时/在展开,将纯文本列表与其他列表(如无序列表和有序列表)一同处理。当折叠一个标题时,纯文本列表也会被折叠起来。当展开一个标题时,纯文本列表也会被展开。
(setq org-cycle-include-plain-lists 'integrate)
;; 主题
(use-package tao-theme
:ensure t
:config)
(load-theme 'tao-yang)
;; Open file in externnal App
(defun xah-show-in-desktop ()
"Show current file in desktop.
(Mac Finder, Windows Explorer, Linux file manager)
This command can be called when in a file buffer or in `dired'.
URL `http://ergoemacs.org/emacs/emacs_dired_open_file_in_ext_apps.html'
Version 2020-11-20 2021-01-18"
(interactive)
(let (($path (if (buffer-file-name) (buffer-file-name) default-directory)))
(cond
((string-equal system-type "windows-nt")
(shell-command (format "PowerShell -Command Start-Process Explorer -FilePath %s" (shell-quote-argument default-directory)))
;; todo. need to make window highlight the file
)
((string-equal system-type "darwin")
(if (eq major-mode 'dired-mode)
(let (($files (dired-get-marked-files )))
(if (eq (length $files) 0)
(shell-command (concat "open " (shell-quote-argument (expand-file-name default-directory ))))
(shell-command (concat "open -R " (shell-quote-argument (car (dired-get-marked-files )))))))
(shell-command
(concat "open -R " (shell-quote-argument $path)))))
((string-equal system-type "gnu/linux")
(let (
(process-connection-type nil)
(openFileProgram (if (file-exists-p "/usr/bin/gvfs-open")
"/usr/bin/gvfs-open"
"/usr/bin/xdg-open")))
(start-process "" nil openFileProgram (shell-quote-argument $path)))
;; (shell-command "xdg-open .") ;; 2013-02-10 this sometimes froze emacs till the folder is closed. eg with nautilus
))))
(defun xah-open-in-vscode ()
"Open current file or dir in vscode.
URL `http://xahlee.info/emacs/emacs/emacs_open_in_vscode.html'
Version: 2020-02-13 2021-01-18 2022-08-04 2023-06-26"
(interactive)
(let ((xpath (if buffer-file-name buffer-file-name (expand-file-name default-directory))))
(message "path is %s" xpath)
(cond
((string-equal system-type "darwin")
(shell-command (format "open -a Visual\\ Studio\\ Code.app %s" (shell-quote-argument xpath))))
((string-equal system-type "windows-nt")
(shell-command (format "code.cmd %s" (shell-quote-argument xpath))))
((string-equal system-type "gnu/linux")
(shell-command (format "code %s" (shell-quote-argument xpath)))))))
(defun buffer-narrowed-p ()
"Return non-nil if the current buffer is narrowed."
(not (and (= (point-min) 1)
(= (point-max) (1+ (buffer-size))))))
(defun line-number-at-pos-in-file (filename pos)
"Return line number at POS in FILENAME."
(string-to-number
(shell-command-to-string
(format "dd if=%s bs=1 count=%d 2>/dev/null | wc -l"
(shell-quote-argument filename) pos))))
(defun ref/open-in-org-vscode-workspace ()
"Open current org file in vscode workspace."
(interactive)
(let (($path (if (buffer-file-name) (buffer-file-name) (expand-file-name default-directory ) ))
;; 当 buffer narrowed, line-number-at-pos 获取的是当前可视区的位置,不是 file 的位置,因此要转成 file 的位置
;; 转的过程中碰到的问题: point 函数返回的是字符数,无论是 linux 还是 emacs, 都没有很好的通过字节数找到行数的方法,因此将通过粗略
;; 的验证,我自己的知识库在使用 UTF8 编码时,一字符大概换算成 1.707 字节,来获取大概的文件位置
($lnum (if (buffer-narrowed-p) (line-number-at-pos-in-file (buffer-file-name) (floor (* 1.707 (point)))) (line-number-at-pos)))
($cnum (1+(current-column)))
)
(cond
((string-equal system-type "darwin")
(shell-command (format "code \"%s\" -g \"%s:%s:%s\"" (expand-file-name org-directory) $path $lnum $cnum)))
;;(shell-command (format "open -a Visual\\ Studio\\ Code.app \"%s\"" $path)))
((string-equal system-type "windows-nt")
(shell-command (format "Code \"%s\"" $path)))
((string-equal system-type "gnu/linux")
(shell-command (format "code -g \"%s:%s\"" $path $lnum $cnum))))))
(defun xah-open-in-terminal ()
"Open the current dir in a new terminal window.
on Microsoft Windows, it starts cross-platform PowerShell pwsh. You need to have it installed.
URL `http://ergoemacs.org/emacs/emacs_dired_open_file_in_ext_apps.html'
Version 2020-11-21 2021-01-18"
(interactive)
(cond
((string-equal system-type "windows-nt")
(let ((process-connection-type nil))
(shell-command (concat "PowerShell -Command Start-Process pwsh -WorkingDirectory " (shell-quote-argument default-directory)))
;;
))
((string-equal system-type "darwin")
(shell-command (concat "open -a terminal " (shell-quote-argument (expand-file-name default-directory )))))
((string-equal system-type "gnu/linux")
(let ((process-connection-type nil))
(start-process "" nil "x-terminal-emulator"
(concat "--working-directory=" default-directory))))))
(use-package keyfreq
:ensure t
:config
(setq keyfreq-excluded-commands
'(insert-self-forward
self-insert-command
mwheel-scroll
command-backward
char-previous
line-char
next-mwheel
line-scroll
org-self-insert-command
)
keyfreq-mode t
keyfreq-autosave-mode t
)
)
搜索: color-rg
- rg的功能
- 指定目录搜索
- 全文替换
备注:
- 按下r后,按 y 或者 n 来表示是否替换当前行,按!替换所有。 这个和Emacs的行为一致
- 如果搜索出来的结果有些不想处理,可以通过filter/delete 去除一些搜索结果再替换
(add-to-list 'load-path "~/.emacs.d/site-lisp/color-rg")
(require 'color-rg)
;; (use-package color-rg
;; :load-path "site-lisp/color-rg"
;; :ensure t
;; :bind(
;; ("s-F" . color-rg-search-symbol)
;; )
;; )
;; using `isearch-forward', type "M-s M-s" to search current isearch string with color-rg.
;; 搜索 headline?
(setq helm-org-ql-actions
'(("Show heading in source buffer" . helm-org-ql-show-marker)))
(use-package ob-mermaid
:ensure t
:after org
:config
(org-babel-do-load-languages
'org-babel-load-languages
'((mermaid . t)))
)
- 表格对齐
;; 解决在中文下,表格无法自动对齐的问题
(use-package valign :ensure t :after org :config (add-hook 'org-mode-hook #'valign-mode) (valign-mode 1) ) ;; org-modern 的 table 渲染和 valigin 冲突,因此禁止 org-modern-table (setq org-modern-table nil)
- 中文分词
参考: https://github.com/kanglmf/emacs-chinese-word-segmentation
;; ;; (add-to-list 'load-path "~/.emacs.d/site-lisp/chinese-word-segmentation") ;; (require 'cns) ;; (setq cns-prog "~/.emacs.d/site-lisp/chinese-word-segmentation/chinese-word-segmentation" ;; cns-dict-directory "~/.emacs.d/site-lisp/chinese-word-segmentation/dict" ;; cns-recent-segmentation-limit 20 ;; cns-debug t ;; disable debug output, default is t ;; ) ;; (when (featurep 'cns) ;; (add-hook 'find-file-hook 'cns-auto-enable)) ;; (add-to-list 'load-path "~/.emacs.d/site-lisp/chinese-word-segmentation") (setq cns-prog "~/.emacs.d/site-lisp/chinese-word-segmentation/cnws" cns-dict-directory "~/.emacs.d/site-lisp/chinese-word-segmentation/cppjieba/dict" cns-recent-segmentation-limit 20 cns-debug nil ;; disable debug output, default is t ) (require 'cns nil t) (when (featurep 'cns) (add-hook 'find-file-hook 'cns-auto-enable))
- 使用内置的中文输入法
pyim,使用内置的中文输入法的原因:emacs 的快捷键操作和中文输入时,需要频繁切换中英文输入法,打破了使用时行云流水的感觉,使用内置的中文输入法,则可以做到中文输入时同时使用 emacs 的快捷键
(use-package pyim :ensure t :config (setq pyim-dcache-directory "~/.config/emacs/pyim/dcache") (setq pyim-cloudim 'baidu) ) (use-package pyim-basedict :ensure t :requires pyim :config (pyim-basedict-enable) ) (require 'pyim) (setq default-input-method "pyim") ;; 开启这些 probe 后,能实现以下效果 ;; 1. 当中文/英文后有空格时,会自动切换输入法 ;; 2. 配合 pyim-convert-string-at-point 的快捷键,能实现手工切换输入法 (setq-default pyim-english-input-switch-functions '(pyim-probe-isearch-mode pyim-probe-program-mode pyim-probe-org-structure-template pyim-probe-org-speed-commands pyim-probe-auto-english minibufferp )) (setq-default pyim-punctuation-half-width-functions '(pyim-probe-punctuation-line-beginning pyim-probe-punctuation-after-punctuation)) ;; 开启拼音搜索功能 (pyim-isearch-mode 1) (setq-default pyim-punctuation-translate-p '(no)) (global-set-key (kbd "C-M-\\") 'pyim-convert-string-at-point) ;与 pyim-probe-dynamic-english 配合 (require 'pyim-basedict) (pyim-basedict-enable)
- 测试
(pyim-char-before-to-string 1);;ceshi ;; 测试 (pyim-string-match-p "\\cc" "a") (pyim-string-match-p "\\cc" "测") (pyim-string-match-p "\\cc" ".") (pyim-string-match-p "\\cc" "。") (> (point) (save-excursion (back-to-indentation) (point)))
- 翻译
(use-package go-translate :ensure t :config (setq gt-langs '(en zh)) ) (require 'go-translate)
;; 因为 org-directory 是在云存储中,org 编辑的临时文件存在原目录会导致临时文件不停的上传下载,因此设置到一个不是云存储的目录
;; 获取 XDG_CACHE_HOME 环境变量的值,如果未设置则使用默认值 ~/.cache
(defconst xdg-cache-home
(or (getenv "XDG_CACHE_HOME")
(expand-file-name "~/.cache")))
;; 设置 lock-file-name-transforms
(setq lock-file-name-transforms
`((".*" ,(concat xdg-cache-home "/emacs/locks/\\1") t)))
;; 确保锁文件目录存在
(make-directory (concat xdg-cache-home "/emacs/locks") t)
Org-mode 添加了一个新的功能:当执行(C-c C-c
(org-babel-execute-src-block
)) Markdown 代码块时,会自动将代码块的内容复制到系统剪贴板。这对于快速分享 Markdown 内容特别有用。
;; 首先声明变量
(defvar org-babel-default-header-args:markdown '()
"Default arguments for evaluating a markdown source block.")
(defun org-babel-execute:markdown (body params)
"Execute a Markdown code block by copying its content to clipboard.
The result is not displayed in the org buffer."
(with-temp-buffer
(insert body)
(kill-new body))
;; Return empty string to avoid showing result
"已复制")
;; 设置默认参数
(add-to-list 'org-babel-default-header-args:markdown
'(:results . "silent"))
(use-package gptel
:ensure t
:config
;; 配置 Gemini 后端并设置为默认
(setq gptel-backend
(gptel-make-gemini "Gemini"
:key ""
:stream t)
;; 设置默认模型
gptel-model 'gemini-pro
;; 设置代理
gptel-proxy "socks5://127.0.0.1:7897")
(setq gptel-default-mode 'org-mode))
(gptel-make-gpt4all "GPT4All" ;Name of your choosing
:protocol "http"
:host "10.186.65.37:8000" ;Where it's running
:models '(o1-mini
o1-preview
gpt-4
gpt-4-turbo
gpt-4-turbo-128k
claude-3.5-sonnet
claude-3.5-sonnet-200k
gemini-1.5-pro
gemini-1.5-pro-128k
gemini-1.5-pro-search
code-llama-13b
code-llama-34b
stable-diffusion-3
stable-diffusion-3-turbo
))
这样在 prompt 代码块中直接执行 C-c C-c
就能获得执行结果
当前存在的问题: 目前每次执行结果都被 examp 包裹, 导出成 html 时不显示 prompt 的输出结果
;; 定义 prompt 的执行函数,遵循 Org Babel 的命名约定
(defun org-babel-execute:prompt (body params)
"使用 `gptel-request' 执行给定的 BODY 作为 PARAMS 的提示。
并将 LLM 的响应插入到 Org Babel 块的结果部分。
BODY: 要发送给 LLM 的提示文本。
PARAMS: Babel 块的参数列表(目前未使用,但保留以便未来扩展)。"
;; 将 BODY 复制到剪贴板
(when body
(cond
;; 如果 Emacs 处于图形界面
((display-graphic-p)
(gui-set-selection 'CLIPBOARD body)
(message "提示已复制到剪贴板"))
;; 如果 Emacs 处于终端模式
(t
(with-temp-buffer
(insert body)
(clipboard-kill-ring-save (point-min) (point-max)))
(message "提示已复制到剪贴板"))))
(let* ((prompt body)
(buffer (current-buffer))
(position (point-marker))
(system-message ""))
;; 确保 position 是一个 marker
(unless (marker-position position)
(setq position (copy-marker position buffer)))
;; 发送异步请求
(gptel-request
prompt
:buffer buffer
:position position
:system system-message
:callback
(lambda (response info)
;; 在回调中处理响应
(with-current-buffer (plist-get info :buffer)
(save-excursion
;; 定位到请求的位置
(goto-char (marker-position (plist-get info :position)))
(if response
(progn
;; 插入 LLM 的响应作为结果
(org-babel-insert-result response)
;; 清理 marker 以防止内存泄漏
(when (marker-buffer (plist-get info :position))
(set-marker (plist-get info :position) nil)))
;; 如果响应为空,插入错误信息
(org-babel-insert-result
(format "LLM 请求失败: %s" (plist-get info :status))))))))
;; 返回 nil 以防止 Org-mode 插入额外内容
nil))
(defun ob-prompt-setup ()
"Setup the 'prompt' org-babel language."
(org-babel-do-load-languages
'org-babel-load-languages
'((prompt . t)))
;; 设置 prompt 语言的执行函数
(org-babel-set-language-info
"prompt"
'(:execute-func . org-babel-execute:prompt)))
(add-to-list 'org-src-lang-modes '("prompt" . markdown))
定义 transient 菜单, transient 结构是利用 [] 的语法糖来表示的,与多维数组(向量)的表示方式类似,比如 python 中是如下格式:
column = [[1], [2], [3], [4]]
row = [\[1, 2, 3, 4]]
比如: [][][] 表示垂直分布3个命令 比如: [[][][]] 表示水平分布的 3 个命令 比如: [[][][]] [[][]] 表示 2 行分组,上面3个,下面 2个 目前分类菜单是垂直布局,transient 布局可调整,具体使用问 ai
如何配置翻译:
翻译功能使用的是 lorniu/go-translate 包,需要在 transient 中设定菜单时,就是使用 (gt-start (translator))
即可,translator 配置方法参考官方说明
(defun ref/toggle-org-preview ()
"Toggle org real-time preview."
(interactive)
(if (timer-running-p preview-org-timer)
(progn
(cancel-timer preview-org-timer)
(setq preview-org-timer nil)
(message "Org real-time preview disabled."))
(setq preview-org-timer (run-with-timer 0 30 'export-org-to-html-and-reload))
(message "Org real-time preview enabled.")))
(defun ref/menu-title-org-preview-switch ()
"Update the org preview command title based on the timer state."
(if (timer-running-p preview-org-timer)
"关闭 org 实时预览"
"org 实时预览"))
(use-package transient
:ensure t
:init
(transient-define-prefix ref/menu ()
"菜单"
["文字工具"
("o" "优化文字" ref/flow/optimize-region)
("t" "翻译" (lambda ()
(interactive)
(gt-start (gt-translator
:engines (gt-youdao-dict-engine)
:render (gt-overlay-render
:then (gt-kill-ring-render))))))]
["Flow"
("e" (lambda () (ref/menu-title-org-preview-switch))
ref/toggle-org-preview)
("a" "Taks" (lambda () (interactive) (org-agenda nil "c")))]
["快捷命令"
("c" "统计kb字数" ref/reset-count)])
(global-set-key (kbd "M-m") 'ref/menu))
(setq dashboard-items '((recents . 3)
(agenda . 5)
(bookmarks . 5)
(projects . 5))
)
(setq dashboard-filter-agenda-entry 'dashboard-no-filter-agenda)
(setq dashboard-match-agenda-entry
"TODO=\"IMMED\"")
(setq dashboard-item-names '(("Agenda for the coming week:" . "重要事项:")))
(setq dashboard-agenda-prefix-format "%i %s %b")
(defun org-get-parent-headline ()
"原本用于 org-agenda-prefix-format 时展示父节点内容,
(setq org-agenda-prefix-format
'((tags . "%i %s %(org-get-parent-headline)")...))
后来发现可以用 %b 因此这个方法暂时没用到
"
(if (/= (org-current-level) 1)
(concat (mapconcat 'identity (org-get-outline-path) " > ") "»» ")
""))
(setq org-agenda-prefix-format
'((agenda . " %i %-12:c")
(todo . " %i %-12:c")
(tags . " %i %?b>\n\t ")
(search . " %i %-12:c")
))
(use-package whole-line-or-region
:ensure t
:config
(setq whole-line-or-region-global-mode 't)
)
(global-set-key (kbd "<f1>") 'count-words)
(global-set-key (kbd "C-x r b") 'bookmark-jump-other-window)
(define-key dired-mode-map (kbd "RET") 'dired-find-alternate-file)
;; 跳转
(global-set-key (kbd "C--") 'goto-last-change)
(define-key org-mode-map (kbd "C-j") 'ref/newline-return)
(define-key org-mode-map (kbd "C-M-j") 'ref/newline-meta-return)
(global-set-key (kbd "C-S-c C-S-c") 'mc/edit-lines)
(global-set-key (kbd "C->") 'mc/mark-next-like-this)
(global-set-key (kbd "C-<") 'mc/mark-previous-like-this)
(global-set-key (kbd "C-c C-<") 'mc/mark-all-like-this)
(global-set-key (kbd "s-F") 'color-rg-search-symbol)
;;(define-key isearch-mode-map (kbd "M-s M-s") 'isearch-toggle-color-rg)
(global-set-key (kbd "M-s-f") 'helm-org-ql-agenda-files)
(global-set-key (kbd "C-M-s-a") 'bh/show-org-agenda)
(global-set-key (kbd "s-w") 'delete-window)
(global-set-key (kbd "M-x") 'helm-M-x)
;; 配置常见编辑器快捷键
(define-key org-mode-map (kbd "s-b") (
lambda ()
(interactive)
(org-emphasize ?\*)
))
(define-key org-mode-map (kbd "s-i") (
lambda ()
(interactive)
(org-emphasize ?\/)
))
(define-key org-mode-map (kbd "s-u") (
lambda ()
(interactive)
(org-emphasize ?\_)
))
(message "custom-post load finished")
;;; custom-post.el ends here
[fn:1]zmonter的系列文章[1/3]:
强大的 Org mode(1): 简单介绍与基本使用- [ ] 强大的 Org mode(2): 任务管理: 如何设置状态以及状态流转
- [ ] 强大的 Org mode(3): 表格的基本操作及公式、绘图
- [X] 强大的 Org mode(4): 使用 capture 功能快速记录