mirror of
https://github.com/golang/go
synced 2024-11-12 09:00:22 -07:00
misc/emacs: Rewrite gofmt to use own function for applying patch instead of using diff-mode.
Instead of relying on gofmt's diff output (which is a unified diff), we manually invoke diff -n and produce an RCS format diff, which can easily be parsed in Emacs, with the go--apply-rcs-patch function. This fixes undocumented issues with the old implementation such as skipping over hunks of changes, and it fixes the documented issue of not being able to handle file names that include whitespace. It can also apply the patch on a buffer that has no file name attached at all. Last but not least, it greatly simplifies the gofmt function itself. Fixes #4766. Fixes #4475. R=adonovan, cw, patrick.allen.higgins, sameer CC=golang-dev https://golang.org/cl/7516046
This commit is contained in:
parent
b000f2286c
commit
77ddbf1ff0
@ -5,7 +5,6 @@
|
|||||||
;; license that can be found in the LICENSE file.
|
;; license that can be found in the LICENSE file.
|
||||||
|
|
||||||
(require 'cl)
|
(require 'cl)
|
||||||
(require 'diff-mode)
|
|
||||||
(require 'ffap)
|
(require 'ffap)
|
||||||
(require 'url)
|
(require 'url)
|
||||||
|
|
||||||
@ -64,7 +63,6 @@
|
|||||||
(concat "\\_<" s "\\_>")))
|
(concat "\\_<" s "\\_>")))
|
||||||
|
|
||||||
(defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]")
|
(defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]")
|
||||||
(defconst gofmt-stdin-tag "<standard input>")
|
|
||||||
(defconst go-identifier-regexp "[[:word:][:multibyte:]]+")
|
(defconst go-identifier-regexp "[[:word:][:multibyte:]]+")
|
||||||
(defconst go-label-regexp go-identifier-regexp)
|
(defconst go-label-regexp go-identifier-regexp)
|
||||||
(defconst go-type-regexp "[[:word:][:multibyte:]*]+")
|
(defconst go-type-regexp "[[:word:][:multibyte:]*]+")
|
||||||
@ -418,99 +416,91 @@ recommended that you look at goflymake
|
|||||||
;;;###autoload
|
;;;###autoload
|
||||||
(add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode))
|
(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 ()
|
(defun gofmt ()
|
||||||
"Pipe the current buffer through the external tool `gofmt`.
|
"Formats the current buffer according to the gofmt tool."
|
||||||
Replace the current buffer on success; display errors on failure."
|
|
||||||
|
|
||||||
(interactive)
|
(interactive)
|
||||||
(let ((currconf (current-window-configuration)))
|
(let ((tmpfile (make-temp-file "gofmt" nil ".go"))
|
||||||
(let ((srcbuf (current-buffer))
|
(patchbuf (get-buffer-create "*Gofmt patch*"))
|
||||||
(filename buffer-file-name)
|
(errbuf (get-buffer-create "*Gofmt Errors*"))
|
||||||
(patchbuf (get-buffer-create "*Gofmt patch*")))
|
(coding-system-for-read 'utf-8)
|
||||||
(with-current-buffer patchbuf
|
(coding-system-for-write 'utf-8))
|
||||||
(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
|
(with-current-buffer errbuf
|
||||||
;; end-of-file newline, so add one
|
(setq buffer-read-only nil)
|
||||||
(if (/= (char-after (1- (point-max))) ?\n)
|
(erase-buffer))
|
||||||
(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)
|
|
||||||
(with-current-buffer patchbuf
|
(with-current-buffer patchbuf
|
||||||
(goto-char (point-min))
|
(erase-buffer))
|
||||||
;; 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)
|
(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.
|
;; Convert the gofmt stderr to something understood by the compilation mode.
|
||||||
(with-current-buffer errbuf
|
(with-current-buffer errbuf
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(insert "gofmt errors:\n")
|
(insert "gofmt errors:\n")
|
||||||
(if (search-forward gofmt-stdin-tag nil t)
|
(while (search-forward-regexp (concat "^\\(" (regexp-quote tmpfile) "\\):") nil t)
|
||||||
(replace-match (file-name-nondirectory filename) nil t))
|
(replace-match (file-name-nondirectory filename) t t nil 1))
|
||||||
(display-buffer errbuf)
|
(display-buffer errbuf)
|
||||||
(compilation-mode)))
|
(compilation-mode)))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user