Emacs term-mode: stop setting remote default-directory on some hosts - emacs

I use term-mode to run Bash shells within Emacs. On remote hosts, term-mode's directory tracking feature helpfully sets default-directory to have the host name in it, so that tab completion and file access is done remotely via Tramp. Sometimes, however, I use remote hosts that mostly share the same filesystems as my workstation, as they load the same directories from NFS. In these cases, Tramp slows me down too much. I would like, when using these systems, for Emacs to set the default-directory locally. To do this I have copied term-handle-ansi-terminal-messages from the system term.el to a new file loaded by my .emacs. I replace this part:
((= command-code ?h)
(setq term-ansi-at-host argument))
with this:
((= command-code ?h)
(setq term-ansi-at-host-real argument)
(setq term-ansi-at-host
;; if it has an equivalent filesystem group, set to system-name
(if (term-equivalent-filesystem-host-group-p argument)
(system-name)
argument)))
This calls a term-equivalent-filesystem-host-group-p function that tells whether a host should be treated as having an equivalent filesystem.
This method has the desired effect but copying and modifying system Lisp code isn't robust to any future changes in the code. I don't think advising the function would be possible without duplicating half its functionality (either the message loop or the setting of default-directory and ange-ftp-… variables).
Is there a better way to do this?

I think you can get darn close with advice. I'd define advice as follows:
(defadvice term-handle-ansi-terminal-messages
(before rewrite-remote-paths-to-local (message) activate)
(when (and (string-match ".*\eAnSiTh.\\([^\r\n]+\\)\r?\n" message)
(term-equivalent-filesystem-host-group-p (match-string 1 message)))
(setq term-ansi-at-host-real (match-string 1 message))
(setq message (replace-match (system-name) t t message 1))))
All this does is look for a substring of the input to term-handle-ansi-terminal-messages that's going to fall into the case in question, and proactively rewrite the path. Now there's no need to fiddle with the internals of term-handle-ansi-terminal-messages, since it'll never see the remote path.
Is this different than modifying the function? I'd say yes, but this is open to interpretation. In particular, I think that the only knowledge the above code depends on is the format of the submessage term-long-function-name-you-already-know is going to look for, which is really an attribute of the terminal protocol, not the internals of term-now-i'm-just-being-silly. There are some potential problems, such as new code that changes behavior based on the 7th character in the match. (This is the lone . in the string-match above.)
Needless to say, I didn't test this code at all.

Related

Open the same file with emacs from two user accounts

I have two user accounts on my Mac.
I would like to open the same text file on both accounts with emacs.
I can put the file in a directory where both users have read and write access.
When I add text to one process I would like it to show up in the other buffer and vice verse automatically.
I have tried combinations of auto-save-mode and auto-revert-mode to try to auto save an auto revert, but that does not seem to work quite right.
Is there some normal way to do this with emacs?
I think you should learn about collaborative editing. There is a page on the emacs wiki about it link
Not a full answer. Just some thoughts. Nevertheless, this is formulated as answer since I want to supplement it with elisp code and maybe refine it later on. This is not possible with comments.
I assume that you are actually working with two emacsen at the two accounts. Furthermore, I assume that the problem with auto-revert-buffer is the timing, i.e. that auto-revert-buffer works with polling and not on demand.
If you want to update on demand you have to setup both emacsen as servers and give them the possibility to communicate bidirectionally.
You could do something like that via emacsclient with the option -e over ssh.
If you are in a private network you can also let the emacs servers directly communicate.
I had no luck with emacsclient and the default server-setup as sockets since emacsclient checks the ownership for the sockets. (Note: First, I used emacsclient for testing. Afterwards, I verified that the communication also works between two emacsen.)
But the code below shows a possibility with the emacs server communicating over tcp/ip.
Both emacsen should put their server files into the same directory. Naturally, you need to name the servers differently. Then you can communicate via the command server-eval-at.
Note, this stuff is security relevant and should only be used on a secure private network.
The below elisp code is not like a package but more like a collection of useful commands.
Nevertheless, on one server you can use the commands as they are. On the other one you need small modifications. E.g.: changing server-name.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Communication over tcp:
(setq server-use-tcp t)
(setq server-auth-dir "/tmp/eserv/")
(if (file-directory-p server-auth-dir)
(progn
(unless (file-accessible-directory-p server-auth-dir)
(error "Cannot access server-auth-dir"))
(unless (file-writable-p server-auth-dir)
(error "Cannot write to server-auth-dir"))
)
(mkdir server-auth-dir t)
(unless (shell-command (concat "chgrp users \"" server-auth-dir "\""))
(error "Cannot change group of server file directory to users."))
(chmod server-auth-dir (file-modes-symbolic-to-number "g+xr" (file-modes server-auth-dir))))
;;
(defadvice server-ensure-safe-dir (around unsafe activate)
"We are on a private network and do not fear intrusion.
Furthermore, the directory is already set up.
This is a SECURITY HOLE if you do not know what you are doing!"
(let ((dir (ad-get-arg 0)))
(unless (file-exists-p dir)
(error "File %s does not exist." dir))
(unless (file-directory-p dir)
(error "File %s is not a directory." dir))))
(server-force-delete)
(server-start)
(defvar server-file nil
"Full name of server file.")
(setq server-file (concat server-auth-dir server-name))
(unless (shell-command (concat "chgrp users \"" server-file "\""))
(error "Cannot change group of server file to users."))
(chmod server-file (file-modes-symbolic-to-number "g+r" (file-modes server-file)))
Afterwards, I discovered that communication of two emacsen is also possible via sockets. One only needs to get around the security and to soft-link the server sockets in the auth-directories.
So, you have the choice.

How to use a minibuffer-exit-hook with read-string

I have not been able to get the minibuffer-exit-hook to play nice with read-string. As far as I can tell, I should no longer be in the minibuffer after finishing up with read-string. However, the condition (minibufferp) says I'm still in the minibuffer even though read-string finished. read-string is written in C, so I can't add the hook there (i.e., at the tail end of the read-string function).
"Documentation [minibuffer-exit-hook]:  Normal hook run just after exit from minibuffer.
[After thinking a little more about this, I'm pretty sure it's a bug -- so I filed a bug report: bug#16524. As I learn more, I'll update this thread.
(defun test ()
(interactive)
(read-string "Prompt: " "testing"))
(add-hook 'minibuffer-exit-hook (lambda ()
(cond
((minibufferp)
(message "Focus is still in the minibuffer: %s" (buffer-name)))
(t (message "Contragulations -- focus is now in: %s." (buffer-name))))))
The doc string is not exact; that's all. The hook is run when inputting text in the minibuffer is done (no longer possible). The buffer that is current when it is run is still the minibuffer. (And that's the way it should be, FWIW.)
Note that the Elisp manual puts it slightly differently (but again, not very precisely):
This is a normal hook that is run whenever the minibuffer is
entered.
("Whenever", meaning about the same time as, not necessarily after.)
If you want to do something after every use of read-string in your code, then define a function that does the following: first (read-string...), then whatever you want done next. And use that function.
If you need to affect also other invocations of read-string, besides those you write in your code, then advise function read-string to perform whatever action after the vanilla code finishes.
For example:
(defadvice read-string (after fooness activate)
(message "buffer: %S" (current-buffer)))
[Note: Yes, you can advise primitives (functions written in C). You used to even be able to advise special forms, but they regressively took away that feature.]
Running a hook after you truly exited the minibuffer is rather pointless: you could be in any kind of buffer (since minibuffer use can be triggered from anywhere) and you hence know very little about the current context (unless you use a buffer-local exit-hook, I guess).
If you want to run a hook when the selected window changes, then your best option is probably to use a post-command-hook that stores the current selected-window in an auxiliary variable and uses it to compare to the previous selected-window.

Open multiple instance of a file-

How can I open multiple different instances of file in Emacs? That is to say, the instances are totally independent of each other but write to the same file.
I actually want to have a reference to the original file in one buffers side-by-side with the buffer in which I will be editing while referring to the original content. I don't find opening a temporary buffer, yanking the entire original content into it and have it side-by-side, as an elegant solution.
Other solutions are welcome as well.
I have tried using clone-indirect-buffer and C-x C-v but it doesn't server the purpose.
You can create a new buffer (C-xb*new*) and insert the content of the file to it with C-xifilename.
As I read through the find-file code, I see this warning coded into it:
"The file %s is already visited normally.
You have asked to visit it literally,
meaning no coding system decoding, format conversion, or local variables.
But Emacs can only visit a file in one way at a time.
Do you want to revisit the file literally now? "
Something that would imply that Emacs' code specifically tries to protect against the situation when the same file is visited multiple times, but the buffers are out of sync with each other. However... you could open two copies of Emacs, in which case they would not know about each other visiting the same file and so would allow this situation to happen.
I can understand that the above isn't a very nice option, but it looks like adding that kind of functionality will require some time understanding the reasons behind it being specifically prevented in the first place.
I've tried this:
M-:(switch-to-buffer (find-file-noselect-1 (create-file-buffer (buffer-file-name)) (buffer-file-name) t nil (buffer-file-name) 1))
And it seems like it would work, but I'm not sure of consequences - maybe different major modes may rely on the original Emacs treatment of files and their editing history, so use with care. The last number 1 is the number to be displayed after the file name, as in Foo.bar<1> So, you'd need to change that, if you need more copies.
As mentioned, you might be able to use clone-buffer, although you'll have to let-bind buffer-file-name around the call since clone-buffer otherwise will refuse to clone it. Another option is to do:
M-x set-visited-file-name RET toto RET
C-x C-f thefile RET
C-x b RET
M-x set-visited-file-name RET thefile RET
the last set-visited-file-name should ask you if you really want to do that, but you can answer that you do and Emacs will accept your choice. Arguably, clone-buffer should not reject to do it, so you might like to submit a bug-report asking to make it behave similarly to what set-visited-file-name does.
You may have some luck with clone-buffer, depending on your mode. It has certain limitations that you can read about in the docs.
Otherwise, here's something quick and dirty:
(defun dodgy-clone-buffer ()
"Clone the current buffer. The clone will write to the original file."
(interactive)
(switch-to-buffer-other-window
(eval `(with-current-buffer
;; Create a new buffer or clear an existing one.
(get-buffer-create ,(format "*clone: %s*" (buffer-name)))
(delete-region (point-min) (point-max))
(insert ,(buffer-string))
(setq buffer-file-name ,(buffer-file-name))
(funcall ',major-mode)
(current-buffer)))))

Create buffer and immediately hide it?

I'm looking for a way to create a buffer and immediately hide it. It is a buffer for technical information, not interesting to the user, and it is used with shell-command to process the output.
kill-buffer - is not what I need, because I need that buffer to be live.
delete-window - doesn't do it either because there's no way to make sure how exactly the buffer will open (it may create a new window or may take over another window).
It doesn't help if I create the buffer before supplying it to shell-command Regardless of whether it existed before, it will bring it to front and, if there was only one window at the time it did it, it will create an additional window, but if there were more windows, then it basically does something random. Some times it will create a new window... other times it won't.
EDIT:
The example below illustrates the problem:
(defun haxe-start-waiting-server (&optional compiler host port)
"Starts Haxe `haxe-compiler' on `haxe-server-host':`haxe-server-port'
with \"--wait\" for the future requests made by autocompletion
or flymake.
This function is bound to \\[haxe-start-waiting-server]"
(interactive
(let ((compiler-i
(read-string "Haxe compiler: "
haxe-compiler t haxe-compiler))
(host-i
(read-string "Haxe server host: "
haxe-server-host t haxe-server-host))
(port-i
(read-number "Haxe server port: " haxe-server-port)))
(list compiler-i host-i port-i)))
(unless (called-interactively-p 'interactive)
(unless compiler (setq compiler haxe-compiler))
(unless host (setq compiler haxe-server-host))
(unless port (setq compiler haxe-server-port)))
(save-excursion
(let ((new-buffer
(get-buffer-create
(generate-new-buffer-name
" *haxe-waiting-server*"))))
(async-shell-command
(concat compiler " --wait "
host ":" (number-to-string port))
new-buffer)
(bury-buffer new-buffer))))
If you want everything to happen in the background, you may need save-window-excursion instead of save-excursion.
From the Emacs manual:
Buffers that are ephemeral and generally uninteresting to the user
have names starting with a space, so that the list-buffers and
buffer-menu commands don't mention them (but if such a buffer visits a
file, it is mentioned). A name starting with space also initially
disables recording undo information; see Undo.
For finer control on buffer behavior, you might want to use start-process instead of async-shell-command. From its documentation:
In Elisp, you will often be better served by calling `start-process'
directly, since it offers more control and does not impose the use of
a shell (with its need to quote arguments).

How can I use Emacs tramp to ssh to a remote host and edit a file as another user on an ad-hoc basis?

/multi used to work for me, now it's gone and I'm frustrated.
What I want to do is, in my dream world:
/myuser#remotehost:sudo:anotheruser:/some/path/to/file
...and have ido-mode work.
The key thing here is that 'myuser', 'remotehost' and 'anotheruser' are all very ad-hoc, I use a huge array of remote hosts, often with different users and sudo-ing to a wide range of different users.
What do I need to add and how can I test it without reloading emacs over and over?
As of this commit, TRAMP supports ad-hoc multiple hops again.
Roughly speaking, you use it like this:
/ssh:transituser#remotehost|sudo:user#remotehost:/some/file
I haven't got it to work reliably with ido-mode yet, which is a shame, but it's a lot better than nothing! :-)
The following code may help:
(defun find-file-as-root ()
"Find a file as root."
(interactive)
(let* ((parsed (when (tramp-tramp-file-p default-directory)
(coerce (tramp-dissect-file-name default-directory)
'list)))
(default-directory
(if parsed
(apply 'tramp-make-tramp-file-name
(append '("sudo" "root") (cddr parsed)))
(tramp-make-tramp-file-name "sudo" "root" "localhost"
default-directory))))
(call-interactively 'find-file)))
I had it in my .emacs file, and it seems to come from here: http://atomized.org/2011/01/toggle-between-root-non-root-in-emacs-with-tramp/
I haven't used it extensively but it seems like that is a step in the right direction.