branch: externals/matlab-mode commit 64e1805d36a362111ac3518a4fd7428c60483079 Author: John Ciolfi <john.ciolfi...@gmail.com> Commit: John Ciolfi <john.ciolfi...@gmail.com>
Fix and improve remote matlab-shell Prior to the commit for https://github.com/mathworks/Emacs-MATLAB-Mode/issues/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. --- README.org | 6 +- doc/remote-matlab-shell.org | 64 +++++++++++++++ matlab-shell.el | 175 +++++++++++++++++++++++++++++++----------- tests/mstest.el | 10 ++- toolbox/+emacs/@Stack/Stack.m | 18 +++-- toolbox/+emacs/set.m | 11 ++- toolbox/ebclear.m | 12 +-- toolbox/ebstack.m | 27 ++++--- toolbox/ebstatus.m | 6 +- toolbox/ebstop.m | 12 ++- toolbox/emacscd.m | 6 +- toolbox/emacsinit.m | 3 +- toolbox/emacsnetshell.m | 5 +- toolbox/emacsrun.m | 7 +- toolbox/emacsrunregion.m | 14 ++-- toolbox/emacsstripremote.m | 41 ++++++++++ toolbox/emacstipstring.m | 9 ++- toolbox/help.m | 8 +- toolbox/opentoline.m | 17 ++-- 19 files changed, 349 insertions(+), 102 deletions(-) diff --git a/README.org b/README.org index 6edf015c8d..b76d9381bc 100644 --- a/README.org +++ b/README.org @@ -1,7 +1,7 @@ #+startup: showall #+options: toc:nil -# Copyright 2024 Free Software Foundation, Inc. +# Copyright 2016-2025 Free Software Foundation, Inc. * Emacs MATLAB-mode @@ -14,7 +14,11 @@ 2. *M-x matlab-shell* for running and debugging MATLAB within Emacs (Unix-only). + - MATLAB command window errors are hyper-linked and files open in Emacs + - Debugging support is available from the MATLAB menu. - matlab-shell uses company-mode for completions. + - You can use Emacs TRAMP and =M-x matlab-shell= to run remote MATLAB within your local Emacs + session, see [[file:doc/remote-matlab-shell.org][doc/remote-matlab-shell.org]]. 3. *Code sections* support. MATLAB script code files often contain many commands and lines of text. You typically focus your efforts on a single part of your code at a time, working with the code diff --git a/doc/remote-matlab-shell.org b/doc/remote-matlab-shell.org new file mode 100644 index 0000000000..ef90ef65a1 --- /dev/null +++ b/doc/remote-matlab-shell.org @@ -0,0 +1,64 @@ +# File: doc/remote-matlab-shell.org +# +#+startup: showall +#+options: toc:nil +# +# Copyright 2016-2025 Free Software Foundation, Inc. + +* Remote M-x matlab-shell + +You can use Emacs TRAMP to run matlab-shell on a remote system. + +1. First verify you can connect to the remote system in a terminal + + For example, we can use ssh to connect: + + #+begin_src bash + ssh user@system pwd + #+end_src + + You should configure your ssh keys to avoid prompting, which will make things smoother with + Emacs. + +2. In Emacs visit a remote location. + + A typical method is to use File menu to visit a file or ~C-x C-f~ + + #+begin_example + C-x C-f + Find File: /ssh:USER@SYSTEM:~ + #+end_example + + will open dired-mode to the USER home (~) directory on the remote SYSTEM. + +3. Run matlab-shell + + With the current buffer as a remote location, e.g. dired-mode buffer of a remote location, run: + + #+begin_example + M-x matlab-shell + #+end_example + + matlab-shell will copy files to the remote system and place them in =~/.emacs-matlab-shell/=. These are + needed for matlab-shell to work correctly on the remote system. + + If you get a message that Emacs couldn't find matlab on the remote system, you need to tell Emacs + where matlab is located and there are several ways to do this, see + [[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 + using the Bash shell. You can setup your remote =/ssh:USER@HOST:~/.profile= to place the location of MATLAB on + your PATH: + + #+begin_src bash + # ~/.profile + PATH=/usr/local/MATLAB/Ryyyyab/bin/$PATH # Replace Ryyyyab with the MATLAB release you are using + export PATH + #+end_src + + After that you can add to your local =~/.emacs=, + + #+begin_src emacs-lisp + (eval-after-load + '(add-to-list 'tramp-remote-path 'tramp-own-remote-path)) + #+end_src + +# LocalWords: showall dired usr Ryyyyab diff --git a/matlab-shell.el b/matlab-shell.el index 29164a618e..91d303567f 100644 --- a/matlab-shell.el +++ b/matlab-shell.el @@ -330,22 +330,29 @@ otherwise an error is signaled." ;; Case: "matlab" (or something similar), locate it on the executable path ;; else locate in standard install locations. (t - (setq abs-matlab-exe (executable-find matlab-shell-command)) - (when (not abs-matlab-exe) - (if (string= matlab-shell-command "matlab") - ;; Get latest matlab command exe from the default installation location. - (let* ((default-loc (cdr (assoc system-type matlab-shell--default-command))) - (default-matlab (when default-loc - (car (last (sort - (file-expand-wildcards default-loc) - #'string<)))))) - (when (not default-matlab) - (matlab-shell--matlab-not-found no-error default-loc)) - (when (not (file-executable-p default-matlab)) - (user-error "%s is not executable" default-matlab)) - (setq abs-matlab-exe default-matlab)) - ;; else unable to locate it - (matlab-shell--matlab-not-found no-error))))) + (let ((remote (file-remote-p default-directory))) + (if remote + (if (setq abs-matlab-exe (executable-find matlab-shell-command t)) + (setq abs-matlab-exe (concat remote abs-matlab-exe)) + (user-error "Unable to locate matlab executable on %s +See https://github.com/mathworks/Emacs-MATLAB-Mode/doc/remote-matlab-emacs.org for tips" remote)) + ;; else look local + (setq abs-matlab-exe (executable-find matlab-shell-command)) + (when (not abs-matlab-exe) + (if (string= matlab-shell-command "matlab") + ;; Get latest matlab command exe from the default installation location. + (let* ((default-loc (cdr (assoc system-type matlab-shell--default-command))) + (default-matlab (when default-loc + (car (last (sort + (file-expand-wildcards default-loc) + #'string<)))))) + (when (not default-matlab) + (matlab-shell--matlab-not-found no-error default-loc)) + (when (not (file-executable-p default-matlab)) + (user-error "%s is not executable" default-matlab)) + (setq abs-matlab-exe default-matlab)) + ;; else unable to locate it + (matlab-shell--matlab-not-found no-error))))))) ;; Return existing absolute path to the MATLAB command executable abs-matlab-exe) @@ -596,9 +603,12 @@ Try C-h f matlab-shell RET")) (let* ((windowid (frame-parameter (selected-frame) 'outer-window-id)) (newvar (concat "WINDOWID=" windowid)) (process-environment (cons newvar process-environment)) - (abs-matlab-exe (matlab-shell--abs-matlab-exe))) + (abs-matlab-exe (matlab-shell--abs-matlab-exe)) + (matlab-exe (if (file-remote-p abs-matlab-exe) + matlab-shell-command + abs-matlab-exe))) (message "Running: %s" abs-matlab-exe) - (apply #'make-comint matlab-shell-buffer-name abs-matlab-exe + (apply #'make-comint matlab-shell-buffer-name matlab-exe nil matlab-shell-command-switches)) ;; Enable GUD @@ -1065,30 +1075,102 @@ system." (and mlfile (file-exists-p dir))) "Add the `matlab-shell' MATLAB toolbox to the MATLAB path on startup.") - -(defun matlab-shell-first-prompt-fcn () +(defun matlab--shell-toolbox-and-bin-sha1 (matlab-dir &optional recursive-call) + "Compute the SHA1 of the Emacs MATLAB-DIR toolbox and bin directories. +RECURSIVE-CALL should be nil when called from top-level." + (let (sha1-all) + (when (not (directory-name-p matlab-dir)) + (error "Directory, %s, does not end in a /" matlab-dir)) + (when (not (file-directory-p matlab-dir)) + (error "Directory, %s, does not exist" matlab-dir)) + (setq matlab-dir (file-truename matlab-dir)) + (let ((dirs (if recursive-call + (list matlab-dir) + (list (concat matlab-dir "toolbox/") + (concat matlab-dir "bin/"))))) + (dolist (dir dirs) + (when (not (file-directory-p dir)) + (error "Directory, %s, does not exist" dir)) + (dolist (file-name (sort (directory-files dir) #'string<)) + (when (and (not (string-match "~$" file-name)) + (not (string-match "^\\(?:#\\|\\.\\)" file-name))) + ;; Not a: backup~, #backup, ".", ".., or .hidden file. + (let ((abs-file (concat dir file-name))) + (if (file-directory-p abs-file) + (setq sha1-all + (concat sha1-all + (matlab--shell-toolbox-and-bin-sha1 (concat abs-file "/") t))) + ;; Plain file to add to sha1-all. + (with-temp-buffer + (insert-file-contents-literally abs-file) + (setq sha1-all (concat sha1-all (secure-hash 'sha1 (current-buffer))))))))))) + (when (not recursive-call) + ;; sha1-all contains a long list of the individual hash's, reduce to one hash. + (when (not sha1-all) + (error "Directory, %s, contains no plain files" matlab-dir)) + (setq sha1-all (secure-hash 'sha1 sha1-all))) + sha1-all)) + +(defun matlab--shell-remote-toolbox-dir (local-toolbox-dir) + "Return matlab-emacs toolbox directory path on the remote system. +This will be a copy the LOCAL-TOOLBOX-DIR toolbox and ../bin directories +to the remote system. This will copy files to the remote system if the +remote directory is missing or out of date. Returns: +~/.emacs-matlab-mode/toolbox/" + (let* ((matlab-dir (file-name-as-directory + (file-name-directory (directory-file-name local-toolbox-dir)))) + (sha1 (matlab--shell-toolbox-and-bin-sha1 matlab-dir)) + (local-dir-on-remote "~/.emacs-matlab-mode/") + (remote (if (file-remote-p default-directory) (file-remote-p default-directory) + (error "%s is not remote" default-directory))) + (remote-dir (concat remote local-dir-on-remote)) + (remote-sha1-file (concat remote-dir ".sha1.txt"))) + (when (or (not (file-exists-p remote-sha1-file)) + (not (string= sha1 (with-temp-buffer + (insert-file-contents-literally remote-sha1-file) + (buffer-substring (point-min) (point-max)))))) + (delete-directory remote-dir t) + (copy-directory local-toolbox-dir remote-dir t t) + (copy-directory (concat local-toolbox-dir "../bin/") remote-dir t t) + ;; Save SHA1. This is used to avoid future copies when remote is up to date. + (write-region sha1 nil remote-sha1-file)) + ;; result + (concat local-dir-on-remote "toolbox/"))) + +(cl-defun matlab-shell-first-prompt-fcn () "Hook run when the first prompt is seen. Sends commands to the MATLAB shell to initialize the MATLAB process." ;; Don't do this again (remove-hook 'matlab-shell-prompt-appears-hook #'matlab-shell-first-prompt-fcn) - ;; Init this session of MATLAB. - (if matlab-shell-use-emacs-toolbox - ;; Use our local toolbox directory. - (let* ((path (expand-file-name "toolbox" (file-name-directory - (locate-library "matlab")))) - (initcmd (expand-file-name "emacsinit" path)) - (nsa (if matlab-shell-autostart-netshell "emacs.set('netshell', true);" "")) - (ecc (matlab-shell--get-emacsclient-command)) - (ecca (if ecc (format "emacs.set('clientcmd', '%s');" ecc) "")) - (args (list nsa ecca)) - (cmd (format "run('%s');%s" initcmd (apply #'concat args)))) - (matlab-shell-send-command (string-replace (expand-file-name "~/") "~/" cmd)) - ) - + (when (not matlab-shell-use-emacs-toolbox) ;; Setup is misconfigured - we need emacsinit because it tells us how to debug (error "Unable to initialize matlab, emacsinit.m and other files missing")) + ;; Run emacsinit.m which sets up the MATLAB environment to include the matlab-mode + ;; "toolbox". This is used for items like debugging, e.g. ebstop.m. + ;; Also setup emacsclient such that ">> edit file" works. + (let* ((local-toolbox-dir (expand-file-name "toolbox/" + (file-name-directory (locate-library "matlab")))) + (toolbox-dir (if (file-remote-p default-directory) + ;; Case: Remote matlab-shell via tramp + (matlab--shell-remote-toolbox-dir local-toolbox-dir) + local-toolbox-dir)) + (emacs-init (concat toolbox-dir "emacsinit")) + (e-client-command (matlab-shell--get-emacsclient-command)) + (remote-location (file-remote-p default-directory)) + (e-set-args (replace-regexp-in-string + "^, " "" ;; strip leading ", " + (concat (when matlab-shell-autostart-netshell ", 'netshell', true") + (when e-client-command (format ", 'clientcmd', '%s'" + e-client-command)) + (when remote-location (format ", 'remoteLocation', '%s'" + remote-location))))) + (cmd (format "run('%s');%s" emacs-init (if e-set-args + (format " emacs.set(%s);" e-set-args) + "")))) + (matlab-shell-send-command (string-replace (expand-file-name "~/") "~/" cmd))) + ;; Init any user commands (if matlab-custom-startup-command ;; Wait for next prompt, then send. @@ -2039,21 +2121,26 @@ a file name, or nil if no conversion done.") ;; (matlab-shell-mref-to-filename "eltest.utils.testme>localfcn") (defun matlab-shell-mref-to-filename (fileref) - "Convert the MATLAB file reference FILEREF into an actual file name. + "Convert MATLAB file reference FILEREF into an file Emacs can load. MATLAB can refer to functions on the path by a short name, or by a .p extension, and a host of different ways. Convert this reference into -something Emacs can load." +something Emacs can load. If matlab-shell is running remote via tramp, +returned file will be prefixed with the remote location." (interactive "sFileref: ") (with-current-buffer (matlab-shell-active-p) - (let ((C matlab-shell-mref-converters) - (ans nil)) + (let ((remote-location (file-remote-p default-directory)) + (C matlab-shell-mref-converters) + ans) (while (and C (not ans)) (let ((tmp (funcall (car C) fileref))) - (when (and tmp (file-exists-p tmp)) - (setq ans tmp)) - ) + (when tmp + (when (and remote-location (not (file-remote-p tmp))) + (setq tmp (concat remote-location tmp))) + (when (file-exists-p tmp) + (setq ans tmp)))) (setq C (cdr C))) - (when (called-interactively-p 'any) (message "Found: %S" ans)) + (when (called-interactively-p 'any) + (message "Found: %S" ans)) ans))) (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." ;; LocalWords: keymap subjob kbd emacscd featurep fboundp EDU msbn pc Thx Chappaz windowid tcp lang ;; LocalWords: postoutput capturetext EMACSCAP captext STARTCAP progn eol dbhot erroexamples cdr ;; LocalWords: ENDPT dolist overlaystack mref deref errortext ERRORTXT shellerror Emacsen iq nt buf -;; LocalWords: auth mlfile emacsinit initcmd nsa ecc ecca clientcmd EMAACSCAP buffname showbuff +;; LocalWords: auth mlfile EMAACSCAP buffname showbuff symlink'd emacsinit sha dirs ebstop ;; LocalWords: evalforms Histed pmark memq promptend numchars integerp emacsdocomplete mycmd ba ;; LocalWords: nreverse emacsdocompletion byteswap stringp cbuff mapcar bw FCN's alist substr usr ;; LocalWords: BUILTINFLAG dired bol bobp numberp princ minibuffer fn matlabregex lastcmd notimeout ;; LocalWords: stacktop eltest testme localfcn LF fileref funcall ef ec basec sk nondirectory utils ;; LocalWords: ignoredups boundp edir sexp Fixup mapc emacsrun noshow cnt ellipsis newf bss noselect -;; LocalWords: fname mlx xemacs linux darwin truename +;; LocalWords: fname mlx xemacs linux darwin truename clientcmd diff --git a/tests/mstest.el b/tests/mstest.el index d31dc318a9..2de2416d8c 100644 --- a/tests/mstest.el +++ b/tests/mstest.el @@ -174,7 +174,15 @@ (mstest-savestate) (user-error "%S" ERR)))) (CL (cdr (nth 2 CLO))) - (EXP '("emacs" "emacscd" "emacsdocomplete" "emacsinit" "emacsnetshell" "emacsrun" "emacsrunregion" "emacstipstring")) + (EXP '("emacs" + "emacscd" + "emacsdocomplete" + "emacsinit" + "emacsnetshell" + "emacsrun" + "emacsrunregion" + "emacsstripremote" + "emacstipstring")) (cnt 1)) (while (and CL EXP) (when (not (string= (car EXP) (car (car CL)))) diff --git a/toolbox/+emacs/@Stack/Stack.m b/toolbox/+emacs/@Stack/Stack.m index 7f9b89dbc3..85b5ffbbed 100644 --- a/toolbox/+emacs/@Stack/Stack.m +++ b/toolbox/+emacs/@Stack/Stack.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,6 +12,7 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + classdef Stack < handle % Class STACK - Manage Emacs' stack state. @@ -150,10 +151,15 @@ function str=stackFrames(ST) end function nf = fixFile(filename) -% Fix FILENAME so it has no escape chars, that way we can send to Emacs. - - nf = regexprep(filename,"\", "/"); - +% FIXFILE - Cleanup file for Emacs +% +% Prefix FILENAME with the TRAMP remote location if matlab-shell is running remotely. This is needed +% to enable debugging, e.g. ebstack, etc. +% +% Replace Windows path separators with POSIX separators such that they do not look like escape +% characters, that way we can send to Emacs. + + nf = [getenv('EMACS_MATLAB_SHELL_REMOTE'), regexprep(filename, "\", "/")]; end function thesame = stackEqual(stack1, stack2) @@ -173,3 +179,5 @@ function thesame = stackEqual(stack1, stack2) end end + +% LocalWords: Netshell ebstack dbhotlink progn mlg EMACSCAP newstack newframe gud FIXFILE diff --git a/toolbox/+emacs/set.m b/toolbox/+emacs/set.m index d809f3e64b..f41a40b83a 100644 --- a/toolbox/+emacs/set.m +++ b/toolbox/+emacs/set.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,17 +12,22 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function set(varargin) -% Setup an Emacs option based on Name/Value pairs. +% emacs.set: setup an Emacs option based on Name/Value pairs. +% % Valid options include: % % netshell - Initialize a netshell connection. % clientcmd - What to use for `edit' client command +% followstack - Used by Emacs Server +% remoteLocation - Use by remote matlab-shell, see doc/remote-matlab-shell.org P = inputParser; addParameter(P, 'netshell', 0, @isnumeric) addParameter(P, 'clientcmd', "", @ischar) addParameter(P, 'followstack', -1, @isnumeric) + addParameter(P, 'remoteLocation', "", @ischar) parse(P, varargin{:}); @@ -30,6 +35,8 @@ function set(varargin) netshellport = P.Results.netshell; followstack = P.Results.followstack; + setenv('EMACS_MATLAB_SHELL_REMOTE', P.Results.remoteLocation); + %% Client Command if ~isempty(clientcommand) if usejava('jvm') diff --git a/toolbox/ebclear.m b/toolbox/ebclear.m index 0e7e6f4d32..9841249f25 100644 --- a/toolbox/ebclear.m +++ b/toolbox/ebclear.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,14 +12,16 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function ebclear(varargin) % Emacs version of dbstop. Tells emacs which breakpoints are active. - - dbclear(varargin{:}); - + + args = emacsstripremote(varargin); + + dbclear(args); + % Send emacs some breakpoints bp = getappdata(groot, 'EmacsBreakpoints'); bp.updateEmacs; end - diff --git a/toolbox/ebstack.m b/toolbox/ebstack.m index cfa9af5af8..10d15de410 100644 --- a/toolbox/ebstack.m +++ b/toolbox/ebstack.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,18 +12,23 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function ebstack(FRAMEIDX) -% Emacs version of dbstack. Updates Emacs for where we are in the stack. - +% EBSTACK - Emacs version of dbstack. +% +% Updates Emacs for where we are in the stack. + [ST, I] = dbstack('-completenames'); - - % Send emacs our updated stack - es = getappdata(groot, 'EmacsStack'); - if nargin == 1 - I = FRAMEIDX; - end - - es.updateEmacs(ST, I); + % Send emacs our updated stack + es = getappdata(groot, 'EmacsStack'); + + if nargin == 1 + I = FRAMEIDX; + end + + es.updateEmacs(ST, I); end + +% LocalWords: completenames diff --git a/toolbox/ebstatus.m b/toolbox/ebstatus.m index b886380bbb..ea48e6df41 100644 --- a/toolbox/ebstatus.m +++ b/toolbox/ebstatus.m @@ -1,4 +1,5 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. + % 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 @@ -12,8 +13,9 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function ebstatus -% Send emacs some breakpoints +%EBSTATUS - Send emacs some breakpoints bp = getappdata(groot, 'EmacsBreakpoints'); bp.updateEmacs(true); diff --git a/toolbox/ebstop.m b/toolbox/ebstop.m index 6c17d6e94d..dce2c59e1e 100644 --- a/toolbox/ebstop.m +++ b/toolbox/ebstop.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,14 +12,18 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function ebstop(varargin) -% Emacs version of dbstop. Tells emacs which breakpoints are active. +% EBSTOP - Emacs version of dbstop. + +% Tells Emacs which breakpoints are active. + + args = emacsstripremote(varargin); - dbstop(varargin{:}); + dbstop(args{:}); % Send emacs some breakpoints bp = getappdata(groot, 'EmacsBreakpoints'); bp.updateEmacs; - end diff --git a/toolbox/emacscd.m b/toolbox/emacscd.m index 0bfadf5620..28c0cb7f9d 100644 --- a/toolbox/emacscd.m +++ b/toolbox/emacscd.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,8 +12,10 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function emacscd(dir) -% CD to DIRECTORY in a way that Emacs Shell won't see via dirtrack. +% EMACSCD - CD to DIRECTORY in a way that Emacs Shell won't see via dirtrack. +% % Instead, show example of how to tell Emacs what the new directory is. if nargin == 1 diff --git a/toolbox/emacsinit.m b/toolbox/emacsinit.m index 165c1b792f..aaceb630f6 100644 --- a/toolbox/emacsinit.m +++ b/toolbox/emacsinit.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,6 +12,7 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function emacsinit() % EMACSINIT Initialize the current MATLAB session for matlab-shell-mode % diff --git a/toolbox/emacsnetshell.m b/toolbox/emacsnetshell.m index 9b8f93c3ab..98b0a602c9 100644 --- a/toolbox/emacsnetshell.m +++ b/toolbox/emacsnetshell.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,8 +12,9 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function nso = emacsnetshell(cmd, data) -% Create a connection to an EMACS editor server. +% EMACSNETSHELL - Create a connection to an EMACS editor server. % % emacsnetshell('init') - Initialize the connection with Emacs. % emacs will send commands to MATLAB with additional connectivity diff --git a/toolbox/emacsrun.m b/toolbox/emacsrun.m index 1a9f8025d2..30fd81b22b 100644 --- a/toolbox/emacsrun.m +++ b/toolbox/emacsrun.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,12 +12,15 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function emacsrun(mfile, varargin) -% Run code from MFILE. +% EMACSRUN - Run code from MFILE. +% % Assumes MFILE was recently edited, and proactively clears that function. % % Command sent by Emacs for save-and-go functionality + mfile = emacsstripremote(mfile) % Now figure out if shortFileName is on the path. [ fullFilePath, shortFileName ] = fileparts(mfile); onpath = ~isempty(which(shortFileName)); diff --git a/toolbox/emacsrunregion.m b/toolbox/emacsrunregion.m index 3ec9e87b03..cce7e2d1ee 100644 --- a/toolbox/emacsrunregion.m +++ b/toolbox/emacsrunregion.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,17 +12,15 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function emacsrunregion(file, startchar, endchar) -% Run code from FILE between STARTCHAR and ENDCHAR. +% EMACSRUNREGION - Run code from FILE between STARTCHAR and ENDCHAR. +% % Command sent by Emacs for run code sections and run-region functionality. - % Filter out emacs tramp file path prefix - trampMatch = regexp(file, {'/*:',':/'}); - if (~isempty(trampMatch{1})) - file = file((trampMatch{2}+1):end); - end + file = emacsstripremote(file); - if ~exist(file,'file') + if ~exist(file, 'file') error('You must save your region into a file accessible by MATLAB process.'); end diff --git a/toolbox/emacsstripremote.m b/toolbox/emacsstripremote.m new file mode 100644 index 0000000000..2554f3be8a --- /dev/null +++ b/toolbox/emacsstripremote.m @@ -0,0 +1,41 @@ +% Copyright 2025 Free Software Foundation, Inc. + +% 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 <http://www.gnu.org/licenses/>. + +function out = emacsstripremote(in) +% EMACSSTRIPREMOTE - strip Emacs TRAMP remote file path prefix +% +% IN can be either a string or cell array. If it's a cell array it's assumed to +% be arguments to one of the MATLAB debugger commands. +% +% OUT will be IN updated if needed. +% +% TRAMP remote file syntax: +% /method:host:/path/to/file +% +% Examples: +% /ssh:user@host:~/project/file.m => ~/project/file.m +% /ssh:user@host:/work/project/file.m => /work/file.m +% /ssh:user@host:C:/work/file.m => C:/work/file.m + + re = '^/[^:]+:[^:]+:'; + if iscell(in) + out = in; + if length(out) >= 2 && strcmp(out{1},'in') + out{2} = regexprep(in{2}, '^/[^:]+:[^:]+:', '', 'once'); + end + else + out = regexprep(in, '^/[^:]+:[^:]+:', '', 'once'); + end +end diff --git a/toolbox/emacstipstring.m b/toolbox/emacstipstring.m index 754cee1af9..af795f1a2c 100644 --- a/toolbox/emacstipstring.m +++ b/toolbox/emacstipstring.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,11 +12,12 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function emacstipstring(expr) -% Take EXPR, and convert into a tooltip friendly string. +% EMACSSTRIPSTRING - Take EXPR, and convert into a tooltip friendly string. +% % This utility is mean to be used by emacs to create text to display % whie debugging code. - + disp(expr) end - diff --git a/toolbox/help.m b/toolbox/help.m index 55932bc6d1..eca0e64a45 100644 --- a/toolbox/help.m +++ b/toolbox/help.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,8 +12,10 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function [out, docTopic] = help(varargin) -% Provide help, augmented so Emacs picks it up to display in a special buffer. +% HELP- Provide help, augmented so Emacs picks it up to display in a special buffer. +% % See the help for the built-in help command by asking for "help help" in % MATLAB, which will redirect to the correct location. @@ -92,3 +94,5 @@ function [out, docTopic] = help(varargin) [out, docTopic] = help(args{:}); end end + +% LocalWords: completenames EMACSCAP diff --git a/toolbox/opentoline.m b/toolbox/opentoline.m index 0f0017b84e..5622596e57 100644 --- a/toolbox/opentoline.m +++ b/toolbox/opentoline.m @@ -1,4 +1,4 @@ -% Copyright (C) 2024 Eric Ludlam (and others) +% Copyright 2019-2025 Free Software Foundation, Inc. % 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 @@ -12,26 +12,29 @@ % You should have received a copy of the GNU General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. + function opentoline(file, line, column) -%OPENTOLINE Open to specified line in function file in Emacs. +% OPENTOLINE Open to specified line in function file in Emacs. +% % This is a hack to override the built-in opentoline program in MATLAB. % % Remove this M file from your path to get the old behavior. + file = emacsstripremote(file) editor = system_dependent('getpref', 'EditorOtherEditor'); editor = editor(2:end); - + if nargin==3 linecol = sprintf('+%d:%d',line,column); else linecol = sprintf('+%d',line); end - + f = which(file); if ~isempty(f) file=f; - end - + end + if ispc % On Windows, we need to wrap the editor command in double quotes % in case it contains spaces @@ -42,3 +45,5 @@ function opentoline(file, line, column) system([editor ' "' linecol '" "' file '" &']); end end + +% LocalWords: linecol