大模型时代我们怎么玩Emacs:4. Org 文件里的链接和反链改造

· 1670字 · 4分钟

1 前言 🔗

在大模型时代,一切皆有可能。对于 Emacs 同样如此,大模型拉低学习了 Emacs 的门槛,我们通过大模型可以做到以前我们敢想不敢做,或敢做没时间做,或有时间不会做的事。

今天继续这个新的系列,跟大家分享在大模型时代,我们可以如何“玩” Emacs,我们应该怎么用大模型来满足自己对 Emacs 使用的需求。

2 Org 文件里的链接和反链改造 🔗

2.1 背景 🔗

我通过 Org mode 和 Denote 包来构筑我的知识体系。Org mode 有一个很好的功能就是链接,他可以让你在某一个 Org 文件内,增加链接到另外一个文件的某个标题行,这样能让知识链接成网,长期坚持,好处多多。

Org mode 自带一个 org-store-link 命令,可以将当前位置记录下来,然后通过 org-insert-link 命令插入到其他的 Org 文件:

但这个功能有一个不够完善的地方,就是它的链接是单向的,我们从上面那个例子可以看到在文档的最末,链接到了 第二级 这个标题行,但是在 第二级 标题行,我们并不知道这个标题行被链接到哪些地方。

org-super-links 就是用来完善这个需求的。有了这个插件,我们可以通过 org-super-links-store-link 来存储某个位置,然后通过 org-super-links-insert-link 来插入链接:

通过 org-super-links 插件确实能够在我们插入某个链接时,在源文件的标题行以 drawer 来显示反链信息。

对我来说,如果能在插入的地方所在的标题行也以 drawer 的方式显示插入链接信息,那就更加完美了,相当于在插入和被插入的标题行,都在标题行下以 drawer 的方式来记录链接信息,我们无论在哪,都能很方便的看到彼此的双向信息。这就是本次实验的初衷。

这一次的改造,跟之前几次不同,我们换了一种方式,我们将 org-super-links 插件的主体源代码全部 copy 复制到 ChatGPT 中,得益于 GPT-4o 强大的窗口 token 数,整个插件的源代码都可以被 ChatGPT 阅读,并基于它的理解,我们进行相关的改造。


接着,就要逐步地清晰地描述自己的需求:


接着是不断的测试,并将错误情况告诉 ChatGPT,让它不断的调整代码,以及加入这段最重要的需求描述:

2.5 最终效果 🔗

  1. 通过 org-super-links-store-link 来存储源文件的标题行位置
  2. 在目标位置,通过 org-super-links-insert-link 来插入
    1. 当目标位置在一个标题行上,以 drawer 的方式来记录链接信息,同时在源文件标题行以 drawer 方式记录反链信息
    2. 当目标位置不在标题行上,即在正文中,那不仅仅以 drawer 的方式来记录链接信息,同时在光标处插入该链接信息,并且在源文件标题行以 drawer 方式记录反链信息
  3. 这样,所有的链接和反链信息都可以在源文件标题行和目的文件标题行的 drawer 清晰的看到,并且不妨碍在正文中的链接

2.6 最终生成的代码 🔗

  ;; 为了实现在存储位置时带上文件和标题行的信息,进行了如下的魔改
  (defun org-super-links-store-link (&optional GOTO KEYS)
    "Store a point to the register for use in function `org-super-links-insert-link'.
This is primarily intended to be called before `org-capture', but
could possibly even be used to replace `org-store-link' IF
function `org-super-links-insert-link' is used to replace `org-insert-link'.  This
has not been thoroughly tested outside of links to/form org files.
GOTO and KEYS are unused."
    (interactive "P")
    (ignore GOTO)
    (ignore KEYS)
    (save-excursion
      (let ((c1 (make-marker))
            (headline (org-get-heading t t t t)))  ;; 获取标题行
        (set-marker c1 (point) (current-buffer))
        (set-register ?^ (cons c1 headline))  ;; 存储标记和标题行
        (message "Link copied with headline: %s" headline))))

  (defun org-super-links-insert-link ()
    "Insert a super link from the register."
    (interactive)
    (let* ((target (get-register ?^)))
      (if target
          (let ((marker (car target))
                (headline (cdr target)))
            (org-super-links--insert-link marker headline)
            (set-register ?^ nil))
        (message "No link to insert!"))))

  (defun org-super-links--insert-link (target headline &optional no-forward)
    "Insert link to marker TARGET at current `point`, and create backlink to here.
Include HEADLINE in the link.
If NO-FORWARD is non-nil skip creating the forward link.  Currently only used when converting a link."
    (let* ((source (point-marker))
           (source-headline (org-get-heading t t t t))
           (source-link (org-super-links-links-action source 'org-super-links-pre-link-hook))
           (target-link (org-super-links-links-action target 'org-super-links-pre-backlink-hook))
           (source-formatted-link (org-super-links-link-builder source-link))
           (target-formatted-link (org-super-links-link-builder target-link))
           (source-file (file-relative-name (buffer-file-name (marker-buffer source))
                                            (file-name-directory (buffer-file-name (marker-buffer target)))))
           (target-file (buffer-file-name (marker-buffer target)))
           (source-link (format "file:%s::%s" source-file source-headline))
           (target-link (format "file:%s::%s" target-file headline)))
      (with-current-buffer (marker-buffer target)
        (save-excursion
          (goto-char (marker-position target))
          (when (derived-mode-p 'org-mode)
            (org-super-links-insert-backlink source-link source-headline))))
      (unless no-forward
        (with-current-buffer (marker-buffer source)
          (save-excursion
            (goto-char (marker-position source))
            (if (org-at-heading-p)
                (org-super-links-insert-relatedlink target-link headline)
              (org-super-links-insert-inline-link target-link headline source-headline)))))))

  (defun org-super-links-insert-inline-link (target-link headline source-headline)
    "Insert TARGET-LINK with HEADLINE at point and add related drawer at current heading."
    ;; (insert (org-super-links-link-prefix))
    (org-insert-link nil target-link headline)
    (insert (org-super-links-link-postfix))
    (org-back-to-heading)
    (org-super-links-insert-relatedlink target-link headline))

  (defun org-super-links-insert-backlink (link desc)
    "Insert backlink to LINK with DESC.
Where the backlink is placed is determined by the variable `org-super-links-backlink-into-drawer`."
    (let* ((org-log-into-drawer (org-super-links-backlink-into-drawer))
           (description (org-super-links-default-description-formatter link desc))
           (beg (org-log-beginning t)))
      (goto-char beg)
      (insert (org-super-links-backlink-prefix))
      (insert (org-link-make-string link description))
      (insert (org-super-links-backlink-postfix))
      (org-indent-region beg (point))))

  (defun org-super-links-insert-relatedlink (link desc)
    "Insert LINK with DESC into related drawer."
    (let* ((org-log-into-drawer (org-super-links-related-into-drawer))
           (drawer-name (or org-log-into-drawer "RELATED"))
           (beg (org-log-beginning t)))
      (goto-char beg)
      (unless (org-at-heading-p)
        (org-back-to-heading t))
      (org-narrow-to-subtree)
      (let ((drawer-beg (re-search-forward (format ":%s:" drawer-name) nil t)))
        (if drawer-beg
            (progn
              (goto-char drawer-beg)
              (forward-line)
              (insert (org-super-links-link-prefix))
              (org-insert-link nil link desc)
              (insert (org-super-links-link-postfix) "\n")
              (org-indent-region drawer-beg (point)))
          (goto-char (point-max))
          (insert (format ":%s:\n" drawer-name))
          (insert (org-super-links-link-prefix))
          (org-insert-link nil link desc)
          (insert (org-super-links-link-postfix) "\n")
          (insert (format ":%s:\n" "END"))
          (org-indent-region beg (point))))
      (widen)))

  (defun org-super-links-related-into-drawer ()
    "Name of the related drawer, as a string, or nil.
This is the value of `org-super-links-related-into-drawer`.  However, if the
current entry has or inherits a RELATED_INTO_DRAWER property, it will
be used instead of the default value."
    (let ((p (org-entry-get nil "RELATED_INTO_DRAWER" 'inherit t)))
      (cond ((equal p "nil") nil)
            ((equal p "t") org-super-links-related-drawer-default-name)
            ((stringp p) p)
            (p org-super-links-related-drawer-default-name)
            ((stringp org-super-links-related-into-drawer) org-super-links-related-into-drawer)
            (org-super-links-related-into-drawer org-super-links-related-drawer-default-name))))

3 结语 🔗

今天这个案例,是另外一种思路,我们将整个插件的源代码通过提示词一起给到大模型,由大模型做整体的理解和记忆,然后基于整体源代码,提出我们的需求、进行调试,最终达到我们的目标。

后续,将会继续分享几个案例。

  • org 文件导出为 HTML 的 css 改造

我也会持续的使用 ChatGPT 和 Emacs,持续分享我的心得,感谢您的阅读。