commit 0619483254e8cd54f371849bbbcd2e8e6df3a71e Author: Allard Hendriksen Date: Tue Jan 2 18:58:25 2018 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/ob-tmux.el b/ob-tmux.el new file mode 100644 index 0000000..bbef876 --- /dev/null +++ b/ob-tmux.el @@ -0,0 +1,143 @@ +;;; ob-tmux.el --- Babel Support for Interactive Terminal -*- lexical-binding: t; -*- + +;; Copyright (C) 2009-2017 Free Software Foundation, Inc. + +;; Author: Benjamin Andresen, Allard Hendriksen +;; Keywords: literate programming, interactive shell +;; Homepage: http://orgmode.org + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: + +;; Org-Babel support for interactive terminals. Mostly shell scripts. +;; Heavily inspired by 'eev' from Eduardo Ochs +;; +;; Adding :cmd and :terminal as header arguments +;; :terminal must support the -T (title) and -e (command) parameter +;; +;; You can test the default setup. (xterm + sh) with +;; M-x org-babel-tmux-test RET + +;;; Code: +(require 'ob) + +(defvar org-babel-tmux-location "tmux" + "The command location for tmux. +In case you want to use a different tmux than one selected by your $PATH") + +(defvar org-babel-default-header-args:tmux + '((:results . "silent") (:session . "default") (:cmd . "bash") (:terminal . "gnome-terminal")) + "Default arguments to use when running tmux source blocks.") + +(defun org-babel-execute:tmux (body params) + "Send a block of code via tmux to a terminal using Babel. +\"default\" session is used when none is specified." + (message "Sending source code block to interactive terminal session...") + (save-window-excursion + (let* ((session (cdr (assq :session params))) + (socket (org-babel-tmux-session-socketname session))) + (unless socket (org-babel-prep-session:tmux session params)) + (org-babel-tmux-session-execute-string + session (org-babel-expand-body:generic body params))))) + +(defun org-babel-prep-session:tmux (_session params) + "Prepare SESSION according to the header arguments specified in PARAMS." + (let* ((session (cdr (assq :session params))) + (cmd (cdr (assq :cmd params))) + (terminal (cdr (assq :terminal params))) + (process-name (concat "org-babel: terminal (" session ")"))) + (apply 'start-process process-name "*Messages*" + terminal + `("--" + ,org-babel-tmux-location + "new-session" "-A" "-s" + ,(concat "org-babel-session-" session))) + ;; XXX: Is there a better way than the following? + (while (not (org-babel-tmux-session-socketname session)) + ;; wait until tmux session is available before returning + ))) + +;; helper functions + +(defun org-babel-tmux-session-execute-string (session body) + "If SESSION exists, send BODY to it." + (let ((socket (org-babel-tmux-session-socketname session))) + (when socket + (let ((tmpfile (org-babel-tmux-session-write-temp-file session body))) + (shell-command + (concat "cat " tmpfile + " | xargs -I{} tmux send-keys -t org-babel-session-" session ":1 '{}' Enter")))))) + +(defun org-babel-tmux-session-socketname (session) + "Check if SESSION exists by parsing output of \"tmux ls\"." + (let* ((tmux-ls (shell-command-to-string "tmux ls")) + (sockets (delq + nil + (mapcar + (lambda (x) + (when (string-match (rx "windows") x) + x)) + (split-string tmux-ls "\n")))) + (match-socket (car + (delq + nil + (mapcar + (lambda (x) + (when (string-match + (concat "org-babel-session-" session) x) + x)) + sockets))))) + (when match-socket (car (split-string match-socket ":"))))) + +(defun org-babel-tmux-session-write-temp-file (_session body) + "Save BODY in a temp file that is named after SESSION." + (let ((tmpfile (org-babel-temp-file "ob-tmux-"))) + (with-temp-file tmpfile + (insert body) + + ;; org-babel has superfluous spaces + (goto-char (point-min)) + (delete-matching-lines "^ +$")) + tmpfile)) + +;; (defun org-babel-screen-test () +;; "Test if the default setup works. +;; The terminal should shortly flicker." +;; (interactive) +;; (let* ((random-string (format "%s" (random 99999))) +;; (tmpfile (org-babel-temp-file "ob-screen-test-")) +;; (body (concat "echo '" random-string "' > " tmpfile "\nexit\n")) +;; tmp-string) +;; (org-babel-execute:screen body org-babel-default-header-args:screen) +;; ;; XXX: need to find a better way to do the following +;; (while (not (file-readable-p tmpfile)) +;; ;; do something, otherwise this will be optimized away +;; (format "org-babel-screen: File not readable yet.")) +;; (setq tmp-string (with-temp-buffer +;; (insert-file-contents-literally tmpfile) +;; (buffer-substring (point-min) (point-max)))) +;; (delete-file tmpfile) +;; (message (concat "org-babel-screen: Setup " +;; (if (string-match random-string tmp-string) +;; "WORKS." +;; "DOESN'T work."))))) + +(provide 'ob-tmux) + + + +;;; ob-tmux.el ends here