;; 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 gofmt-stdin-tag "<standard input>")
(defconst go-identifier-regexp "[[:word:][:multibyte:]]+")
(defconst go-label-regexp go-identifier-regexp)
(defconst go-type-regexp "[[:word:][:multibyte:]*]+")
;;;###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)))