|
| 1 | +;;; pi-coding-agent-org.el --- Org links for pi coding agent -*- lexical-binding: t; -*- |
| 2 | + |
| 3 | +;; Copyright (C) 2026 Daniel Nouri |
| 4 | + |
| 5 | +;; Author: Daniel Nouri <daniel.nouri@gmail.com> |
| 6 | +;; Maintainer: Daniel Nouri <daniel.nouri@gmail.com> |
| 7 | +;; URL: https://github.com/dnouri/pi-coding-agent |
| 8 | + |
| 9 | +;; SPDX-License-Identifier: GPL-3.0-or-later |
| 10 | + |
| 11 | +;; This file is not part of GNU Emacs. |
| 12 | + |
| 13 | +;;; Commentary: |
| 14 | + |
| 15 | +;; Optional Org link support for pi-coding-agent sessions. |
| 16 | +;; Loading the base pi-coding-agent package does not require Org; users can load |
| 17 | +;; this file from `org-mode' configuration to enable pi: links. |
| 18 | + |
| 19 | +;;; Code: |
| 20 | + |
| 21 | +(require 'pi-coding-agent) |
| 22 | +(require 'org) |
| 23 | +(require 'subr-x) |
| 24 | +(require 'url-util) |
| 25 | + |
| 26 | +(defun pi-coding-agent-org--query-encode (value) |
| 27 | + "URL-encode VALUE for a pi Org link query parameter." |
| 28 | + (url-hexify-string (or value ""))) |
| 29 | + |
| 30 | +(defun pi-coding-agent-org--query-decode (value) |
| 31 | + "URL-decode VALUE from a pi Org link query parameter." |
| 32 | + (url-unhex-string (replace-regexp-in-string "+" " " (or value "")))) |
| 33 | + |
| 34 | +(defun pi-coding-agent-org--parse-link (path) |
| 35 | + "Parse a pi Org link PATH. |
| 36 | +Returns a plist with :kind and decoded query parameter keys such as :file, |
| 37 | +:dir, and :name." |
| 38 | + (let* ((parts (split-string path "\\?" t)) |
| 39 | + (kind (car parts)) |
| 40 | + (query (cadr parts)) |
| 41 | + (plist (list :kind kind))) |
| 42 | + (when query |
| 43 | + (dolist (part (split-string query "&" t)) |
| 44 | + (when (string-match "\\`\\([^=]+\\)=\\(.*\\)\\'" part) |
| 45 | + (let ((key (intern (concat ":" (match-string 1 part)))) |
| 46 | + (value (pi-coding-agent-org--query-decode |
| 47 | + (match-string 2 part)))) |
| 48 | + (setq plist (plist-put plist key value)))))) |
| 49 | + plist)) |
| 50 | + |
| 51 | +(defun pi-coding-agent-org-session-link-target (&optional file dir name) |
| 52 | + "Return a pi: session link target from FILE or DIR and NAME. |
| 53 | +FILE is preferred as the durable session identity. DIR and NAME are used for |
| 54 | +links to named sessions that do not yet have a known durable session file." |
| 55 | + (concat |
| 56 | + "session?" |
| 57 | + (cond |
| 58 | + ((and file (not (string-empty-p file))) |
| 59 | + (concat "file=" (pi-coding-agent-org--query-encode |
| 60 | + (expand-file-name file)) |
| 61 | + (when dir |
| 62 | + (concat "&dir=" (pi-coding-agent-org--query-encode |
| 63 | + (file-name-as-directory |
| 64 | + (expand-file-name dir))))) |
| 65 | + (when (and name (not (string-empty-p name))) |
| 66 | + (concat "&name=" (pi-coding-agent-org--query-encode name))))) |
| 67 | + ((and dir name (not (string-empty-p name))) |
| 68 | + (concat "dir=" (pi-coding-agent-org--query-encode |
| 69 | + (file-name-as-directory (expand-file-name dir))) |
| 70 | + "&name=" (pi-coding-agent-org--query-encode name))) |
| 71 | + (dir |
| 72 | + (concat "dir=" (pi-coding-agent-org--query-encode |
| 73 | + (file-name-as-directory (expand-file-name dir))))) |
| 74 | + (t |
| 75 | + (user-error "No pi session file or directory known"))))) |
| 76 | + |
| 77 | +(defun pi-coding-agent-org-session-link (&optional file dir name description) |
| 78 | + "Return an Org bracket link for a pi session. |
| 79 | +FILE, DIR, and NAME are passed to `pi-coding-agent-org-session-link-target'. |
| 80 | +DESCRIPTION defaults to a generic session label." |
| 81 | + (format "[[pi:%s][%s]]" |
| 82 | + (pi-coding-agent-org-session-link-target file dir name) |
| 83 | + (or description "Open pi session"))) |
| 84 | + |
| 85 | +(defun pi-coding-agent-org--current-session-link-data (&optional chat-buffer) |
| 86 | + "Return Org link data for CHAT-BUFFER or the current pi session. |
| 87 | +The result is a plist with :link and :description, or nil when point is not in |
| 88 | +a pi chat/input session buffer." |
| 89 | + (let ((chat-buf (or chat-buffer (pi-coding-agent--get-chat-buffer)))) |
| 90 | + (when (and (buffer-live-p chat-buf) |
| 91 | + (with-current-buffer chat-buf |
| 92 | + (derived-mode-p 'pi-coding-agent-chat-mode))) |
| 93 | + (with-current-buffer chat-buf |
| 94 | + (let* ((file (pi-coding-agent-session-file chat-buf)) |
| 95 | + (dir (pi-coding-agent--chat-session-directory chat-buf)) |
| 96 | + (name (pi-coding-agent-session-display-name chat-buf)) |
| 97 | + (target (pi-coding-agent-org-session-link-target file dir name)) |
| 98 | + (description (if (and name (not (string-empty-p name))) |
| 99 | + (format "pi session: %s" name) |
| 100 | + "pi session"))) |
| 101 | + (list :link (concat "pi:" target) |
| 102 | + :description description)))))) |
| 103 | + |
| 104 | +(defun pi-coding-agent-org-store-session-link (&optional _interactive) |
| 105 | + "Store an Org link to the current pi session. |
| 106 | +This is used by `org-store-link' from pi chat and input buffers." |
| 107 | + (when-let* ((data (pi-coding-agent-org--current-session-link-data))) |
| 108 | + (org-link-store-props :type "pi" |
| 109 | + :link (plist-get data :link) |
| 110 | + :description (plist-get data :description)) |
| 111 | + t)) |
| 112 | + |
| 113 | +;;;###autoload |
| 114 | +(defun pi-coding-agent-org-open-link (path _) |
| 115 | + "Open pi Org link PATH." |
| 116 | + (let* ((info (pi-coding-agent-org--parse-link path)) |
| 117 | + (kind (plist-get info :kind))) |
| 118 | + (pcase kind |
| 119 | + ("session" |
| 120 | + (cond |
| 121 | + ((plist-get info :file) |
| 122 | + (pi-coding-agent-session-open-file |
| 123 | + (plist-get info :file) |
| 124 | + (plist-get info :dir) |
| 125 | + (plist-get info :name))) |
| 126 | + ((plist-get info :dir) |
| 127 | + (pi-coding-agent-session-open |
| 128 | + (plist-get info :dir) |
| 129 | + (plist-get info :name))) |
| 130 | + (t |
| 131 | + (user-error "pi session link lacks file or dir")))) |
| 132 | + (_ |
| 133 | + (user-error "Unsupported pi link kind: %s" kind))))) |
| 134 | + |
| 135 | +(defun pi-coding-agent-org-export-link (path description _backend) |
| 136 | + "Export pi Org link PATH with DESCRIPTION." |
| 137 | + (or description (format "pi:%s" path))) |
| 138 | + |
| 139 | +(org-link-set-parameters "pi" |
| 140 | + :follow #'pi-coding-agent-org-open-link |
| 141 | + :export #'pi-coding-agent-org-export-link |
| 142 | + :store #'pi-coding-agent-org-store-session-link) |
| 143 | + |
| 144 | +(provide 'pi-coding-agent-org) |
| 145 | +;;; pi-coding-agent-org.el ends here |
0 commit comments