Skip to content

Latest commit

 

History

History
1719 lines (1520 loc) · 71.3 KB

custom-post.org

File metadata and controls

1719 lines (1520 loc) · 71.3 KB

Blog

为什么使用emacs

todo 和 文档还有项目的规划是在同一个语言体系(系统)下;个人很多todo 都是是在写规划或者文章时,所生产.过往的GTD 工具碰到的最难受的问题就是总是需要手工关联任务和文章

使用说明

本文档作为 org 文件 提供,您可以在 Emacs 中加载它并通过 C-c C-v C-t (org-babel-tangle) 导出 custom-post.el,文件中包含本文档中所有 emacs 配置的 elisp 代码,可以将它放到你的Emacs配置目录中

准备工作:

  1. 手动下载一些 package
git clone [email protected]:kanglmf/emacs-chinese-word-segmentation.git  ~/.emacs.d/site-lisp/chinese-word-segmentation/ #chinese-word-segmentation

org mode 配置

org TODO 设置

注意事项

  1. TODO 关键字尽量不要使用中文, 因为 org-tag-view 无法支持中文
  2. 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)))))

capture 设置

参考: zmonster[fn:1]
;; 设置 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)

Obsolete 的配置

  • 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")
              ))
        

Ref的 Emacs 自定义设置

使用到的插件

  • org-ql: 友好的heading 查询插件 重要参考 org-ql: org 的查询语法* Footnotes

org-mode

org-mode 的使用流程主要用于 GTD+个人 KB (第二知识库)

org agenda 设置

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"))))))))

org mode 其它配置

标签配置:需要区分生活和工作
(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)

配置 org-roam

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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
;;  当在 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) 

  )

链接至 agenda

参考: 已经完成的headline无法搜索出来
(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))

org 查询功能加强

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))))

Refile

  • 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 维护代码的好处是,树状结构人读比较清晰,文档齐全且跟代码同时更新

  • 如何编辑(因为 org-mode lsp 不是具体的编程语言) 在代码块执行 org-edit-special(C-c ')

配置 org-mode 的文件存储路径(当前主要针对与图片)

存储的需要考虑的内容

  1. 如果 org file 可能移动,因此不能使用相对路径
  2. 需要 ox-hugo 能支持的方式,否则生成的 blog 无法展示图片

目的:建立一个 static 的附件目录,并保障 org-directory 下不论哪一级的 org 文件引用 static 中的文件时都是以 org-directory 为相对路径,这样做的好处是,不论 org 文件本身怎么移动,只要是引用的 static/ 下的文件,是一直能找到的 通过两个函数实现以上目的

  1. org-link-file-path-type 是控制着 orgmode 存储 url 的行为,通过 ref/org-static-file-link 存储时的是 org-directory 的相对路径,
  2. 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 mode 插件

  • 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)
        )
      )
        

针对使用场景的配置

GTD 设置

(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)
        

追踪公司项目进展

公司项目[简称项目]运转并不是完全由我负责,但是经常关注项目的进展,需要获取的信息 进展/质量/问题,因此需要能筛选处一列表:那些是需要回顾的项目,其中存在的问题
  1. 我的org文件目录除了存放TODO ,还存放知识,因此不能每次回顾所有的文件,而必须明确的列出那些是必须回顾的项目

方案:

  1. 所有的项目放在project/下,每次去扫目录下文件,完成的项目archieve: 文件太多
  2. 为所有的项目标记 :项目: 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)

ox-hugo 博客维护

;; 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))

阅读

hightlight

  • 简易版本 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)
        

headline 跳转和操作

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 复选框元素

slide 导出

(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))

编辑/操作习惯配置

emacs 中不使用鼠标时的选中方式:The Mark and the Region 通用的方式:=smart region= mark,如何通过光标移动,如C-f/b等开始选择区域,然后就能针对这个区域开始调用命令 重要功能:
  • 快速选中(并复制):选中行,选中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)
  )

多光标编辑

快捷键是参考 vscode multiple-cousor

快速插入 headline 的链接

利用的是 org-super-links 插件 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)

实用的工具

在其它的 App 中打开当前buffer的文件

;; 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))))))

统计emacs常用命令调用

(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

需要实现的功能:
  1. rg的功能
  • 指定目录搜索
  1. 全文替换

备注:

  1. 按下r后,按 y 或者 n 来表示是否替换当前行,按!替换所有。 这个和Emacs的行为一致
  2. 如果搜索出来的结果有些不想处理,可以通过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 操作

;; 搜索 headline?
(setq  helm-org-ql-actions
       '(("Show heading in source buffer" . helm-org-ql-show-marker)))

mermaid

(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)

Markdown 配置

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"))

AI 配置

(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
            ))

五星功能: 实现 ob-prompt,

这样在 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))

Dashboard

(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")
        ))

Test

(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 ?\_)
                                      ))

End

(message "custom-post load finished")
;;; custom-post.el ends here

Footnotes

[fn:1]zmonter的系列文章[1/3]: