 ;;;###autoload
 (add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode))
 
+(defun go--apply-rcs-patch (patch-buffer)
+  "Apply an RCS-formatted diff from PATCH-BUFFER to the current
+buffer."
+  (let ((target-buffer (current-buffer))
+        ;; Relative offset between buffer line numbers and line numbers
+        ;; in patch.
+        ;;
+        ;; Line numbers in the patch are based on the source file, so
+        ;; we have to keep an offset when making changes to the
+        ;; buffer.
+        ;;
+        ;; Appending lines decrements the offset (possibly making it
+        ;; negative), deleting lines increments it. This order
+        ;; simplifies the forward-line invocations.
+        (line-offset 0))
+    (save-excursion
+      (with-current-buffer patch-buffer
+        (goto-char (point-min))
+        (while (not (eobp))
+          (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
+            (error "invalid rcs patch or internal error in go--apply-rcs-patch"))
+          (forward-line)
+          (let ((action (match-string 1))
+                (from (string-to-number (match-string 2)))
+                (len  (string-to-number (match-string 3))))
+            (cond
+             ((equal action "a")
+              (let ((start (point)))
+                (forward-line len)
+                (let ((text (buffer-substring start (point))))
+                  (with-current-buffer target-buffer
+                    (decf line-offset len)
+                    (goto-char (point-min))
+                    (forward-line (- from len line-offset))
+                    (insert text)))))
+             ((equal action "d")
+              (with-current-buffer target-buffer
+                (goto-char (point-min))
+                (forward-line (- from line-offset 1))
+                (incf line-offset len)
+                (go--kill-whole-line len)))
+             (t
+              (error "invalid rcs patch or internal error in go--apply-rcs-patch")))))))))
+
 (defun gofmt ()
-  "Pipe the current buffer through the external tool `gofmt`.
-Replace the current buffer on success; display errors on failure."
+  "Formats the current buffer according to the gofmt tool."
 
   (interactive)
-  (let ((currconf (current-window-configuration)))
-    (let ((srcbuf (current-buffer))
-          (filename buffer-file-name)
-          (patchbuf (get-buffer-create "*Gofmt patch*")))
-      (with-current-buffer patchbuf
-        (let ((errbuf (get-buffer-create "*Gofmt Errors*"))
-              ;; use utf-8 with subprocesses
-              (coding-system-for-read 'utf-8)
-              (coding-system-for-write 'utf-8))
-          (with-current-buffer errbuf
-            (setq buffer-read-only nil)
-            (erase-buffer))
-          (with-current-buffer srcbuf
-            (save-restriction
-              (let (deactivate-mark)
-                (widen)
-                ;; If this is a new file, diff-mode can't apply a
-                ;; patch to a non-exisiting file, so replace the buffer
-                ;; completely with the output of 'gofmt'.
-                ;; If the file exists, patch it to keep the 'undo' list happy.
-                (let* ((newfile (not (file-exists-p filename)))
-                       (flag (if newfile "" " -d")))
-
-                  ;; diff-mode doesn't work too well with missing
-                  ;; end-of-file newline, so add one
-                  (if (/= (char-after (1- (point-max))) ?\n)
-                      (save-excursion
-                        (goto-char (point-max))
-                        (insert ?\n)))
-
-                  (if (zerop (shell-command-on-region (point-min) (point-max)
-                                                      (concat "gofmt" flag)
-                                                      patchbuf nil errbuf))
-                      ;; gofmt succeeded: replace buffer or apply patch hunks.
-                      (let ((old-point (point))
-                            (old-mark (mark t)))
-                        (kill-buffer errbuf)
-                        (if newfile
-                            ;; New file, replace it (diff-mode won't work)
-                            (gofmt--replace-buffer srcbuf patchbuf)
-                          ;; Existing file, patch it
-                          (gofmt--apply-patch filename srcbuf patchbuf))
-                        (goto-char (min old-point (point-max)))
-                        ;; Restore the mark and point
-                        (if old-mark (push-mark (min old-mark (point-max)) t))
-                        (set-window-configuration currconf))
-
-                    ;; gofmt failed: display the errors
-                    (message "Could not apply gofmt. Check errors for details")
-                    (gofmt--process-errors filename errbuf))))))
-
-          ;; Collapse any window opened on outbuf if shell-command-on-region
-          ;; displayed it.
-          (delete-windows-on patchbuf)))
-      (kill-buffer patchbuf))))
-
-(defun gofmt--replace-buffer (srcbuf patchbuf)
-  (with-current-buffer srcbuf
-    (erase-buffer)
-    (insert-buffer-substring patchbuf))
-  (message "Applied gofmt"))
-
-(defun gofmt--apply-patch (filename srcbuf patchbuf)
-  ;; apply all the patch hunks
-  (let (changed)
+  (let ((tmpfile (make-temp-file "gofmt" nil ".go"))
+        (patchbuf (get-buffer-create "*Gofmt patch*"))
+        (errbuf (get-buffer-create "*Gofmt Errors*"))
+        (coding-system-for-read 'utf-8)
+        (coding-system-for-write 'utf-8))
+
+    (with-current-buffer errbuf
+      (setq buffer-read-only nil)
+      (erase-buffer))
     (with-current-buffer patchbuf
-      (goto-char (point-min))
-      ;; The .* is for TMPDIR, but to avoid dealing with TMPDIR
-      ;; having a trailing / or not, it's easier to just search for .*
-      ;; especially as we're only replacing the first instance.
-      (if (re-search-forward "^--- \\(.*/gofmt[0-9]*\\)" nil t)
-          (replace-match filename nil nil nil 1))
-      (condition-case nil
-          (while t
-            (diff-hunk-next)
-            (diff-apply-hunk)
-            (setq changed t))
-        ;; When there's no more hunks, diff-hunk-next signals an error, ignore it
-        (error nil)))
-    (if changed (message "Applied gofmt") (message "Buffer was already gofmted"))))
-
-(defun gofmt--process-errors (filename errbuf)
+      (erase-buffer))
+
+    (write-region nil nil tmpfile)
+
+    ;; We're using errbuf for the mixed stdout and stderr output. This
+    ;; is not an issue because gofmt -w does not produce any stdout
+    ;; output in case of success.
+    (if (zerop (call-process "gofmt" nil errbuf nil "-w" tmpfile))
+        (if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
+            (message "Buffer is already gofmted")
+          (go--apply-rcs-patch patchbuf)
+          (kill-buffer errbuf)
+          (message "Applied gofmt"))
+      (message "Could not apply gofmt. Check errors for details")
+      (gofmt--process-errors (buffer-file-name) tmpfile errbuf))
+
+    (kill-buffer patchbuf)
+    (delete-file tmpfile)))
+
+
+(defun gofmt--process-errors (filename tmpfile errbuf)
   ;; Convert the gofmt stderr to something understood by the compilation mode.
   (with-current-buffer errbuf
     (goto-char (point-min))
     (insert "gofmt errors:\n")
-    (if (search-forward gofmt-stdin-tag nil t)
-        (replace-match (file-name-nondirectory filename) nil t))
+    (while (search-forward-regexp (concat "^\\(" (regexp-quote tmpfile) "\\):") nil t)
+      (replace-match (file-name-nondirectory filename) t t nil 1))
     (display-buffer errbuf)
     (compilation-mode)))
 

;; license that can be found in the LICENSE file.

(require 'cl)
(require 'diff-mode)
(require 'ffap)
(require 'url)


    (concat "\\_<" s "\\_>")))

(defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]")
(defconst go-identifier-regexp "[[:word:][:multibyte:]]+")
(defconst go-label-regexp go-identifier-regexp)
(defconst go-type-regexp "[[:word:][:multibyte:]*]+")
