@@ -368,18 +368,32 @@ or nil on non-zero exit. Otherwise return t on zero exit, nil on non-zero."
368368
369369(defun enkan-repl--terminal-tmux--list-window-cwds (session )
370370 " Return list of (WINDOW . CWD) pairs in tmux SESSION."
371+ (mapcar
372+ (lambda (info )
373+ (cons (plist-get info :window )
374+ (plist-get info :cwd )))
375+ (enkan-repl--terminal-tmux--list-window-info session)))
376+
377+ (defun enkan-repl--terminal-tmux--list-window-info (session )
378+ " Return plist entries describing tmux windows in SESSION.
379+ Each entry contains :window, :cwd, and :pane. :pane is the stable tmux
380+ pane id (for example, \" %12\" ) used for command targets."
371381 (let ((out (enkan-repl--terminal-tmux--call
372382 (list " list-windows" " -t" session " -F"
373- " #{window_name}\t #{pane_current_path}" )
383+ " #{window_name}\t #{pane_current_path}\t #{pane_id} " )
374384 t )))
375385 (when (and out (not (string-empty-p out)))
376386 (delq
377387 nil
378388 (mapcar
379389 (lambda (line )
380- (pcase-let ((`(,window ,cwd ) (split-string line " \t " )))
381- (when (and window cwd (not (string-empty-p window)))
382- (cons window (unless (string-empty-p cwd) cwd)))))
390+ (pcase-let ((`(,window ,cwd ,pane ) (split-string line " \t " )))
391+ (when (and window (not (string-empty-p window)))
392+ (list :window window
393+ :cwd (unless (string-empty-p cwd) cwd)
394+ :pane (and pane
395+ (not (string-empty-p pane))
396+ pane)))))
383397 (split-string out " \n " t ))))))
384398
385399(defun enkan-repl--terminal-tmux--derive-base-name (dir )
@@ -399,20 +413,38 @@ Tries BASE, then BASE-2, BASE-3, ... until an unused name is found."
399413 (setq candidate (format " %s -%d " base n)))
400414 candidate))
401415
402- (defun enkan-repl--terminal-tmux--make-id (session window )
403- " Return tmux target identifier for SESSION:WINDOW."
404- (format " %s :%s " session window))
416+ (defun enkan-repl--terminal-tmux--make-id (session window &optional pane )
417+ " Return enkan tmux identifier for SESSION, WINDOW, and optional PANE.
418+ When PANE is available, the identifier keeps WINDOW for Emacs-side metadata
419+ but tmux commands target the stable PANE id. This avoids tmux parsing window
420+ names such as dr-remote.jp as pane selectors."
421+ (if (and (stringp pane) (not (string-empty-p pane)))
422+ (format " %s :%s |%s " session window pane)
423+ (format " %s :%s " session window)))
405424
406425(defun enkan-repl--terminal-tmux--id-window (id )
407- " Return the window component of tmux ID (after the colon) ."
426+ " Return the window component of tmux ID."
408427 (when (and (stringp id) (string-match " :\\ (.+\\ )$" id))
428+ (let ((window (match-string 1 id)))
429+ (if (string-match " \\ `\\ (.*\\ )|%[0-9]+\\ '" window)
430+ (match-string 1 window)
431+ window))))
432+
433+ (defun enkan-repl--terminal-tmux--id-pane (id )
434+ " Return the pane id component of tmux ID, or nil for legacy IDs."
435+ (when (and (stringp id) (string-match " |\\ (%[0-9]+\\ )\\ '" id))
409436 (match-string 1 id)))
410437
411438(defun enkan-repl--terminal-tmux--id-session (id )
412439 " Return the session component of tmux ID (before the colon)."
413440 (when (and (stringp id) (string-match " ^\\ ([^:]+\\ ):" id))
414441 (match-string 1 id)))
415442
443+ (defun enkan-repl--terminal-tmux--target (id )
444+ " Return the actual tmux target for enkan tmux ID."
445+ (or (enkan-repl--terminal-tmux--id-pane id)
446+ id))
447+
416448(defun enkan-repl--terminal-tmux-start (dir )
417449 " Tmux backend: start a new session/window in DIR for current workspace.
418450Ensures the workspace's tmux session exists (creating it on demand) and
@@ -426,27 +458,37 @@ target identifier (e.g. \"enkan-01:lat\")."
426458 (cond
427459 ; ; Session does not exist: create it with the first window in DIR.
428460 ((not (enkan-repl--terminal-tmux--has-session session))
429- (unless (enkan-repl--terminal-tmux--call
430- (list " new-session" " -d" " -s" session " -c" cdir " -n" base))
431- (user-error " Tmux new-session failed for %s" session))
432- (enkan-repl--terminal-tmux--make-id session base))
461+ (let ((pane (enkan-repl--terminal-tmux--call
462+ (list " new-session" " -d" " -P" " -F" " #{pane_id}"
463+ " -s" session " -c" cdir " -n" base)
464+ t )))
465+ (unless (and pane (not (string-empty-p pane)))
466+ (user-error " Tmux new-session failed for %s" session))
467+ (enkan-repl--terminal-tmux--make-id session base pane)))
433468 ; ; Session exists: add a new window with a non-colliding name.
434469 (t
435470 (let ((win (enkan-repl--terminal-tmux--next-instance-name session base)))
436- (unless (enkan-repl--terminal-tmux--call
437- (list " new-window" " -t" session " -n" win " -c" cdir))
438- (user-error " Tmux new-window failed for %s:%s" session win))
439- (enkan-repl--terminal-tmux--make-id session win))))))
471+ (let ((pane (enkan-repl--terminal-tmux--call
472+ (list " new-window" " -P" " -F" " #{pane_id}"
473+ " -t" session " -n" win " -c" cdir)
474+ t )))
475+ (unless (and pane (not (string-empty-p pane)))
476+ (user-error " Tmux new-window failed for %s:%s" session win))
477+ (enkan-repl--terminal-tmux--make-id session win pane)))))))
440478
441479(defun enkan-repl--terminal-tmux-send (id text &optional newline )
442480 " Tmux backend: send TEXT to ID via send-keys -l.
443481When NEWLINE is non-nil, follow with an Enter key. Returns t on success."
444482 (when (and id text)
445483 (and (enkan-repl--terminal-tmux--call
446- (list " send-keys" " -t" id " -l" text))
484+ (list " send-keys" " -t"
485+ (enkan-repl--terminal-tmux--target id)
486+ " -l" text))
447487 (or (not newline)
448488 (enkan-repl--terminal-tmux--call
449- (list " send-keys" " -t" id " Enter" ))))))
489+ (list " send-keys" " -t"
490+ (enkan-repl--terminal-tmux--target id)
491+ " Enter" ))))))
450492
451493(defun enkan-repl--terminal-tmux-send-key (id key )
452494 " Tmux backend: send special KEY (`escape' , `enter' , integer 1..9) to ID."
@@ -455,29 +497,42 @@ When NEWLINE is non-nil, follow with an Enter key. Returns t on success."
455497 ('enter " Enter" )
456498 ((pred integerp) (number-to-string key))
457499 (_ (user-error " Unsupported terminal key: %S" key)))))
458- (enkan-repl--terminal-tmux--call (list " send-keys" " -t" id arg))))
500+ (enkan-repl--terminal-tmux--call
501+ (list " send-keys" " -t" (enkan-repl--terminal-tmux--target id) arg))))
459502
460503(defun enkan-repl--terminal-tmux-alive-p (id )
461504 " Tmux backend: t if SESSION:WINDOW described by ID still exists."
462505 (when id
463506 (let ((session (enkan-repl--terminal-tmux--id-session id))
507+ (pane (enkan-repl--terminal-tmux--id-pane id))
464508 (window (enkan-repl--terminal-tmux--id-window id)))
465- (and session window
509+ (and session
466510 (enkan-repl--terminal-tmux--has-session session)
467- (member window (enkan-repl--terminal-tmux--list-windows session))
511+ (if pane
512+ (enkan-repl--terminal-tmux--call
513+ (list " display-message" " -p" " -t" pane " #{pane_id}" )
514+ t )
515+ (and window
516+ (member window
517+ (enkan-repl--terminal-tmux--list-windows session))))
468518 t ))))
469519
470520(defun enkan-repl--terminal-tmux-list ()
471521 " Tmux backend: list all window identifiers in the current workspace's session."
472522 (let ((session (enkan-repl--terminal-tmux--workspace-session)))
473523 (when (and session (enkan-repl--terminal-tmux--has-session session))
474- (mapcar (lambda (w ) (enkan-repl--terminal-tmux--make-id session w))
475- (enkan-repl--terminal-tmux--list-windows session)))))
524+ (mapcar (lambda (info )
525+ (enkan-repl--terminal-tmux--make-id
526+ session
527+ (plist-get info :window )
528+ (plist-get info :pane )))
529+ (enkan-repl--terminal-tmux--list-window-info session)))))
476530
477531(defun enkan-repl--terminal-tmux-kill (id )
478532 " Tmux backend: kill the window described by ID."
479533 (when id
480- (enkan-repl--terminal-tmux--call (list " kill-window" " -t" id))))
534+ (enkan-repl--terminal-tmux--call
535+ (list " kill-window" " -t" (enkan-repl--terminal-tmux--target id)))))
481536
482537; ;;;; tmux mirror buffer
483538
@@ -741,24 +796,31 @@ tmux capture process."
741796 0.2 ))
742797 (out (enkan-repl--terminal-tmux--call
743798 (list " list-windows" " -t" session " -F"
744- " #{window_name}\t #{window_bell_flag}" )
799+ " #{window_name}\t #{pane_id} \t #{ window_bell_flag}" )
745800 t )))
746801 (when (stringp out)
747802 (delq
748803 nil
749804 (mapcar
750805 (lambda (line )
751- (pcase-let ((`(,window ,flag ) (split-string line " \t " )))
752- (when (and window (string= flag " 1" ))
753- (enkan-repl--terminal-tmux--make-id session window))))
806+ (let ((fields (split-string line " \t " )))
807+ (pcase fields
808+ (`(,window ,pane ,flag )
809+ (when (and window (string= flag " 1" ))
810+ (enkan-repl--terminal-tmux--make-id session window pane)))
811+ (`(,window ,flag )
812+ (when (and window (string= flag " 1" ))
813+ (enkan-repl--terminal-tmux--make-id session window))))))
754814 (split-string out " \n " t ))))))
755815
756816(defun enkan-repl--terminal-tmux--all-targets (session )
757817 " Return all tmux target ids in SESSION."
758- (let ((windows (enkan-repl--terminal-tmux--list-windows session)))
759- (mapcar (lambda (window )
760- (enkan-repl--terminal-tmux--make-id session window))
761- windows)))
818+ (mapcar (lambda (info )
819+ (enkan-repl--terminal-tmux--make-id
820+ session
821+ (plist-get info :window )
822+ (plist-get info :pane )))
823+ (enkan-repl--terminal-tmux--list-window-info session)))
762824
763825(defun enkan-repl--terminal-tmux--alert-capture (target )
764826 " Return a small bounded capture from TARGET for alert detection."
@@ -777,7 +839,7 @@ tmux capture process."
777839 (out (enkan-repl--terminal-tmux--call
778840 (list " capture-pane" " -p" " -J" " -S"
779841 (format " -%d " lines)
780- " -t" target)
842+ " -t" (enkan-repl--terminal-tmux-- target target) )
781843 t )))
782844 (when (stringp out)
783845 (if (and max-chars (> (length out) max-chars))
@@ -1112,7 +1174,8 @@ CALLBACK receives the cwd string, or nil on failure."
11121174 (user-error " Tmux executable not found: %s" enkan-repl-tmux-executable))
11131175 (let* ((output-buffer (generate-new-buffer " *enkan-repl tmux cwd*" ))
11141176 (command (list enkan-repl-tmux-executable
1115- " display-message" " -p" " -t" id
1177+ " display-message" " -p" " -t"
1178+ (enkan-repl--terminal-tmux--target id)
11161179 " #{pane_current_path}" )))
11171180 (make-process
11181181 :name (format " enkan-tmux-cwd %s " id)
@@ -1285,7 +1348,7 @@ arguments: CONTENT, or nil on failure, and the tmux process exit status."
12851348 (let* ((command (list enkan-repl-tmux-executable
12861349 " capture-pane" " -p" " -J" " -S"
12871350 (format " -%d " (max 0 lines))
1288- " -t" id ))
1351+ " -t" (enkan-repl--terminal-tmux--target id) ))
12891352 (max-chars (and (integerp enkan-repl-tmux-mirror-max-chars)
12901353 (> enkan-repl-tmux-mirror-max-chars 0 )
12911354 enkan-repl-tmux-mirror-max-chars))
@@ -1295,15 +1358,15 @@ arguments: CONTENT, or nil on failure, and the tmux process exit status."
12951358 process)
12961359 (cl-labels
12971360 ((finish
1298- (status)
1299- (unless done
1300- (setq done t )
1301- (when timer
1302- (cancel-timer timer)
1303- (setq timer nil ))
1304- (funcall callback
1305- (and (integerp status) (zerop status) content)
1306- status))))
1361+ (status)
1362+ (unless done
1363+ (setq done t )
1364+ (when timer
1365+ (cancel-timer timer)
1366+ (setq timer nil ))
1367+ (funcall callback
1368+ (and (integerp status) (zerop status) content)
1369+ status))))
13071370 (setq process
13081371 (make-process
13091372 :name (format " enkan-tmux-capture %s " id)
0 commit comments