aboutsummaryrefslogtreecommitdiffstats
path: root/lisp
diff options
context:
space:
mode:
authorMichael Albinus2019-11-20 13:45:30 +0100
committerMichael Albinus2019-11-20 13:45:30 +0100
commit035931777bd89b939436fd1d8a2b8d5a80ede095 (patch)
treeb0ce714884bc106eaa95415754e807cb40c761e6 /lisp
parent5c5c1b559313d06aab6516ff1b1acf1da3a01c7d (diff)
downloademacs-035931777bd89b939436fd1d8a2b8d5a80ede095.tar.gz
emacs-035931777bd89b939436fd1d8a2b8d5a80ede095.zip
Add renaming of remote buffer file names to Tramp
* doc/misc/tramp.texi (Default User): Fix typo. (Cleanup remote connections): Adapt arguments of `tramp-cleanup-connection'. (Renaming remote files): New node. (Frequently Asked Questions): New item "How to save files when a remote host isn't reachable anymore?". * etc/NEWS: Add `tramp-rename-files' and `tramp-rename-these-files'. * lisp/net/tramp-cmds.el (tramp-default-rename-alist) (tramp-confirm-rename-file-names): New defcustoms. (tramp-rename-read-file-name-dir) (tramp-rename-read-file-name-init): New defsubsts. (tramp-default-rename-file, tramp-rename-files) (tramp-rename-these-files): New defuns. * lisp/net/tramp-integration.el (ido, ivy): Integrate with them.
Diffstat (limited to 'lisp')
-rw-r--r--lisp/net/tramp-cmds.el267
-rw-r--r--lisp/net/tramp-integration.el16
2 files changed, 278 insertions, 5 deletions
diff --git a/lisp/net/tramp-cmds.el b/lisp/net/tramp-cmds.el
index 56ccf738070..96b11c7f524 100644
--- a/lisp/net/tramp-cmds.el
+++ b/lisp/net/tramp-cmds.el
@@ -195,6 +195,268 @@ This includes password cache, file cache, connection cache, buffers."
195 (dolist (name (tramp-list-remote-buffers)) 195 (dolist (name (tramp-list-remote-buffers))
196 (when (bufferp (get-buffer name)) (kill-buffer name)))) 196 (when (bufferp (get-buffer name)) (kill-buffer name))))
197 197
198;;;###tramp-autoload
199(defcustom tramp-default-rename-alist nil
200 "Default target for renaming remote buffer file names.
201This is an alist of cons cells (SOURCE . TARGET). The first
202matching item specifies the target to be applied for renaming
203buffer file names from source via `tramp-rename-files'. SOURCE
204is a regular expressions, which matches a remote file name.
205TARGET must be a directory name, which could be remote (including
206remote directories Tramp infers by default, such as
207\"/method:user@host:\").
208
209TARGET can contain the patterns %m, %u or %h, which are replaced
210by the method name, user name or host name of SOURCE when calling
211`tramp-rename-files'.
212
213SOURCE could also be a Lisp form, which will be evaluated. The
214result must be a string or nil, which is interpreted as a regular
215expression which always matches."
216 :group 'tramp
217 :version "27.1"
218 :type '(repeat (cons (choice :tag "Source regexp" regexp sexp)
219 (choice :tag "Target name" string (const nil)))))
220
221;;;###tramp-autoload
222(defcustom tramp-confirm-rename-file-names t
223 "Whether renaming a buffer file name must be confirmed."
224 :group 'tramp
225 :version "27.1"
226 :type 'boolean)
227
228(defun tramp-default-rename-file (string)
229 "Determine default file name for renaming according to STRING.
230The user option `tramp-default-rename-alist' is consulted,
231finding the default mapping. If there is no matching entry, the
232function returns nil"
233 (when (tramp-tramp-file-p string)
234 (let ((tdra tramp-default-rename-alist)
235 (method (or (file-remote-p string 'method) ""))
236 (user (or (file-remote-p string 'user) ""))
237 (host (or (file-remote-p string 'host) ""))
238 item result)
239 (while (setq item (pop tdra))
240 (when (string-match-p (or (eval (car item)) "") string)
241 (setq tdra nil
242 result
243 (format-spec
244 (cdr item) (format-spec-make ?m method ?u user ?h host)))))
245 result)))
246
247(defsubst tramp-rename-read-file-name-dir (string)
248 "Return the DIR entry to be applied in `read-file-name', based on STRING."
249 (when (tramp-tramp-file-p string)
250 (substring (file-remote-p string) 0 -1)))
251
252(defsubst tramp-rename-read-file-name-init (string)
253 "Return the INIT entry to be applied in `read-file-name', based on STRING."
254 (when (tramp-tramp-file-p string)
255 (string-remove-prefix (tramp-rename-read-file-name-dir string) string)))
256
257;;;###tramp-autoload
258(defun tramp-rename-files (source target)
259 "Replace in all buffers the visiting file name from SOURCE to TARGET.
260SOURCE is a remote directory name, which could contain also a
261localname part. TARGET is the directory name SOURCE is replaced
262with. Often, TARGET is a remote directory name on another host,
263but it can also be a local directory name. If TARGET has no
264local part, the local part from SOURCE is used.
265
266If TARGET is nil, it is selected according to the first match in
267`tramp-default-rename-alist'. If called interactively, this
268match is offered as initial value for selection.
269
270On all buffers, which have a `buffer-file-name' matching SOURCE,
271this name is modified by replacing SOURCE with TARGET. This is
272applied by calling `set-visited-file-name'. The new
273`buffer-file-name' is prompted for modification in the
274minibuffer. The buffers are marked modified, and must be saved
275explicitly.
276
277If user option `tramp-confirm-rename-file-names' is nil, changing
278the file name happens without confirmation. This requires a
279matching entry in `tramp-default-rename-alist'.
280
281Remote buffers related to the remote connection identified by
282SOURCE, which are not visiting files, or which are visiting files
283not matching SOURCE, are not modified.
284
285Interactively, TARGET is selected from `tramp-default-rename-alist'
286without confirmation if the prefix argument is non-nil.
287
288The remote connection identified by SOURCE is flushed by
289`tramp-cleanup-connection'."
290 (interactive
291 (let ((connections
292 (mapcar #'tramp-make-tramp-file-name (tramp-list-connections)))
293 ;; Completion packages do their voodoo in `completing-read'
294 ;; and `read-file-name', which is often incompatible with
295 ;; Tramp. Ignore them.
296 (completing-read-function #'completing-read-default)
297 (read-file-name-function #'read-file-name-default)
298 source target)
299 (if (null connections)
300 (tramp-user-error nil "There are no remote connections.")
301 (setq source
302 ;; Likely, the source remote connection is broken. So we
303 ;; shall avoid any action on it.
304 (let (non-essential)
305 (completing-read-default
306 "Enter old Tramp connection: "
307 ;; Completion function.
308 (completion-table-dynamic
309 (lambda (string)
310 (cond
311 ;; Initially, show existing remote connections.
312 ((not (tramp-tramp-file-p string))
313 (all-completions string connections))
314 ;; There is a selected remote connection. Show
315 ;; its longest common directory path of respective
316 ;; buffers.
317 (t (mapcar
318 (lambda (buffer)
319 (let ((bfn (buffer-file-name buffer)))
320 (and (buffer-live-p buffer)
321 (tramp-equal-remote string bfn)
322 (stringp bfn) (file-name-directory bfn))))
323 (tramp-list-remote-buffers))))))
324 #'tramp-tramp-file-p t
325 ;; If the current buffer is a remote one, it is likely
326 ;; that this connection is meant. So we offer it as
327 ;; initial value. Otherwise, use the longest remote
328 ;; connection path as initial value.
329 (or (file-remote-p default-directory)
330 (try-completion "" connections))))
331
332 target
333 (when (null current-prefix-arg)
334 ;; The source remote connection shall not trigger any action.
335 ;; FIXME: Better error prompt when trying to access source host.
336 (let* ((default (or (tramp-default-rename-file source) source))
337 (dir (tramp-rename-read-file-name-dir default))
338 (init (tramp-rename-read-file-name-init default))
339 (tramp-ignored-file-name-regexp
340 (regexp-quote (file-remote-p source))))
341 (read-file-name-default
342 "Enter new Tramp connection: "
343 dir default 'confirm init #'file-directory-p)))))
344
345 (list source target)))
346
347 (unless (tramp-tramp-file-p source)
348 (tramp-user-error nil "Source %s must be remote." source))
349 (when (null target)
350 (or (setq target (tramp-default-rename-file source))
351 (tramp-user-error
352 nil
353 (eval-when-compile
354 (concat "There is no target specified. "
355 "Check `tramp-default-rename-alist' for a proper entry.")))))
356 (when (tramp-equal-remote source target)
357 (tramp-user-error nil "Source and target must have different remote."))
358
359 ;; Append local file name if none is specified.
360 (when (string-equal (file-remote-p target) target)
361 (setq target (concat target (file-remote-p source 'localname))))
362 ;; Make them directoy names.
363 (setq source (directory-file-name source)
364 target (directory-file-name target))
365
366 ;; Rename visited file names of source buffers.
367 (save-window-excursion
368 (save-current-buffer
369 (let ((help-form "\
370Type SPC or `y' to set visited file name,
371DEL or `n' to skip to next,
372`e' to edit the visited file name,
373ESC or `q' to quit without changing further buffers,
374`!' to change all remaining buffers with no more questions.")
375 (query-choices '(?y ?\s ?n ?\177 ?! ?e ?q ?\e))
376 (query (unless tramp-confirm-rename-file-names ?!))
377 changed-buffers)
378 (dolist (buffer (tramp-list-remote-buffers))
379 (switch-to-buffer buffer)
380 (let* ((bfn (buffer-file-name))
381 (new-bfn (and (stringp bfn)
382 (replace-regexp-in-string
383 (regexp-quote source) target bfn)))
384 (prompt (format-message
385 "Set visited file name to `%s' [Type yn!eq or %s] "
386 new-bfn (key-description (vector help-char)))))
387 (when (and (buffer-live-p buffer) (stringp bfn)
388 (string-prefix-p source bfn)
389 ;; Skip, and don't ask again.
390 (not (memq query '(?q ?\e))))
391 ;; Read prompt.
392 (unless (eq query ?!)
393 (setq query (read-char-choice prompt query-choices)))
394 ;; Edit the new buffer file name.
395 (when (eq query ?e)
396 (setq new-bfn
397 (read-file-name
398 "New visited file name: "
399 (file-name-directory new-bfn) new-bfn)))
400 ;; Set buffer file name. Remember the change.
401 (when (memq query '(?y ?\s ?! ?e))
402 (setq changed-buffers
403 (cons (list buffer bfn (buffer-modified-p))
404 changed-buffers))
405 (set-visited-file-name new-bfn))
406 ;; Quit. Revert changes if prompted by user.
407 (when (and (memq query '(?q ?\e)) changed-buffers
408 (y-or-n-p "Do you want to revert applied changes?"))
409 (dolist (item changed-buffers)
410 (with-current-buffer (car item)
411 (set-visited-file-name (nth 1 item))
412 (set-buffer-modified-p (nth 2 item)))))
413 ;; Cleanup echo area.
414 (message nil)))))))
415
416 ;; Cleanup.
417 (tramp-cleanup-connection (tramp-dissect-file-name source)))
418
419;;;###tramp-autoload
420(defun tramp-rename-these-files (target)
421 "Replace visiting file names to TARGET.
422The current buffer must be related to a remote connection. In
423all buffers, which are visiting a file with the same directory
424name, the buffer file name is changed.
425
426Interactively, TARGET is selected from `tramp-default-rename-alist'
427without confirmation if the prefix argument is non-nil.
428
429For details, see `tramp-rename-files'."
430 (interactive
431 (let ((source default-directory)
432 target
433 ;; Completion packages do their voodoo in `completing-read'
434 ;; and `read-file-name', which is often incompatible with
435 ;; Tramp. Ignore them.
436 (completing-read-function #'completing-read-default)
437 (read-file-name-function #'read-file-name-default))
438 (if (not (tramp-tramp-file-p source))
439 (tramp-user-error
440 nil
441 (substitute-command-keys
442 (concat "Current buffer is not remote. "
443 "Consider `\\[tramp-rename-files]' instead.")))
444 (setq target
445 (when (null current-prefix-arg)
446 ;; The source remote connection shall not trigger any action.
447 ;; FIXME: Better error prompt when trying to access source host.
448 (let* ((default (or (tramp-default-rename-file source) source))
449 (dir (tramp-rename-read-file-name-dir default))
450 (init (tramp-rename-read-file-name-init default))
451 (tramp-ignored-file-name-regexp
452 (regexp-quote (file-remote-p source))))
453 (read-file-name-default
454 (format "Change Tramp connection `%s': " source)
455 dir default 'confirm init #'file-directory-p)))))
456 (list target)))
457
458 (tramp-rename-files default-directory target))
459
198;; Tramp version is useful in a number of situations. 460;; Tramp version is useful in a number of situations.
199 461
200;;;###tramp-autoload 462;;;###tramp-autoload
@@ -424,11 +686,6 @@ please ensure that the buffers are attached to your email.\n\n"))
424 686
425;; * Clean up unused *tramp/foo* buffers after a while. (Pete Forman) 687;; * Clean up unused *tramp/foo* buffers after a while. (Pete Forman)
426;; 688;;
427;; * WIBNI there was an interactive command prompting for Tramp
428;; method, hostname, username and filename and translates the user
429;; input into the correct filename syntax (depending on the Emacs
430;; flavor) (Reiner Steib)
431;;
432;; * Let the user edit the connection properties interactively. 689;; * Let the user edit the connection properties interactively.
433;; Something like `gnus-server-edit-server' in Gnus' *Server* buffer. 690;; Something like `gnus-server-edit-server' in Gnus' *Server* buffer.
434 691
diff --git a/lisp/net/tramp-integration.el b/lisp/net/tramp-integration.el
index 0bb19ed9c4d..0c3107603fb 100644
--- a/lisp/net/tramp-integration.el
+++ b/lisp/net/tramp-integration.el
@@ -36,6 +36,8 @@
36(declare-function tramp-file-name-equal-p "tramp") 36(declare-function tramp-file-name-equal-p "tramp")
37(declare-function tramp-tramp-file-p "tramp") 37(declare-function tramp-tramp-file-p "tramp")
38(defvar eshell-path-env) 38(defvar eshell-path-env)
39(defvar ido-read-file-name-non-ido)
40(defvar ivy-completing-read-handlers-alist)
39(defvar recentf-exclude) 41(defvar recentf-exclude)
40(defvar tramp-current-connection) 42(defvar tramp-current-connection)
41(defvar tramp-postfix-host-format) 43(defvar tramp-postfix-host-format)
@@ -170,6 +172,20 @@ NAME must be equal to `tramp-current-connection'."
170 (remove-hook 'tramp-cleanup-all-connections-hook 172 (remove-hook 'tramp-cleanup-all-connections-hook
171 #'tramp-recentf-cleanup-all)))) 173 #'tramp-recentf-cleanup-all))))
172 174
175;;; Integration of ido.el:
176
177(with-eval-after-load 'ido
178 (add-to-list 'ido-read-file-name-non-ido 'tramp-rename-files)
179 (add-to-list 'ido-read-file-name-non-ido 'tramp-these-rename-files))
180
181;;; Integration of ivy.el:
182
183(with-eval-after-load 'ivy
184 (add-to-list 'ivy-completing-read-handlers-alist
185 '(tramp-rename-files . completing-read-default))
186 (add-to-list 'ivy-completing-read-handlers-alist
187 '(tramp-these-rename-files . completing-read-default)))
188
173;;; Default connection-local variables for Tramp: 189;;; Default connection-local variables for Tramp:
174 190
175(defconst tramp-connection-local-default-profile 191(defconst tramp-connection-local-default-profile