Commit 0c851d78 authored by Stefan Monnier's avatar Stefan Monnier

Use built-in network primitives.

(server-program, server-previous-string): Remove.
(server-previous-strings): New var.
(server-socket-name): New var.
(server-log): Minor change to the output format.
(server-sentinel): Clean up global state when a client disconnects.
(server-unquote-arg): New fun.
(server-start): Use server-socket-name and make-network-process.
(server-process-filter): Now talks to the clients directly.
Normalize file name after unquoting and decoding.
(server-buffer-done): Just close the connection.
(server-switch-buffer): Handle the case where all windows are
dedicated or minibuffers.
parent 7f0d55f2
...@@ -82,30 +82,25 @@ ...@@ -82,30 +82,25 @@
"Emacs running as a server process." "Emacs running as a server process."
:group 'external) :group 'external)
(defcustom server-program (expand-file-name "emacsserver" exec-directory)
"*The program to use as the edit server."
:group 'server
:type 'string)
(defcustom server-visit-hook nil (defcustom server-visit-hook nil
"*List of hooks to call when visiting a file for the Emacs server." "*Hook run when visiting a file for the Emacs server."
:group 'server :group 'server
:type '(repeat function)) :type 'hook)
(defcustom server-switch-hook nil (defcustom server-switch-hook nil
"*List of hooks to call when switching to a buffer for the Emacs server." "*Hook run when switching to a buffer for the Emacs server."
:group 'server :group 'server
:type '(repeat function)) :type 'hook)
(defcustom server-done-hook nil (defcustom server-done-hook nil
"*List of hooks to call when done editing a buffer for the Emacs server." "*Hook run when done editing a buffer for the Emacs server."
:group 'server :group 'server
:type '(repeat function)) :type 'hook)
(defvar server-process nil (defvar server-process nil
"The current server process") "The current server process")
(defvar server-previous-string "") (defvar server-previous-strings nil)
(defvar server-clients nil (defvar server-clients nil
"List of current server clients. "List of current server clients.
...@@ -152,6 +147,13 @@ This means that the server should not kill the buffer when you say you ...@@ -152,6 +147,13 @@ This means that the server should not kill the buffer when you say you
are done with it in the server.") are done with it in the server.")
(make-variable-buffer-local 'server-existing-buffer) (make-variable-buffer-local 'server-existing-buffer)
(defvar server-socket-name
(if (or (not (file-writable-p "~/"))
(and (file-writable-p "/tmp/")
(not (zerop (logand (file-modes "/tmp/") 512)))))
(format "/tmp/esrv%d-%s" (user-uid) (system-name))
(format "~/.emacs-server-%s" (system-name))))
;; If a *server* buffer exists, ;; If a *server* buffer exists,
;; write STRING to it for logging purposes. ;; write STRING to it for logging purposes.
(defun server-log (string &optional client) (defun server-log (string &optional client)
...@@ -159,15 +161,32 @@ are done with it in the server.") ...@@ -159,15 +161,32 @@ are done with it in the server.")
(with-current-buffer "*server*" (with-current-buffer "*server*"
(goto-char (point-max)) (goto-char (point-max))
(insert (current-time-string) (insert (current-time-string)
(if client (format " <%s>: " client) " ") (if client (format " %s:" client) " ")
string) string)
(or (bolp) (newline))))) (or (bolp) (newline)))))
(defun server-sentinel (proc msg) (defun server-sentinel (proc msg)
(cond ((eq (process-status proc) 'exit) ;; Purge server-previous-strings of the now irrelevant entry.
(server-log (message "Server subprocess exited"))) (setq server-previous-strings
((eq (process-status proc) 'signal) (delq (assq proc server-previous-strings) server-previous-strings))
(server-log (message "Server subprocess killed"))))) (let ((ps (assq proc server-clients)))
(dolist (buf (cdr ps))
(with-current-buffer buf
;; Remove PROC from the clients of each buffer.
(setq server-buffer-clients (delq proc server-buffer-clients))))
;; Remove PROC from the list of clients.
(if ps (setq server-clients (delq ps server-clients))))
(server-log (format "Status changed to %s" (process-status proc)) proc))
(defun server-unquote-arg (arg)
(replace-regexp-in-string
"&." (lambda (s)
(case (aref s 1)
(?& "&")
(?- "-")
(?n "\n")
(t " ")))
arg t t))
;;;###autoload ;;;###autoload
(defun server-start (&optional leave-dead) (defun server-start (&optional leave-dead)
...@@ -182,24 +201,7 @@ Prefix arg means just kill any existing server communications subprocess." ...@@ -182,24 +201,7 @@ Prefix arg means just kill any existing server communications subprocess."
;; kill it dead! ;; kill it dead!
(condition-case () (delete-process server-process) (error nil)) (condition-case () (delete-process server-process) (error nil))
;; Delete the socket files made by previous server invocations. ;; Delete the socket files made by previous server invocations.
(let* ((sysname (system-name)) (condition-case () (delete-file server-socket-name) (error nil))
(dot-index (string-match "\\." sysname)))
(condition-case ()
(delete-file (format "~/.emacs-server-%s" sysname))
(error nil))
(condition-case ()
(delete-file (format "/tmp/esrv%d-%s" (user-uid) sysname))
(error nil))
;; In case the server file name was made with a domainless hostname,
;; try deleting that name too.
(if dot-index
(let ((shortname (substring sysname 0 dot-index)))
(condition-case ()
(delete-file (format "~/.emacs-server-%s" shortname))
(error nil))
(condition-case ()
(delete-file (format "/tmp/esrv%d-%s" (user-uid) shortname))
(error nil)))))
;; If this Emacs already had a server, clear out associated status. ;; If this Emacs already had a server, clear out associated status.
(while server-clients (while server-clients
(let ((buffer (nth 1 (car server-clients)))) (let ((buffer (nth 1 (car server-clients))))
...@@ -207,23 +209,29 @@ Prefix arg means just kill any existing server communications subprocess." ...@@ -207,23 +209,29 @@ Prefix arg means just kill any existing server communications subprocess."
(unless leave-dead (unless leave-dead
(if server-process (if server-process
(server-log (message "Restarting server"))) (server-log (message "Restarting server")))
;; Using a pty is wasteful, and the separate session causes (let ((umask (default-file-modes)))
;; annoyance sometimes (some systems kill idle sessions). (unwind-protect
(let ((process-connection-type nil)) (progn
(setq server-process (start-process "server" nil server-program))) (set-default-file-modes ?\700)
(set-process-sentinel server-process 'server-sentinel) (setq server-process
(set-process-filter server-process 'server-process-filter) (make-network-process
;; We must receive file names without being decoded. Those are :name "server" :family 'local :server t :noquery t
;; decoded by server-process-filter accoding to :service server-socket-name
;; file-name-coding-system. :sentinel 'server-sentinel :filter 'server-process-filter
(set-process-coding-system server-process 'raw-text 'raw-text) ;; We must receive file names without being decoded.
(process-kill-without-query server-process))) ;; Those are decoded by server-process-filter according
;; to file-name-coding-system.
:coding 'raw-text)))
(set-default-file-modes umask)))))
;Process a request from the server to edit some files. ;Process a request from the server to edit some files.
;Format of STRING is "Client: CLIENTID PATH PATH PATH... \n" ;Format of STRING is "PATH PATH PATH... \n"
(defun server-process-filter (proc string) (defun server-process-filter (proc string)
(server-log string) (server-log string proc)
(setq string (concat server-previous-string string)) (let ((ps (assq proc server-previous-strings)))
(when (cdr ps)
(setq string (concat (cdr ps) string))
(setcdr ps nil)))
;; If the input is multiple lines, ;; If the input is multiple lines,
;; process each line individually. ;; process each line individually.
(while (string-match "\n" string) (while (string-match "\n" string)
...@@ -236,70 +244,56 @@ Prefix arg means just kill any existing server communications subprocess." ...@@ -236,70 +244,56 @@ Prefix arg means just kill any existing server communications subprocess."
(lineno 1) (lineno 1)
(columnno 0)) (columnno 0))
;; Remove this line from STRING. ;; Remove this line from STRING.
(setq string (substring string (match-end 0))) (setq string (substring string (match-end 0)))
(if (string-match "^Error: " request) (setq client (cons proc nil))
(message "Server error: %s" (substring request (match-end 0))) (while (string-match "[^ ]* " request)
(if (string-match "^Client: " request) (let ((arg (substring request (match-beginning 0) (1- (match-end 0))))
(progn (pos 0))
(setq request (substring request (match-end 0))) (setq request (substring request (match-end 0)))
(setq client (list (substring request 0 (string-match " " request)))) (cond
(setq request (substring request (match-end 0))) ((equal "-nowait" arg) (setq nowait t))
(while (string-match "[^ ]+ " request) ;; ARG is a line number option.
(let ((arg ((string-match "\\`\\+[0-9]+\\'" arg)
(substring request (match-beginning 0) (1- (match-end 0)))) (setq lineno (string-to-int (substring arg 1))))
(pos 0)) ;; ARG is line number:column option.
(setq request (substring request (match-end 0))) ((string-match "\\`+\\([0-9]+\\):\\([0-9]+\\)\\'" arg)
(cond (setq lineno (string-to-int (match-string 1 arg))
((string-match "\\`-nowait" arg) columnno (string-to-int (match-string 2 arg))))
(setq nowait t)) (t
;; ARG is a line number option. ;; Undo the quoting that emacsclient does
((string-match "\\`\\+[0-9]+\\'" arg) ;; for certain special characters.
(setq lineno (string-to-int (substring arg 1)))) (setq arg (server-unquote-arg arg))
;; ARG is line number:column option. ;; Now decode the file name if necessary.
((string-match "\\`+\\([0-9]+\\):\\([0-9]+\\)\\'" arg) (if coding-system
(setq lineno (string-to-int (match-string 1 arg)) (setq arg (decode-coding-string arg coding-system)))
columnno (string-to-int (match-string 2 arg)))) ;; ARG is a file name.
(t ;; Collapse multiple slashes to single slashes.
;; ARG is a file name. (setq arg (command-line-normalize-file-name arg))
;; Collapse multiple slashes to single slashes. (push (list arg lineno columnno) files)
(setq arg (command-line-normalize-file-name arg)) (setq lineno 1)
;; Undo the quoting that emacsclient does (setq columnno 0)))))
;; for certain special characters. (when files
(setq arg (run-hooks 'pre-command-hook)
(replace-regexp-in-string (server-visit-files files client nowait)
"&." (lambda (s) (run-hooks 'post-command-hook))
(case (aref s 1) ;; CLIENT is now a list (CLIENTNUM BUFFERS...)
(?& "&") (if (null (cdr client))
(?- "-") ;; This client is empty; get rid of it immediately.
(?n "\n") (progn
(t " "))) (delete-process proc)
arg t t)) (server-log "Close empty client" proc))
;; Now decode the file name if necessary. ;; We visited some buffer for this client.
(if coding-system (or nowait (push client server-clients))
(setq arg (decode-coding-string arg coding-system))) (server-switch-buffer (nth 1 client))
(push (list arg lineno columnno) files) (run-hooks 'server-switch-hook)
(setq lineno 1) (unless nowait
(setq columnno 0))))) (message (substitute-command-keys
(when files "When done with a buffer, type \\[server-edit]"))))))
(run-hooks 'pre-command-hook)
(server-visit-files files client nowait)
(run-hooks 'post-command-hook))
;; CLIENT is now a list (CLIENTNUM BUFFERS...)
(if (null (cdr client))
;; This client is empty; get rid of it immediately.
(progn
(send-string server-process
(format "Close: %s Done\n" (car client)))
(server-log "Close empty client" (car client)))
;; We visited some buffer for this client.
(or nowait (push client server-clients))
(server-switch-buffer (nth 1 client))
(run-hooks 'server-switch-hook)
(unless nowait
(message (substitute-command-keys
"When done with a buffer, type \\[server-edit]")))))))))
;; Save for later any partial line that remains. ;; Save for later any partial line that remains.
(setq server-previous-string string)) (when (> (length string) 0)
(let ((ps (assq proc server-previous-strings)))
(if ps (setcdr ps string)
(push (cons proc string) server-previous-strings)))))
(defun server-goto-line-column (file-line-col) (defun server-goto-line-column (file-line-col)
(goto-line (nth 1 file-line-col)) (goto-line (nth 1 file-line-col))
...@@ -356,10 +350,8 @@ NEXT-BUFFER is another server buffer, as a suggestion for what to select next, ...@@ -356,10 +350,8 @@ NEXT-BUFFER is another server buffer, as a suggestion for what to select next,
or nil. KILLED is t if we killed BUFFER (typically, because it was visiting or nil. KILLED is t if we killed BUFFER (typically, because it was visiting
a temp file). a temp file).
FOR-KILLING if non-nil indicates that we are called from `kill-buffer'." FOR-KILLING if non-nil indicates that we are called from `kill-buffer'."
(let ((running (eq (process-status server-process) 'run)) (let ((next-buffer nil)
(next-buffer nil)
(killed nil) (killed nil)
(first t)
(old-clients server-clients)) (old-clients server-clients))
(while old-clients (while old-clients
(let ((client (car old-clients))) (let ((client (car old-clients)))
...@@ -375,16 +367,9 @@ FOR-KILLING if non-nil indicates that we are called from `kill-buffer'." ...@@ -375,16 +367,9 @@ FOR-KILLING if non-nil indicates that we are called from `kill-buffer'."
(setq tail (cdr tail)))) (setq tail (cdr tail))))
;; If client now has no pending buffers, ;; If client now has no pending buffers,
;; tell it that it is done, and forget it entirely. ;; tell it that it is done, and forget it entirely.
(if (cdr client) nil (unless (cdr client)
(if running (delete-process (car client))
(progn (server-log "Close" (car client))
;; Don't send emacsserver two commands in close succession.
;; It cannot handle that.
(or first (sit-for 1))
(setq first nil)
(send-string server-process
(format "Close: %s Done\n" (car client)))
(server-log "Close" (car client))))
(setq server-clients (delq client server-clients)))) (setq server-clients (delq client server-clients))))
(setq old-clients (cdr old-clients))) (setq old-clients (cdr old-clients)))
(if (and (bufferp buffer) (buffer-name buffer)) (if (and (bufferp buffer) (buffer-name buffer))
...@@ -519,8 +504,7 @@ Arg NEXT-BUFFER is a suggestion; if it is a live buffer, use it." ...@@ -519,8 +504,7 @@ Arg NEXT-BUFFER is a suggestion; if it is a live buffer, use it."
(if (null next-buffer) (if (null next-buffer)
(if server-clients (if server-clients
(server-switch-buffer (nth 1 (car server-clients)) killed-one) (server-switch-buffer (nth 1 (car server-clients)) killed-one)
(unless (or killed-one (unless (or killed-one (window-dedicated-p (selected-window)))
(window-dedicated-p (selected-window)))
(switch-to-buffer (other-buffer)))) (switch-to-buffer (other-buffer))))
(if (not (buffer-name next-buffer)) (if (not (buffer-name next-buffer))
;; If NEXT-BUFFER is a dead buffer, remove the server records for it ;; If NEXT-BUFFER is a dead buffer, remove the server records for it
...@@ -550,8 +534,11 @@ Arg NEXT-BUFFER is a suggestion; if it is a live buffer, use it." ...@@ -550,8 +534,11 @@ Arg NEXT-BUFFER is a suggestion; if it is a live buffer, use it."
(select-window (get-window-with-predicate (select-window (get-window-with-predicate
(lambda (w) (not (window-dedicated-p w))) (lambda (w) (not (window-dedicated-p w)))
'nomini 'visible (selected-window)))) 'nomini 'visible (selected-window))))
(set-window-dedicated-p (selected-window) nil) (condition-case nil
(switch-to-buffer next-buffer)))))) (switch-to-buffer next-buffer)
;; After all the above, we might still have ended up with
;; a minibuffer/dedicated-window (if there's no other).
(error (pop-to-buffer next-buffer))))))))
(global-set-key "\C-x#" 'server-edit) (global-set-key "\C-x#" 'server-edit)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment