Skip to content

Commit 64e1805

Browse files
committed
Fix and improve remote matlab-shell
Prior to the commit for #26 remote M-x matlab-shell partially worked. You could run M-x matlab-shell on a tramp remote location, but debugger, hyperlinks, etc. didn't work. This commit enables remote matlab-shell and enables remote debugging, hyperlinks, etc. The only item remaining that I'm aware of, as of this commit, is to get emacsclient tunneling though ssh so that ">> edit foo" works in the remote matlab-shell.
1 parent a55c2dd commit 64e1805

19 files changed

+349
-102
lines changed

README.org

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#+startup: showall
22
#+options: toc:nil
33

4-
# Copyright 2024 Free Software Foundation, Inc.
4+
# Copyright 2016-2025 Free Software Foundation, Inc.
55

66
* Emacs MATLAB-mode
77

@@ -14,7 +14,11 @@
1414

1515
2. *M-x matlab-shell* for running and debugging MATLAB within Emacs (Unix-only).
1616

17+
- MATLAB command window errors are hyper-linked and files open in Emacs
18+
- Debugging support is available from the MATLAB menu.
1719
- matlab-shell uses company-mode for completions.
20+
- You can use Emacs TRAMP and =M-x matlab-shell= to run remote MATLAB within your local Emacs
21+
session, see [[file:doc/remote-matlab-shell.org][doc/remote-matlab-shell.org]].
1822

1923
3. *Code sections* support. MATLAB script code files often contain many commands and lines of text.
2024
You typically focus your efforts on a single part of your code at a time, working with the code

doc/remote-matlab-shell.org

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# File: doc/remote-matlab-shell.org
2+
#
3+
#+startup: showall
4+
#+options: toc:nil
5+
#
6+
# Copyright 2016-2025 Free Software Foundation, Inc.
7+
8+
* Remote M-x matlab-shell
9+
10+
You can use Emacs TRAMP to run matlab-shell on a remote system.
11+
12+
1. First verify you can connect to the remote system in a terminal
13+
14+
For example, we can use ssh to connect:
15+
16+
#+begin_src bash
17+
ssh user@system pwd
18+
#+end_src
19+
20+
You should configure your ssh keys to avoid prompting, which will make things smoother with
21+
Emacs.
22+
23+
2. In Emacs visit a remote location.
24+
25+
A typical method is to use File menu to visit a file or ~C-x C-f~
26+
27+
#+begin_example
28+
C-x C-f
29+
Find File: /ssh:USER@SYSTEM:~
30+
#+end_example
31+
32+
will open dired-mode to the USER home (~) directory on the remote SYSTEM.
33+
34+
3. Run matlab-shell
35+
36+
With the current buffer as a remote location, e.g. dired-mode buffer of a remote location, run:
37+
38+
#+begin_example
39+
M-x matlab-shell
40+
#+end_example
41+
42+
matlab-shell will copy files to the remote system and place them in =~/.emacs-matlab-shell/=. These are
43+
needed for matlab-shell to work correctly on the remote system.
44+
45+
If you get a message that Emacs couldn't find matlab on the remote system, you need to tell Emacs
46+
where matlab is located and there are several ways to do this, see
47+
[[https://www.gnu.org/software/emacs/manual/html_node/tramp/Remote-programs.html][How TRAMP finds and uses programs on remote host]]. For example, suppose your remote system is Linux and you are
48+
using the Bash shell. You can setup your remote =/ssh:USER@HOST:~/.profile= to place the location of MATLAB on
49+
your PATH:
50+
51+
#+begin_src bash
52+
# ~/.profile
53+
PATH=/usr/local/MATLAB/Ryyyyab/bin/$PATH # Replace Ryyyyab with the MATLAB release you are using
54+
export PATH
55+
#+end_src
56+
57+
After that you can add to your local =~/.emacs=,
58+
59+
#+begin_src emacs-lisp
60+
(eval-after-load
61+
'(add-to-list 'tramp-remote-path 'tramp-own-remote-path))
62+
#+end_src
63+
64+
# LocalWords: showall dired usr Ryyyyab

matlab-shell.el

Lines changed: 131 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -330,22 +330,29 @@ otherwise an error is signaled."
330330
;; Case: "matlab" (or something similar), locate it on the executable path
331331
;; else locate in standard install locations.
332332
(t
333-
(setq abs-matlab-exe (executable-find matlab-shell-command))
334-
(when (not abs-matlab-exe)
335-
(if (string= matlab-shell-command "matlab")
336-
;; Get latest matlab command exe from the default installation location.
337-
(let* ((default-loc (cdr (assoc system-type matlab-shell--default-command)))
338-
(default-matlab (when default-loc
339-
(car (last (sort
340-
(file-expand-wildcards default-loc)
341-
#'string<))))))
342-
(when (not default-matlab)
343-
(matlab-shell--matlab-not-found no-error default-loc))
344-
(when (not (file-executable-p default-matlab))
345-
(user-error "%s is not executable" default-matlab))
346-
(setq abs-matlab-exe default-matlab))
347-
;; else unable to locate it
348-
(matlab-shell--matlab-not-found no-error)))))
333+
(let ((remote (file-remote-p default-directory)))
334+
(if remote
335+
(if (setq abs-matlab-exe (executable-find matlab-shell-command t))
336+
(setq abs-matlab-exe (concat remote abs-matlab-exe))
337+
(user-error "Unable to locate matlab executable on %s
338+
See https://github.com/mathworks/Emacs-MATLAB-Mode/doc/remote-matlab-emacs.org for tips" remote))
339+
;; else look local
340+
(setq abs-matlab-exe (executable-find matlab-shell-command))
341+
(when (not abs-matlab-exe)
342+
(if (string= matlab-shell-command "matlab")
343+
;; Get latest matlab command exe from the default installation location.
344+
(let* ((default-loc (cdr (assoc system-type matlab-shell--default-command)))
345+
(default-matlab (when default-loc
346+
(car (last (sort
347+
(file-expand-wildcards default-loc)
348+
#'string<))))))
349+
(when (not default-matlab)
350+
(matlab-shell--matlab-not-found no-error default-loc))
351+
(when (not (file-executable-p default-matlab))
352+
(user-error "%s is not executable" default-matlab))
353+
(setq abs-matlab-exe default-matlab))
354+
;; else unable to locate it
355+
(matlab-shell--matlab-not-found no-error)))))))
349356

350357
;; Return existing absolute path to the MATLAB command executable
351358
abs-matlab-exe)
@@ -596,9 +603,12 @@ Try C-h f matlab-shell RET"))
596603
(let* ((windowid (frame-parameter (selected-frame) 'outer-window-id))
597604
(newvar (concat "WINDOWID=" windowid))
598605
(process-environment (cons newvar process-environment))
599-
(abs-matlab-exe (matlab-shell--abs-matlab-exe)))
606+
(abs-matlab-exe (matlab-shell--abs-matlab-exe))
607+
(matlab-exe (if (file-remote-p abs-matlab-exe)
608+
matlab-shell-command
609+
abs-matlab-exe)))
600610
(message "Running: %s" abs-matlab-exe)
601-
(apply #'make-comint matlab-shell-buffer-name abs-matlab-exe
611+
(apply #'make-comint matlab-shell-buffer-name matlab-exe
602612
nil matlab-shell-command-switches))
603613

604614
;; Enable GUD
@@ -1065,30 +1075,102 @@ system."
10651075
(and mlfile (file-exists-p dir)))
10661076
"Add the `matlab-shell' MATLAB toolbox to the MATLAB path on startup.")
10671077

1068-
1069-
(defun matlab-shell-first-prompt-fcn ()
1078+
(defun matlab--shell-toolbox-and-bin-sha1 (matlab-dir &optional recursive-call)
1079+
"Compute the SHA1 of the Emacs MATLAB-DIR toolbox and bin directories.
1080+
RECURSIVE-CALL should be nil when called from top-level."
1081+
(let (sha1-all)
1082+
(when (not (directory-name-p matlab-dir))
1083+
(error "Directory, %s, does not end in a /" matlab-dir))
1084+
(when (not (file-directory-p matlab-dir))
1085+
(error "Directory, %s, does not exist" matlab-dir))
1086+
(setq matlab-dir (file-truename matlab-dir))
1087+
(let ((dirs (if recursive-call
1088+
(list matlab-dir)
1089+
(list (concat matlab-dir "toolbox/")
1090+
(concat matlab-dir "bin/")))))
1091+
(dolist (dir dirs)
1092+
(when (not (file-directory-p dir))
1093+
(error "Directory, %s, does not exist" dir))
1094+
(dolist (file-name (sort (directory-files dir) #'string<))
1095+
(when (and (not (string-match "~$" file-name))
1096+
(not (string-match "^\\(?:#\\|\\.\\)" file-name)))
1097+
;; Not a: backup~, #backup, ".", ".., or .hidden file.
1098+
(let ((abs-file (concat dir file-name)))
1099+
(if (file-directory-p abs-file)
1100+
(setq sha1-all
1101+
(concat sha1-all
1102+
(matlab--shell-toolbox-and-bin-sha1 (concat abs-file "/") t)))
1103+
;; Plain file to add to sha1-all.
1104+
(with-temp-buffer
1105+
(insert-file-contents-literally abs-file)
1106+
(setq sha1-all (concat sha1-all (secure-hash 'sha1 (current-buffer)))))))))))
1107+
(when (not recursive-call)
1108+
;; sha1-all contains a long list of the individual hash's, reduce to one hash.
1109+
(when (not sha1-all)
1110+
(error "Directory, %s, contains no plain files" matlab-dir))
1111+
(setq sha1-all (secure-hash 'sha1 sha1-all)))
1112+
sha1-all))
1113+
1114+
(defun matlab--shell-remote-toolbox-dir (local-toolbox-dir)
1115+
"Return matlab-emacs toolbox directory path on the remote system.
1116+
This will be a copy the LOCAL-TOOLBOX-DIR toolbox and ../bin directories
1117+
to the remote system. This will copy files to the remote system if the
1118+
remote directory is missing or out of date. Returns:
1119+
~/.emacs-matlab-mode/toolbox/"
1120+
(let* ((matlab-dir (file-name-as-directory
1121+
(file-name-directory (directory-file-name local-toolbox-dir))))
1122+
(sha1 (matlab--shell-toolbox-and-bin-sha1 matlab-dir))
1123+
(local-dir-on-remote "~/.emacs-matlab-mode/")
1124+
(remote (if (file-remote-p default-directory) (file-remote-p default-directory)
1125+
(error "%s is not remote" default-directory)))
1126+
(remote-dir (concat remote local-dir-on-remote))
1127+
(remote-sha1-file (concat remote-dir ".sha1.txt")))
1128+
(when (or (not (file-exists-p remote-sha1-file))
1129+
(not (string= sha1 (with-temp-buffer
1130+
(insert-file-contents-literally remote-sha1-file)
1131+
(buffer-substring (point-min) (point-max))))))
1132+
(delete-directory remote-dir t)
1133+
(copy-directory local-toolbox-dir remote-dir t t)
1134+
(copy-directory (concat local-toolbox-dir "../bin/") remote-dir t t)
1135+
;; Save SHA1. This is used to avoid future copies when remote is up to date.
1136+
(write-region sha1 nil remote-sha1-file))
1137+
;; result
1138+
(concat local-dir-on-remote "toolbox/")))
1139+
1140+
(cl-defun matlab-shell-first-prompt-fcn ()
10701141
"Hook run when the first prompt is seen.
10711142
Sends commands to the MATLAB shell to initialize the MATLAB process."
10721143
;; Don't do this again
10731144
(remove-hook 'matlab-shell-prompt-appears-hook #'matlab-shell-first-prompt-fcn)
10741145

1075-
;; Init this session of MATLAB.
1076-
(if matlab-shell-use-emacs-toolbox
1077-
;; Use our local toolbox directory.
1078-
(let* ((path (expand-file-name "toolbox" (file-name-directory
1079-
(locate-library "matlab"))))
1080-
(initcmd (expand-file-name "emacsinit" path))
1081-
(nsa (if matlab-shell-autostart-netshell "emacs.set('netshell', true);" ""))
1082-
(ecc (matlab-shell--get-emacsclient-command))
1083-
(ecca (if ecc (format "emacs.set('clientcmd', '%s');" ecc) ""))
1084-
(args (list nsa ecca))
1085-
(cmd (format "run('%s');%s" initcmd (apply #'concat args))))
1086-
(matlab-shell-send-command (string-replace (expand-file-name "~/") "~/" cmd))
1087-
)
1088-
1146+
(when (not matlab-shell-use-emacs-toolbox)
10891147
;; Setup is misconfigured - we need emacsinit because it tells us how to debug
10901148
(error "Unable to initialize matlab, emacsinit.m and other files missing"))
10911149

1150+
;; Run emacsinit.m which sets up the MATLAB environment to include the matlab-mode
1151+
;; "toolbox". This is used for items like debugging, e.g. ebstop.m.
1152+
;; Also setup emacsclient such that ">> edit file" works.
1153+
(let* ((local-toolbox-dir (expand-file-name "toolbox/"
1154+
(file-name-directory (locate-library "matlab"))))
1155+
(toolbox-dir (if (file-remote-p default-directory)
1156+
;; Case: Remote matlab-shell via tramp
1157+
(matlab--shell-remote-toolbox-dir local-toolbox-dir)
1158+
local-toolbox-dir))
1159+
(emacs-init (concat toolbox-dir "emacsinit"))
1160+
(e-client-command (matlab-shell--get-emacsclient-command))
1161+
(remote-location (file-remote-p default-directory))
1162+
(e-set-args (replace-regexp-in-string
1163+
"^, " "" ;; strip leading ", "
1164+
(concat (when matlab-shell-autostart-netshell ", 'netshell', true")
1165+
(when e-client-command (format ", 'clientcmd', '%s'"
1166+
e-client-command))
1167+
(when remote-location (format ", 'remoteLocation', '%s'"
1168+
remote-location)))))
1169+
(cmd (format "run('%s');%s" emacs-init (if e-set-args
1170+
(format " emacs.set(%s);" e-set-args)
1171+
""))))
1172+
(matlab-shell-send-command (string-replace (expand-file-name "~/") "~/" cmd)))
1173+
10921174
;; Init any user commands
10931175
(if matlab-custom-startup-command
10941176
;; Wait for next prompt, then send.
@@ -2039,21 +2121,26 @@ a file name, or nil if no conversion done.")
20392121
;; (matlab-shell-mref-to-filename "eltest.utils.testme>localfcn")
20402122

20412123
(defun matlab-shell-mref-to-filename (fileref)
2042-
"Convert the MATLAB file reference FILEREF into an actual file name.
2124+
"Convert MATLAB file reference FILEREF into an file Emacs can load.
20432125
MATLAB can refer to functions on the path by a short name, or by a .p
20442126
extension, and a host of different ways. Convert this reference into
2045-
something Emacs can load."
2127+
something Emacs can load. If matlab-shell is running remote via tramp,
2128+
returned file will be prefixed with the remote location."
20462129
(interactive "sFileref: ")
20472130
(with-current-buffer (matlab-shell-active-p)
2048-
(let ((C matlab-shell-mref-converters)
2049-
(ans nil))
2131+
(let ((remote-location (file-remote-p default-directory))
2132+
(C matlab-shell-mref-converters)
2133+
ans)
20502134
(while (and C (not ans))
20512135
(let ((tmp (funcall (car C) fileref)))
2052-
(when (and tmp (file-exists-p tmp))
2053-
(setq ans tmp))
2054-
)
2136+
(when tmp
2137+
(when (and remote-location (not (file-remote-p tmp)))
2138+
(setq tmp (concat remote-location tmp)))
2139+
(when (file-exists-p tmp)
2140+
(setq ans tmp))))
20552141
(setq C (cdr C)))
2056-
(when (called-interactively-p 'any) (message "Found: %S" ans))
2142+
(when (called-interactively-p 'any)
2143+
(message "Found: %S" ans))
20572144
ans)))
20582145

20592146
(defun matlab-find-other-window-file-line-column (ef el ec &optional debug)
@@ -2601,10 +2688,10 @@ Argument FNAME specifies if we should echo the region to the command line."
26012688
;; LocalWords: keymap subjob kbd emacscd featurep fboundp EDU msbn pc Thx Chappaz windowid tcp lang
26022689
;; LocalWords: postoutput capturetext EMACSCAP captext STARTCAP progn eol dbhot erroexamples cdr
26032690
;; LocalWords: ENDPT dolist overlaystack mref deref errortext ERRORTXT shellerror Emacsen iq nt buf
2604-
;; LocalWords: auth mlfile emacsinit initcmd nsa ecc ecca clientcmd EMAACSCAP buffname showbuff
2691+
;; LocalWords: auth mlfile EMAACSCAP buffname showbuff symlink'd emacsinit sha dirs ebstop
26052692
;; LocalWords: evalforms Histed pmark memq promptend numchars integerp emacsdocomplete mycmd ba
26062693
;; LocalWords: nreverse emacsdocompletion byteswap stringp cbuff mapcar bw FCN's alist substr usr
26072694
;; LocalWords: BUILTINFLAG dired bol bobp numberp princ minibuffer fn matlabregex lastcmd notimeout
26082695
;; LocalWords: stacktop eltest testme localfcn LF fileref funcall ef ec basec sk nondirectory utils
26092696
;; LocalWords: ignoredups boundp edir sexp Fixup mapc emacsrun noshow cnt ellipsis newf bss noselect
2610-
;; LocalWords: fname mlx xemacs linux darwin truename
2697+
;; LocalWords: fname mlx xemacs linux darwin truename clientcmd

tests/mstest.el

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,15 @@
174174
(mstest-savestate)
175175
(user-error "%S" ERR))))
176176
(CL (cdr (nth 2 CLO)))
177-
(EXP '("emacs" "emacscd" "emacsdocomplete" "emacsinit" "emacsnetshell" "emacsrun" "emacsrunregion" "emacstipstring"))
177+
(EXP '("emacs"
178+
"emacscd"
179+
"emacsdocomplete"
180+
"emacsinit"
181+
"emacsnetshell"
182+
"emacsrun"
183+
"emacsrunregion"
184+
"emacsstripremote"
185+
"emacstipstring"))
178186
(cnt 1))
179187
(while (and CL EXP)
180188
(when (not (string= (car EXP) (car (car CL))))

toolbox/+emacs/@Stack/Stack.m

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
% Copyright (C) 2024 Eric Ludlam (and others)
1+
% Copyright 2019-2025 Free Software Foundation, Inc.
22

33
% This program is free software: you can redistribute it and/or modify
44
% it under the terms of the GNU General Public License as published by
@@ -12,6 +12,7 @@
1212

1313
% You should have received a copy of the GNU General Public License
1414
% along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
1516
classdef Stack < handle
1617
% Class STACK - Manage Emacs' stack state.
1718

@@ -150,10 +151,15 @@ function updateForHotLinks(es, newstack, newframe)
150151
end
151152

152153
function nf = fixFile(filename)
153-
% Fix FILENAME so it has no escape chars, that way we can send to Emacs.
154-
155-
nf = regexprep(filename,"\", "/");
156-
154+
% FIXFILE - Cleanup file for Emacs
155+
%
156+
% Prefix FILENAME with the TRAMP remote location if matlab-shell is running remotely. This is needed
157+
% to enable debugging, e.g. ebstack, etc.
158+
%
159+
% Replace Windows path separators with POSIX separators such that they do not look like escape
160+
% characters, that way we can send to Emacs.
161+
162+
nf = [getenv('EMACS_MATLAB_SHELL_REMOTE'), regexprep(filename, "\", "/")];
157163
end
158164

159165
function thesame = stackEqual(stack1, stack2)
@@ -173,3 +179,5 @@ function updateForHotLinks(es, newstack, newframe)
173179
end
174180

175181
end
182+
183+
% LocalWords: Netshell ebstack dbhotlink progn mlg EMACSCAP newstack newframe gud FIXFILE

0 commit comments

Comments
 (0)