aboutsummaryrefslogtreecommitdiff
path: root/modules/ai-term-backend-eat.el
blob: 6d512831bcb400e062e5bd1212a2f3e1df4fa3b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
;;; ai-term-backend-eat.el --- EAT terminal backend for ai-term -*- lexical-binding: t; -*-

;; Author: Craig Jennings <c@cjennings.net>

;;; Commentary:
;;
;; Layer: 3 (Domain Workflow).
;; Category: D.
;; Load shape: library.
;; Top-level side effects: binds M-SPC in `eat-semi-char-mode-map' after EAT
;;   loads.
;; Runtime requires: ai-term-sessions.
;; Direct test load: yes.
;;
;; EAT backend of ai-term: terminal buffer creation and tmux reattach, the
;; pty send helper, and the EAT keymap integration.  The name is
;; backend-specific on purpose -- a future terminal backend lands as a
;; sibling ai-term-backend-<name>.el and everything above this layer stays
;; put.  The display routing the created buffer goes through lives in
;; ai-term-display (the display-buffer-alist rule); this module only calls
;; `display-buffer'.

;;; Code:

(require 'ai-term-sessions)

(declare-function eat "eat" (&optional program arg))
(declare-function cj/ai-term-next "ai-term" ())
(defvar eat-buffer-name)
(defvar eat-semi-char-mode-map)

(defun cj/--ai-term-send-string (buffer string)
  "Send STRING to BUFFER's terminal process (the agent's shell).
Sends to the pty directly so the launch command reaches the shell EAT runs."
  (let ((proc (get-buffer-process buffer)))
    (when (process-live-p proc)
      (process-send-string proc string))))

(defun cj/--ai-term-show-or-create (dir name)
  "Show or create the AI-term buffer for project DIR with buffer NAME.

If a buffer named NAME exists with a live process, display it.  If
the buffer exists but its process is dead, kill it and recreate.  If
no such buffer exists, create a new EAT terminal in DIR and send
the project's tmux launch command (see `cj/--ai-term-launch-command') so
the same project basename reattaches across Emacs restarts.

EAT runs a plain shell with no auto-tmux hook, so the named
`tmux new-session -A' launch command is the only thing that starts the
session -- the spike confirmed EAT + tmux detach and reattach exactly
like ghostel + tmux did.

Records DIR in `cj/--ai-term-mru' (whichever branch runs) so the
project picker can list recently-opened projects first.  Returns the
buffer."
  (cj/--ai-term-record-mru dir)
  (let ((existing (get-buffer name)))
    (cond
     ((and existing (cj/--ai-term-process-live-p existing))
      (display-buffer existing)
      existing)
     (t
      (when existing
        (kill-buffer existing))
      ;; `eat' switches to its buffer in the selected window before our
      ;; display-buffer-alist rule can route it; `save-window-excursion'
      ;; reverts that, and the explicit display-buffer below routes the buffer
      ;; through the alist into the agent slot.  `eat-buffer-name' is bound to
      ;; NAME so the terminal is created under the agent name; EAT (unlike
      ;; ghostel) does not rename the buffer from the terminal's OSC title, so
      ;; the "agent [" prefix that buffer detection and the display rule key on
      ;; stays put.
      (save-window-excursion
        (let ((default-directory dir)
              (eat-buffer-name name))
          (eat)))
      (let ((buf (get-buffer name)))
        (with-current-buffer buf
          (cj/--ai-term-send-string
           buf (concat (cj/--ai-term-launch-command dir) "\n")))
        (display-buffer buf)
        buf)))))

;; In EAT's semi-char mode, keys not bound in `eat-semi-char-mode-map' are
;; forwarded to the pty.  M-SPC (swap to the next agent) must reach Emacs from
;; inside an agent buffer, so bind it in that map -- no exception-list or rebuild
;; dance like ghostel needed.  C-; is already bound there (eat-config), so the
;; C-; a family resolves through the global prefix without extra wiring.
(with-eval-after-load 'eat
  (keymap-set eat-semi-char-mode-map "M-SPC" #'cj/ai-term-next))

(provide 'ai-term-backend-eat)
;;; ai-term-backend-eat.el ends here