;;; matlab-netshell.el --- Control MATLAB from a network port. ;; ;; Copyright (C) 2019 Eric Ludlam ;; ;; Author: Eric Ludlam ;; ;; This program 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. ;; This program 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 this program. If not, see https://www.gnu.org/licenses/. ;;; Commentary: ;; ;; Form a back-channale for Emacs to chat with a running MATLAB. ;; Allows you to edit for and with a MATLAB sessions, even if it is not in a ;; matlab-shell buffer. (require 'matlab) ;;; Code: (defvar matlab-netshell-listen-port 32475 "Port used for the Emacs server listening for MATLAB connections.") (defvar matlab-netshell-server-name "*MATLAB netshell*" "Name used for the Netshell server") (defvar matlab-netshell-clients nil "List of clients created from the MATLAB netshell server.") ;;;###autoload (defun matlab-netshell-server-active-p () "Return non-nil if there is an active MATLAB netshell server." (let ((buff (get-buffer matlab-netshell-server-name))) (when (and buff (get-buffer-process buff)) t))) ;;;###autoload (defun matlab-netshell-server-start () "Start the MATLAB netshell server." (interactive) (make-network-process :name matlab-netshell-server-name :buffer matlab-netshell-server-name :family 'ipv4 :host 'local :service matlab-netshell-listen-port :filter #'matlab-netshell-filter :sentinel #'matlab-netshell-sentinel :server t) (setq matlab-netshell-clients nil) ) (defun matlab-netshell-client () "Return a netshell client." (car matlab-netshell-clients)) (defun matlab-netshell-server-stop nil "Stop the MATLAB Netshell server." (interactive) (dolist (C matlab-netshell-clients) (delete-process C)) (setq matlab-netshell-clients nil) (delete-process matlab-netshell-server-name) ) (defvar matlab-netshell-acc "" "Text Accumulator for MATLAB's netshell.") (make-variable-buffer-local 'matlab-netshell-acc) (defun matlab-netshell-filter (proc string) "Filter used for MATLAB Netshell processes." ;; Accumulate from the process (setq matlab-netshell-acc (concat matlab-netshell-acc string)) ;; Wait for a NULL command terminator. (while (string-match "\0" matlab-netshell-acc) (let* ((cmdstr (substring matlab-netshell-acc 0 (match-beginning 0)))) ;; Trim off our new command from accumulator. (setq matlab-netshell-acc (substring matlab-netshell-acc (match-end 0))) ;; Is this a test packet? If so, we're done. (when (not (string= cmdstr "")) ;; Find the newline. (unless (string-match "\n" cmdstr) (message "Unable to find command in MATLAB command. Ignoring.")) (let ((cmd (substring cmdstr 0 (match-beginning 0))) (data (let ((me (match-end 0))) (if (> (length cmdstr) me) (substring cmdstr me) "")))) (matlab-netshell-execute-command proc cmd data) ))))) (defun matlab-netshell-execute-command (proc cmd data) "For MATLAB associated with PROC, execute CMD with DATA. The CMD is a request from MATLAB to do something in Emacs. A common command might be to display data to the user as a response from some Emacs based request." ;; Log the command (with-current-buffer (process-buffer proc) (goto-char (point-max)) (when (not (= (current-column) 0)) (insert "\n")) (insert "Command: " cmd "\n") (when (not (string= data "")) (insert "Data: :" data "\n")) ) ;; Interpret the command. (cond ((string= "init" cmd) nil ; Yep, thanks for letting me know (message "MATLAB connection initialized.") ) ((string= "ack" cmd) (message "Ack recieved. Send ACK back.") (matlab-netshell-send "nowledge" "")) ((string= "nowledge" cmd) (message "Acknowledgement recieved.")) ((string= "output" cmd) (message "Ouput: %S" data)) ((string= "error" cmd) (message "MATLAB Error: %s" data)) (t (message "Unknown command from matlab: %S" cmd) ))) (defun matlab-netshell-sentinel (proc msg) "Sentinel used for MATLAB Netshell processes. Identify when a connection is lost, and close down services." (cond ((string-match "^open from " msg) ;; New connection - set it up. (setq matlab-netshell-clients (cons proc matlab-netshell-clients)) (let ((newbuff (get-buffer-create (process-name proc)))) (set-process-buffer proc newbuff)) (message "MATLAB Has connected!")) ((string= msg "connection broken by remote peer\n") (setq matlab-netshell-clients (delq proc matlab-netshell-clients)) (message (format "MATLAB has dropped its connecction"))) (t (message "Unhandled event.")))) (defun matlab-netshell-send(cmd data) "Send a command to MATLAB shell connection with DATA." (let ((C (car matlab-netshell-clients))) (if C (process-send-string C (concat cmd "\n" data "\0")) (error "No MATLAB network connection to send to.")))) (defun matlab-netshell-eval (mcode) "Send MSG to the active MATLAB shell connection to eval." (interactive "sMCode: ") (let ((C (car matlab-netshell-clients))) (if C (process-send-string C (concat "eval\n" mcode "\0")) (error "No MATLAB network connection to send to.")))) (defun matlab-netshell-evalc (mcode) "Send MSG to the active MATLAB shell connection to eval." (interactive "sMCode: ") (let ((C (car matlab-netshell-clients))) (if C (process-send-string C (concat "evalc\n" mcode "\0")) (error "No MATLAB network connection to send to.")))) (defun matlab-netshell-ack () "Send an ACK to MATLAB to see if it can respond." (interactive) (matlab-netshell-send "ack" "")) ;;(matlab-netshell-server-start) ;;(sleep-for 300) ;;(matlab-netshell-server-stop) (provide 'matlab-netshell) ;;; matlab-netshell.el ends here