 (defconst edit-server-edit-buffer-name "TEXTAREA"
   "Template name of the edit-server text editing buffers.")
 
-(defvar edit-server-proc 'nil
-  "Network process associated with the current edit, made local when
- the edit buffer is created")
-
-(defvar edit-server-frame 'nil
-  "The frame created for a new edit-server process, made local when
- then edit buffer is created")
-
-(defvar edit-server-clients '() 
+(defvar edit-server-clients ()
   "List of all client processes associated with the server process.")
 
-(defvar edit-server-phase nil 
+;; Buffer local variables
+;;
+;; These are all required to associate the edit buffer with the
+;; correct connection to the client and allow for the buffer to be sent
+;; back when ready. They are `permanent-local` to avoid being reset if
+;; the buffer changes major modes.
+
+(defvar edit-server-proc nil
+  "Network process associated with the current edit.")
+(make-variable-buffer-local 'edit-server-proc)
+(put 'edit-server-proc 'permanent-local t)
+
+(defvar edit-server-frame nil
+  "The frame created for a new edit-server process.")
+(make-variable-buffer-local 'edit-server-frame)
+(put 'edit-server-frame 'permanent-local t)
+
+(defvar edit-server-phase nil
   "Symbol indicating the state of the HTTP request parsing.")
-
-(defvar edit-server-received nil 
-  "Number of bytes received so far in the client buffer. 
+(make-variable-buffer-local 'edit-server-phase)
+(put 'edit-server-phase 'permanent-local t)
+
+(defvar edit-server-received nil
+  "Number of bytes received so far in the client buffer.
 Depending on the character encoding, may be different from the buffer length.")
-
-(defvar edit-server-request nil 
+(make-variable-buffer-local 'edit-server-received)
+(put 'edit-server-received 'permanent-local t)
+
+(defvar edit-server-request nil
   "The HTTP request (GET, HEAD, POST) received.")
-
-(defvar edit-server-content-length nil 
+(make-variable-buffer-local 'edit-server-request)
+(put 'edit-server-request 'permanent-local t)
+
+(defvar edit-server-content-length nil
   "The value gotten from the HTTP `Content-Length' header.")
-
-(defvar edit-server-url nil 
+(make-variable-buffer-local 'edit-server-content-length)
+(put 'edit-server-content-length 'permanent-local t)
+
+(defvar edit-server-url nil
   "The value gotten from the HTTP `x-url' header.")
-
-;; Mode magic
-;
-; We want to re-map some of the keys to trigger edit-server-done
-; instead of the usual emacs like behaviour. However using
-; local-set-key will affect all buffers of the same mode, hence we
-; define a special (derived) mode for handling editing of text areas.
-;
-
-(define-derived-mode edit-server-text-mode text-mode "Edit Server Text Mode"
-  "A derived version of text-mode with a few common Emacs keystrokes
-rebound to more functions that can deal with the response to the
-edit-server request.
+(make-variable-buffer-local 'edit-server-url)
+(put 'edit-server-url 'permanent-local t)
+
+;;; Mode magic
+;;
+;; We want to re-map some of the keys to trigger edit-server-done
+;; instead of the usual emacs like behaviour. However using
+;; local-set-key will affect all buffers of the same mode, hence we
+;; define a special (derived) mode for handling editing of text areas.
+
+
+(defvar edit-server-edit-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "C-x C-s") 'edit-server-save)
+    (define-key map (kbd "C-x #")   'edit-server-done)
+    (define-key map (kbd "C-c C-c") 'edit-server-done)
+    (define-key map (kbd "C-x C-c") 'edit-server-abort)
+    map)
+  "Keymap for minor mode `edit-server-edit-mode'.
+
+Redefine a few common Emacs keystrokes to functions that can
+deal with the response to the edit-server request.
 
 Any of the following keys will close the buffer and send the text
-to the HTTP client: C-x #, C-x C-s, C-c C-c.
+to the HTTP client: C-x #, C-c C-c.
+
+Pressing C-x C-s will save the current state to the kill-ring.
 
 If any of the above isused with a prefix argument, the
-unmodified text is sent back instead.
-"
-  :group 'edit-server)
-(define-key edit-server-text-mode-map (kbd "C-x #") 'edit-server-done)
-(define-key edit-server-text-mode-map (kbd "C-x C-s") 'edit-server-done)
-(define-key edit-server-text-mode-map (kbd "C-c C-c") 'edit-server-done)
-(define-key edit-server-text-mode-map (kbd "C-x C-c") 'edit-server-abort)
+unmodified text is sent back instead.")
+
+(define-minor-mode edit-server-edit-mode
+  "Minor mode enabled on buffers opened by `edit-server-accept'.
+
+Its sole purpose is currently to enable
+`edit-server-edit-mode-map', which overrides common keystrokes to
+send a response back to the client."
+  :group 'edit-server
+  :lighter " EditSrv"
+  :init-value nil
+  :keymap edit-server-edit-mode-map)
 
 
 ;; Edit Server socket code
-;
-
-(defun edit-server-start (&optional verbose) 
+
+(defun edit-server-start (&optional verbose)
   "Start the edit server.
 
-If argument VERBOSE is non-nil, logs all server activity to buffer `*edit-server-log*'.
-When called interactivity, a prefix argument will cause it to be verbose.
-"
+If argument VERBOSE is non-nil, logs all server activity to buffer
+`*edit-server-log*'.  When called interactivity, a prefix argument
+will cause it to be verbose."
   (interactive "P")
   (if (or (process-status "edit-server")
-          (null (condition-case err
-                    (make-network-process
-                     :name "edit-server"
-                     :buffer edit-server-process-buffer-name
-                     :family 'ipv4
-                     :host (if edit-server-host
-                               edit-server-host
-                             'local)
-                     :service edit-server-port
-                     :log 'edit-server-accept
-                     :server t
-                     :noquery t)
-                  (file-error nil))))
+	  (null (condition-case err
+		    (make-network-process
+		     :name "edit-server"
+		     :buffer edit-server-process-buffer-name
+		     :family 'ipv4
+		     :host (or edit-server-host 'local)
+		     :service edit-server-port
+		     :log 'edit-server-accept
+		     :server t
+		     :noquery t)
+		  (file-error nil))))
       (message "An edit-server process is already running")
     (setq edit-server-clients '())
     (if verbose (get-buffer-create edit-server-log-buffer-name))

  ;; requires us to keep track of the processing state.
  (with-current-buffer (process-buffer proc)
    (insert string)
    (setq edit-server-received 
          (+ edit-server-received (string-bytes string)))
    (when (eq edit-server-phase 'wait)
      ;; look for a complete HTTP request string
      (save-excursion
        (goto-char (point-min))
        (when (re-search-forward "^\\([A-Z]+\\)\\s-+\\(\\S-+\\)\\s-+\\(HTTP/[0-9\.]+\\)\r?\n" nil t)
          (edit-server-log proc 
                           "Got HTTP `%s' request, processing in buffer `%s'..." 
                           (match-string 1) (current-buffer))
          (setq edit-server-request (match-string 1))
          (setq edit-server-content-length nil)
          (setq edit-server-phase 'head))))
    
    (when (eq edit-server-phase 'head)
      ;; look for "Content-length" header
      (save-excursion
        (goto-char (point-min))
        (when (re-search-forward "^Content-Length:\\s-+\\([0-9]+\\)" nil t)
          (setq edit-server-content-length (string-to-number (match-string 1)))))
      ;; look for "x-url" header
      (save-excursion
        (goto-char (point-min))
        (when (re-search-forward "^x-url: .*//\\(.*\\)/" nil t)
          (setq edit-server-url (match-string 1))))
      ;; look for head/body separator
      (save-excursion
        (goto-char (point-min))
        (when (re-search-forward "\\(\r?\n\\)\\{2\\}" nil t)
          ;; HTTP headers are pure ASCII (1 char = 1 byte), so we can subtract
          ;; the buffer position from the count of received bytes
          (setq edit-server-received
                (- edit-server-received (- (match-end 0) (point-min))))
          ;; discard headers - keep only HTTP content in buffer
          (delete-region (point-min) (match-end 0))
          (setq edit-server-phase 'body))))
    
    (when (eq edit-server-phase 'body)
      (if (and edit-server-content-length
               (> edit-server-content-length edit-server-received))
          (edit-server-log proc 
                           "Received %d bytes of %d ..." 
                           edit-server-received edit-server-content-length)
        ;; all content transferred - process request now
        (cond
         ((string= edit-server-request "POST")
          ;; create editing buffer, and move content to it
          (edit-server-create-edit-buffer proc))
         (t
          ;; send 200 OK response to any other request
          (edit-server-send-response proc "edit-server is running.\n" t)))
        ;; wait for another connection to arrive
        (setq edit-server-received 0)
        (setq edit-server-phase 'wait)))))

(defun edit-server-create-frame(buffer)
  "Create a frame for the edit server"
  (if edit-server-new-frame
      (let ((new-frame
	     (if (featurep 'aquamacs)
                 (make-frame edit-server-new-frame-alist)
               (make-frame-on-display (getenv "DISPLAY")
                                      edit-server-new-frame-alist))))
	(if (not edit-server-new-frame-mode-line)
            (setq mode-line-format nil))
	(select-frame new-frame)
	(if (and (eq window-system 'x)
		 (fboundp 'x-send-client-message))
	    (x-send-client-message nil 0 nil
				   "_NET_ACTIVE_WINDOW" 32
				   '(1 0 0)))
	(raise-frame new-frame)
        (set-window-buffer (frame-selected-window new-frame) buffer)
	new-frame)
    (pop-to-buffer buffer)
    nil))

(defun edit-server-create-edit-buffer(proc)
  "Create an edit buffer, place content in it and save the network
  process for the final call back"
  (let ((buffer (generate-new-buffer (if edit-server-url
					 edit-server-url
				       edit-server-edit-buffer-name))))
    (with-current-buffer buffer
      (and (fboundp 'set-buffer-multibyte)
           (set-buffer-multibyte t))) ; djb
    (copy-to-buffer buffer (point-min) (point-max))
    (with-current-buffer buffer
      (not-modified)
      (edit-server-text-mode)
      (add-hook 'kill-buffer-hook 'edit-server-abort* nil t)
      (buffer-enable-undo)
      (set (make-local-variable 'edit-server-proc) proc)
      (set (make-local-variable 'edit-server-frame)
	   (edit-server-create-frame buffer))
      (run-hooks 'edit-server-start-hook))))

(defun edit-server-send-response (proc &optional body close)
  "Send an HTTP 200 OK response back to process PROC.
Optional second argument BODY specifies the response content:
  - If nil, the HTTP response will have null content.
  - If a string, the string is sent as response content.
  - Any other value will cause the contents of the current 
    buffer to be sent.
If optional third argument CLOSE is non-nil, then process PROC
and its buffer are killed with `edit-server-kill-client'."
  (interactive)
  (if (processp proc)
      (let ((response-header (concat
                          "HTTP/1.0 200 OK\n"
                          (format "Server: Emacs/%s\n" emacs-version)
                          "Date: "
                          (format-time-string
                           "%a, %d %b %Y %H:%M:%S GMT\n"
                           (current-time)))))
        (process-send-string proc response-header)
        (process-send-string proc "\n")
        (cond
         ((stringp body) (process-send-string proc body))
         ((not body) nil)
         (t (process-send-region proc (point-min) (point-max))))
        (process-send-eof proc)
        (if close 
            (edit-server-kill-client proc))
        (edit-server-log proc "Editing done, sent HTTP OK response."))
    (message "edit-server-send-response: invalid proc (bug?)")))

(defun edit-server-kill-client (proc)

;; To open pages for editing in new buffers in your existing Emacs
;; instance:
;;
;;   (when (require 'edit-server nil t)
;;     (setq edit-server-new-frame nil)
;;     (edit-server-start))
;;
;; To open pages for editing in new frames using a running emacs
;; started in --daemon mode:
;;
;;   (when (and (require 'edit-server nil t) (daemonp))
;;     (edit-server-start))
;;
;; Buffers are edited in `text-mode' by default; to use a different
;; major mode, change `edit-server-default-major-mode' or customize
;; `edit-server-url-major-mode-alist' to specify major modes based
;; on the remote URL:
;;
;;   (setq edit-server-url-major-mode-alist
;;         '(("github\\.com" . markdown-mode)))
;;
;; Alternatively, set the mode in `edit-server-start-hook'.  For
;; example:
;;
;; (add-hook 'edit-server-start-hook
;;           (lambda ()
;;             (when (string-match "github.com" (buffer-name))
;; 	      (markdown-mode))))


;;; Code:

;; uncomment to debug
;; (setq debug-on-error t)
;; (setq edebug-all-defs t)

(unless (featurep 'make-network-process)
  (error "Incompatible version of [X]Emacs - lacks make-network-process"))

;;; Customization

(defcustom edit-server-port 9292
  "Local port the edit server listens to."
  :group 'edit-server
