@@ 1,6 1,6 @@
;;; pkg-org.el --- Org-mode configuration and dynamic system -*- lexical-binding: t -*-
-;; Copyright (C) 2024 Cytrogen
+;; Copyright (C) 2026 Cytrogen
;; This file contains:
;; - Org directory setup
@@ 232,12 232,56 @@ Format: ((key label file ((subkey sublabel headline template override-file) ...)
(define-key org-agenda-mode-map (kbd "S") #'my/reading-start-book)
(define-key org-agenda-mode-map (kbd "F") #'my/reading-finish-book))
+ ;; --- 课程笔记 Capture ---
+ (defvar my/class-notes-date nil "临时存储日期,供模板使用")
+
+ (defun my/get-class-subjects ()
+ "获取 reading.org 中 课程笔记 下的所有科目。"
+ (let ((reading-file (expand-file-name "reading.org" org-directory))
+ (subjects '()))
+ (when (file-exists-p reading-file)
+ (with-temp-buffer
+ (insert-file-contents reading-file)
+ (goto-char (point-min))
+ (when (re-search-forward "^\\* 课程笔记" nil t)
+ (let ((end (save-excursion
+ (or (re-search-forward "^\\* " nil t) (point-max)))))
+ (while (re-search-forward "^\\*\\* \\(.+\\)" end t)
+ (push (match-string-no-properties 1) subjects))))))
+ (nreverse subjects)))
+
+ (defun my/class-notes-goto-subject ()
+ "提示选择科目和日期,定位到科目标题下。"
+ (let* ((subjects (my/get-class-subjects))
+ (subject (completing-read "科目: " subjects nil nil))
+ (date (read-string "日期: " (format-time-string "%Y-%m-%d"))))
+ (setq my/class-notes-date date)
+ (goto-char (point-min))
+ (if (re-search-forward "^\\* 课程笔记" nil t)
+ (let ((subtree-end (save-excursion (org-end-of-subtree t) (point))))
+ (if (re-search-forward (format "^\\*\\* %s$" (regexp-quote subject)) subtree-end t)
+ (beginning-of-line)
+ ;; 科目不存在,在子树末尾创建
+ (goto-char subtree-end)
+ (insert "\n** " subject)
+ (beginning-of-line)))
+ ;; 课程笔记也不存在
+ (goto-char (point-max))
+ (insert "\n* 课程笔记\n** " subject)
+ (beginning-of-line))))
+
;; --- Refile Configuration ---
;; 强化 Refile 功能,允许将条目移动到项目或分类的具体标题下
(setq org-refile-use-outline-path 'file)
(setq org-outline-path-complete-in-steps nil)
(setq org-refile-allow-creating-parent-nodes 'confirm)
+ ;; --- 数学公式显示配置 ---
+ ;; 启用 pretty entities 显示上标/下标
+ (setq org-pretty-entities t)
+ ;; 可选:只在需要时显示(光标不在实体上时)
+ (setq org-pretty-entities-include-sub-superscripts t)
+
;; --- Source Block Editing Configuration ---
;; 让 TAB 在源代码块中按语言原生方式工作
(setq org-src-tab-acts-natively t)
@@ 248,7 292,21 @@ Format: ((key label file ((subkey sublabel headline template override-file) ...)
;; 在当前窗口编辑源代码块(避免分割窗口)
(setq org-src-window-setup 'current-window)
;; 禁用返回编辑 buffer 时的确认提示
- (setq org-src-ask-before-returning-to-edit-buffer nil))
+ (setq org-src-ask-before-returning-to-edit-buffer nil)
+
+ ;; --- Link Configuration ---
+ ;; 按 RET 时直接打开链接
+ (setq org-return-follows-link t)
+
+ ;; --- Org-Babel Configuration ---
+ ;; 启用语言支持,实现类似 Jupyter Notebook 的体验
+ (org-babel-do-load-languages
+ 'org-babel-load-languages
+ '((shell . t) ; 用于编译和运行命令
+ (emacs-lisp . t))) ; 默认支持
+
+ ;; 执行代码块时不询问确认(方便快速执行)
+ (setq org-confirm-babel-evaluate nil))
;; Custom Org Functions
;; Helper functions for enhanced workflow
@@ 308,6 366,17 @@ Format: ((key label file ((subkey sublabel headline template override-file) ...)
(push (list combined-key label type capture-target template) templates)))))
(setq org-capture-templates (reverse templates))
+ ;; 替换课程笔记模板为使用 file+function
+ (setq org-capture-templates
+ (mapcar (lambda (tpl)
+ (if (equal (car tpl) "Rc")
+ `("Rc" "课程笔记" entry
+ (file+function ,(expand-file-name "reading.org" org-directory)
+ my/class-notes-goto-subject)
+ "*** %(symbol-value 'my/class-notes-date)\n%?"
+ :empty-lines 1)
+ tpl))
+ org-capture-templates))
(message "Org capture templates updated.")))
(defun my/quick-refile ()
@@ 383,5 452,95 @@ Step 2: Choose Sub Category (Context) using completing-read.
(with-eval-after-load 'org
(my/apply-org-structure))
+;; 中文行内标记支持
+;; 允许中文字符紧贴 org 强调标记符(*粗体*、/斜体/ 等),无需额外空格
+(with-eval-after-load 'org
+ (setcar org-emphasis-regexp-components
+ "-[:space:]('\"{\x200B[:nonascii:]")
+ (setcar (nthcdr 1 org-emphasis-regexp-components)
+ "-[:space:].,:!?;'\")}\\[\x200B[:nonascii:]")
+ (org-set-emph-re 'org-emphasis-regexp-components org-emphasis-regexp-components)
+ ;; 使用 _{xxx}_ 语法时,防止下划线被解释为下标
+ (setq org-use-sub-superscripts '{})
+ (setq org-export-with-sub-superscripts '{}))
+
+;;; ---- 下划线语法迁移 ----
+
+(defun my/migrate-underline-syntax (&optional dry-run)
+ "Migrate _xxx_ to _{xxx}_ in all org files under `org-directory'.
+With prefix argument (C-u), perform a DRY-RUN showing changes without modifying files."
+ (interactive "P")
+ (let ((files (directory-files-recursively org-directory "\\.org$"))
+ (total-replacements 0)
+ (modified-files '())
+ ;; Regex matching Org emphasis _xxx_ pattern
+ ;; Matches _ + non-space/non-underscore + optional middle + non-space/non-underscore + _
+ ;; but NOT already in _{xxx}_ form
+ (underline-re "_\\([^ _\n]\\(?:[^_\n]*[^ _\n]\\)?\\)_"))
+ (dolist (file files)
+ (with-temp-buffer
+ (insert-file-contents file)
+ (let ((file-replacements 0)
+ (regions-to-skip '()))
+ ;; Collect regions to skip: src blocks, example blocks, property drawers
+ (goto-char (point-min))
+ (while (re-search-forward
+ "^[ \t]*#\\+BEGIN_\\(SRC\\|EXAMPLE\\)\\b.*\n" nil t)
+ (let ((beg (match-beginning 0)))
+ (when (re-search-forward
+ (format "^[ \t]*#\\+END_%s" (match-string 1)) nil t)
+ (push (cons beg (line-end-position)) regions-to-skip))))
+ (goto-char (point-min))
+ (while (re-search-forward "^[ \t]*:PROPERTIES:" nil t)
+ (let ((beg (match-beginning 0)))
+ (when (re-search-forward "^[ \t]*:END:" nil t)
+ (push (cons beg (line-end-position)) regions-to-skip))))
+ ;; Collect inline code =...= and ~...~
+ (goto-char (point-min))
+ (while (re-search-forward "\\(?:=\\([^=\n]+\\)=\\)\\|\\(?:~\\([^~\n]+\\)~\\)" nil t)
+ (push (cons (match-beginning 0) (match-end 0)) regions-to-skip))
+ ;; Collect links [[...]]
+ (goto-char (point-min))
+ (while (re-search-forward "\\[\\[\\([^]]*\\)\\]\\(?:\\[\\([^]]*\\)\\]\\)?\\]" nil t)
+ (push (cons (match-beginning 0) (match-end 0)) regions-to-skip))
+ ;; Collect #+KEYWORD: lines
+ (goto-char (point-min))
+ (while (re-search-forward "^[ \t]*#\\+[A-Z_]+:.*$" nil t)
+ (push (cons (match-beginning 0) (match-end 0)) regions-to-skip))
+ ;; Now do replacements, skipping protected regions
+ (goto-char (point-min))
+ (while (re-search-forward underline-re nil t)
+ (let ((pos (match-beginning 0))
+ (skip nil))
+ ;; Check if inside a skip region
+ (dolist (region regions-to-skip)
+ (when (and (>= pos (car region)) (<= pos (cdr region)))
+ (setq skip t)))
+ ;; Skip if already in _{...}_ form (preceded by _ and { )
+ (when (and (not skip)
+ (> pos 0)
+ (eq (char-before (match-beginning 0)) ?{))
+ (setq skip t))
+ (unless skip
+ (cl-incf file-replacements)
+ (unless dry-run
+ (replace-match "_{\\1}_")))))
+ (when (> file-replacements 0)
+ (cl-incf total-replacements file-replacements)
+ (push (cons file file-replacements) modified-files)
+ (unless dry-run
+ (write-region (point-min) (point-max) file))))))
+ ;; Report results
+ (if (= total-replacements 0)
+ (message "No _xxx_ patterns found to migrate.")
+ (with-current-buffer (get-buffer-create "*underline-migration*")
+ (erase-buffer)
+ (insert (format "%s — %d replacements in %d files:\n\n"
+ (if dry-run "DRY RUN" "MIGRATED")
+ total-replacements (length modified-files)))
+ (dolist (entry (nreverse modified-files))
+ (insert (format " %s: %d\n" (car entry) (cdr entry))))
+ (display-buffer (current-buffer))))))
+
(provide 'pkg-org)
;;; pkg-org.el ends here=
\ No newline at end of file