;;; server.el --- Lisp code for GNU Emacs running as server process
;; Copyright (C) 1986, 1987, 1992, 1994, 1995, 1996, 1997, 1998, 1999, 2000,
-;; 2001, 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+;; 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
;; Author: William Sommerfeld <wesommer@athena.mit.edu>
;; Maintainer: FSF
;; This file is part of GNU Emacs.
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; 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 3, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, 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
;; 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., 51 Franklin Street, Fifth Floor,
-;; Boston, MA 02110-1301, USA.
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
:version "22.1")
(put 'server-host 'risky-local-variable t)
-(defcustom server-auth-dir (concat user-emacs-directory "server/")
+(defcustom server-auth-dir (locate-user-emacs-file "server/")
"Directory for server authentication files."
:group 'server
:type 'directory
it with the Emacs server. If nil, kill only buffers as specified by
`server-temp-file-regexp'.
Please note that only buffers that still have a client are killed,
-i.e. buffers visited with \"emacsclient --no-wait\" are never killed in
-this way."
+i.e. buffers visited with \"emacsclient --no-wait\" are never killed
+in this way."
:group 'server
:type 'boolean
:version "21.1")
are done with it in the server.")
(make-variable-buffer-local 'server-existing-buffer)
-(defvar server-name "server")
-
-(defvar server-socket-dir nil
+(defcustom server-name "server"
+ "The name of the Emacs server, if this Emacs process creates one.
+The command `server-start' makes use of this. It should not be
+changed while a server is running."
+ :group 'server
+ :type 'string
+ :version "23.1")
+
+;; We do not use `temporary-file-directory' here, because emacsclient
+;; does not read the init file.
+(defvar server-socket-dir
+ (and (featurep 'make-network-process '(:family local))
+ (format "%s/emacs%d" (or (getenv "TMPDIR") "/tmp") (user-uid)))
"The directory in which to place the server socket.
-Initialized by `server-start'.")
+If local sockets are not supported, this is nil.")
(defun server-clients-with (property value)
"Return a list of clients with PROPERTY set to VALUE."
`(let ((process-environment process-environment))
(dolist (,var ,vars)
(let ((,value (getenv-internal ,var ,env)))
- (push (if (null ,value)
- ,var
- (concat ,var "=" ,value))
+ (push (if (stringp ,value)
+ (concat ,var "=" ,value)
+ ,var)
process-environment)))
(progn ,@body))))
(defun server-delete-client (proc &optional noframe)
- "Delete CLIENT, including its buffers, terminals and frames.
-If NOFRAME is non-nil, let the frames live. (To be used from
-`delete-frame-functions'.)"
- (server-log (concat "server-delete-client" (if noframe " noframe"))
- proc)
+ "Delete PROC, including its buffers, terminals and frames.
+If NOFRAME is non-nil, let the frames live.
+Updates `server-clients'."
+ (server-log (concat "server-delete-client" (if noframe " noframe")) proc)
;; Force a new lookup of client (prevents infinite recursion).
(when (memq proc server-clients)
(let ((buffers (process-get proc 'buffers)))
(server-log "Deleted" proc))))
+(defvar server-log-time-function 'current-time-string
+ "Function to generate timestamps for `server-buffer'.")
+
+(defconst server-buffer " *server*"
+ "Buffer used internally by Emacs's server.
+One use is to log the I/O for debugging purposes (see `server-log'),
+the other is to provide a current buffer in which the process filter can
+safely let-bind buffer-local variables like `default-directory'.")
+
+(defvar server-log nil
+ "If non-nil, log the server's inputs and outputs in the `server-buffer'.")
+
(defun server-log (string &optional client)
- "If a *server* buffer exists, write STRING to it for logging purposes.
-If CLIENT is non-nil, add a description of it to the logged
-message."
- (when (get-buffer "*server*")
- (with-current-buffer "*server*"
+ "If `server-log' is non-nil, log STRING to `server-buffer'.
+If CLIENT is non-nil, add a description of it to the logged message."
+ (when server-log
+ (with-current-buffer (get-buffer-create server-buffer)
(goto-char (point-max))
- (insert (current-time-string)
+ (insert (funcall server-log-time-function)
(cond
- ((null client) " ")
- ((listp client) (format " %s: " (car client)))
- (t (format " %s: " client)))
+ ((null client) " ")
+ ((listp client) (format " %s: " (car client)))
+ (t (format " %s: " client)))
string)
(or (bolp) (newline)))))
(process-query-on-exit-flag proc))
(set-process-query-on-exit-flag proc nil))
;; Delete the associated connection file, if applicable.
- ;; This is actually problematic: the file may have been overwritten by
- ;; another Emacs server in the mean time, so it's not ours any more.
- ;; (and (process-contact proc :server)
- ;; (eq (process-status proc) 'closed)
- ;; (ignore-errors (delete-file (process-get proc :server-file))))
+ ;; Although there's no 100% guarantee that the file is owned by the
+ ;; running Emacs instance, server-start uses server-running-p to check
+ ;; for possible servers before doing anything, so it *should* be ours.
+ (and (process-contact proc :server)
+ (eq (process-status proc) 'closed)
+ (ignore-errors (delete-file (process-get proc :server-file))))
(server-log (format "Status changed to %s: %s" (process-status proc) msg) proc)
(server-delete-client proc))
(defun server-select-display (display)
;; If the current frame is on `display' we're all set.
- ;; Similarly if we are unable to open a frames on other displays, there's
+ ;; Similarly if we are unable to open frames on other displays, there's
;; nothing more we can do.
(unless (or (not (fboundp 'make-frame-on-display))
(equal (frame-parameter (selected-frame) 'display) display))
display
;; Make it display (and remember) some dummy buffer, so
;; we can detect later if the frame is in use or not.
- `((server-dummmy-buffer . ,buffer)
+ `((server-dummy-buffer . ,buffer)
;; This frame may be deleted later (see
;; server-unselect-display) so we want it to be as
;; unobtrusive as possible.
(set-frame-parameter frame 'server-dummy-buffer nil)))
(defun server-handle-delete-frame (frame)
- "Delete the client connection when the emacsclient frame is deleted."
+ "Delete the client connection when the emacsclient frame is deleted.
+\(To be used from `delete-frame-functions'.)"
(let ((proc (frame-parameter frame 'client)))
(when (and (frame-live-p frame)
proc
arg t t))
(defun server-send-string (proc string)
- "A wrapper around `proc-send-string' for logging."
+ "A wrapper around `process-send-string' for logging."
(server-log (concat "Sent " string) proc)
(process-send-string proc string))
Emacs distribution as your standard \"editor\".
Optional argument LEAVE-DEAD (interactively, a prefix arg) means just
-kill any existing server communications subprocess."
+kill any existing server communications subprocess.
+
+If a server is already running, the server is not started.
+To force-start a server, do \\[server-force-delete] and then
+\\[server-start]."
(interactive "P")
- (when (or
- (not server-clients)
- (yes-or-no-p
- "The current server still has clients; delete them? "))
- ;; It is safe to get the user id now.
- (setq server-socket-dir (or server-socket-dir
- (format "/tmp/emacs%d" (user-uid))))
- (when server-process
- ;; kill it dead!
- (ignore-errors (delete-process server-process)))
- ;; Delete the socket files made by previous server invocations.
- (condition-case ()
- (delete-file (expand-file-name server-name server-socket-dir))
- (error nil))
- ;; If this Emacs already had a server, clear out associated status.
- (while server-clients
- (server-delete-client (car server-clients)))
- ;; Now any previous server is properly stopped.
- (if leave-dead
- (progn
- (server-log (message "Server stopped"))
- (setq server-process nil))
- (let* ((server-dir (if server-use-tcp server-auth-dir server-socket-dir))
- (server-file (expand-file-name server-name server-dir)))
+ (when (or (not server-clients)
+ ;; Ask the user before deleting existing clients---except
+ ;; when we can't get user input, which may happen when
+ ;; doing emacsclient --eval "(kill-emacs)" in daemon mode.
+ (if (and (daemonp)
+ (null (cdr (frame-list)))
+ (eq (selected-frame) terminal-frame))
+ leave-dead
+ (yes-or-no-p
+ "The current server still has clients; delete them? ")))
+ (let* ((server-dir (if server-use-tcp server-auth-dir server-socket-dir))
+ (server-file (expand-file-name server-name server-dir)))
+ (when server-process
+ ;; kill it dead!
+ (ignore-errors (delete-process server-process)))
+ ;; Delete the socket files made by previous server invocations.
+ (if (not (eq t (server-running-p server-name)))
+ ;; Remove any leftover socket or authentication file
+ (ignore-errors (delete-file server-file))
+ (setq server-mode nil) ;; already set by the minor mode code
+ (display-warning
+ 'server
+ (concat "Unable to start the Emacs server.\n"
+ (format "There is an existing Emacs server, named %S.\n"
+ server-name)
+ "To start the server in this Emacs process, stop the existing
+server or call `M-x server-force-delete' to forcibly disconnect it.")
+ :warning)
+ (setq leave-dead t))
+ ;; If this Emacs already had a server, clear out associated status.
+ (while server-clients
+ (server-delete-client (car server-clients)))
+ ;; Now any previous server is properly stopped.
+ (if leave-dead
+ (progn
+ (unless (eq t leave-dead) (server-log (message "Server stopped")))
+ (setq server-process nil))
;; Make sure there is a safe directory in which to place the socket.
(server-ensure-safe-dir server-dir)
- ;; Remove any leftover socket or authentication file.
- (ignore-errors (delete-file server-file))
(when server-process
(server-log (message "Restarting server")))
(letf (((default-file-modes) ?\700))
(add-hook 'delete-frame-functions 'server-handle-delete-frame)
(add-hook 'kill-buffer-query-functions 'server-kill-buffer-query-function)
(add-hook 'kill-emacs-query-functions 'server-kill-emacs-query-function)
+ (add-hook 'kill-emacs-hook (lambda () (server-mode -1))) ;Cleanup upon exit.
(setq server-process
(apply #'make-network-process
:name server-name
:filter 'server-process-filter
;; We must receive file names without being decoded.
;; Those are decoded by server-process-filter according
- ;; to file-name-coding-system.
- :coding 'raw-text
- ;; The rest of the args depends on the kind of socket used.
+ ;; to file-name-coding-system. Also don't get
+ ;; confused by CRs since we don't quote them.
+ :coding 'raw-text-unix
+ ;; The other args depend on the kind of socket used.
(if server-use-tcp
(list :family nil
:service t
:service server-file
:plist '(:authenticated t)))))
(unless server-process (error "Could not start server process"))
+ (process-put server-process :server-file server-file)
(when server-use-tcp
(let ((auth-key
(loop
" " (int-to-string (emacs-pid))
"\n" auth-key)))))))))
-(defun server-running-p (&optional name)
- "Test whether server NAME is running."
+;;;###autoload
+(defun server-force-delete (&optional name)
+ "Unconditionally delete connection file for server NAME.
+If server is running, it is first stopped.
+NAME defaults to `server-name'. With argument, ask for NAME."
(interactive
(list (if current-prefix-arg
(read-string "Server name: " nil nil server-name))))
+ (when server-mode (with-temp-message nil (server-mode -1)))
+ (let ((file (expand-file-name (or name server-name)
+ (if server-use-tcp
+ server-auth-dir
+ server-socket-dir))))
+ (condition-case nil
+ (progn
+ (delete-file file)
+ (message "Connection file %S deleted" file))
+ (file-error
+ (message "No connection file %S" file)))))
+
+(defun server-running-p (&optional name)
+ "Test whether server NAME is running.
+
+Return values:
+ nil the server is definitely not running.
+ t the server seems to be running.
+ something else we cannot determine whether it's running without using
+ commands which may have to wait for a long time."
(unless name (setq name server-name))
(condition-case nil
- (progn
+ (if server-use-tcp
+ (with-temp-buffer
+ (insert-file-contents-literally (expand-file-name name server-auth-dir))
+ (or (and (looking-at "127\\.0\\.0\\.1:[0-9]+ \\([0-9]+\\)")
+ (assq 'comm
+ (process-attributes
+ (string-to-number (match-string 1))))
+ t)
+ :other))
(delete-process
(make-network-process
:name "server-client-test" :family 'local :server nil :noquery t
(server-quote-arg text)))))))))
(defun server-create-tty-frame (tty type proc)
+ (unless tty
+ (error "Invalid terminal device"))
+ (unless type
+ (error "Invalid terminal type"))
(add-to-list 'frame-inherited-parameters 'client)
(let ((frame
(server-with-environment (process-get proc 'env)
"BAUDRATE" "COLUMNS" "ESCDELAY" "HOME" "LINES"
"NCURSES_ASSUMED_COLORS" "NCURSES_NO_PADDING"
"NCURSES_NO_SETBUF" "TERM" "TERMCAP" "TERMINFO"
- "TERMINFO_DIRS" "TERMPATH"
+ "TERMINFO_DIRS" "TERMPATH"
;; rxvt wants these
"COLORFGBG" "COLORTERM")
- (make-frame-on-tty tty type
- ;; Ignore nowait here; we always need to
- ;; clean up opened ttys when the client dies.
- `((client . ,proc)
- ;; This is a leftover from an earlier
- ;; attempt at making it possible for process
- ;; run in the server process to use the
- ;; environment of the client process.
- ;; It has no effect now and to make it work
- ;; we'd need to decide how to make
- ;; process-environment interact with client
- ;; envvars, and then to change the
- ;; C functions `child_setup' and
- ;; `getenv_internal' accordingly.
- (environment . ,(process-get proc 'env)))))))
-
+ (make-frame `((window-system . nil)
+ (tty . ,tty)
+ (tty-type . ,type)
+ ;; Ignore nowait here; we always need to
+ ;; clean up opened ttys when the client dies.
+ (client . ,proc)
+ ;; This is a leftover from an earlier
+ ;; attempt at making it possible for process
+ ;; run in the server process to use the
+ ;; environment of the client process.
+ ;; It has no effect now and to make it work
+ ;; we'd need to decide how to make
+ ;; process-environment interact with client
+ ;; envvars, and then to change the
+ ;; C functions `child_setup' and
+ ;; `getenv_internal' accordingly.
+ (environment . ,(process-get proc 'env)))))))
+
;; ttys don't use the `display' parameter, but callproc.c does to set
;; the DISPLAY environment on subprocesses.
(set-frame-parameter frame 'display
(error "Please specify display"))
params)))
(server-log (format "%s created" frame) proc)
- ;; XXX We need to ensure the parameters are really set because Emacs
- ;; forgets unhandled initialization parameters for X frames at
- ;; the moment.
- (modify-frame-parameters frame params)
(select-frame frame)
(process-put proc 'frame frame)
(process-put proc 'terminal (frame-terminal frame))
(switch-to-buffer (get-buffer-create "*scratch*") 'norecord)
frame)))
-
(defun server-goto-toplevel (proc)
(condition-case nil
;; If we're running isearch, we must abort it to allow Emacs to
(defun* server-process-filter (proc string)
"Process a request from the server to edit some files.
PROC is the server process. STRING consists of a sequence of
-commands prefixed by a dash. Some commands have arguments; these
-are &-quoted and need to be decoded by `server-unquote-arg'. The
-filter parses and executes these commands.
+commands prefixed by a dash. Some commands have arguments;
+these are &-quoted and need to be decoded by `server-unquote-arg'.
+The filter parses and executes these commands.
To illustrate the protocol, here is an example command that
emacsclient sends to create a new X frame (note that the whole
sequence is sent on a single line):
- -env HOME /home/lorentey
- -env DISPLAY :0.0
+ -env HOME=/home/lorentey
+ -env DISPLAY=:0.0
... lots of other -env commands
-display :0.0
-window-system
on this tty until it gets a -resume command.
`-resume'
- Resume this tty frame. The client sends this string when it
+ Resume this tty frame. The client sends this string when it
gets the SIGCONT signal and it is the foreground process on its
controlling tty.
`-ignore COMMENT'
- Do nothing, but put the comment in the server
- log. Useful for debugging.
+ Do nothing, but put the comment in the server log.
+ Useful for debugging.
The following commands are accepted by the client:
used to forward window change signals to it.
`-window-system-unsupported'
- Signals that the server does not
- support creating X frames; the client must try again with a tty
- frame.
+ Signals that the server does not support creating X frames;
+ the client must try again with a tty frame.
`-print STRING'
Print STRING on stdout. Used to send values
Signal an error (but continue processing).
`-suspend'
- Suspend this terminal, i.e., stop the client process. Sent
- when the user presses C-z."
+ Suspend this terminal, i.e., stop the client process.
+ Sent when the user presses C-z."
(server-log (concat "Received " string) proc)
;; First things first: let's check the authentication
(unless (process-get proc :authenticated)
- (if (and (string-match "-auth \\(.*?\\)\n" string)
+ (if (and (string-match "-auth \\([!-~]+\\)\n?" string)
(equal (match-string 1 string) (process-get proc :auth-key)))
(progn
(setq string (substring string (match-end 0)))
;; Save for later any partial line that remains.
(when (> (length string) 0)
(process-put proc 'previous-string string))
-
+
;; In earlier versions of server.el (where we used an `emacsserver'
;; process), there could be multiple lines. Nowadays this is not
;; supported any more.
frame ; The frame that was opened for the client (if any).
display ; Open the frame on this display.
dontkill ; t if the client should not be killed.
- (commands ())
+ commands
dir
- (tty-name nil) ;nil, `window-system', or the tty name.
- tty-type ;string.
- (files nil)
- (lineno 1)
- (columnno 0))
+ use-current-frame
+ tty-name ;nil, `window-system', or the tty name.
+ tty-type ;string.
+ files
+ filepos
+ command-line-args-left
+ arg)
;; Remove this line from STRING.
(setq string (substring string (match-end 0)))
- (while (string-match " *[^ ]* " request)
- (let ((arg (substring request (match-beginning 0)
- (1- (match-end 0)))))
- (setq request (substring request (match-end 0)))
+ (setq command-line-args-left
+ (mapcar 'server-unquote-arg (split-string request " " t)))
+ (while (setq arg (pop command-line-args-left))
(cond
;; -version CLIENT-VERSION: obsolete at birth.
- ((and (equal "-version" arg) (string-match "[^ ]+ " request))
- (setq request (substring request (match-end 0))))
+ ((and (equal "-version" arg) command-line-args-left)
+ (pop command-line-args-left))
;; -nowait: Emacsclient won't wait for a result.
((equal "-nowait" arg) (setq nowait t))
;; -current-frame: Don't create frames.
- ((equal "-current-frame" arg) (setq tty-name nil))
+ ((equal "-current-frame" arg) (setq use-current-frame t))
;; -display DISPLAY:
;; Open X frames on the given display instead of the default.
- ((and (equal "-display" arg)
- (string-match "\\([^ ]*\\) " request))
- (setq display (match-string 1 request))
- (setq request (substring request (match-end 0))))
+ ((and (equal "-display" arg) command-line-args-left)
+ (setq display (pop command-line-args-left))
+ (if (zerop (length display)) (setq display nil)))
;; -window-system: Open a new X frame.
((equal "-window-system" arg)
;; -ignore COMMENT: Noop; useful for debugging emacsclient.
;; (The given comment appears in the server log.)
- ((and (equal "-ignore" arg) (string-match "[^ ]* " request))
- (setq dontkill t
- request (substring request (match-end 0))))
+ ((and (equal "-ignore" arg) command-line-args-left
+ (setq dontkill t)
+ (pop command-line-args-left)))
;; -tty DEVICE-NAME TYPE: Open a new tty frame at the client.
((and (equal "-tty" arg)
- (string-match "\\([^ ]*\\) \\([^ ]*\\) " request))
- (setq tty-name (match-string 1 request))
- (setq tty-type (match-string 2 request))
- (setq dontkill t)
- (setq request (substring request (match-end 0))))
+ (cdr command-line-args-left))
+ (setq tty-name (pop command-line-args-left)
+ tty-type (pop command-line-args-left)
+ dontkill (or dontkill
+ (not use-current-frame))))
;; -position LINE[:COLUMN]: Set point to the given
;; position in the next file.
((and (equal "-position" arg)
- (string-match "\\+\\([0-9]+\\)\\(?::\\([0-9]+\\)\\)? "
- request))
- (setq lineno (string-to-number (match-string 1 request))
- columnno (if (null (match-end 2)) 0
- (string-to-number (match-string 2 request)))
- request (substring request (match-end 0))))
+ command-line-args-left
+ (string-match "\\+\\([0-9]+\\)\\(?::\\([0-9]+\\)\\)?"
+ (car command-line-args-left)))
+ (setq arg (pop command-line-args-left))
+ (setq filepos
+ (cons (string-to-number (match-string 1 arg))
+ (string-to-number (or (match-string 2 arg) "")))))
;; -file FILENAME: Load the given file.
((and (equal "-file" arg)
- (string-match "\\([^ ]+\\) " request))
- (let ((file (server-unquote-arg (match-string 1 request))))
- (setq request (substring request (match-end 0)))
+ command-line-args-left)
+ (let ((file (pop command-line-args-left)))
(if coding-system
(setq file (decode-coding-string file coding-system)))
- (setq file (command-line-normalize-file-name file))
- (push (list file lineno columnno) files)
- (server-log (format "New file: %s (%d:%d)"
- file lineno columnno) proc))
- (setq lineno 1
- columnno 0))
+ (setq file (expand-file-name file dir))
+ (push (cons file filepos) files)
+ (server-log (format "New file: %s %s"
+ file (or filepos "")) proc))
+ (setq filepos nil))
;; -eval EXPR: Evaluate a Lisp expression.
((and (equal "-eval" arg)
- (string-match "\\([^ ]+\\) " request))
- (lexical-let ((expr (server-unquote-arg
- (match-string 1 request))))
- (setq request (substring request (match-end 0)))
+ command-line-args-left)
+ (if use-current-frame
+ (setq use-current-frame 'always))
+ (lexical-let ((expr (pop command-line-args-left)))
(if coding-system
(setq expr (decode-coding-string expr coding-system)))
(push (lambda () (server-eval-and-print expr proc))
commands)
- (setq lineno 1
- columnno 0)))
+ (setq filepos nil)))
;; -env NAME=VALUE: An environment variable.
- ((and (equal "-env" arg) (string-match "\\([^ ]+\\) " request))
- (let ((var (server-unquote-arg (match-string 1 request))))
+ ((and (equal "-env" arg) command-line-args-left)
+ (let ((var (pop command-line-args-left)))
;; XXX Variables should be encoded as in getenv/setenv.
- (setq request (substring request (match-end 0)))
(process-put proc 'env
(cons var (process-get proc 'env)))))
;; -dir DIRNAME: The cwd of the emacsclient process.
- ((and (equal "-dir" arg) (string-match "\\([^ ]+\\) " request))
- (setq dir (server-unquote-arg (match-string 1 request)))
- (setq request (substring request (match-end 0)))
+ ((and (equal "-dir" arg) command-line-args-left)
+ (setq dir (pop command-line-args-left))
(if coding-system
(setq dir (decode-coding-string dir coding-system)))
(setq dir (command-line-normalize-file-name dir)))
;; Unknown command.
- (t (error "Unknown command: %s" arg)))))
-
- (setq frame
- (case tty-name
- ((nil) (if display (server-select-display display)))
- ((window-system)
- (server-create-window-system-frame display nowait proc))
- (t (server-create-tty-frame tty-name tty-type proc))))
-
- (process-put proc 'continuation
- (lexical-let ((proc proc)
- (files files)
- (nowait nowait)
- (commands commands)
- (dontkill dontkill)
- (frame frame)
- (tty-name tty-name))
- (lambda ()
- (server-execute proc files nowait commands
- dontkill frame tty-name))))
+ (t (error "Unknown command: %s" arg))))
+
+ (setq frame
+ (cond
+ ((and use-current-frame
+ (or (eq use-current-frame 'always)
+ ;; We can't use the Emacs daemon's
+ ;; terminal frame.
+ (not (and (daemonp)
+ (null (cdr (frame-list)))
+ (eq (selected-frame)
+ terminal-frame)))))
+ (setq tty-name nil tty-type nil)
+ (if display (server-select-display display)))
+ ((eq tty-name 'window-system)
+ (server-create-window-system-frame display nowait proc))
+ ;; When resuming on a tty, tty-name is nil.
+ (tty-name
+ (server-create-tty-frame tty-name tty-type proc))))
+
+ (process-put
+ proc 'continuation
+ (lexical-let ((proc proc)
+ (files files)
+ (nowait nowait)
+ (commands commands)
+ (dontkill dontkill)
+ (frame frame)
+ (dir dir)
+ (tty-name tty-name))
+ (lambda ()
+ (with-current-buffer (get-buffer-create server-buffer)
+ ;; Use the same cwd as the emacsclient, if possible, so
+ ;; relative file names work correctly, even in `eval'.
+ (let ((default-directory
+ (if (and dir (file-directory-p dir))
+ dir default-directory)))
+ (server-execute proc files nowait commands
+ dontkill frame tty-name))))))
(when (or frame files)
(server-goto-toplevel proc))
(error (server-return-error proc err))))
(defun server-execute (proc files nowait commands dontkill frame tty-name)
- (condition-case err
- (let* ((buffers
- (when files
- (run-hooks 'pre-command-hook)
- (prog1 (server-visit-files files proc nowait)
- (run-hooks 'post-command-hook)))))
-
- (mapc 'funcall (nreverse commands))
-
- ;; Delete the client if necessary.
- (cond
- (nowait
- ;; Client requested nowait; return immediately.
- (server-log "Close nowait client" proc)
- (server-delete-client proc))
- ((and (not dontkill) (null buffers))
- ;; This client is empty; get rid of it immediately.
- (server-log "Close empty client" proc)
- (server-delete-client proc)))
- (cond
- ((or isearch-mode (minibufferp))
- nil)
- ((and frame (null buffers))
- (message "%s" (substitute-command-keys
- "When done with this frame, type \\[delete-frame]")))
- ((not (null buffers))
- (server-switch-buffer (car buffers))
- (run-hooks 'server-switch-hook)
- (unless nowait
+ ;; This is run from timers and process-filters, i.e. "asynchronously".
+ ;; But w.r.t the user, this is not really asynchronous since the timer
+ ;; is run after 0s and the process-filter is run in response to the
+ ;; user running `emacsclient'. So it is OK to override the
+ ;; inhibit-quit flag, which is good since `commands' (as well as
+ ;; find-file-noselect via the major-mode) can run arbitrary code,
+ ;; including code that needs to wait.
+ (with-local-quit
+ (condition-case err
+ (let* ((buffers
+ (when files
+ (run-hooks 'pre-command-hook)
+ (prog1 (server-visit-files files proc nowait)
+ (run-hooks 'post-command-hook)))))
+
+ (mapc 'funcall (nreverse commands))
+
+ ;; Delete the client if necessary.
+ (cond
+ (nowait
+ ;; Client requested nowait; return immediately.
+ (server-log "Close nowait client" proc)
+ (server-delete-client proc))
+ ((and (not dontkill) (null buffers))
+ ;; This client is empty; get rid of it immediately.
+ (server-log "Close empty client" proc)
+ (server-delete-client proc)))
+ (cond
+ ((or isearch-mode (minibufferp))
+ nil)
+ ((and frame (null buffers))
(message "%s" (substitute-command-keys
- "When done with a buffer, type \\[server-edit]")))))
- (when (and frame (null tty-name))
- (server-unselect-display frame)))
- (error (server-return-error proc err))))
+ "When done with this frame, type \\[delete-frame]")))
+ ((not (null buffers))
+ (server-switch-buffer (car buffers) nil (cdr (car files)))
+ (run-hooks 'server-switch-hook)
+ (unless nowait
+ (message "%s" (substitute-command-keys
+ "When done with a buffer, type \\[server-edit]")))))
+ (when (and frame (null tty-name))
+ (server-unselect-display frame)))
+ (error (server-return-error proc err)))))
(defun server-return-error (proc err)
(ignore-errors
(server-log (error-message-string err) proc)
(delete-process proc)))
-(defun server-goto-line-column (file-line-col)
- "Move point to the position indicated in FILE-LINE-COL.
-FILE-LINE-COL should be a three-element list as described in
-`server-visit-files'."
- (goto-line (nth 1 file-line-col))
- (let ((column-number (nth 2 file-line-col)))
- (if (> column-number 0)
- (move-to-column (1- column-number)))))
+(defun server-goto-line-column (line-col)
+ "Move point to the position indicated in LINE-COL.
+LINE-COL should be a pair (LINE . COL)."
+ (when line-col
+ (goto-line (car line-col))
+ (let ((column-number (cdr line-col)))
+ (when (> column-number 0)
+ (move-to-column (1- column-number))))))
(defun server-visit-files (files proc &optional nowait)
"Find FILES and return a list of buffers created.
-FILES is an alist whose elements are (FILENAME LINENUMBER COLUMNNUMBER).
+FILES is an alist whose elements are (FILENAME . FILEPOS)
+where FILEPOS can be nil or a pair (LINENUMBER . COLUMNNUMBER).
PROC is the client that requested this operation.
NOWAIT non-nil means this client is not waiting for the results,
so don't mark these buffers specially, just visit them normally."
(filen (car file))
(obuf (get-file-buffer filen)))
(add-to-history 'file-name-history filen)
- (if (and obuf (set-buffer obuf))
- (progn
- (cond ((file-exists-p filen)
- (when (not (verify-visited-file-modtime obuf))
- (revert-buffer t nil)))
- (t
- (when (y-or-n-p
- (concat "File no longer exists: " filen
- ", write buffer to file? "))
- (write-file filen))))
- (unless server-buffer-clients
- (setq server-existing-buffer t))
- (server-goto-line-column file))
- (set-buffer (find-file-noselect filen))
- (server-goto-line-column file)
- (run-hooks 'server-visit-hook)))
+ (if (null obuf)
+ (set-buffer (find-file-noselect filen))
+ (set-buffer obuf)
+ (cond ((file-exists-p filen)
+ (when (not (verify-visited-file-modtime obuf))
+ (revert-buffer t nil)))
+ (t
+ (when (y-or-n-p
+ (concat "File no longer exists: " filen
+ ", write buffer to file? "))
+ (write-file filen))))
+ (unless server-buffer-clients
+ (setq server-existing-buffer t)))
+ (server-goto-line-column (cdr file))
+ (run-hooks 'server-visit-hook))
(unless nowait
;; When the buffer is killed, inform the clients.
(add-hook 'kill-buffer-hook 'server-kill-buffer nil t)
;; tell it that it is done, and forget it entirely.
(unless buffers
(server-log "Close" proc)
- (server-delete-client proc)))))
+ (if for-killing
+ ;; `server-delete-client' might delete the client's
+ ;; frames, which might change the current buffer. We
+ ;; don't want that (bug#640).
+ (save-current-buffer
+ (server-delete-client proc))
+ (server-delete-client proc))))))
(when (and (bufferp buffer) (buffer-name buffer))
;; We may or may not kill this buffer;
;; if we do, do not call server-buffer-done recursively
The variable `server-temp-file-regexp' controls which filenames
are considered temporary."
(and (buffer-file-name buffer)
- (string-match server-temp-file-regexp (buffer-file-name buffer))))
+ (string-match-p server-temp-file-regexp (buffer-file-name buffer))))
(defun server-done ()
"Offer to save current buffer, mark it as \"done\" for clients.
(buffer-name (current-buffer))))))
(defun server-kill-emacs-query-function ()
- "Ask before exiting Emacs it has live clients."
+ "Ask before exiting Emacs if it has live clients."
(or (not server-clients)
(let (live-client)
(dolist (proc server-clients live-client)
(server-clients (apply 'server-switch-buffer (server-done)))
(t (message "No server editing buffers exist"))))
-(defun server-switch-buffer (&optional next-buffer killed-one)
+(defun server-switch-buffer (&optional next-buffer killed-one filepos)
"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."
+done that.
+
+FILEPOS specifies a new buffer position for NEXT-BUFFER, if we
+visit NEXT-BUFFER in an existing window. If non-nil, it should
+be a cons cell (LINENUMBER . COLUMNNUMBER)."
(if (null next-buffer)
(progn
(let ((rest server-clients))
(while (and rest (not next-buffer))
(let ((proc (car rest)))
- ;; Only look at frameless clients.
- (when (not (process-get proc 'frame))
+ ;; Only look at frameless clients, or those in the selected
+ ;; frame.
+ (when (or (not (process-get proc 'frame))
+ (eq (process-get proc 'frame) (selected-frame)))
(setq next-buffer (car (process-get proc 'buffers))))
(setq rest (cdr rest)))))
(and next-buffer (server-switch-buffer next-buffer killed-one))
(funcall server-window next-buffer)
(let ((win (get-buffer-window next-buffer 0)))
(if (and win (not server-window))
- ;; The buffer is already displayed: just reuse the window.
+ ;; The buffer is already displayed: just reuse the
+ ;; window. If FILEPOS is non-nil, use it to replace the
+ ;; window's own value of point.
(progn
(select-window win)
- (set-buffer next-buffer))
+ (set-buffer next-buffer)
+ (when filepos
+ (server-goto-line-column filepos)))
;; Otherwise, let's find an appropriate window.
(cond ((window-live-p server-window)
(select-window server-window))
(select-frame-set-input-focus (window-frame (selected-window))))))
;;;###autoload
-(defun server-save-buffers-kill-terminal (proc &optional arg)
- "Offer to save each buffer, then kill PROC.
-
-With prefix arg, silently save all file-visiting buffers, then kill.
+(defun server-save-buffers-kill-terminal (arg)
+ ;; Called from save-buffers-kill-terminal in files.el.
+ "Offer to save each buffer, then kill the current client.
+With ARG non-nil, silently save all file-visiting buffers, then kill.
If emacsclient was started with a list of filenames to edit, then
only these files will be asked to be saved."
- (let ((buffers (process-get proc 'buffers)))
- ;; If client is bufferless, emulate a normal Emacs session
- ;; exit and offer to save all buffers. Otherwise, offer to
- ;; save only the buffers belonging to the client.
- (save-some-buffers arg
- (if buffers
- (lambda () (memq (current-buffer) buffers))
- t))
- (server-delete-client proc)))
+ (let ((proc (frame-parameter (selected-frame) 'client)))
+ (cond ((eq proc 'nowait)
+ ;; Nowait frames have no client buffer list.
+ (if (cdr (frame-list))
+ (progn (save-some-buffers arg)
+ (delete-frame))
+ ;; If we're the last frame standing, kill Emacs.
+ (save-buffers-kill-emacs arg)))
+ ((processp proc)
+ (let ((buffers (process-get proc 'buffers)))
+ ;; If client is bufferless, emulate a normal Emacs exit
+ ;; and offer to save all buffers. Otherwise, offer to
+ ;; save only the buffers belonging to the client.
+ (save-some-buffers
+ arg (if buffers
+ (lambda () (memq (current-buffer) buffers))
+ t))
+ (server-delete-client proc)))
+ (t (error "Invalid client frame")))))
(define-key ctl-x-map "#" 'server-edit)
-(defun server-unload-hook ()
+(defun server-unload-function ()
"Unload the server library."
(server-mode -1)
- (remove-hook 'suspend-tty-functions 'server-handle-suspend-tty)
- (remove-hook 'delete-frame-functions 'server-handle-delete-frame)
- (remove-hook 'kill-buffer-query-functions 'server-kill-buffer-query-function)
- (remove-hook 'kill-emacs-query-functions 'server-kill-emacs-query-function)
- (remove-hook 'kill-buffer-hook 'server-kill-buffer))
-
-(add-hook 'kill-emacs-hook (lambda () (server-mode -1))) ;Cleanup upon exit.
-(add-hook 'server-unload-hook 'server-unload-hook)
+ (substitute-key-definition 'server-edit nil ctl-x-map)
+ (save-current-buffer
+ (dolist (buffer (buffer-list))
+ (set-buffer buffer)
+ (remove-hook 'kill-buffer-hook 'server-kill-buffer t)))
+ ;; continue standard unloading
+ nil)
+
\f
(provide 'server)