diff options
author | Hilko Bengen <bengen@debian.org> | 2017-04-02 11:35:01 +0200 |
---|---|---|
committer | Hilko Bengen <bengen@debian.org> | 2017-04-02 11:35:10 +0200 |
commit | bf9ce4c27346c921b8b70b9650bc74e77a9ff00e (patch) | |
tree | d8c2dcf10ceff79fd2e4917538cae98036c4618c | |
parent | 468815f7da22ed21c824c8edd60e121e7ebfba70 (diff) | |
parent | 99b40584eb81185d410b46742b6bd8016e6d75a8 (diff) |
Merge tag 'upstream/1.5.0'
Upstream version 1.5.0
-rw-r--r-- | AUTHORS | 14 | ||||
-rw-r--r-- | go-guru.el | 551 | ||||
-rw-r--r-- | go-mode.el | 312 | ||||
-rw-r--r-- | go-rename.el | 108 |
4 files changed, 799 insertions, 186 deletions
@@ -1,9 +1,13 @@ Aaron France <aaron.l.france@gmail.com> Alan Donovan <adonovan@google.com> +Alan Donovan <alan@alandonovan.net> +Andrew Gerrand <adg@golang.org> Austin Clements <aclements@csail.mit.edu> Ben Fried <ben.fried@gmail.com> Bobby Powers <bobbypowers@gmail.com> Charles Lee <zombie.fml@gmail.com> +Daniel Morsing <daniel.morsing@gmail.com> +Dominik Honnef <dominik.honnef@gmail.com> Dominik Honnef <dominik@honnef.co> Eric Eisner <eric.d.eisner@gmail.com> Erin Keenan <erinok@gmail.com> @@ -15,12 +19,16 @@ Iwasaki Yudai <yudai.iwasaki@ntti3.com> James Aguilar <jaguilar@google.com> Jan Newmarch <jan.newmarch@gmail.com> Jean-Marc Eurin <jmeurin@google.com> +Jeff Hodges <jeff@somethingsimilar.com> Juergen Hoetzel <juergen@archlinux.org> Kevin Ballard <kevin@sb.org> +Konstantin Shaposhnikov <k.shaposhnikov@gmail.com> Lowe Thiderman <lowe.thiderman@gmail.com> Mark Petrovic <mark.petrovic@xoom.com> Mats Lidell <mats.lidell@cag.se> +Matt Armstrong <marmstrong@google.com> Peter Kleiweg <pkleiweg@xs4all.nl> +Philipp Stephani <phst@google.com> Quan Yong Zhai <qyzhai@gmail.com> Robert Zaremba <robert.zaremba@zoho.com> Rui Ueyama <ruiu@google.com> @@ -30,7 +38,13 @@ Rüdiger Sonderfeld <ruediger@c-plusplus.net> Sameer Ajmani <sameer@golang.org> Scott Lawrence <bytbox@gmail.com> Steven Elliot Harris <seharris@gmail.com> +Syohei YOSHIDA <syohex@gmail.com> Taiki Sugawara <buzz.taiki@gmail.com> Viacheslav Chimishuk <vchimishuk@yandex-team.ru> Will <will@glozer.net> Yasuyuki Oka <yasuyk@gmail.com> +Yutian Li <hotpxless@gmail.com> +Zac Bergquist <zbergquist99@gmail.com> +kostya-sh <kostya-sh@users.noreply.github.com> +nverno <noah.v.peart@gmail.com> +nwidger <niels.widger@gmail.com> diff --git a/go-guru.el b/go-guru.el new file mode 100644 index 0000000..cab5eb3 --- /dev/null +++ b/go-guru.el @@ -0,0 +1,551 @@ +;;; go-guru.el --- Integration of the Go 'guru' analysis tool into Emacs. + +;; Copyright 2016 The Go Authors. All rights reserved. +;; Use of this source code is governed by a BSD-style +;; license that can be found in the LICENSE file. + +;; Version: 0.1 +;; Package-Requires: ((go-mode "1.3.1") (cl-lib "0.5")) +;; Keywords: tools + +;;; Commentary: + +;; To enable the Go guru in Emacs, use this command to download, +;; build, and install the tool in $GOROOT/bin: +;; +;; $ go get golang.org/x/tools/cmd/guru +;; +;; Verify that the tool is on your $PATH: +;; +;; $ guru -help +;; Go source code guru. +;; Usage: guru [flags] <mode> <position> +;; ... +;; +;; Then copy this file to a directory on your `load-path', +;; and add this to your ~/.emacs: +;; +;; (require 'go-guru) +;; +;; Inside a buffer of Go source code, select an expression of +;; interest, and type `C-c C-o d' (for "describe") or run one of the +;; other go-guru-xxx commands. If you use `menu-bar-mode', these +;; commands are available from the Guru menu. +;; +;; To enable identifier highlighting mode in a Go source buffer, use: +;; +;; (go-guru-hl-identifier-mode) +;; +;; To enable it automatically in all Go source buffers, +;; add this to your ~/.emacs: +;; +;; (add-hook 'go-mode-hook #'go-guru-hl-identifier-mode) +;; +;; See http://golang.org/s/using-guru for more information about guru. + +;;; Code: + +(require 'compile) +(require 'easymenu) +(require 'go-mode) +(require 'json) +(require 'simple) +(require 'cl-lib) + +(defgroup go-guru nil + "Options specific to the Go guru." + :group 'go) + +(defcustom go-guru-command "guru" + "The Go guru command." + :type 'string + :group 'go-guru) + +(defcustom go-guru-scope "" + "The scope of the analysis. See `go-guru-set-scope'." + :type 'string + :group 'go-guru) + +(defvar go-guru--scope-history + nil + "History of values supplied to `go-guru-set-scope'.") + +(defcustom go-guru-build-tags '() + "Build tags passed to guru." + :type '(repeat string) + :group 'go-guru) + +(defface go-guru-hl-identifier-face + '((t (:inherit highlight))) + "Face used for highlighting identifiers in `go-guru-hl-identifier'." + :group 'go-guru) + +(defcustom go-guru-debug nil + "Print debug messages when running guru." + :type 'boolean + :group 'go-guru) + +(defcustom go-guru-hl-identifier-idle-time 0.5 + "How long to wait after user input before highlighting the current identifier." + :type 'float + :group 'go-guru) + +(defvar go-guru--current-hl-identifier-idle-time + 0 + "The current delay for hl-identifier-mode.") + +(defvar go-guru--hl-identifier-timer + nil + "The global timer used for highlighting identifiers.") + +(defvar go-guru--last-enclosing + nil + "The remaining enclosing regions of the previous go-expand-region invocation.") + +;; Extend go-mode-map. +(let ((m (define-prefix-command 'go-guru-map))) + (define-key m "d" #'go-guru-describe) + (define-key m "f" #'go-guru-freevars) + (define-key m "i" #'go-guru-implements) + (define-key m "c" #'go-guru-peers) ; c for channel + (define-key m "r" #'go-guru-referrers) + (define-key m "j" #'go-guru-definition) ; j for jump + (define-key m "p" #'go-guru-pointsto) + (define-key m "s" #'go-guru-callstack) ; s for stack + (define-key m "e" #'go-guru-whicherrs) ; e for error + (define-key m "<" #'go-guru-callers) + (define-key m ">" #'go-guru-callees) + (define-key m "x" #'go-guru-expand-region)) ;; x for expand + +(define-key go-mode-map (kbd "C-c C-o") #'go-guru-map) + +(easy-menu-define go-guru-mode-menu go-mode-map + "Menu for Go Guru." + '("Guru" + ["Jump to Definition" go-guru-definition t] + ["Show Referrers" go-guru-referrers t] + ["Show Free Names" go-guru-freevars t] + ["Describe Expression" go-guru-describe t] + ["Show Implements" go-guru-implements t] + "---" + ["Show Callers" go-guru-callers t] + ["Show Callees" go-guru-callees t] + ["Show Callstack" go-guru-callstack t] + "---" + ["Show Points-To" go-guru-pointsto t] + ["Show Which Errors" go-guru-whicherrs t] + ["Show Channel Peers" go-guru-peers t] + "---" + ["Set pointer analysis scope..." go-guru-set-scope t])) + +;;;###autoload +(defun go-guru-set-scope () + "Set the scope for the Go guru, prompting the user to edit the previous scope. + +The scope restricts analysis to the specified packages. +Its value is a comma-separated list of patterns of these forms: + golang.org/x/tools/cmd/guru # a single package + golang.org/x/tools/... # all packages beneath dir + ... # the entire workspace. + +A pattern preceded by '-' is negative, so the scope + encoding/...,-encoding/xml +matches all encoding packages except encoding/xml." + (interactive) + (let ((scope (read-from-minibuffer "Go guru scope: " + go-guru-scope + nil + nil + 'go-guru--scope-history))) + (if (string-equal "" scope) + (error "You must specify a non-empty scope for the Go guru")) + (setq go-guru-scope scope))) + +(defun go-guru--set-scope-if-empty () + (if (string-equal "" go-guru-scope) + (go-guru-set-scope))) + +(defun go-guru--json (mode) + "Execute the Go guru in the specified MODE, passing it the +selected region of the current buffer, requesting JSON output. +Parse and return the resulting JSON object." + ;; A "what" query works even in a buffer without a file name. + (let* ((filename (file-truename (or buffer-file-name "synthetic.go"))) + (cmd (go-guru--command mode filename '("-json"))) + (buf (current-buffer)) + ;; Use temporary buffers to avoid conflict with go-guru--start. + (json-buffer (generate-new-buffer "*go-guru-json-output*")) + (input-buffer (generate-new-buffer "*go-guru-json-input*"))) + (unwind-protect + ;; Run guru, feeding it the input buffer (modified files). + (with-current-buffer input-buffer + (go-guru--insert-modified-files) + (unless (buffer-file-name buf) + (go-guru--insert-modified-file filename buf)) + (let ((exitcode (apply #'call-process-region + (append (list (point-min) + (point-max) + (car cmd) ; guru + nil ; delete + json-buffer ; output + nil) ; display + (cdr cmd))))) ; args + (with-current-buffer json-buffer + (unless (zerop exitcode) + ;; Failed: use buffer contents (sans final \n) as an error. + (error "%s" (buffer-substring (point-min) (1- (point-max))))) + ;; Success: parse JSON. + (goto-char (point-min)) + (json-read)))) + ;; Clean up temporary buffers. + (kill-buffer json-buffer) + (kill-buffer input-buffer)))) + +(define-compilation-mode go-guru-output-mode "Go guru" + "Go guru output mode is a variant of `compilation-mode' for the +output of the Go guru tool." + (set (make-local-variable 'compilation-error-screen-columns) nil) + (set (make-local-variable 'compilation-filter-hook) #'go-guru--compilation-filter-hook) + (set (make-local-variable 'compilation-start-hook) #'go-guru--compilation-start-hook)) + +(defun go-guru--compilation-filter-hook () + "Post-process a blob of input to the go-guru-output buffer." + ;; For readability, truncate each "file:line:col:" prefix to a fixed width. + ;; If the prefix is longer than 20, show "…/last/19chars.go". + ;; This usually includes the last segment of the package name. + ;; Hide the line and column numbers. + (let ((start compilation-filter-start) + (end (point))) + (goto-char start) + (unless (bolp) + ;; TODO(adonovan): not quite right: the filter may be called + ;; with chunks of output containing incomplete lines. Moving to + ;; beginning-of-line may cause duplicate post-processing. + (beginning-of-line)) + (setq start (point)) + (while (< start end) + (let ((p (search-forward ": " end t))) + (if (null p) + (setq start end) ; break out of loop + (setq p (1- p)) ; exclude final space + (let* ((posn (buffer-substring-no-properties start p)) + (flen (cl-search ":" posn)) ; length of filename + (filename (if (< flen 19) + (substring posn 0 flen) + (concat "…" (substring posn (- flen 19) flen))))) + (put-text-property start p 'display filename) + (forward-line 1) + (setq start (point)))))))) + +(defun go-guru--compilation-start-hook (proc) + "Erase default output header inserted by `compilation-mode'." + (with-current-buffer (process-buffer proc) + (let ((inhibit-read-only t)) + (goto-char (point-min)) + (delete-region (point) (point-max))))) + +(defun go-guru--start (mode) + "Start an asynchronous Go guru process for the specified query +MODE, passing it the selected region of the current buffer, and +feeding its standard input with the contents of all modified Go +buffers. Its output is handled by `go-guru-output-mode', a +variant of `compilation-mode'." + (or buffer-file-name + (error "Cannot use guru on a buffer without a file name")) + (let* ((filename (file-truename buffer-file-name)) + (cmd (mapconcat #'shell-quote-argument (go-guru--command mode filename) " ")) + (process-connection-type nil) ; use pipe (not pty) so EOF closes stdin + (procbuf (compilation-start cmd 'go-guru-output-mode))) + (with-current-buffer procbuf + (setq truncate-lines t)) ; the output is neater without line wrapping + (with-current-buffer (get-buffer-create "*go-guru-input*") + (erase-buffer) + (go-guru--insert-modified-files) + (process-send-region procbuf (point-min) (point-max)) + (process-send-eof procbuf)) + procbuf)) + +(defun go-guru--command (mode filename &optional flags) + "Return a command and argument list for a Go guru query of MODE, passing it +the selected region of the current buffer. FILENAME is the +effective name of the current buffer." + (let* ((posn (if (use-region-p) + (format "%s:#%d,#%d" + filename + (1- (position-bytes (region-beginning))) + (1- (position-bytes (region-end)))) + (format "%s:#%d" + filename + (1- (position-bytes (point)))))) + (cmd (append (list go-guru-command + "-modified" + "-scope" go-guru-scope + (format "-tags=%s" (mapconcat 'identity go-guru-build-tags ","))) + flags + (list mode + posn)))) + ;; Log the command to *Messages*, for debugging. + (when go-guru-debug + (message "go-guru--command: %s" cmd) + (message nil)) ; clear/shrink minibuffer + cmd)) + +(defun go-guru--insert-modified-files () + "Insert the contents of each modified Go buffer into the +current buffer in the format specified by guru's -modified flag." + (mapc #'(lambda (b) + (and (buffer-modified-p b) + (buffer-file-name b) + (string= (file-name-extension (buffer-file-name b)) "go") + (go-guru--insert-modified-file (buffer-file-name b) b))) + (buffer-list))) + +(defun go-guru--insert-modified-file (name buffer) + (insert (format "%s\n%d\n" name (go-guru--buffer-size-bytes buffer))) + (insert-buffer-substring buffer)) + +(defun go-guru--buffer-size-bytes (&optional buffer) + "Return the number of bytes in the current buffer. +If BUFFER, return the number of characters in that buffer instead." + (with-current-buffer (or buffer (current-buffer)) + (string-bytes (buffer-substring (point-min) + (point-max))))) + +(defun go-guru--goto-byte (offset) + "Go to the OFFSETth byte in the buffer." + (goto-char (byte-to-position offset))) + +(defun go-guru--goto-byte-column (offset) + "Go to the OFFSETth byte in the current line." + (goto-char (byte-to-position (+ (position-bytes (point-at-bol)) (1- offset))))) + +(defun go-guru--goto-pos (posn) + "Find the file containing the position POSN (of the form `file:line:col') +set the point to it, switching the current buffer." + (let ((file-line-pos (split-string posn ":"))) + (find-file (car file-line-pos)) + (goto-char (point-min)) + (forward-line (1- (string-to-number (cadr file-line-pos)))) + (go-guru--goto-byte-column (string-to-number (cl-caddr file-line-pos))))) + +(defun go-guru--goto-pos-no-file (posn) + "Given `file:line:col', go to the line and column. The file +component will be ignored." + (let ((file-line-pos (split-string posn ":"))) + (goto-char (point-min)) + (forward-line (1- (string-to-number (cadr file-line-pos)))) + (go-guru--goto-byte-column (string-to-number (cl-caddr file-line-pos))))) + +;;;###autoload +(defun go-guru-callees () + "Show possible callees of the function call at the current point." + (interactive) + (go-guru--set-scope-if-empty) + (go-guru--start "callees")) + +;;;###autoload +(defun go-guru-callers () + "Show the set of callers of the function containing the current point." + (interactive) + (go-guru--set-scope-if-empty) + (go-guru--start "callers")) + +;;;###autoload +(defun go-guru-callstack () + "Show an arbitrary path from a root of the call graph to the +function containing the current point." + (interactive) + (go-guru--set-scope-if-empty) + (go-guru--start "callstack")) + +;;;###autoload +(defun go-guru-definition () + "Jump to the definition of the selected identifier." + (interactive) + (or buffer-file-name + (error "Cannot use guru on a buffer without a file name")) + (let* ((res (go-guru--json "definition")) + (desc (cdr (assoc 'desc res)))) + (push-mark) + (ring-insert find-tag-marker-ring (point-marker)) + (go-guru--goto-pos (cdr (assoc 'objpos res))) + (message "%s" desc))) + +;;;###autoload +(defun go-guru-describe () + "Describe the selected syntax, its kind, type and methods." + (interactive) + (go-guru--start "describe")) + +;;;###autoload +(defun go-guru-pointsto () + "Show what the selected expression points to." + (interactive) + (go-guru--set-scope-if-empty) + (go-guru--start "pointsto")) + +;;;###autoload +(defun go-guru-implements () + "Describe the 'implements' relation for types in the package +containing the current point." + (interactive) + (go-guru--start "implements")) + +;;;###autoload +(defun go-guru-freevars () + "Enumerate the free variables of the current selection." + (interactive) + (go-guru--start "freevars")) + +;;;###autoload +(defun go-guru-peers () + "Enumerate the set of possible corresponding sends/receives for +this channel receive/send operation." + (interactive) + (go-guru--set-scope-if-empty) + (go-guru--start "peers")) + +;;;###autoload +(defun go-guru-referrers () + "Enumerate all references to the object denoted by the selected +identifier." + (interactive) + (go-guru--start "referrers")) + +;;;###autoload +(defun go-guru-whicherrs () + "Show globals, constants and types to which the selected +expression (of type 'error') may refer." + (interactive) + (go-guru--set-scope-if-empty) + (go-guru--start "whicherrs")) + +(defun go-guru-what () + "Run a 'what' query and return the parsed JSON response as an +association list." + (go-guru--json "what")) + +(defun go-guru--hl-symbols (posn face id) + "Highlight the symbols at the positions POSN by creating +overlays with face FACE. The attribute 'go-guru-overlay on the +overlays will be set to ID." + (save-excursion + (mapc (lambda (pos) + (go-guru--goto-pos-no-file pos) + (let ((x (make-overlay (point) (+ (point) (length (current-word)))))) + (overlay-put x 'go-guru-overlay id) + (overlay-put x 'face face))) + posn))) + +;;;###autoload +(defun go-guru-unhighlight-identifiers () + "Remove highlights from previously highlighted identifier." + (remove-overlays nil nil 'go-guru-overlay 'sameid)) + +;;;###autoload +(defun go-guru-hl-identifier () + "Highlight all instances of the identifier under point. Removes +highlights from previously highlighted identifier." + (interactive) + (go-guru-unhighlight-identifiers) + (go-guru--hl-identifier)) + +(defun go-guru--hl-identifier () + "Highlight all instances of the identifier under point." + (let ((posn (cdr (assoc 'sameids (go-guru-what))))) + (go-guru--hl-symbols posn 'go-guru-hl-identifier-face 'sameid))) + +(defun go-guru--hl-identifiers-function () + "Function run after an idle timeout, highlighting the +identifier at point, if necessary." + (when go-guru-hl-identifier-mode + (unless (go-guru--on-overlay-p 'sameid) + ;; Ignore guru errors. Otherwise, we might end up with an error + ;; every time the timer runs, e.g. because of a malformed + ;; buffer. + (condition-case nil + (go-guru-hl-identifier) + (error nil))) + (unless (eq go-guru--current-hl-identifier-idle-time go-guru-hl-identifier-idle-time) + (go-guru--hl-set-timer)))) + +(defun go-guru--hl-set-timer () + (if go-guru--hl-identifier-timer + (cancel-timer go-guru--hl-identifier-timer)) + (setq go-guru--current-hl-identifier-idle-time go-guru-hl-identifier-idle-time) + (setq go-guru--hl-identifier-timer (run-with-idle-timer + go-guru-hl-identifier-idle-time + t + #'go-guru--hl-identifiers-function))) + +;;;###autoload +(define-minor-mode go-guru-hl-identifier-mode + "Highlight instances of the identifier at point after a short +timeout." + :group 'go-guru + (if go-guru-hl-identifier-mode + (progn + (go-guru--hl-set-timer) + ;; Unhighlight if point moves off identifier + (add-hook 'post-command-hook #'go-guru--hl-identifiers-post-command-hook nil t) + ;; Unhighlight any time the buffer changes + (add-hook 'before-change-functions #'go-guru--hl-identifiers-before-change-function nil t)) + (remove-hook 'post-command-hook #'go-guru--hl-identifiers-post-command-hook t) + (remove-hook 'before-change-functions #'go-guru--hl-identifiers-before-change-function t) + (go-guru-unhighlight-identifiers))) + +(defun go-guru--on-overlay-p (id) + "Return whether point is on a guru overlay of type ID." + (cl-find-if (lambda (el) (eq (overlay-get el 'go-guru-overlay) id)) (overlays-at (point)))) + +(defun go-guru--hl-identifiers-post-command-hook () + (if (and go-guru-hl-identifier-mode + (not (go-guru--on-overlay-p 'sameid))) + (go-guru-unhighlight-identifiers))) + +(defun go-guru--hl-identifiers-before-change-function (_beg _end) + (go-guru-unhighlight-identifiers)) + +;; TODO(dominikh): a future feature may be to cycle through all uses +;; of an identifier. + +(defun go-guru--enclosing () + "Return a list of enclosing regions." + (cdr (assoc 'enclosing (go-guru-what)))) + +(defun go-guru--enclosing-unique () + "Return a list of enclosing regions, with duplicates removed. +Two regions are considered equal if they have the same start and +end point." + (let ((enclosing (go-guru--enclosing))) + (cl-remove-duplicates enclosing + :from-end t + :test (lambda (a b) + (and (= (cdr (assoc 'start a)) + (cdr (assoc 'start b))) + (= (cdr (assoc 'end a)) + (cdr (assoc 'end b)))))))) + +(defun go-guru-expand-region () + "Expand region to the next enclosing syntactic unit." + (interactive) + (let* ((enclosing (if (eq last-command #'go-guru-expand-region) + go-guru--last-enclosing + (go-guru--enclosing-unique))) + (block (if (> (length enclosing) 0) (elt enclosing 0)))) + (when block + (go-guru--goto-byte (1+ (cdr (assoc 'start block)))) + (set-mark (byte-to-position (1+ (cdr (assoc 'end block))))) + (setq go-guru--last-enclosing (cl-subseq enclosing 1)) + (message "Region: %s" (cdr (assoc 'desc block))) + (setq deactivate-mark nil)))) + + +(provide 'go-guru) + +;; Local variables: +;; indent-tabs-mode: t +;; tab-width: 8 +;; End + +;;; go-guru.el ends here @@ -1,11 +1,13 @@ ;;; go-mode.el --- Major mode for the Go programming language -;; Copyright 2013 The go-mode Authors. All rights reserved. +;;; Commentary: + +;; Copyright 2013 The go-mode Authors. All rights reserved. ;; Use of this source code is governed by a BSD-style ;; license that can be found in the LICENSE file. ;; Author: The go-mode Authors -;; Version: 1.4.0 +;; Version: 1.5.0 ;; Keywords: languages go ;; URL: https://github.com/dominikh/go-mode.el ;; @@ -20,35 +22,14 @@ (require 'find-file) (require 'ring) (require 'url) +(require 'xref nil :noerror) ; xref is new in Emacs 25.1 -;; XEmacs compatibility guidelines -;; - Minimum required version of XEmacs: 21.5.32 -;; - Feature that cannot be backported: POSIX character classes in -;; regular expressions -;; - Functions that could be backported but won't because 21.5.32 -;; covers them: plenty. -;; - Features that are still partly broken: -;; - godef will not work correctly if multibyte characters are -;; being used -;; - Fontification will not handle unicode correctly -;; -;; - Do not use \_< and \_> regexp delimiters directly; use -;; go--regexp-enclose-in-symbol -;; -;; - The character `_` must not be a symbol constituent but a -;; character constituent -;; -;; - Do not use process-lines -;; -;; - Use go--old-completion-list-style when using a plain list as the -;; collection for completing-read -;; -;; - Use go--position-bytes instead of position-bytes -(defmacro go--xemacs-p () - (featurep 'xemacs)) -(defmacro go--has-syntax-propertize-p () - (boundp 'syntax-propertize-function)) +(eval-when-compile + (defmacro go--forward-word (&optional arg) + (if (fboundp 'forward-word-strictly) + `(forward-word-strictly ,arg) + `(forward-word ,arg)))) (defun go--delete-whole-line (&optional arg) "Delete the current line without putting it in the `kill-ring'. @@ -76,43 +57,6 @@ function." (delete-region (progn (forward-visible-line 0) (point)) (progn (forward-visible-line arg) (point)))))) -;; declare-function is an empty macro that only byte-compile cares -;; about. Wrap in always false if to satisfy Emacsen without that -;; macro. -(if nil - (declare-function go--position-bytes "go-mode" (point))) - -;; XEmacs unfortunately does not offer position-bytes. We can fall -;; back to just using (point), but it will be incorrect as soon as -;; multibyte characters are being used. -(if (fboundp 'position-bytes) - (defalias 'go--position-bytes #'position-bytes) - (defun go--position-bytes (point) point)) - -(defun go--old-completion-list-style (list) - (mapcar (lambda (x) (cons x nil)) list)) - -;; GNU Emacs 24 has prog-mode, older GNU Emacs and XEmacs do not, so -;; copy its definition for those. -(if (not (fboundp 'prog-mode)) - (define-derived-mode prog-mode fundamental-mode "Prog" - "Major mode for editing source code." - (set (make-local-variable 'require-final-newline) mode-require-final-newline) - (set (make-local-variable 'parse-sexp-ignore-comments) t) - (setq bidi-paragraph-direction 'left-to-right))) - -(defun go--regexp-enclose-in-symbol (s) - "Enclose S as regexp symbol. -XEmacs does not support \\_<, GNU Emacs does. In GNU Emacs we -make extensive use of \\_< to support unicode in identifiers. -Until we come up with a better solution for XEmacs, this solution -will break fontification in XEmacs for identifiers such as -\"typeµ\". XEmacs will consider \"type\" a keyword, GNU Emacs -won't." - (if (go--xemacs-p) - (concat "\\<" s "\\>") - (concat "\\_<" s "\\_>"))) - (defun go-goto-opening-parenthesis (&optional _legacy-unused) "Move up one level of parentheses." ;; The old implementation of go-goto-opening-parenthesis had an @@ -126,14 +70,20 @@ won't." (defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]") +(defconst go--max-dangling-operator-length 2 + "The maximum length of dangling operators. +This must be at least the length of the longest string matched by +‘go-dangling-operators-regexp.’, and must be updated whenever +that constant is changed.") + (defconst go-identifier-regexp "[[:word:][:multibyte:]]+") (defconst go-type-name-no-prefix-regexp "\\(?:[[:word:][:multibyte:]]+\\.\\)?[[:word:][:multibyte:]]+") (defconst go-qualified-identifier-regexp (concat go-identifier-regexp "\\." go-identifier-regexp)) (defconst go-label-regexp go-identifier-regexp) (defconst go-type-regexp "[[:word:][:multibyte:]*]+") -(defconst go-func-regexp (concat (go--regexp-enclose-in-symbol "func") "\\s *\\(" go-identifier-regexp "\\)")) +(defconst go-func-regexp (concat "\\_<func\\_>\\s *\\(" go-identifier-regexp "\\)")) (defconst go-func-meth-regexp (concat - (go--regexp-enclose-in-symbol "func") "\\s *\\(?:(\\s *" + "\\_<func\\_>\\s *\\(?:(\\s *" "\\(" go-identifier-regexp "\\s +\\)?" go-type-regexp "\\s *)\\s *\\)?\\(" go-identifier-regexp @@ -199,7 +149,7 @@ point to the wrapper script." (defcustom gofmt-command "gofmt" "The 'gofmt' command. Some users may replace this with 'goimports' -from https://github.com/bradfitz/goimports." +from https://golang.org/x/tools/cmd/goimports." :type 'string :group 'go) @@ -234,14 +184,13 @@ a `before-save-hook'." :group 'go) (defcustom go-packages-function 'go-packages-native - "Function called by `go-packages' to determine the list of -available packages. This is used in e.g. tab completion in -`go-import-add'. + "Function called by `go-packages' to determine the list of available packages. +This is used in e.g. tab completion in `go-import-add'. This package provides two functions: `go-packages-native' uses elisp to find all .a files in all /pkg/ directories. `go-packages-go-list' uses 'go list all' to determine all Go -packages. `go-packages-go-list' generally produces more accurate +packages. `go-packages-go-list' generally produces more accurate results, but can be slower than `go-packages-native'." :type 'function :package-version '(go-mode . 1.4.0) @@ -254,30 +203,31 @@ results, but can be slower than `go-packages-native'." "Functions to call in sequence to detect a project's GOPATH. The functions in this list will be called one after another, -until a function returns non-nil. The order of the functions in +until a function returns non-nil. The order of the functions in this list is important, as some project layouts may superficially -look like others. For example, a subset of wgo projects look like -gb projects. That's why we need to detect wgo first, to avoid +look like others. For example, a subset of wgo projects look like +gb projects. That's why we need to detect wgo first, to avoid mis-identifying them as gb projects." :type '(repeat function) :group 'go) (defcustom godoc-command "go doc" - "Which executable to use for `godoc'. This can either be -'godoc' or 'go doc', both as an absolute path or an executable in -PATH." + "Which executable to use for `godoc'. +This can either be 'godoc' or 'go doc', both as an absolute path +or an executable in PATH." :type 'string :group 'go) (defcustom godoc-and-godef-command "godoc" - "Which executable to use for `godoc' in -`godoc-and-godef-command'. Must be 'godoc' and not 'go doc' and -can be an absolute path or an executable in PATH." + "Which executable to use for `godoc' in `godoc-and-godef-command'. +Must be 'godoc' and not 'go doc' and can be an absolute path or +an executable in PATH." :type 'string :group 'go) (defcustom godoc-use-completing-read nil - "Provide auto-completion for godoc. Only really desirable when using `godoc' instead of `go doc'." + "Provide auto-completion for godoc. +Only really desirable when using `godoc' instead of `go doc'." :type 'boolean :group 'godoc) @@ -286,24 +236,24 @@ can be an absolute path or an executable in PATH." identifier at a given position. This package provides two functions: `godoc-and-godef' uses a -combination of godef and godoc to find the documentation. This -approach has several caveats. See its documentation for more -information. The second function, `godoc-gogetdoc' uses an +combination of godef and godoc to find the documentation. This +approach has several caveats. See its documentation for more +information. The second function, `godoc-gogetdoc' uses an additional tool that correctly determines the documentation for -any identifier. It provides better results than -`godoc-and-godef'. " +any identifier. It provides better results than +`godoc-and-godef'." :type 'function :group 'godoc) (defun godoc-and-godef (point) - "Use a combination of godef and godoc to guess the documentation. + "Use a combination of godef and godoc to guess the documentation at POINT. Due to a limitation in godoc, it is not possible to differentiate between functions and methods, which may cause `godoc-at-point' -to display more documentation than desired. Furthermore, it +to display more documentation than desired. Furthermore, it doesn't work on package names or variables. -Consider using godoc-gogetdoc instead for more accurate results." +Consider using ‘godoc-gogetdoc’ instead for more accurate results." (condition-case nil (let* ((output (godef--call point)) (file (car output)) @@ -320,14 +270,14 @@ Consider using godoc-gogetdoc instead for more accurate results." (file-error (message "Could not run godef binary")))) (defun godoc-gogetdoc (point) - "Use the gogetdoc tool to find the documentation for an identifier. + "Use the gogetdoc tool to find the documentation for an identifier at POINT. You can install gogetdoc with 'go get -u github.com/zmb3/gogetdoc'." (if (not (buffer-file-name (go--coverage-origin-buffer))) ;; TODO: gogetdoc supports unsaved files, but not introducing ;; new artifical files, so this limitation will stay for now. (error "Cannot use gogetdoc on a buffer without a file name")) - (let ((posn (format "%s:#%d" (shell-quote-argument (file-truename buffer-file-name)) (1- (go--position-bytes point)))) + (let ((posn (format "%s:#%d" (shell-quote-argument (file-truename buffer-file-name)) (1- (position-bytes point)))) (out (godoc--get-buffer "<at point>"))) (with-current-buffer (get-buffer-create "*go-gogetdoc-input*") (setq buffer-read-only nil) @@ -429,15 +379,14 @@ For mode=set, all covered lines will have this weight." (modify-syntax-entry ?= "." st) (modify-syntax-entry ?< "." st) (modify-syntax-entry ?> "." st) - (modify-syntax-entry ?/ (if (go--xemacs-p) ". 1456" ". 124b") st) + (modify-syntax-entry ?/ ". 124b" st) (modify-syntax-entry ?* ". 23" st) (modify-syntax-entry ?\n "> b" st) (modify-syntax-entry ?\" "\"" st) (modify-syntax-entry ?\' "\"" st) (modify-syntax-entry ?` "\"" st) (modify-syntax-entry ?\\ "\\" st) - ;; It would be nicer to have _ as a symbol constituent, but that - ;; would trip up XEmacs, which does not support the \_< anchor + ;; TODO make _ a symbol constituent now that xemacs is gone (modify-syntax-entry ?_ "w" st) st) @@ -450,9 +399,9 @@ For mode=set, all covered lines will have this weight." `((go--match-func ,@(mapcar (lambda (x) `(,x font-lock-type-face)) (number-sequence 1 go--font-lock-func-param-num-groups))) - (,(go--regexp-enclose-in-symbol (regexp-opt go-mode-keywords t)) . font-lock-keyword-face) - (,(concat "\\(" (go--regexp-enclose-in-symbol (regexp-opt go-builtins t)) "\\)[[:space:]]*(") 1 font-lock-builtin-face) - (,(go--regexp-enclose-in-symbol (regexp-opt go-constants t)) . font-lock-constant-face) + (,(concat "\\_<" (regexp-opt go-mode-keywords t) "\\_>") . font-lock-keyword-face) + (,(concat "\\(\\_<" (regexp-opt go-builtins t) "\\_>\\)[[:space:]]*(") 1 font-lock-builtin-face) + (,(concat "\\_<" (regexp-opt go-constants t) "\\_>") . font-lock-constant-face) (,go-func-regexp 1 font-lock-function-name-face)) ;; function (not method) name (if go-fontify-function-calls @@ -462,29 +411,21 @@ For mode=set, all covered lines will have this weight." `( ("\\(`[^`]*`\\)" 1 font-lock-multiline) ;; raw string literal, needed for font-lock-syntactic-keywords - (,(concat (go--regexp-enclose-in-symbol "type") "[[:space:]]+\\([^[:space:](]+\\)") 1 font-lock-type-face) ;; types - (,(concat (go--regexp-enclose-in-symbol "type") "[[:space:]]+" go-identifier-regexp "[[:space:]]*" go-type-name-regexp) 1 font-lock-type-face) ;; types + (,(concat "\\_<type\\_>[[:space:]]+\\([^[:space:](]+\\)") 1 font-lock-type-face) ;; types + (,(concat "\\_<type\\_>[[:space:]]+" go-identifier-regexp "[[:space:]]*" go-type-name-regexp) 1 font-lock-type-face) ;; types (,(concat "[^[:word:][:multibyte:]]\\[\\([[:digit:]]+\\|\\.\\.\\.\\)?\\]" go-type-name-regexp) 2 font-lock-type-face) ;; Arrays/slices (,(concat "\\(" go-identifier-regexp "\\)" "{") 1 font-lock-type-face) - (,(concat (go--regexp-enclose-in-symbol "map") "\\[[^]]+\\]" go-type-name-regexp) 1 font-lock-type-face) ;; map value type - (,(concat (go--regexp-enclose-in-symbol "map") "\\[" go-type-name-regexp) 1 font-lock-type-face) ;; map key type - (,(concat (go--regexp-enclose-in-symbol "chan") "[[:space:]]*\\(?:<-[[:space:]]*\\)?" go-type-name-regexp) 1 font-lock-type-face) ;; channel type - (,(concat (go--regexp-enclose-in-symbol "\\(?:new\\|make\\)") "\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp) 1 font-lock-type-face) ;; new/make type + (,(concat "\\_<map\\_>\\[[^]]+\\]" go-type-name-regexp) 1 font-lock-type-face) ;; map value type + (,(concat "\\_<map\\_>\\[" go-type-name-regexp) 1 font-lock-type-face) ;; map key type + (,(concat "\\_<chan\\_>[[:space:]]*\\(?:<-[[:space:]]*\\)?" go-type-name-regexp) 1 font-lock-type-face) ;; channel type + (,(concat "\\_<\\(?:new\\|make\\)\\_>\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp) 1 font-lock-type-face) ;; new/make type ;; TODO do we actually need this one or isn't it just a function call? (,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face) ;; Type conversion ;; Like the original go-mode this also marks compound literal ;; fields. There, it was marked as to fix, but I grew quite ;; accustomed to it, so it'll stay for now. (,(concat "^[[:space:]]*\\(" go-label-regexp "\\)[[:space:]]*:\\(\\S.\\|$\\)") 1 font-lock-constant-face) ;; Labels and compound literal fields - (,(concat (go--regexp-enclose-in-symbol "\\(goto\\|break\\|continue\\)") "[[:space:]]*\\(" go-label-regexp "\\)") 2 font-lock-constant-face)))) ;; labels in goto/break/continue - -(defconst go--font-lock-syntactic-keywords - ;; Override syntax property of raw string literal contents, so that - ;; backslashes have no special meaning in ``. Used in Emacs 23 or older. - '((go--match-raw-string-literal - (1 (7 . ?`)) - (2 (15 . nil)) ;; 15 = "generic string" - (3 (7 . ?`))))) + (,(concat "\\_<\\(goto\\|break\\|continue\\)\\_>[[:space:]]*\\(" go-label-regexp "\\)") 2 font-lock-constant-face)))) ;; labels in goto/break/continue (let ((m (define-prefix-command 'go-goto-map))) (define-key m "a" #'go-goto-arguments) @@ -506,7 +447,7 @@ For mode=set, all covered lines will have this weight." (define-key m (kbd "C-c C-d") #'godef-describe) (define-key m (kbd "C-c C-f") 'go-goto-map) m) - "Keymap used by go-mode.") + "Keymap used by ‘go-mode’.") (easy-menu-define go-mode-menu go-mode-map "Menu for Go mode." @@ -561,13 +502,13 @@ STOP-AT-STRING is not true, over strings." (let (pos (start-pos (point))) (skip-chars-backward "\n\s\t") (if (and (save-excursion (beginning-of-line) (go-in-string-p)) - (looking-back "`") + (= (char-before) ?`) (not stop-at-string)) (backward-char)) (if (and (go-in-string-p) (not stop-at-string)) (go-goto-beginning-of-string-or-comment)) - (if (looking-back "\\*/") + (if (looking-back "\\*/" (line-beginning-position)) (backward-char)) (if (go-in-comment-p) (go-goto-beginning-of-string-or-comment)) @@ -587,19 +528,6 @@ STOP-AT-STRING is not true, over strings." (- (point-max) (point-min)))) -(defun go--match-raw-string-literal (end) - "Search for a raw string literal. -Set point to the end of the occurence found on success. Return nil on failure." - (unless (go-in-string-or-comment-p) - (when (search-forward "`" end t) - (goto-char (match-beginning 0)) - (if (go-in-string-or-comment-p) - (progn (goto-char (match-end 0)) - (go--match-raw-string-literal end)) - (when (looking-at "\\(`\\)\\([^`]*\\)\\(`\\)") - (goto-char (match-end 0)) - t))))) - (defun go-previous-line-has-dangling-op-p () "Return non-nil if the current line is a continuation line." (let* ((cur-line (line-number-at-pos)) @@ -608,7 +536,8 @@ Set point to the end of the occurence found on success. Return nil on failure." (save-excursion (beginning-of-line) (go--backward-irrelevant t) - (setq val (looking-back go-dangling-operators-regexp)) + (setq val (looking-back go-dangling-operators-regexp + (- (point) go--max-dangling-operator-length))) (if (not (go--buffer-narrowed-p)) (puthash cur-line val go-dangling-cache)))) val)) @@ -618,9 +547,9 @@ Set point to the end of the occurence found on success. Return nil on failure." function definition. We do this by first calling (beginning-of-defun), which will take -us to the start of *some* function. We then look for the opening +us to the start of *some* function. We then look for the opening curly brace of that function and compare its position against the -curly brace we are checking. If they match, we return non-nil." +curly brace we are checking. If they match, we return non-nil." (if (= (char-after) ?\{) (save-excursion (let ((old-point (point)) @@ -663,7 +592,9 @@ current line will be returned." (if (go-previous-line-has-dangling-op-p) (- (current-indentation) tab-width) (go--indentation-for-opening-parenthesis))) - ((progn (go--backward-irrelevant t) (looking-back go-dangling-operators-regexp)) + ((progn (go--backward-irrelevant t) + (looking-back go-dangling-operators-regexp + (- (point) go--max-dangling-operator-length))) ;; only one nesting for all dangling operators in one operation (if (go-previous-line-has-dangling-op-p) (current-indentation) @@ -738,7 +669,8 @@ current line will be returned." (skip-chars-forward "^{") (forward-char) (or (go-in-string-or-comment-p) - (looking-back "\\(struct\\|interface\\)\\s-*{")))) + (looking-back "\\(struct\\|interface\\)\\s-*{" + (line-beginning-position))))) (setq orig-level (go-paren-level)) (while (>= (go-paren-level) orig-level) (skip-chars-forward "^}") @@ -792,7 +724,7 @@ parenthesis before a comma, it stops at it." "Search for identifiers used as type names from a function parameter list, and set the identifier positions as the results of last search. Return t if search succeeded." - (when (re-search-forward (go--regexp-enclose-in-symbol "func") end t) + (when (re-search-forward "\\_<func\\_>" end t) (let ((regions (go--match-func-type-names end))) (if (null regions) ;; Nothing to highlight. This can happen if the current func @@ -823,8 +755,8 @@ of last search. Return t if search succeeded." (nconc regions (go--match-function-result end)))))) (defun go--parameter-list-type (end) - "Return `present' if the parameter list has names, or `absent' if -not, assuming point is at the beginning of a parameter list, just + "Return `present' if the parameter list has names, or `absent' if not. +Assumes point is at the beginning of a parameter list, just after '('." (save-excursion (skip-chars-forward "[:space:]\n" end) @@ -844,7 +776,7 @@ after '('." (defconst go--parameter-type-regexp (concat go--opt-dotdotdot-regexp "[[:space:]*\n]*\\(" go-type-name-no-prefix-regexp "\\)[[:space:]\n]*\\([,)]\\|\\'\\)")) (defconst go--func-type-in-parameter-list-regexp - (concat go--opt-dotdotdot-regexp "[[:space:]*\n]*\\(" (go--regexp-enclose-in-symbol "func") "\\)")) + (concat go--opt-dotdotdot-regexp "[[:space:]*\n]*\\(\\_<func\\_>" "\\)")) (defun go--match-parameters-common (identifier-regexp end) (let ((acc ()) @@ -955,6 +887,12 @@ Function result is a unparenthesized type or a parameter list." (go--match-parameter-list end)) (t nil))) +(defun go--reset-dangling-cache-before-change (&optional _beg _end) + "Reset `go-dangling-cache'. + +This is intended to be called from `before-change-functions'." + (setq go-dangling-cache (make-hash-table :test 'eql))) + ;;;###autoload (define-derived-mode go-mode prog-mode "Go" "Major mode for editing Go source text. @@ -1036,11 +974,7 @@ with goflymake \(see URL `https://github.com/dougm/goflymake'), gocode (set (make-local-variable 'end-of-defun-function) #'go-end-of-defun) (set (make-local-variable 'parse-sexp-lookup-properties) t) - (if (go--has-syntax-propertize-p) - (set (make-local-variable 'syntax-propertize-function) #'go-propertize-syntax) - (set (make-local-variable 'font-lock-syntactic-keywords) - go--font-lock-syntactic-keywords) - (set (make-local-variable 'font-lock-multiline) t)) + (set (make-local-variable 'syntax-propertize-function) #'go-propertize-syntax) (if (boundp 'electric-indent-chars) (set (make-local-variable 'electric-indent-chars) '(?\n ?} ?\)))) @@ -1048,7 +982,7 @@ with goflymake \(see URL `https://github.com/dougm/goflymake'), gocode (set (make-local-variable 'compilation-error-screen-columns) nil) (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql)) - (add-hook 'before-change-functions (lambda (x y) (setq go-dangling-cache (make-hash-table :test 'eql))) t t) + (add-hook 'before-change-functions #'go--reset-dangling-cache-before-change t t) ;; ff-find-other-file (setq ff-other-file-alist 'go-other-file-alist) @@ -1101,7 +1035,7 @@ with goflymake \(see URL `https://github.com/dougm/goflymake'), gocode (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")) + (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))) @@ -1122,7 +1056,7 @@ with goflymake \(see URL `https://github.com/dougm/goflymake'), gocode (cl-incf line-offset len) (go--delete-whole-line len))) (t - (error "invalid rcs patch or internal error in go--apply-rcs-patch"))))))))) + (error "Invalid rcs patch or internal error in go--apply-rcs-patch"))))))))) (defun gofmt--is-goimports-p () (string-equal (file-name-base gofmt-command) "goimports")) @@ -1152,7 +1086,11 @@ with goflymake \(see URL `https://github.com/dougm/goflymake'), gocode (when (and (gofmt--is-goimports-p) buffer-file-name) (setq our-gofmt-args (append our-gofmt-args - (list "-srcdir" (file-name-directory (file-truename buffer-file-name)))))) + ;; srcdir, despite its name, supports + ;; accepting a full path, and some features + ;; of goimports rely on knowing the full + ;; name. + (list "-srcdir" (file-truename buffer-file-name))))) (setq our-gofmt-args (append our-gofmt-args gofmt-args (list "-w" tmpfile))) @@ -1205,9 +1143,9 @@ with goflymake \(see URL `https://github.com/dougm/goflymake'), gocode ;;;###autoload (defun gofmt-before-save () "Add this to .emacs to run gofmt on the current buffer when saving: - (add-hook 'before-save-hook 'gofmt-before-save). +\(add-hook 'before-save-hook 'gofmt-before-save). -Note that this will cause go-mode to get loaded the first time +Note that this will cause ‘go-mode’ to get loaded the first time you save any file, kind of defeating the point of autoloading." (interactive) @@ -1217,11 +1155,11 @@ you save any file, kind of defeating the point of autoloading." "Read a godoc query from the minibuffer." (if godoc-use-completing-read (completing-read "godoc; " - (go--old-completion-list-style (go-packages)) nil nil nil 'go-godoc-history) + (go-packages) nil nil nil 'go-godoc-history) (read-from-minibuffer "godoc: " nil nil nil 'go-godoc-history))) (defun godoc--get-buffer (query) - "Get an empty buffer for a godoc query." + "Get an empty buffer for a godoc QUERY." (let* ((buffer-name (concat "*godoc " query "*")) (buffer (get-buffer buffer-name))) ;; Kill the existing buffer if it already exists. @@ -1246,7 +1184,7 @@ you save any file, kind of defeating the point of autoloading." ;;;###autoload (defun godoc (query) - "Show Go documentation for QUERY, much like M-x man." + "Show Go documentation for QUERY, much like \\<go-mode-map>\\[man]." (interactive (list (godoc--read-query))) (go--godoc query godoc-command)) @@ -1317,7 +1255,7 @@ declaration." (go-play-region (point-min) (point-max))) (defun go-play-region (start end) - "Send the region to the Playground. + "Send the region between START and END to the Playground. If non-nil `go-play-browse-function' is called with the Playground URL." (interactive "r") @@ -1381,7 +1319,7 @@ uncommented, otherwise a new import will be added." (interactive (list current-prefix-arg - (replace-regexp-in-string "^[\"']\\|[\"']$" "" (completing-read "Package: " (go--old-completion-list-style (go-packages)))))) + (replace-regexp-in-string "^[\"']\\|[\"']$" "" (completing-read "Package: " (go-packages))))) (save-excursion (let (as line import-start) (if arg @@ -1440,8 +1378,8 @@ If IGNORE-CASE is non-nil, the comparison is case-insensitive." (funcall go-packages-function)) (defun go-packages-native () - "Return a list of all installed Go packages. It looks for -archive files in /pkg/" + "Return a list of all installed Go packages. +It looks for archive files in /pkg/." (sort (delete-dups (cl-mapcan @@ -1460,7 +1398,7 @@ archive files in /pkg/" #'string<)) (defun go-packages-go-list () - "Return a list of all Go packages, using `go list'" + "Return a list of all Go packages, using `go list'." (process-lines go-command "list" "-e" "all")) (defun go-unused-imports-lines () @@ -1523,29 +1461,27 @@ visit FILENAME and go to line LINE and column COLUMN." (defun godef--call (point) "Call godef, acquiring definition position and expression description at POINT." - (if (go--xemacs-p) - (error "godef does not reliably work in XEmacs, expect bad results")) (if (not (buffer-file-name (go--coverage-origin-buffer))) (error "Cannot use godef on a buffer without a file name") - (let ((outbuf (get-buffer-create "*godef*")) + (let ((outbuf (generate-new-buffer "*godef*")) (coding-system-for-read 'utf-8) (coding-system-for-write 'utf-8)) - (with-current-buffer outbuf - (erase-buffer)) - (call-process-region (point-min) - (point-max) - godef-command - nil - outbuf - nil - "-i" - "-t" - "-f" - (file-truename (buffer-file-name (go--coverage-origin-buffer))) - "-o" - (number-to-string (go--position-bytes point))) - (with-current-buffer outbuf - (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n"))))) + (prog2 + (call-process-region (point-min) + (point-max) + godef-command + nil + outbuf + nil + "-i" + "-t" + "-f" + (file-truename (buffer-file-name (go--coverage-origin-buffer))) + "-o" + (number-to-string (position-bytes point))) + (with-current-buffer outbuf + (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n")) + (kill-buffer outbuf))))) (defun godef--successful-p (output) (not (or (string= "-" output) @@ -1580,7 +1516,10 @@ description at POINT." (if (not (godef--successful-p file)) (message "%s" (godef--error file)) (push-mark) - (ring-insert find-tag-marker-ring (point-marker)) + (if (eval-when-compile (fboundp 'xref-push-marker-stack)) + ;; TODO: Integrate this facility with XRef. + (xref-push-marker-stack) + (ring-insert find-tag-marker-ring (point-marker))) (godef--find-file-line-column file other-window))) (file-error (message "Could not run godef binary")))) @@ -1758,7 +1697,7 @@ If ARG is non-nil, anonymous functions are ignored." ;; should search forward instead. (when (not (looking-at "\\<func\\>")) (re-search-forward "\\<func\\>" nil t) - (forward-word -1)) + (go--forward-word -1)) ;; If we have landed at an anonymous function, it is possible that we ;; were not inside it but below it. If we were not inside it, we should @@ -1789,7 +1728,8 @@ If ARG is non-nil, anonymous functions are ignored." (skip-chars-forward "^{") (forward-char) (or (go-in-string-or-comment-p) - (looking-back "\\(struct\\|interface\\)\\s-*{")))) + (looking-back "\\(struct\\|interface\\)\\s-*{" + (line-beginning-position))))) (backward-char)) (defun go--in-function-p (compare-point) @@ -1801,7 +1741,7 @@ If ARG is non-nil, anonymous functions are ignored." (go--goto-opening-curly-brace) (unless (looking-at "{") - (error "expected to be looking at opening curly brace")) + (error "Expected to be looking at opening curly brace")) (forward-list 1) (and (>= compare-point start) (<= compare-point (point)))))) @@ -1824,7 +1764,7 @@ If ARG is non-nil, anonymous functions are skipped." (when (looking-at "\\<func (") (setq words 3 chars 2)) - (forward-word words) + (go--forward-word words) (forward-char chars) (when (looking-at "Test") (forward-char 4))))) @@ -1835,7 +1775,7 @@ If ARG is non-nil, anonymous functions are skipped." If ARG is non-nil, anonymous functions are skipped." (interactive "P") (go-goto-function-name arg) - (forward-word 1) + (go--forward-word 1) (forward-char 1)) (defun go--goto-return-values (&optional arg) @@ -1870,7 +1810,7 @@ If ARG is non-nil, anonymous functions are skipped." If there is none, add parenthesis to add one. Anonymous functions cannot have method receivers, so when this is called -interactively anonymous functions will be skipped. If called programmatically, +interactively anonymous functions will be skipped. If called programmatically, an error is raised unless ARG is non-nil." (interactive "P") @@ -1892,7 +1832,7 @@ an error is raised unless ARG is non-nil." If there is none, add one beginning with the name of the current function. Anonymous functions do not have docstrings, so when this is called -interactively anonymous functions will be skipped. If called programmatically, +interactively anonymous functions will be skipped. If called programmatically, an error is raised unless ARG is non-nil." (interactive "P") @@ -1930,7 +1870,7 @@ an error is raised unless ARG is non-nil." "Return the name of the surrounding function. If ARG is non-nil, anonymous functions will be ignored and the -name returned will be that of the top-level function. If ARG is +name returned will be that of the top-level function. If ARG is nil and the surrounding function is anonymous, nil will be returned." (when (or (not (go--in-anonymous-funcion-p)) diff --git a/go-rename.el b/go-rename.el new file mode 100644 index 0000000..5b75e65 --- /dev/null +++ b/go-rename.el @@ -0,0 +1,108 @@ +;;; go-rename.el --- Integration of the 'gorename' tool into Emacs. + +;; Copyright 2014 The Go Authors. All rights reserved. +;; Use of this source code is governed by a BSD-style +;; license that can be found in the LICENSE file. + +;; Version: 0.1 +;; Package-Requires: ((go-mode "1.3.1")) +;; Keywords: tools + +;;; Commentary: + +;; To install: + +;; % go get golang.org/x/tools/cmd/gorename +;; % go build golang.org/x/tools/cmd/gorename +;; % mv gorename $HOME/bin/ # or elsewhere on $PATH + +;; The go-rename-command variable can be customized to specify an +;; alternative location for the installed command. + +;;; Code: + +(require 'cl-lib) +(require 'compile) +(require 'go-mode) +(require 'thingatpt) + +(defgroup go-rename nil + "Options specific to the Go rename." + :group 'go) + +(defcustom go-rename-command "gorename" + "The `gorename' command; by the default, $PATH is searched." + :type 'string + :group 'go-rename) + +;;;###autoload +(defun go-rename (new-name &optional force) + "Rename the entity denoted by the identifier at point, using +the `gorename' tool. With FORCE, call `gorename' with the +`-force' flag." + (interactive (list (read-string "New name: " (thing-at-point 'symbol)) + current-prefix-arg)) + (if (not buffer-file-name) + (error "Cannot use go-rename on a buffer without a file name")) + ;; It's not sufficient to save the current buffer if modified, + ;; since if gofmt-before-save is on the before-save-hook, + ;; saving will disturb the selected region. + (if (buffer-modified-p) + (error "Please save the current buffer before invoking go-rename")) + ;; Prompt-save all other modified Go buffers, since they might get written. + (save-some-buffers nil #'(lambda () + (and (buffer-file-name) + (string= (file-name-extension (buffer-file-name)) ".go")))) + (let* ((posflag (format "-offset=%s:#%d" + buffer-file-name + (1- (position-bytes (point))))) + (env-vars (go-root-and-paths)) + (goroot-env (concat "GOROOT=" (car env-vars))) + (gopath-env (concat "GOPATH=" (mapconcat #'identity (cdr env-vars) ":"))) + success) + (with-current-buffer (get-buffer-create "*go-rename*") + (setq buffer-read-only nil) + (erase-buffer) + (let ((args (append (list go-rename-command nil t nil posflag "-to" new-name) (if force '("-force"))))) + ;; Log the command to *Messages*, for debugging. + (message "Command: %s:" args) + (message "Running gorename...") + ;; Use dynamic binding to modify/restore the environment + (setq success (zerop (let ((process-environment (cl-list* goroot-env gopath-env process-environment))) + (apply #'call-process args)))) + (insert "\n") + (compilation-mode) + (setq compilation-error-screen-columns nil) + + ;; On success, print the one-line result in the message bar, + ;; and hide the *go-rename* buffer. + (if success + (progn + (message "%s" (go--buffer-string-no-trailing-space)) + (gofmt--kill-error-buffer (current-buffer))) + ;; failure + (let ((w (display-buffer (current-buffer)))) + (message "gorename exited") + (set-window-point w (point-min))))))) + + ;; Reload the modified files, saving line/col. + ;; (Don't restore the point since the text has changed.) + ;; + ;; TODO(adonovan): should we also do this for all other files + ;; that were updated (the tool can print them)? + (let ((line (line-number-at-pos)) + (col (current-column))) + (revert-buffer t t t) ; safe, because we just saved it + (goto-char (point-min)) + (forward-line (1- line)) + (forward-char col))) + + +(defun go--buffer-string-no-trailing-space () + (replace-regexp-in-string "[\t\n ]*\\'" + "" + (buffer-substring (point-min) (point-max)))) + +(provide 'go-rename) + +;;; go-rename.el ends here |