;;; server.el --- Lisp code for GNU Emacs running as server process.;; Copyright (C) 1986, 1987, 1992, 1994, 1995 Free Software Foundation, Inc.;; Author: William Sommerfeld <wesommer@athena.mit.edu>;; Keywords: processes;; Changes by peck@sun.com and by rms.;; This file is part of GNU Emacs.;; GNU Emacs 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 2, or (at your option);; any later version.;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,;; Boston, MA 02111-1307, USA.;;; Commentary:;; This Lisp code is run in Emacs when it is to operate as;; a server for other processes.;; Load this library and do M-x server-edit to enable Emacs as a server.;; Emacs runs the program ../arch-lib/emacsserver as a subprocess;; for communication with clients. If there are no client buffers to edit, ;; server-edit acts like (switch-to-buffer (other-buffer));; When some other program runs "the editor" to edit a file,;; "the editor" can be the Emacs client program ../lib-src/emacsclient.;; This program transmits the file names to Emacs through;; the server subprocess, and Emacs visits them and lets you edit them.;; Note that any number of clients may dispatch files to emacs to be edited.;; When you finish editing a Server buffer, again call server-edit;; to mark that buffer as done for the client and switch to the next ;; Server buffer. When all the buffers for a client have been edited ;; and exited with server-edit, the client "editor" will return;; to the program that invoked it. ;; Your editing commands and Emacs's display output go to and from;; the terminal in the usual way. Thus, server operation is possible;; only when Emacs can talk to the terminal at the time you invoke;; the client. This is possible in four cases:;; 1. On a window system, where Emacs runs in one window and the;; program that wants to use "the editor" runs in another.;; 2. On a multi-terminal system, where Emacs runs on one terminal and the;; program that wants to use "the editor" runs on another.;; 3. When the program that wants to use "the editor" is running;; as a subprocess of Emacs.;; 4. On a system with job control, when Emacs is suspended, the program;; that wants to use "the editor" will stop and display;; "Waiting for Emacs...". It can then be suspended, and Emacs can be;; brought into the foreground for editing. When done editing, Emacs is;; suspended again, and the client program is brought into the foreground.;; The buffer local variable "server-buffer-clients" lists ;; the clients who are waiting for this buffer to be edited. ;; The global variable "server-clients" lists all the waiting clients,;; and which files are yet to be edited for each.;;; Code:(defvarserver-program(expand-file-name"emacsserver"exec-directory)"*The program to use as the edit server.")(defvarserver-visit-hooknil"*List of hooks to call when visiting a file for the Emacs server.")(defvarserver-switch-hooknil"*List of hooks to call when switching to a buffer for the Emacs server.")(defvarserver-done-hooknil"*List of hooks to call when done editing a buffer for the Emacs server.")(defvarserver-processnil"the current server process")(defvarserver-previous-string"")(defvarserver-clientsnil"List of current server clients.Each element is (CLIENTID BUFFERS...) where CLIENTID is a stringthat can be given to the server process to identify a client.When a buffer is marked as \"done\", it is removed from this list.")(defvarserver-buffer-clientsnil"List of clientids for clients requesting editing of current buffer.")(make-variable-buffer-local'server-buffer-clients);; Changing major modes should not erase this local.(put'server-buffer-clients'permanent-localt)(defvarserver-windownil"*The window to use for selecting Emacs server buffers.If nil, use the selected window.If it is a frame, use the frame's selected window.")(defvarserver-temp-file-regexp"^/tmp/Re\\|/draft$""*Regexp which should match filenames of temporary fileswhich are deleted and reused after each editby the programs that invoke the emacs server.")(or(assq'server-buffer-clientsminor-mode-alist)(setqminor-mode-alist(cons'(server-buffer-clients" Server")minor-mode-alist)));; If a *server* buffer exists,;; write STRING to it for logging purposes.(defunserver-log(string)(if(get-buffer"*server*")(save-excursion(set-buffer"*server*")(goto-char(point-max))(insert(current-time-string)" "string)(or(bolp)(newline)))))(defunserver-sentinel(procmsg)(cond((eq(process-statusproc)'exit)(server-log(message"Server subprocess exited")))((eq(process-statusproc)'signal)(server-log(message"Server subprocess killed")))));;;###autoload(defunserver-start(&optionalleave-dead)"Allow this Emacs process to be a server for client processes.This starts a server communications subprocess through whichclient \"editors\" can send your editing commands to this Emacs job.To use the server, set up the program `emacsclient' in theEmacs distribution as your standard \"editor\".Prefix arg means just kill any existing server communications subprocess."(interactive"P");; kill it dead!(ifserver-process(progn(set-process-sentinelserver-processnil)(condition-case()(delete-processserver-process)(errornil))));; Delete the socket files made by previous server invocations.(let*((sysname(system-name))(dot-index(string-match"\\."sysname)))(condition-case()(delete-file(format"~/.emacs-server-%s"sysname))(errornil))(condition-case()(delete-file(format"/tmp/esrv%d-%s"(user-uid)sysname))(errornil));; In case the server file name was made with a domainless hostname,;; try deleting that name too.(ifdot-index(let((shortname(substringsysname0dot-index)))(condition-case()(delete-file(format"~/.emacs-server-%s"shortname))(errornil))(condition-case()(delete-file(format"/tmp/esrv%d-%s"(user-uid)shortname))(errornil)))));; If this Emacs already had a server, clear out associated status.(whileserver-clients(let((buffer(nth1(carserver-clients))))(server-buffer-donebuffer)))(ifleave-deadnil(ifserver-process(server-log(message"Restarting server")));; Using a pty is wasteful, and the separate session causes;; annoyance sometimes (some systems kill idle sessions).(let((process-connection-typenil))(setqserver-process(start-process"server"nilserver-program)))(set-process-sentinelserver-process'server-sentinel)(set-process-filterserver-process'server-process-filter)(process-kill-without-queryserver-process)));Process a request from the server to edit some files.;Format of STRING is "Client: CLIENTID PATH PATH PATH... \n"(defunserver-process-filter(procstring)(server-logstring)(setqstring(concatserver-previous-stringstring));; If the input is multiple lines,;; process each line individually.(while(string-match"\n"string)(let((request(substringstring0(match-beginning0)))clientnowait(filesnil)(lineno1));; Remove this line from STRING.(setqstring(substringstring(match-end0)))(if(string-match"^Error: "request)(message"Server error: %s"(substringrequest(match-end0)))(if(string-match"^Client: "request)(progn(setqrequest(substringrequest(match-end0)))(setqclient(list(substringrequest0(string-match" "request))))(setqrequest(substringrequest(match-end0)))(while(string-match"[^ ]+ "request)(let((arg(substringrequest(match-beginning0)(1-(match-end0))))(pos0))(setqrequest(substringrequest(match-end0)))(if(string-match"\\`-nowait"arg)(setqnowaitt)(if(string-match"\\`\\+[0-9]+\\'"arg);; ARG is a line number option.(setqlineno(read(substringarg1)));; ARG is a file name.;; Collapse multiple slashes to single slashes.(setqarg(command-line-normalize-file-namearg));; Undo the quoting that emacsclient does;; for certain special characters.(while(string-match"&."argpos)(setqpos(1+(match-beginning0)))(let((nextchar(arefargpos)))(cond((=nextchar?&)(setqarg(replace-match"&"ttarg)))((=nextchar?-)(setqarg(replace-match"-"ttarg)))(t(setqarg(replace-match" "ttarg))))))(setqfiles(cons(listarglineno)files))(setqlineno1)))))(server-visit-filesfilesclientnowait);; CLIENT is now a list (CLIENTNUM BUFFERS...)(ornowait(setqserver-clients(consclientserver-clients)))(server-switch-buffer(nth1client))(run-hooks'server-switch-hook)(message(substitute-command-keys"When done with a buffer, type \\[server-edit]")))))));; Save for later any partial line that remains.(setqserver-previous-stringstring))(defunserver-visit-files(filesclient&optionalnowait)"Finds FILES and returns the list CLIENT with the buffers nconc'd.FILES is an alist whose elements are (FILENAME LINENUMBER).NOWAIT non-nil means this client is not waiting for the results,so don't mark these buffers specially, just visit them normally.";; Bind last-nonmenu-event to force use of keyboard, not mouse, for queries.(let(client-record(last-nonmenu-eventt)(obuf(current-buffer)));; Restore the current buffer afterward, but not using save-excursion,;; because we don't want to save point in this buffer;; if it happens to be one of those specified by the server.(unwind-protect(whilefiles;; If there is an existing buffer modified or the file is modified,;; revert it.;; If there is an existing buffer with deleted file, offer to write it.(let*((filen(car(carfiles)))(obuf(get-file-bufferfilen)))(if(andobuf(set-bufferobuf))(if(file-exists-pfilen)(if(or(not(verify-visited-file-modtimeobuf))(buffer-modified-pobuf))(revert-buffertnil))(if(y-or-n-p(concat"File no longer exists: "filen", write buffer to file? "))(write-filefilen)))(set-buffer(find-file-noselectfilen))(run-hooks'server-visit-hook)))(goto-line(nth1(carfiles)))(if(notnowait)(setqserver-buffer-clients(cons(carclient)server-buffer-clients)))(setqclient-record(cons(current-buffer)client-record))(setqfiles(cdrfiles)))(set-bufferobuf))(nconcclientclient-record)))(defunserver-buffer-done(buffer)"Mark BUFFER as \"done\" for its client(s).This buries the buffer, then returns a list of the form (NEXT-BUFFER KILLED).NEXT-BUFFER is another server buffer, as a suggestion for what to select next,or nil. KILLED is t if we killed BUFFER (because it was a temp file)."(let((running(eq(process-statusserver-process)'run))(next-buffernil)(killednil)(firstt)(old-clientsserver-clients))(whileold-clients(let((client(carold-clients)))(ornext-buffer(setqnext-buffer(nth1(memqbufferclient))))(delqbufferclient);; Delete all dead buffers from CLIENT.(let((tailclient))(whiletail(and(bufferp(cartail))(null(buffer-name(cartail)))(delq(cartail)client))(setqtail(cdrtail))));; If client now has no pending buffers,;; tell it that it is done, and forget it entirely.(if(cdrclient)nil(ifrunning(progn;; Don't send emacsserver two commands in close succession.;; It cannot handle that.(orfirst(sit-for1))(setqfirstnil)(send-stringserver-process(format"Close: %s Done\n"(carclient)))(server-log(format"Close: %s Done\n"(carclient)))))(setqserver-clients(delqclientserver-clients))))(setqold-clients(cdrold-clients)))(if(and(bufferpbuffer)(buffer-namebuffer))(progn(save-excursion(set-bufferbuffer)(setqserver-buffer-clientsnil)(run-hooks'server-done-hook))(if(server-temp-file-pbuffer)(progn(kill-bufferbuffer)(setqkilledt))(bury-bufferbuffer))))(listnext-bufferkilled)))(defunserver-temp-file-p(buffer)"Return non-nil if BUFFER contains a file considered temporary.These are files whose names suggest they are repeatedlyreused to pass information to another program.The variable `server-temp-file-regexp' controls which filenamesare considered temporary."(and(buffer-file-namebuffer)(string-matchserver-temp-file-regexp(buffer-file-namebuffer))))(defunserver-done()"Offer to save current buffer, mark it as \"done\" for clients.This buries the buffer, then returns a list of the form (NEXT-BUFFER KILLED).NEXT-BUFFER is another server buffer, as a suggestion for what to select next,or nil. KILLED is t if we killed the BUFFER (because it was a temp file)."(let((buffer(current-buffer)))(ifserver-buffer-clients(progn(if(server-temp-file-pbuffer);; For a temp file, save, and do make a non-numeric backup;; (unless make-backup-files is nil).(let((version-controlnil)(buffer-backed-upnil))(save-buffer))(if(and(buffer-modified-p)(y-or-n-p(concat"Save file "buffer-file-name"? ")))(save-bufferbuffer)))(server-buffer-donebuffer)))));; Ask before killing a server buffer.;; It was suggested to release its client instead,;; but I think that is dangerous--the client would proceed;; using whatever is on disk in that file. -- rms.(defunserver-kill-buffer-query-function()(or(notserver-buffer-clients)(yes-or-no-p(format"Buffer `%s' still has clients; kill it? "(buffer-name(current-buffer))))))(add-hook'kill-buffer-query-functions'server-kill-buffer-query-function)(defunserver-kill-emacs-query-function()(let(live-client(tailserver-clients));; See if any clients have any buffers that are still alive.(whiletail(if(memqt(mapcar'stringp(mapcar'buffer-name(cdr(cartail)))))(setqlive-clientt))(setqtail(cdrtail)))(or(notlive-client)(yes-or-no-p"Server buffers still have clients; exit anyway? "))))(add-hook'kill-emacs-query-functions'server-kill-emacs-query-function)(defunserver-edit(&optionalarg)"Switch to next server editing buffer; say \"Done\" for current buffer.If a server buffer is current, it is marked \"done\" and optionally saved.When all of a client's buffers are marked as \"done\", the client is notified.Temporary files such as MH <draft> files are always saved and backed up,no questions asked. (The variable `make-backup-files', if nil, stillinhibits a backup; you can set it locally in a particular buffer toprevent a backup for it.) The variable `server-temp-file-regexp' controlswhich filenames are considered temporary.If invoked with a prefix argument, or if there is no server process running, starts server process and that is all. Invoked by \\[server-edit]."(interactive"P")(if(orarg(notserver-process)(memq(process-statusserver-process)'(signalexit)))(server-startnil)(apply'server-switch-buffer(server-done))))(defunserver-switch-buffer(&optionalnext-bufferkilled-one)"Switch to another buffer, preferably one that has a client.Arg NEXT-BUFFER is a suggestion; if it is a live buffer, use it.";; KILLED-ONE is t in a recursive call;; if we have already killed one temp-file server buffer.;; This means we should avoid the final "switch to some other buffer";; since we've already effectively done that.(cond((and(windowpserver-window)(window-live-pserver-window))(select-windowserver-window))((framepserver-window)(if(not(frame-live-pserver-window))(setqserver-window(make-frame)))(select-window(frame-selected-windowserver-window))))(if(window-minibuffer-p(selected-window))(select-window(next-windownil'nomini0)));; Move to a non-dedicated window, if we have one.(let((last-window(previous-windownil'nomini0)))(while(and(window-dedicated-p(selected-window))(not(eqlast-window(selected-window))))(select-window(next-windownil'nomini0))))(set-window-dedicated-p(selected-window)nil)(ifnext-buffer(if(and(bufferpnext-buffer)(buffer-namenext-buffer))(switch-to-buffernext-buffer);; If NEXT-BUFFER is a dead buffer,;; remove the server records for it;; and try the next surviving server buffer.(apply'server-switch-buffer(server-buffer-donenext-buffer)))(ifserver-clients(server-switch-buffer(nth1(carserver-clients))killed-one)(if(notkilled-one)(switch-to-buffer(other-buffer))))))(global-set-key"\C-x#"'server-edit)(provide'server);;; server.el ends here