diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 8b5ffae57d6..3cd20d6babf 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -3698,6 +3698,10 @@ def __PYTHON_EL_eval_file(filename, tempname, delete): "Code used to evaluate files in inferior Python processes. The coding cookie regexp is specified in PEP 263.") +(defconst python-shell-local-prefix "/local:" + "A prefix used to indicate that a file is local. +It is used when sending file names to remote Python processes.") + (defun python-shell-comint-watch-for-first-prompt-output-filter (output) "Run `python-shell-first-prompt-hook' when first prompt is found in OUTPUT." (when (not python-shell--first-prompt-received) @@ -4016,6 +4020,27 @@ there for compatibility with CEDET.") (signal 'wrong-type-argument (list 'stringp text))))) "Encode TEXT as a valid Python string.") +(defun python-shell--convert-file-name-to-send (process file-name) + "Convert the FILE-NAME for sending to the inferior Python PROCESS. +If PROCESS is local and FILE-NAME is prefixed with +`python-shell-local-prefix', remove the prefix. If PROCESS is remote +and the FILE-NAME is not prefixed, prepend `python-shell-local-prefix'. +If PROCESS is remote and the file is on the same remote host, remove the +remote prefix. Otherwise, return the file name as is." + (when file-name + (let ((process-prefix + (file-remote-p + (with-current-buffer (process-buffer process) default-directory))) + (local-prefix (string-prefix-p python-shell-local-prefix file-name))) + (cond + ((and (not process-prefix) local-prefix) + (string-remove-prefix python-shell-local-prefix file-name)) + ((and process-prefix (not (or local-prefix (file-remote-p file-name)))) + (concat python-shell-local-prefix (file-local-name file-name))) + ((and process-prefix (string= (file-remote-p file-name) process-prefix)) + (file-local-name file-name)) + (t file-name))))) + (defun python-shell-send-string (string &optional process msg) "Send STRING to inferior Python PROCESS. When optional argument MSG is non-nil, forces display of a @@ -4023,11 +4048,13 @@ user-friendly message if there's no process running; defaults to t when called interactively." (interactive (list (read-string "Python command: ") nil t)) - (let ((process (or process (python-shell-get-process-or-error msg))) - (code (format "__PYTHON_EL_eval(%s, %s)\n" - (python-shell--encode-string string) - (python-shell--encode-string (or (buffer-file-name) - ""))))) + (let* ((process (or process (python-shell-get-process-or-error msg))) + (code (format "__PYTHON_EL_eval(%s, %s)\n" + (python-shell--encode-string string) + (python-shell--encode-string + (or (python-shell--convert-file-name-to-send + process (buffer-file-name)) + ""))))) (unless python-shell-output-filter-in-progress (with-current-buffer (process-buffer process) (save-excursion @@ -4358,7 +4385,8 @@ t when called interactively." temp-file-name (with-temp-buffer (insert-file-contents file-name) (python-shell--save-temp-file (current-buffer)))))) - (let* ((file-name (file-local-name (expand-file-name file-name))) + (let* ((file-name (python-shell--convert-file-name-to-send + process (expand-file-name file-name))) (temp-file-name (when temp-file-name (file-local-name (expand-file-name temp-file-name))))) @@ -5082,8 +5110,12 @@ Never set this variable directly, use "Set the buffer for FILE-NAME as the tracked buffer. Internally it uses the `python-pdbtrack-tracked-buffer' variable. Returns the tracked buffer." - (let* ((file-name-prospect (concat (file-remote-p default-directory) - file-name)) + (let* ((file-name-prospect + (if (string-prefix-p python-shell-local-prefix file-name) + (string-remove-prefix python-shell-local-prefix file-name) + (if (file-remote-p file-name) + file-name + (concat (file-remote-p default-directory) file-name)))) (file-buffer (get-file-buffer file-name-prospect))) (unless file-buffer (cond diff --git a/test/lisp/progmodes/python-tests.el b/test/lisp/progmodes/python-tests.el index d65ef39abb4..b9130da495d 100644 --- a/test/lisp/progmodes/python-tests.el +++ b/test/lisp/progmodes/python-tests.el @@ -4602,6 +4602,32 @@ and `python-shell-interpreter-args' in the new shell buffer." "^\\(o\\.t \\|\\)"))) (ignore-errors (delete-file startup-file)))))) +(ert-deftest python-shell--convert-file-name-to-send-1 () + "Test parameters consist of a list of the following three elements. +1. The variable `default-directory' of the process. +2. FILE-NAME argument. +3. The expected return value." + (python-tests-with-temp-buffer-with-shell + "" + (let* ((path "/tmp/tmp.py") + (local-path (concat python-shell-local-prefix path)) + (remote1-path (concat "/ssh:remote1:" path)) + (remote2-path (concat "/ssh:remote2:" path)) + (process (python-shell-get-process))) + (dolist + (test (list (list "/tmp" nil nil) + (list "/tmp" path path) + (list "/tmp" local-path path) + (list "/ssh:remote1:/tmp" path local-path) + (list "/ssh:remote1:/tmp" local-path local-path) + (list "/ssh:remote1:/tmp" remote1-path path) + (list "/ssh:remote1:/tmp" remote2-path remote2-path))) + (with-current-buffer (process-buffer process) + (setq default-directory (nth 0 test))) + (should (string= (python-shell--convert-file-name-to-send + process (nth 1 test)) + (nth 2 test))))))) + (ert-deftest python-shell-buffer-substring-1 () "Selecting a substring of the whole buffer must match its contents." (python-tests-with-temp-buffer