                        (emdocs-disconnect))
               (with-current-buffer buffer
                 (setq-local emdocs-is-network-insert nil)
-                (setq-local emdocs-after-change-lambda
-                            (lambda (beg end prev-length)
-                              (with-current-buffer buffer
-                                (emdocs-after-change-function
-                                 buffer beg end prev-length))))
-                (setq-local emdocs-before-change-lambda
-                            (lambda (beg end)
-                              (with-current-buffer buffer
-                                (emdocs-before-change-function
-                                 buffer beg end))))
                 (setq-local after-change-functions
-                            (cons emdocs-after-change-lambda
+                            (cons #'emdocs-after-change-function
                                   after-change-functions))
                 (setq-local before-change-functions
-                            (cons emdocs-before-change-lambda
+                            (cons #'emdocs-before-change-function
                                   before-change-functions))
                 (setq-local emdocs-undo-list nil)
                 ;; TODO: add removable lambda as with after-change-functions

           :content content))
    "\n")))

(defun emdocs-after-change-function (buffer beg end prev-length)
  "docstring"
  (with-current-buffer (if (bufferp buffer)
                           (buffer-name buffer)
                         buffer)
    (when (= prev-length 0)
      (setq-local emdocs-undo-list
                  (cons (list :type "insert" :point beg :end end
                              :content (buffer-substring-no-properties beg end)
                              :local (not emdocs-is-network-insert))
                        emdocs-undo-list)))
    (when (and (boundp 'emdocs-is-network-insert)
               (not emdocs-is-network-insert))
        (cond ((= prev-length 0)
               (emdocs-emit-keypress-json
                buffer "insert" beg
                (buffer-substring-no-properties beg end)))
              ((= beg end)
               (emdocs-emit-keypress-json
                buffer "delete" beg prev-length))))
    (unless emdocs-is-network-insert
        (setq-local emdocs-undo-posn emdocs-undo-list))))

(defun emdocs-before-change-function (buffer beg end)
  "docstring"
  (with-current-buffer buffer
    (when (/= beg end)
      (setq-local emdocs-undo-list
                  (cons (list :type "delete" :point beg :end end
                              :content (buffer-substring-no-properties beg end)
                              :local (not emdocs-is-network-insert))
                        emdocs-undo-list)))))

(defun emdocs-parse-undo (undo-action-list remote-edits)
  "docstring"
  ;; remote-edits must be in chronological order from head of list!
  ;; this is OPPOSITE the order they are in emdocs-undo-list!
  (if undo-action-list
      (save-excursion
        (cond
         ((string-equal (plist-get (car undo-action-list) :type) "delete")
          (loop
           for edit in remote-edits
           do (cond
               ((string-equal (plist-get edit :type) "insert")
                (when (<= (plist-get edit :point)
                          (plist-get (car undo-action-list) :point))
                  (plist-put (car undo-action-list) :point
                             (+ (plist-get (car undo-action-list) :point)
                                (length (plist-get edit :content))))))
               ((string-equal (plist-get edit :type) "delete")
                (when (<= (plist-get edit :point)
                          (plist-get (car undo-action-list :point)))
                  (if (<= (+ (plist-get edit :point)
                             (length (plist-get edit :content)))
                          (plist-get (car undo-action-list) :point))
                      (plist-put
                       (car undo-action-list) :point
                       (- (plist-get (car undo-action-list) :point)
                          (length (plist-get edit :content))))
                    (plist-put
                     (car undo-action-list) :point
                     (+ (- (plist-get (car undo-action-list) :point)
                           (length (plist-get edit :content)))
                        (- (+ (plist-get edit :point)
                              (length (plist-get edit :content)))
                           (plist-get (car undo-action-list) :point)))))))))
          (goto-char (plist-get (car undo-action-list) :point))
          (insert (plist-get (car undo-action-list) :content)))
         ((string-equal (plist-get (car undo-action-list) :type) "insert")
          (loop
           for edit in remote-edits
           do (cond ((string-equal (plist-get edit :type) "insert")
                     ())
                    ((string-equal (plist-get edit :type) "delete")
                     ())))
          (goto-char (plist-get (car undo-action-list) :point))
          (delete-char (length (plist-get (car undo-action-list) :content))))))
    (message "No more undo information available!")))

(defun emdocs-undo ()
  "docstring"
  (interactive)
  (let ((remote-edits
         (loop while emdocs-undo-posn
               while (not (plist-get emdocs-undo-posn :local))
               collect (car emdocs-undo-posn)
               do (setq-local emdocs-undo-posn (cdr emdocs-undo-posn)))))
    (emdocs-parse-undo emdocs-undo-posn (reverse remote-edits)))
  (setq-local emdocs-undo-posn (cdr emdocs-undo-posn)))

;;; testing language features
;; (loop for num in '(2 3 4)
;;       do (insert (number-to-string num) ",")) ; works; does in order
;; (setq a `(:foo 3))
;; (setf (plist-get a :foo) 4)             ; doesn't work
;; (setq b '(2 3 4 5))
;; (setf (cadr b) 7)                       ; works; can set
;; (defun set-to-3 (arg)
;;   (setf (car arg) 3))
;; (set-to-3 (cdr b))                      ; works; sets appropriately
;; (let* ((list '(2 3 4 5 6 7))
;;        (list-ptr list)
;;        (coll
;;         (loop while list-ptr
;;               while (<= (car list-ptr) 8)
;;               collect (car list-ptr)
;;               do (setq list-ptr (cdr list-ptr)))))
;;   `(,list-ptr ,(reverse coll)))         ; works
;; (loop while nil
;;       collect 3)                        ; works; collect is nil
;; (loop for e in nil
;;       do (insert "hey"))                ; works; no insertion
;; (setq c (list (list :foo "a" :bar "b")
;;               (list :foo "c" :bar "d")))
;; (plist-put (car c) :foo "l")            ; works; destructively modifies c

(defun emdocs-attach-to-buffer (buffer ip)
  "docstring"

          (remove-if #'emdocs-client-dead-p
                     *emdocs-incoming-clients*))
    (setq-local after-change-functions
                (remove #'emdocs-after-change-function
                        after-change-functions))
    (setq-local before-change-functions
                (remove #'emdocs-before-change-function
                        before-change-functions))
    (setq-local emdocs-undo-list nil)))

(defun emdocs-kill-server ()
  "docstring"
