kljohann / clang-format.el

Format code using clang-format
4 stars 0 forks source link

emacs tramp mode does not respect .clang-format in the remote directory #5

Open concretevitamin opened 6 years ago

concretevitamin commented 6 years ago

(This is a cross-post from https://stackoverflow.com/questions/48678302/emacs-tramp-mode-does-not-respect-clang-format-in-the-remote-directory. I couldn't find any maintainer information on elpa so I only traced it here; apologies if this is not the right place.)

I have a git repo with a .clang-format and a simple.c source code. In local mode, namely editing off local disk, it respects the style file. However if I edit using tramp mode the same repo, then clang-format will format the source code using some default style file (I don't know where it's picked up), and will NOT honor the style file existing in the same remote directory.

Q1: How do I fix this?

Q2: (Maybe it's easier) Where does clang-format in tramp-mode pick up the style file by default?

kljohann commented 6 years ago

clang-format.el just passes buffer-file-name (which is something like "/ssh:host:/path/to/file") to clang-format via the -assume-filename flag. As clang-format can not make sense of this file name it (presumably) uses its default style. I see two options:

concretevitamin commented 6 years ago

@kljohann Thanks! Both options work for me and will be tremendously helpful. For Option 2, most remote machines I work with have clang-format installed either in /usr/bin or /usr/local/bin, with the former being the more popular.

Looking forward to any updates on this!

kljohann commented 6 years ago

Just to clarify: I was only trying to come up with possible ways to implement this. As I do not use clang-format via tramp I do not plan to work on this feature. :) If you come up with a patch the best way forward would be via the mailing list or phabricator.

aaron-michaux commented 5 years ago

Being a elisp naif, would love a hint on how to run clang-format on tramp.

laudrup commented 4 years ago

I would really like this package to support remote connections with tramp as well.

I've tried to look a bit into it, but my knowledge of elisp is very limited to say the least.

As far as I can tell, the issue is, that clang-format.el calls call-process-region to actually execute the clang-format process which does not respect/support remote connections.

Instead something like process-file should be used instead which does some magic to support remote connections based on the value of the default-directory variable. It seems like other emacs packages which do support remote connections does something like that so it works transparently.

I've found a file in the tramp package called tramp-util.el:

https://github.com/jwiegley/tramp/blob/master/lisp/tramp-util.el

which seems to wrap the standard emacs process handling functions, but I haven't been able to figure out how to use that or copy the relevant parts, again because of my limited elisp knowledge.

I assume this should be fairly simple to implement for someone more experienced in elisp, but unfortunately that's not me (although I'm willing to try and look a bit more into it).

Anything else I can do to get this supported?

Thanks a lot!

mika314 commented 4 years ago

The workaround: I created a directory on my local machine matching the tramp path. And copied .clang-format file inside.

For example I downloaded .clang-format into /tmp/

$ sudo -i
# mkdir -p /scp:remote:/home/remote_user/prj/game/Source/Server/src/Core/
# mv /tmp/.clang-format /scp\:remote\:/home/remote_user/prj/game/Source/
# chown chown -R local_user /scp\:remote\:/
# exit
$ 
richls commented 4 years ago

Here's how I was able to work around this issue:

  1. I copied the .clang-format file from the remote location into my home directory.
  2. I wrote the following wrapper function to replace the --assume-filename argument with a path referring to a file with the same name in the home directory:
  (defun my-clang-format-region ()
    (interactive)
    (let* ((start (if (use-region-p) (region-beginning) (point)))
           (end (if (use-region-p) (region-end) (point)))
           (assumed-filename (if (file-remote-p buffer-file-name)
                                 (concat (getenv "HOME") "/" (file-name-nondirectory buffer-file-name))
                               buffer-file-name)))
      (clang-format-region (region-beginning) (region-end) clang-format-style assumed-filename)))
  (global-set-key '[(control meta tab)] 'my-clang-format-region)

It seems that --assume-filename can specify a path to a file that doesn't exist. All clang-format seems to care about is the file's extension and directory path; it uses the directory path as a location to look for the .clang-format file. If it doesn't find the file there, it looks in every ancestor directory starting from that location.

This worked for me with versions 9.0 and 10.0 of the clang-format executable, and clang-format.el version 20190824.2216 from melpa.

(Cross-posted as an answer to the original poster's question on StackOverflow.)

(Edited 2020-06-16: fixed the global-set-key function call, which could cause key binding problems if used in the way that I originally posted.)

hmenke commented 4 years ago

@richls Cheers, mate. There is a little mistake in your function though, you don't use start and end. Also a recursive let* binding is not necessary and let suffices. Here is the fixed version:

(defun my-clang-format-region ()
  (interactive)
  (let ((start (if (use-region-p) (region-beginning) (point)))
        (end (if (use-region-p) (region-end) (point)))
        (assumed-filename (if (file-remote-p buffer-file-name)
                               (concat (getenv "HOME") "/" (file-name-nondirectory buffer-file-name))
                             buffer-file-name)))
    (clang-format-region start end clang-format-style assumed-filename)))
richls commented 4 years ago

Right you are, mate! I also found that my argument to global-set-key was wrong — it should be:

(global-set-key '[(control meta tab)] 'my-clang-format-region)
hmenke commented 4 years ago

Currently the function translates any remote name to be located in the home directory,

(if (file-remote-p buffer-file-name)
    (concat (getenv "HOME") "/" (file-name-nondirectory buffer-file-name))
  buffer-file-name)

i.e. /ssh:jdoe@host:/home/jdoe/baz.cpp becomes ~/baz.cpp. This might or might not be what you want.

Sometimes it is more convenient to use instead

(file-local-name buffer-file-name)

which will translate /ssh:jdoe@host:/home/jdoe/baz.cpp into /home/jdoe/baz.cpp. However, this is only really useful if the remote directory structure mirrors the local one (including the user name).

Therefore I have come up with this monstrosity which will use /home/jdoe/baz.cpp if it exists and ~/baz.cpp if it doesn't.

(if (file-remote-p buffer-file-name)
    (let ((local-file (file-local-name buffer-file-name)))
      (if (file-readable-p local-file)
          local-file
        (expand-file-name (file-name-nondirectory buffer-file-name) "~")))
  buffer-file-name)
richls commented 4 years ago

The reason I suggested translating file names to the home directory is that I put a copy of the remote .clang-format file in my home directory. This way, the local clang process always uses that format file for any .cpp file, no matter what path the .cpp file is in on the remote machine and even if that path doesn't exist locally.

If you need to work on multiple projects with different .clang-format files, you'll need to find a way of mapping each remote path to its own local .clang-format file. In my case, the code I work on is scattered across so many subdirectories that it would be impractical to mirror the entire path structure on my local box, but I only ever need one format file. Other situations may require different approaches. It would be a nice compromise if, say, you only needed to mirror paths as far as the directory containing the .clang-format file, and not below that.

heiner commented 1 year ago

To combine the above with clang-format+-mode:

;; Fix clang-format (and clang-format+ mode) in tramp mode.
(defun tramp-aware-clang-format (orig-fun start end &optional style assume-file-name)
  (unless assume-file-name
    (setq assume-file-name
          (if (file-remote-p buffer-file-name)
              (concat (getenv "HOME") "/" (file-name-nondirectory buffer-file-name))
            buffer-file-name)))
  (apply orig-fun (list start end style assume-file-name)))
(advice-add 'clang-format-region :around #'tramp-aware-clang-format)
danbanan commented 1 year ago

To build on the answer from @richls, you can also change the default value of clang-format-style to a local file with:

(setq-default clang-format-style "file:<path to local .clang-format>")

clang-format-style refers to the --style= option in the clang-format CLI.

You should also look into "Per-Directory Local Variables" in Emacs if you have multiple different remote projects that do not use the same format.