Improve network communication between Emacs & MATLAB
matlab-netshell.el: (matlab-netshell-acc, matlab-netshell-filter): Keep an accumulator for input from the tcp process. Packets are seperated by \0. Commands start, end with newline, then data follows. Call below with found commands. (matlab-netshell-execute-command): Log commands recieved. Evaluate different commands. Implement ACK. (matlab-netshell-sentinel): Create buffer for newly formed network processes. (matlab-netshell-send): Fix to send CMD,DATA, not just msg. (matlab-netshell-eval): Send request to eval something in ML. (matlab-netshell-ack): Send ACK request. +emacs/@EmacsServer/EmacsServer.m NEW. Object to handle MATLAB state for connecting over tcp with Emacs. Implementation started in emacsnetshell.m Added command parsing (same as Emacs) and command execution. Implement ACK emacsnetshell.m: Remove old impl. Use EmacsServer for core behavior. Act as interface to object so user can send commands via this function.
This commit is contained in:
parent
25c2fe36c6
commit
8296c09180
|
@ -60,15 +60,62 @@
|
|||
(delete-process matlab-netshell-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."
|
||||
;; We recieved a command from PROC. Interpret the command.
|
||||
(cond ((string-match "^init" string)
|
||||
;; 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."))
|
||||
(t
|
||||
(message "Unknown command from matlab: %S" string)
|
||||
(message "Unknown command from matlab: %S" cmd)
|
||||
)))
|
||||
|
||||
(defun matlab-netshell-sentinel (proc msg)
|
||||
|
@ -77,23 +124,37 @@ 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" proc)))
|
||||
(message (format "MATLAB has dropped its connecction")))
|
||||
|
||||
(t
|
||||
(message "Unhandle event."))))
|
||||
(message "Unhandled event."))))
|
||||
|
||||
(defun matlab-netshell-send (msg)
|
||||
"Send MSG to the active MATLAB shell connection."
|
||||
(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 (msg)
|
||||
"Send MSG to the active MATLAB shell connection to eval."
|
||||
(interactive "sMsg: ")
|
||||
(let ((C (car matlab-netshell-clients)))
|
||||
(if C
|
||||
(process-send-string C (concat msg "\n"))
|
||||
(error "No MATLAB network connection to send to.")
|
||||
)))
|
||||
(process-send-string C (concat "eval\n" msg "\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)
|
||||
|
|
157
toolbox/+emacs/@EmacsServer/EmacsServer.m
Normal file
157
toolbox/+emacs/@EmacsServer/EmacsServer.m
Normal file
|
@ -0,0 +1,157 @@
|
|||
classdef EmacsServer < handle
|
||||
% Class EMACSSERVER - Support a TCP connection to an Emacs server.
|
||||
% Collects input from Emacs, and runs commands.
|
||||
% Sends commands to Emacs from MATLAB if needed.
|
||||
%
|
||||
|
||||
properties (Access='protected')
|
||||
tcpclient;
|
||||
timer;
|
||||
end
|
||||
|
||||
methods
|
||||
function ES = EmacsServer()
|
||||
% Construct the Emacs Server. Create the TCP client and
|
||||
|
||||
ES.tcpclient = tcpclient('localhost', 32475);
|
||||
|
||||
if isempty(ES.tcpclient)
|
||||
delete(ES);
|
||||
error('Unable to connect to Emacs.');
|
||||
end
|
||||
|
||||
ES.timer = timer('Name','Emacs NetShell timer', ...
|
||||
'TimerFcn', {@watch_emacs ES}, ...
|
||||
'Period', 3,...
|
||||
'BusyMode', 'drop',...
|
||||
'ErrorFcn', {@drop_emacs ES},...
|
||||
'ExecutionMode', 'fixedSpacing');
|
||||
|
||||
start(ES.timer);
|
||||
|
||||
ES.SendCommand('init');
|
||||
end
|
||||
|
||||
function delete(ES)
|
||||
stop(ES.timer);
|
||||
|
||||
delete(ES.tcpclient);
|
||||
delete(ES.timer);
|
||||
end
|
||||
|
||||
function SendCommand(ES, cmd, data)
|
||||
% Commands have 2 parts, the COMMAND and DATA.
|
||||
% COMMAND is sent to emacs followed by a newline. This should be a single
|
||||
% word. SendCommand adds the newline.
|
||||
% DATA can be any string.
|
||||
% The full command is terminated by a NULL -> uint8(0)
|
||||
|
||||
write(ES.tcpclient, uint8([ cmd newline]));
|
||||
|
||||
if nargin > 2 && ~isempty(data)
|
||||
write(ES.tcpclient, uint8(data));
|
||||
end
|
||||
write(ES.tcpclient, uint8(0));
|
||||
|
||||
disp([ 'Sent: ' cmd uint8(0)])
|
||||
end
|
||||
end
|
||||
|
||||
properties (Access='protected')
|
||||
accumulator = '';
|
||||
end
|
||||
|
||||
methods (Access='protected')
|
||||
function ReadCommand(ES)
|
||||
|
||||
msg = char(read(ES.tcpclient));
|
||||
|
||||
ES.accumulator = [ ES.accumulator msg ];
|
||||
|
||||
while ~isempty(ES.accumulator)
|
||||
|
||||
k = strfind(ES.accumulator, char(0));
|
||||
|
||||
if isempty(k)
|
||||
% No complete commands. Exit.
|
||||
disp(['partial accumulation: ' ES.accumulator]);
|
||||
return;
|
||||
end
|
||||
|
||||
datamsg = ES.accumulator(1:k(1)-1);
|
||||
ES.accumulator = ES.accumulator(k(1)+1:end);
|
||||
|
||||
% Now peal the datamsg into it's constituant parts.
|
||||
cr = strfind(datamsg, newline);
|
||||
|
||||
if isempty(cr)
|
||||
cmd = datamsg;
|
||||
data = '';
|
||||
else
|
||||
cmd = datamsg(1:cr(1)-1);
|
||||
data = datamsg(cr(1)+1:end);
|
||||
end
|
||||
|
||||
ES.ExecuteRemoteCommand(cmd, data);
|
||||
end
|
||||
end
|
||||
|
||||
function ExecuteRemoteCommand(ES, cmd, data)
|
||||
% When we recieve a command from Emacs, eval it.
|
||||
|
||||
switch cmd
|
||||
|
||||
case 'nowledge'
|
||||
disp('Acknowledgement recieved.');
|
||||
|
||||
case 'ack'
|
||||
disp('Ack Recieved. Sending ack back.');
|
||||
write(ES.tcpclient, uint8('nowledge'));
|
||||
|
||||
case 'eval'
|
||||
try
|
||||
OUT = evalc(data);
|
||||
catch ERR
|
||||
OUT = ERR.message;
|
||||
end
|
||||
write(ES.tcpclient, uint8(OUT));
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
function watch_emacs(~, ~, ES)
|
||||
% Timer Callback Function:
|
||||
% Watch for bytes available from the Emacs network connection, and act on any events.
|
||||
|
||||
ba = ES.tcpclient.BytesAvailable;
|
||||
|
||||
if ba > 0
|
||||
|
||||
ES.ReadCommand();
|
||||
|
||||
else
|
||||
% Nothing recieved
|
||||
%disp('No Luv from Emacs');
|
||||
|
||||
% Check if we are still alive. We can only do that with a
|
||||
% write- so send an empty message.
|
||||
try
|
||||
write(ES.tcpclient, 0);
|
||||
catch
|
||||
disp('Connection to Emacs lost. Shutting down net server');
|
||||
delete(ES);
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function drop_emacs(~, ~, ES)
|
||||
% If the timer throws an error, then shutdown.
|
||||
|
||||
delete(ES);
|
||||
|
||||
disp('Error in timer, dropping connection to Emacs.');
|
||||
|
||||
end
|
|
@ -1,80 +1,41 @@
|
|||
function emacsnetshell(sendmsg)
|
||||
function emacsnetshell(cmd, data)
|
||||
% Create a connection to an EMACS editor server.
|
||||
% If we succeed then setup a timer to watch what comes in from the connection.
|
||||
|
||||
NETSHELL = getappdata(groot, 'EmacsNetShell');
|
||||
|
||||
EMACSSERVER = getappdata(groot, 'EmacsNetShell');
|
||||
|
||||
if nargin == 0
|
||||
sendmsg = 'init';
|
||||
cmd = 'init';
|
||||
end
|
||||
|
||||
if strcmp(sendmsg, 'shutdown')
|
||||
if strcmp(cmd, 'shutdown')
|
||||
% Shutdown our connection to emacs.
|
||||
if ~isempty(NETSHELL)
|
||||
stop(NETSHELL.timer);
|
||||
setappdata(groot, 'EmacsNetShell', []);
|
||||
|
||||
delete(NETSHELL.tcpclient);
|
||||
delete(NETSHELL.timer);
|
||||
setappdata(groot, 'EmacsNetShell', []);
|
||||
if ~isempty(EMACSSERVER)
|
||||
delete(EMACSSERVER);
|
||||
end
|
||||
else
|
||||
if isempty(NETSHELL)
|
||||
|
||||
NETSHELL.tcpclient = tcpclient('localhost', 32475);
|
||||
|
||||
if isempty(NETSHELL.tcpclient)
|
||||
error('Unable to connect to Emacs.');
|
||||
end
|
||||
|
||||
NETSHELL.timer = timer('Name','Emacs NetShell timer', ...
|
||||
'TimerFcn', @watch_emacs, ...
|
||||
'Period', 3,...
|
||||
'BusyMode', 'drop',...
|
||||
'ErrorFcn', @drop_emacs,...
|
||||
'ExecutionMode', 'fixedSpacing');
|
||||
|
||||
setappdata(groot, 'EmacsNetShell', NETSHELL);
|
||||
|
||||
start(NETSHELL.timer);
|
||||
end
|
||||
|
||||
if isstring(sendmsg)
|
||||
sendmsg = char(sendmsg);
|
||||
if ~isempty(EMACSSERVER) && ~isvalid(EMACSSERVER)
|
||||
EMACSSERVER = [];
|
||||
setappdata(groot, 'EmacsNetShell', EMACSSERVER);
|
||||
end
|
||||
|
||||
% We have a client, so lets send our msg.
|
||||
write(NETSHELL.tcpclient, uint8([ sendmsg '\n']));
|
||||
if isempty(EMACSSERVER)
|
||||
EMACSSERVER = emacs.EmacsServer();
|
||||
setappdata(groot, 'EmacsNetShell', EMACSSERVER);
|
||||
end
|
||||
|
||||
if ~ischar(cmd)
|
||||
error('Command must be a char vector.');
|
||||
end
|
||||
|
||||
if nargin == 2
|
||||
EMACSSERVER.SendCommand(cmd, data);
|
||||
else
|
||||
EMACSSERVER.SendCommand(cmd);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
function watch_emacs(~, ~)
|
||||
% Timer Callback Function:
|
||||
% Watch for bytes available from the Emacs network connection, and act on any events.
|
||||
|
||||
NETSHELL = getappdata(groot, 'EmacsNetShell');
|
||||
|
||||
ba = NETSHELL.tcpclient.BytesAvailable;
|
||||
|
||||
if ba > 0
|
||||
msg = char(read(NETSHELL.tcpclient));
|
||||
|
||||
OUT = evalc(msg);
|
||||
|
||||
write(NETSHELL.tcpclient, uint8(OUT));
|
||||
else
|
||||
% Nothing recieved
|
||||
% disp('No Luv from Emacs');
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function drop_emacs(~, ~)
|
||||
% If the timer throws an error, then shutdown.
|
||||
|
||||
emacsnetshell('shutdown');
|
||||
|
||||
disp('Error in timer, dropping connection to Emacs.');
|
||||
|
||||
end
|
Loading…
Reference in a new issue