1 ;;; go-mode.el --- Major mode for the Go programming language
3 ;; Copyright 2013 The go-mode Authors. All rights reserved.
4 ;; Use of this source code is governed by a BSD-style
5 ;; license that can be found in the LICENSE file.
7 ;; Author: The go-mode Authors
9 ;; Keywords: languages go
10 ;; URL: https://github.com/dominikh/go-mode.el
12 ;; This file is not part of GNU Emacs.
23 ;; XEmacs compatibility guidelines
24 ;; - Minimum required version of XEmacs: 21.5.32
25 ;; - Feature that cannot be backported: POSIX character classes in
26 ;; regular expressions
27 ;; - Functions that could be backported but won't because 21.5.32
28 ;; covers them: plenty.
29 ;; - Features that are still partly broken:
30 ;; - godef will not work correctly if multibyte characters are
32 ;; - Fontification will not handle unicode correctly
34 ;; - Do not use \_< and \_> regexp delimiters directly; use
35 ;; go--regexp-enclose-in-symbol
37 ;; - The character `_` must not be a symbol constituent but a
38 ;; character constituent
40 ;; - Do not use process-lines
42 ;; - Use go--old-completion-list-style when using a plain list as the
43 ;; collection for completing-read
45 ;; - Use go--position-bytes instead of position-bytes
46 (defmacro go--xemacs-p ()
49 (defun go--delete-whole-line (&optional arg)
50 "Delete the current line without putting it in the `kill-ring'.
51 Derived from function `kill-whole-line'. ARG is defined as for that
56 (save-excursion (forward-visible-line 0) (eobp)))
57 (signal 'end-of-buffer nil))
60 (save-excursion (end-of-visible-line) (bobp)))
61 (signal 'beginning-of-buffer nil))
63 (delete-region (progn (forward-visible-line 0) (point))
64 (progn (end-of-visible-line) (point))))
66 (delete-region (progn (end-of-visible-line) (point))
67 (progn (forward-visible-line (1+ arg))
72 (delete-region (progn (forward-visible-line 0) (point))
73 (progn (forward-visible-line arg) (point))))))
75 ;; declare-function is an empty macro that only byte-compile cares
76 ;; about. Wrap in always false if to satisfy Emacsen without that
79 (declare-function go--position-bytes "go-mode" (point)))
81 ;; XEmacs unfortunately does not offer position-bytes. We can fall
82 ;; back to just using (point), but it will be incorrect as soon as
83 ;; multibyte characters are being used.
84 (if (fboundp 'position-bytes)
85 (defalias 'go--position-bytes #'position-bytes)
86 (defun go--position-bytes (point) point))
88 (defun go--old-completion-list-style (list)
89 (mapcar (lambda (x) (cons x nil)) list))
91 ;; GNU Emacs 24 has prog-mode, older GNU Emacs and XEmacs do not, so
92 ;; copy its definition for those.
93 (if (not (fboundp 'prog-mode))
94 (define-derived-mode prog-mode fundamental-mode "Prog"
95 "Major mode for editing source code."
96 (set (make-local-variable 'require-final-newline) mode-require-final-newline)
97 (set (make-local-variable 'parse-sexp-ignore-comments) t)
98 (setq bidi-paragraph-direction 'left-to-right)))
100 (defun go--regexp-enclose-in-symbol (s)
101 "Enclose S as regexp symbol.
102 XEmacs does not support \\_<, GNU Emacs does. In GNU Emacs we
103 make extensive use of \\_< to support unicode in identifiers.
104 Until we come up with a better solution for XEmacs, this solution
105 will break fontification in XEmacs for identifiers such as
106 \"typeµ\". XEmacs will consider \"type\" a keyword, GNU Emacs
109 (concat "\\<" s "\\>")
110 (concat "\\_<" s "\\_>")))
112 (defun go-goto-opening-parenthesis (&optional _legacy-unused)
113 "Move up one level of parentheses."
114 ;; The old implementation of go-goto-opening-parenthesis had an
115 ;; optional argument to speed up the function. It didn't change the
116 ;; function's outcome.
118 ;; Silently fail if there's no matching opening parenthesis.
124 (defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]")
125 (defconst go-identifier-regexp "[[:word:][:multibyte:]]+")
126 (defconst go-type-name-no-prefix-regexp "\\(?:[[:word:][:multibyte:]]+\\.\\)?[[:word:][:multibyte:]]+")
127 (defconst go-qualified-identifier-regexp (concat go-identifier-regexp "\\." go-identifier-regexp))
128 (defconst go-label-regexp go-identifier-regexp)
129 (defconst go-type-regexp "[[:word:][:multibyte:]*]+")
130 (defconst go-func-regexp (concat (go--regexp-enclose-in-symbol "func") "\\s *\\(" go-identifier-regexp "\\)"))
131 (defconst go-func-meth-regexp (concat
132 (go--regexp-enclose-in-symbol "func") "\\s *\\(?:(\\s *"
133 "\\(" go-identifier-regexp "\\s +\\)?" go-type-regexp
138 (defconst go-builtins
139 '("append" "cap" "close" "complex" "copy"
140 "delete" "imag" "len" "make" "new"
141 "panic" "print" "println" "real" "recover")
142 "All built-in functions in the Go language. Used for font locking.")
144 (defconst go-mode-keywords
145 '("break" "default" "func" "interface" "select"
146 "case" "defer" "go" "map" "struct"
147 "chan" "else" "goto" "package" "switch"
148 "const" "fallthrough" "if" "range" "type"
149 "continue" "for" "import" "return" "var")
150 "All keywords in the Go language. Used for font locking.")
152 (defconst go-constants '("nil" "true" "false" "iota"))
153 (defconst go-type-name-regexp (concat "\\(?:[*(]\\)*\\(\\(?:" go-identifier-regexp "\\.\\)?" go-identifier-regexp "\\)"))
155 ;; Maximum number of identifiers that can be highlighted as type names
156 ;; in one function type/declaration.
157 (defconst go--font-lock-func-param-num-groups 16)
159 (defvar go-dangling-cache)
160 (defvar go-godoc-history nil)
161 (defvar go--coverage-current-file-name)
164 "Major mode for editing Go code."
165 :link '(url-link "https://github.com/dominikh/go-mode.el")
168 (defgroup go-cover nil
169 "Options specific to `cover`."
172 (defcustom go-fontify-function-calls t
173 "Fontify function and method calls if this is non-nil."
177 (defcustom go-mode-hook nil
178 "Hook called by `go-mode'."
182 (defcustom go-command "go"
184 Some users have multiple Go development trees and invoke the 'go'
185 tool via a wrapper that sets GOROOT and GOPATH based on the
186 current directory. Such users should customize this variable to
187 point to the wrapper script."
191 (defcustom gofmt-command "gofmt"
192 "The 'gofmt' command.
193 Some users may replace this with 'goimports'
194 from https://github.com/bradfitz/goimports."
198 (defcustom gofmt-show-errors 'buffer
199 "Where to display gofmt error output.
200 It can either be displayed in its own buffer, in the echo area, or not at all.
202 Please note that Emacs outputs to the echo area when writing
203 files and will overwrite gofmt's echo output if used from inside
204 a `before-save-hook'."
206 (const :tag "Own buffer" buffer)
207 (const :tag "Echo area" echo)
208 (const :tag "None" nil))
211 (defcustom godef-command "godef"
212 "The 'godef' command."
216 (defcustom go-other-file-alist
217 '(("_test\\.go\\'" (".go"))
218 ("\\.go\\'" ("_test.go")))
219 "See the documentation of `ff-other-file-alist' for details."
220 :type '(repeat (list regexp (choice (repeat string) function)))
223 (defun go--kill-new-message (url)
224 "Make URL the latest kill and print a message."
228 (defcustom go-play-browse-function 'go--kill-new-message
229 "Function to call with the Playground URL.
230 See `go-play-region' for more details."
232 (const :tag "Nothing" nil)
233 (const :tag "Kill + Message" go--kill-new-message)
234 (const :tag "Browse URL" browse-url)
235 (function :tag "Call function"))
238 (defcustom go-coverage-display-buffer-func 'display-buffer-reuse-window
239 "How `go-coverage' should display the coverage buffer.
240 See `display-buffer' for a list of possible functions."
244 (defface go-coverage-untracked
245 '((t (:foreground "#505050")))
246 "Coverage color of untracked code."
249 (defface go-coverage-0
250 '((t (:foreground "#c00000")))
251 "Coverage color for uncovered code."
253 (defface go-coverage-1
254 '((t (:foreground "#808080")))
255 "Coverage color for covered code with weight 1."
257 (defface go-coverage-2
258 '((t (:foreground "#748c83")))
259 "Coverage color for covered code with weight 2."
261 (defface go-coverage-3
262 '((t (:foreground "#689886")))
263 "Coverage color for covered code with weight 3."
265 (defface go-coverage-4
266 '((t (:foreground "#5ca489")))
267 "Coverage color for covered code with weight 4."
269 (defface go-coverage-5
270 '((t (:foreground "#50b08c")))
271 "Coverage color for covered code with weight 5."
273 (defface go-coverage-6
274 '((t (:foreground "#44bc8f")))
275 "Coverage color for covered code with weight 6."
277 (defface go-coverage-7
278 '((t (:foreground "#38c892")))
279 "Coverage color for covered code with weight 7."
281 (defface go-coverage-8
282 '((t (:foreground "#2cd495")))
283 "Coverage color for covered code with weight 8.
284 For mode=set, all covered lines will have this weight."
286 (defface go-coverage-9
287 '((t (:foreground "#20e098")))
288 "Coverage color for covered code with weight 9."
290 (defface go-coverage-10
291 '((t (:foreground "#14ec9b")))
292 "Coverage color for covered code with weight 10."
294 (defface go-coverage-covered
295 '((t (:foreground "#2cd495")))
296 "Coverage color of covered code."
299 (defvar go-mode-syntax-table
300 (let ((st (make-syntax-table)))
301 (modify-syntax-entry ?+ "." st)
302 (modify-syntax-entry ?- "." st)
303 (modify-syntax-entry ?% "." st)
304 (modify-syntax-entry ?& "." st)
305 (modify-syntax-entry ?| "." st)
306 (modify-syntax-entry ?^ "." st)
307 (modify-syntax-entry ?! "." st)
308 (modify-syntax-entry ?= "." st)
309 (modify-syntax-entry ?< "." st)
310 (modify-syntax-entry ?> "." st)
311 (modify-syntax-entry ?/ (if (go--xemacs-p) ". 1456" ". 124b") st)
312 (modify-syntax-entry ?* ". 23" st)
313 (modify-syntax-entry ?\n "> b" st)
314 (modify-syntax-entry ?\" "\"" st)
315 (modify-syntax-entry ?\' "\"" st)
316 (modify-syntax-entry ?` "\"" st)
317 (modify-syntax-entry ?\\ "\\" st)
318 ;; It would be nicer to have _ as a symbol constituent, but that
319 ;; would trip up XEmacs, which does not support the \_< anchor
320 (modify-syntax-entry ?_ "w" st)
323 "Syntax table for Go mode.")
325 (defun go--build-font-lock-keywords ()
326 ;; we cannot use 'symbols in regexp-opt because GNU Emacs <24
327 ;; doesn't understand that
330 ,@(mapcar (lambda (x) `(,x font-lock-type-face))
331 (number-sequence 1 go--font-lock-func-param-num-groups)))
332 (,(go--regexp-enclose-in-symbol (regexp-opt go-mode-keywords t)) . font-lock-keyword-face)
333 (,(concat "\\(" (go--regexp-enclose-in-symbol (regexp-opt go-builtins t)) "\\)[[:space:]]*(") 1 font-lock-builtin-face)
334 (,(go--regexp-enclose-in-symbol (regexp-opt go-constants t)) . font-lock-constant-face)
335 (,go-func-regexp 1 font-lock-function-name-face)) ;; function (not method) name
337 (if go-fontify-function-calls
338 `((,(concat "\\(" go-identifier-regexp "\\)[[:space:]]*(") 1 font-lock-function-name-face) ;; function call/method name
339 (,(concat "[^[:word:][:multibyte:]](\\(" go-identifier-regexp "\\))[[:space:]]*(") 1 font-lock-function-name-face)) ;; bracketed function call
340 `((,go-func-meth-regexp 2 font-lock-function-name-face))) ;; method name
343 ("\\(`[^`]*`\\)" 1 font-lock-multiline) ;; raw string literal, needed for font-lock-syntactic-keywords
344 (,(concat (go--regexp-enclose-in-symbol "type") "[[:space:]]+\\([^[:space:]]+\\)") 1 font-lock-type-face) ;; types
345 (,(concat (go--regexp-enclose-in-symbol "type") "[[:space:]]+" go-identifier-regexp "[[:space:]]*" go-type-name-regexp) 1 font-lock-type-face) ;; types
346 (,(concat "[^[:word:][:multibyte:]]\\[\\([[:digit:]]+\\|\\.\\.\\.\\)?\\]" go-type-name-regexp) 2 font-lock-type-face) ;; Arrays/slices
347 (,(concat "\\(" go-identifier-regexp "\\)" "{") 1 font-lock-type-face)
348 (,(concat (go--regexp-enclose-in-symbol "map") "\\[[^]]+\\]" go-type-name-regexp) 1 font-lock-type-face) ;; map value type
349 (,(concat (go--regexp-enclose-in-symbol "map") "\\[" go-type-name-regexp) 1 font-lock-type-face) ;; map key type
350 (,(concat (go--regexp-enclose-in-symbol "chan") "[[:space:]]*\\(?:<-[[:space:]]*\\)?" go-type-name-regexp) 1 font-lock-type-face) ;; channel type
351 (,(concat (go--regexp-enclose-in-symbol "\\(?:new\\|make\\)") "\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp) 1 font-lock-type-face) ;; new/make type
352 ;; TODO do we actually need this one or isn't it just a function call?
353 (,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face) ;; Type conversion
354 ;; Like the original go-mode this also marks compound literal
355 ;; fields. There, it was marked as to fix, but I grew quite
356 ;; accustomed to it, so it'll stay for now.
357 (,(concat "^[[:space:]]*\\(" go-label-regexp "\\)[[:space:]]*:\\(\\S.\\|$\\)") 1 font-lock-constant-face) ;; Labels and compound literal fields
358 (,(concat (go--regexp-enclose-in-symbol "\\(goto\\|break\\|continue\\)") "[[:space:]]*\\(" go-label-regexp "\\)") 2 font-lock-constant-face)))) ;; labels in goto/break/continue
360 (defconst go--font-lock-syntactic-keywords
361 ;; Override syntax property of raw string literal contents, so that
362 ;; backslashes have no special meaning in ``. Used in Emacs 23 or older.
363 '((go--match-raw-string-literal
365 (2 (15 . nil)) ;; 15 = "generic string"
369 (let ((m (make-sparse-keymap)))
370 (define-key m "}" #'go-mode-insert-and-indent)
371 (define-key m ")" #'go-mode-insert-and-indent)
372 (define-key m "," #'go-mode-insert-and-indent)
373 (define-key m ":" #'go-mode-insert-and-indent)
374 (define-key m "=" #'go-mode-insert-and-indent)
375 (define-key m (kbd "C-c C-a") #'go-import-add)
376 (define-key m (kbd "C-c C-j") #'godef-jump)
377 (define-key m (kbd "C-x 4 C-c C-j") #'godef-jump-other-window)
378 (define-key m (kbd "C-c C-d") #'godef-describe)
380 "Keymap used by Go mode to implement electric keys.")
382 (easy-menu-define go-mode-menu go-mode-map
385 ["Describe Expression" godef-describe t]
386 ["Jump to Definition" godef-jump t]
388 ["Add Import" go-import-add t]
389 ["Remove Unused Imports" go-remove-unused-imports t]
390 ["Go to Imports" go-goto-imports t]
393 ["Send Buffer" go-play-buffer t]
394 ["Send Region" go-play-region t]
395 ["Download" go-download-play t])
397 ["Coverage" go-coverage t]
401 ["Customize Mode" (customize-group 'go) t]))
403 (defun go-mode-insert-and-indent (key)
404 "Invoke the global binding of KEY, then reindent the line."
406 (interactive (list (this-command-keys)))
407 (call-interactively (lookup-key (current-global-map) key))
408 (indent-according-to-mode))
410 (defmacro go-paren-level ()
411 `(car (syntax-ppss)))
413 (defmacro go-in-string-or-comment-p ()
414 `(nth 8 (syntax-ppss)))
416 (defmacro go-in-string-p ()
417 `(nth 3 (syntax-ppss)))
419 (defmacro go-in-comment-p ()
420 `(nth 4 (syntax-ppss)))
422 (defmacro go-goto-beginning-of-string-or-comment ()
423 `(goto-char (nth 8 (syntax-ppss))))
425 (defun go--backward-irrelevant (&optional stop-at-string)
426 "Skip backwards over any characters that are irrelevant for
427 indentation and related tasks.
429 It skips over whitespace, comments, cases and labels and, if
430 STOP-AT-STRING is not true, over strings."
432 (let (pos (start-pos (point)))
433 (skip-chars-backward "\n\s\t")
434 (if (and (save-excursion (beginning-of-line) (go-in-string-p)) (looking-back "`") (not stop-at-string))
436 (if (and (go-in-string-p) (not stop-at-string))
437 (go-goto-beginning-of-string-or-comment))
438 (if (looking-back "\\*/")
440 (if (go-in-comment-p)
441 (go-goto-beginning-of-string-or-comment))
444 (if (or (looking-at (concat "^" go-label-regexp ":")) (looking-at "^[[:space:]]*\\(case .+\\|default\\):"))
447 (if (/= start-pos (point))
448 (go--backward-irrelevant stop-at-string))
449 (/= start-pos (point))))
451 (defun go--buffer-narrowed-p ()
452 "Return non-nil if the current buffer is narrowed."
457 (defun go--match-raw-string-literal (end)
458 "Search for a raw string literal.
459 Set point to the end of the occurence found on success. Return nil on failure."
460 (unless (go-in-string-or-comment-p)
461 (when (search-forward "`" end t)
462 (goto-char (match-beginning 0))
463 (if (go-in-string-or-comment-p)
464 (progn (goto-char (match-end 0))
465 (go--match-raw-string-literal end))
466 (when (looking-at "\\(`\\)\\([^`]*\\)\\(`\\)")
467 (goto-char (match-end 0))
470 (defun go-previous-line-has-dangling-op-p ()
471 "Return non-nil if the current line is a continuation line."
472 (let* ((cur-line (line-number-at-pos))
473 (val (gethash cur-line go-dangling-cache 'nope)))
474 (if (or (go--buffer-narrowed-p) (equal val 'nope))
477 (go--backward-irrelevant t)
478 (setq val (looking-back go-dangling-operators-regexp))
479 (if (not (go--buffer-narrowed-p))
480 (puthash cur-line val go-dangling-cache))))
483 (defun go--at-function-definition ()
484 "Return non-nil if point is on the opening curly brace of a
487 We do this by first calling (beginning-of-defun), which will take
488 us to the start of *some* function. We then look for the opening
489 curly brace of that function and compare its position against the
490 curly brace we are checking. If they match, we return non-nil."
491 (if (= (char-after) ?\{)
493 (let ((old-point (point))
496 (when (looking-at "func ")
497 (setq start-nesting (go-paren-level))
498 (skip-chars-forward "^{")
499 (while (> (go-paren-level) start-nesting)
501 (skip-chars-forward "^{") 0)
502 (if (and (= (go-paren-level) start-nesting) (= old-point (point)))
505 (defun go--indentation-for-opening-parenthesis ()
506 "Return the semantic indentation for the current opening parenthesis.
508 If point is on an opening curly brace and said curly brace
509 belongs to a function declaration, the indentation of the func
510 keyword will be returned. Otherwise the indentation of the
511 current line will be returned."
513 (if (go--at-function-definition)
516 (current-indentation))
517 (current-indentation))))
519 (defun go-indentation-at-point ()
522 (back-to-indentation)
523 (setq start-nesting (go-paren-level))
527 (current-indentation))
528 ((looking-at "[])}]")
529 (go-goto-opening-parenthesis)
530 (if (go-previous-line-has-dangling-op-p)
531 (- (current-indentation) tab-width)
532 (go--indentation-for-opening-parenthesis)))
533 ((progn (go--backward-irrelevant t) (looking-back go-dangling-operators-regexp))
534 ;; only one nesting for all dangling operators in one operation
535 (if (go-previous-line-has-dangling-op-p)
536 (current-indentation)
537 (+ (current-indentation) tab-width)))
538 ((zerop (go-paren-level))
540 ((progn (go-goto-opening-parenthesis) (< (go-paren-level) start-nesting))
541 (if (go-previous-line-has-dangling-op-p)
542 (current-indentation)
543 (+ (go--indentation-for-opening-parenthesis) tab-width)))
545 (current-indentation))))))
547 (defun go-mode-indent-line ()
551 (pos (- (point-max) (point)))
553 (beg (line-beginning-position)))
554 (back-to-indentation)
555 (if (go-in-string-or-comment-p)
557 (setq indent (go-indentation-at-point))
558 (if (looking-at (concat go-label-regexp ":\\([[:space:]]*/.+\\)?$\\|case .+:\\|default:"))
559 (decf indent tab-width))
560 (setq shift-amt (- indent (current-column)))
561 (if (zerop shift-amt)
563 (delete-region beg (point))
565 ;; If initial point was within line's indentation,
566 ;; position after the indentation. Else stay at same point in text.
567 (if (> (- (point-max) pos) (point))
568 (goto-char (- (point-max) pos))))))
570 (defun go-beginning-of-defun (&optional count)
573 (setq count (or count 1))
575 (dotimes (i (abs count))
577 (while (and (not failure)
578 (or first (go-in-string-or-comment-p)))
581 (go--backward-irrelevant)
582 (if (not (re-search-backward go-func-meth-regexp nil t))
584 (if (looking-at go-func-meth-regexp)
586 (if (not (re-search-forward go-func-meth-regexp nil t))
593 (defun go-end-of-defun ()
595 ;; It can happen that we're not placed before a function by emacs
596 (if (not (looking-at "func"))
597 (go-beginning-of-defun -1))
598 ;; Find the { that starts the function, i.e., the next { that isn't
599 ;; preceded by struct or interface, or a comment or struct tag. BUG:
600 ;; breaks if there's a comment between the struct/interface keyword and
601 ;; bracket, like this:
603 ;; struct /* why? */ {
605 (skip-chars-forward "^{")
607 (or (go-in-string-or-comment-p)
608 (looking-back "\\(struct\\|interface\\)\\s-*{"))))
609 (setq orig-level (go-paren-level))
610 (while (>= (go-paren-level) orig-level)
611 (skip-chars-forward "^}")
614 (defun go--find-enclosing-parentheses (position)
615 "Return points of outermost '(' and ')' surrounding POSITION if
616 such parentheses exist.
618 If outermost '(' exists but ')' does not, it returns the next blank
619 line or end-of-buffer position instead of the position of the closing
622 If the starting parenthesis is not found, it returns (POSITION
627 (while (> (go-paren-level) 0)
628 (re-search-backward "[(\\[{]" nil t)
629 (when (looking-at "(")
632 (list position position)
634 (while (and (> (go-paren-level) 0)
635 (search-forward ")" nil t)))
636 (when (> (go-paren-level) 0)
637 (unless (re-search-forward "^[[:space:]]*$" nil t)
638 (goto-char (point-max))))
639 (list beg (point))))))
641 (defun go--search-next-comma (end)
642 "Search forward from point for a comma whose nesting level is
643 the same as point. If it reaches the end of line or a closing
644 parenthesis before a comma, it stops at it."
645 (let ((orig-level (go-paren-level)))
646 (while (and (< (point) end)
647 (or (looking-at "[^,)\n]")
648 (> (go-paren-level) orig-level)))
650 (when (and (looking-at ",")
651 (< (point) (1- end)))
654 (defun go--looking-at-keyword ()
655 (and (looking-at (concat "\\(" go-identifier-regexp "\\)"))
656 (member (match-string 1) go-mode-keywords)))
658 (defun go--match-func (end)
659 "Search for identifiers used as type names from a function
660 parameter list, and set the identifier positions as the results
661 of last search. Return t if search succeeded."
662 (when (re-search-forward (go--regexp-enclose-in-symbol "func") end t)
663 (let ((regions (go--match-func-type-names end)))
665 ;; Nothing to highlight. This can happen if the current func
666 ;; is "func()". Try next one.
668 ;; There are something to highlight. Set those positions as
669 ;; last search results.
670 (setq regions (go--filter-match-data regions end))
672 (set-match-data (go--make-match-data regions))
675 (defun go--match-func-type-names (end)
677 ;; Function declaration (e.g. "func foo(")
678 ((looking-at (concat "[[:space:]\n]*" go-identifier-regexp "[[:space:]\n]*("))
679 (goto-char (match-end 0))
680 (nconc (go--match-parameter-list end)
681 (go--match-function-result end)))
682 ;; Method declaration, function literal, or function type
683 ((looking-at "[[:space:]]*(")
684 (goto-char (match-end 0))
685 (let ((regions (go--match-parameter-list end)))
686 ;; Method declaration (e.g. "func (x y) foo(")
687 (when (looking-at (concat "[[:space:]]*" go-identifier-regexp "[[:space:]\n]*("))
688 (goto-char (match-end 0))
689 (setq regions (nconc regions (go--match-parameter-list end))))
690 (nconc regions (go--match-function-result end))))))
692 (defun go--parameter-list-type (end)
693 "Return `present' if the parameter list has names, or `absent' if
694 not, assuming point is at the beginning of a parameter list, just
697 (skip-chars-forward "[:space:]\n" end)
698 (cond ((> (point) end)
700 ((looking-at (concat go-identifier-regexp "[[:space:]\n]*,"))
701 (goto-char (match-end 0))
702 (go--parameter-list-type end))
703 ((or (looking-at go-qualified-identifier-regexp)
704 (looking-at (concat go-type-name-no-prefix-regexp "[[:space:]\n]*\\(?:)\\|\\'\\)"))
705 (go--looking-at-keyword)
706 (looking-at "[*\\[]\\|\\.\\.\\.\\|\\'"))
710 (defconst go--opt-dotdotdot-regexp "\\(?:\\.\\.\\.\\)?")
711 (defconst go--parameter-type-regexp
712 (concat go--opt-dotdotdot-regexp "[[:space:]*\n]*\\(" go-type-name-no-prefix-regexp "\\)[[:space:]\n]*\\([,)]\\|\\'\\)"))
713 (defconst go--func-type-in-parameter-list-regexp
714 (concat go--opt-dotdotdot-regexp "[[:space:]*\n]*\\(" (go--regexp-enclose-in-symbol "func") "\\)"))
716 (defun go--match-parameters-common (identifier-regexp end)
719 (while (progn (skip-chars-forward "[:space:]\n" end)
720 (and (not (looking-at "\\(?:)\\|\\'\\)"))
725 ((looking-at (concat identifier-regexp go--parameter-type-regexp))
726 (setq acc (nconc acc (list (match-beginning 1) (match-end 1))))
727 (goto-char (match-beginning 2)))
728 ((looking-at (concat identifier-regexp go--func-type-in-parameter-list-regexp))
729 (goto-char (match-beginning 1))
730 (setq acc (nconc acc (go--match-func-type-names end)))
731 (go--search-next-comma end))
733 (go--search-next-comma end))))
734 (when (and (looking-at ")")
739 (defun go--match-parameters-with-identifier-list (end)
740 (go--match-parameters-common
741 (concat go-identifier-regexp "[[:space:]\n]+")
744 (defun go--match-parameters-without-identifier-list (end)
745 (go--match-parameters-common "" end))
747 (defun go--filter-match-data (regions end)
748 "Remove points from REGIONS if they are beyond END.
749 REGIONS are a list whose size is multiple of 2. Element 2n is beginning of a
750 region and 2n+1 is end of it.
752 This function is used to make sure we don't override end point
753 that `font-lock-mode' gave to us."
755 (let* ((vec (vconcat regions))
758 (while (and (< i len)
759 (<= (nth i regions) end)
760 (<= (nth (1+ i) regions) end))
767 (butlast regions (- (length regions) i)))))))
769 (defun go--make-match-data (regions)
770 (let ((deficit (- (* 2 go--font-lock-func-param-num-groups)
773 (let ((last (car (last regions))))
774 (setq regions (nconc regions (make-list deficit last))))))
775 `(,(car regions) ,@(last regions) ,@regions))
777 (defun go--match-parameter-list (end)
778 "Return a list of identifier positions that are used as type
779 names in a function parameter list, assuming point is at the
780 beginning of a parameter list. Return nil if the text after
781 point does not look like a parameter list.
783 Set point to end of closing parenthesis on success.
785 In Go, the names must either all be present or all be absent
786 within a list of parameters.
788 Parsing a parameter list is a little bit complicated because we
789 have to scan through the parameter list to determine whether or
790 not the list has names. Until a type name is found or reaching
791 end of a parameter list, we are not sure which form the parameter
794 For example, X and Y are type names in a parameter list \"(X,
795 Y)\" but are parameter names in \"(X, Y int)\". We cannot say if
796 X is a type name until we see int after Y.
798 Note that even \"(int, float T)\" is a valid parameter
799 list. Builtin type names are not reserved words. In this example,
800 int and float are parameter names and only T is a type name.
802 In this function, we first scan the parameter list to see if the
803 list has names, and then handle it accordingly."
804 (let ((name (go--parameter-list-type end)))
805 (cond ((eq name 'present)
806 (go--match-parameters-with-identifier-list end))
808 (go--match-parameters-without-identifier-list end))
811 (defun go--match-function-result (end)
812 "Return a list of identifier positions that are used as type
813 names in a function result, assuming point is at the beginning of
816 Function result is a unparenthesized type or a parameter list."
817 (cond ((and (looking-at (concat "[[:space:]*]*\\(" go-type-name-no-prefix-regexp "\\)"))
818 (not (member (match-string 1) go-mode-keywords)))
819 (list (match-beginning 1) (match-end 1)))
820 ((looking-at "[[:space:]]*(")
821 (goto-char (match-end 0))
822 (go--match-parameter-list end))
826 (define-derived-mode go-mode prog-mode "Go"
827 "Major mode for editing Go source text.
829 This mode provides (not just) basic editing capabilities for
830 working with Go code. It offers almost complete syntax
831 highlighting, indentation that is almost identical to gofmt and
832 proper parsing of the buffer content to allow features such as
833 navigation by function, manipulation of comments or detection of
836 In addition to these core features, it offers various features to
837 help with writing Go code. You can directly run buffer content
838 through gofmt, read godoc documentation from within Emacs, modify
839 and clean up the list of package imports or interact with the
840 Playground (uploading and downloading pastes).
842 The following extra functions are defined:
847 - `go-remove-unused-imports'
849 - `go-play-buffer' and `go-play-region'
851 - `godef-describe' and `godef-jump'
854 If you want to automatically run `gofmt' before saving a file,
855 add the following hook to your emacs configuration:
857 \(add-hook 'before-save-hook #'gofmt-before-save)
859 If you want to use `godef-jump' instead of etags (or similar),
860 consider binding godef-jump to `M-.', which is the default key
863 \(add-hook 'go-mode-hook (lambda ()
864 (local-set-key (kbd \"M-.\") #'godef-jump)))
866 Please note that godef is an external dependency. You can install
869 go get github.com/rogpeppe/godef
872 If you're looking for even more integration with Go, namely
873 on-the-fly syntax checking, auto-completion and snippets, it is
874 recommended that you look at flycheck
875 \(see URL `https://github.com/flycheck/flycheck') or flymake in combination
876 with goflymake \(see URL `https://github.com/dougm/goflymake'), gocode
877 \(see URL `https://github.com/nsf/gocode'), go-eldoc
878 \(see URL `github.com/syohex/emacs-go-eldoc') and yasnippet-go
879 \(see URL `https://github.com/dominikh/yasnippet-go')"
882 (set (make-local-variable 'font-lock-defaults)
883 '(go--build-font-lock-keywords))
886 (set (make-local-variable 'indent-line-function) #'go-mode-indent-line)
889 (set (make-local-variable 'comment-start) "// ")
890 (set (make-local-variable 'comment-end) "")
891 (set (make-local-variable 'comment-use-syntax) t)
892 (set (make-local-variable 'comment-start-skip) "\\(//+\\|/\\*+\\)\\s *")
894 (set (make-local-variable 'beginning-of-defun-function) #'go-beginning-of-defun)
895 (set (make-local-variable 'end-of-defun-function) #'go-end-of-defun)
897 (set (make-local-variable 'parse-sexp-lookup-properties) t)
898 (if (boundp 'syntax-propertize-function)
899 (set (make-local-variable 'syntax-propertize-function) #'go-propertize-syntax)
900 (set (make-local-variable 'font-lock-syntactic-keywords)
901 go--font-lock-syntactic-keywords)
902 (set (make-local-variable 'font-lock-multiline) t))
904 (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql))
905 (add-hook 'before-change-functions (lambda (x y) (setq go-dangling-cache (make-hash-table :test 'eql))) t t)
907 ;; ff-find-other-file
908 (setq ff-other-file-alist 'go-other-file-alist)
910 (setq imenu-generic-expression
911 '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1)
912 ("func" "^func *\\(.*\\) {" 1)))
913 (imenu-add-to-menubar "Index")
916 (setq indent-tabs-mode t)
918 ;; Handle unit test failure output in compilation-mode
920 ;; Note the final t argument to add-to-list for append, ie put these at the
921 ;; *ends* of compilation-error-regexp-alist[-alist]. We want go-test to be
922 ;; handled first, otherwise other elements will match that don't work, and
923 ;; those alists are traversed in *reverse* order:
924 ;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2001-12/msg00674.html
925 (when (and (boundp 'compilation-error-regexp-alist)
926 (boundp 'compilation-error-regexp-alist-alist))
927 (add-to-list 'compilation-error-regexp-alist 'go-test t)
928 (add-to-list 'compilation-error-regexp-alist-alist
929 '(go-test . ("^\t+\\([^()\t\n]+\\):\\([0-9]+\\):? .*$" 1 2)) t)))
932 (add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode))
934 (defun go--apply-rcs-patch (patch-buffer)
935 "Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer."
936 (let ((target-buffer (current-buffer))
937 ;; Relative offset between buffer line numbers and line numbers
940 ;; Line numbers in the patch are based on the source file, so
941 ;; we have to keep an offset when making changes to the
944 ;; Appending lines decrements the offset (possibly making it
945 ;; negative), deleting lines increments it. This order
946 ;; simplifies the forward-line invocations.
949 (with-current-buffer patch-buffer
950 (goto-char (point-min))
952 (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
953 (error "invalid rcs patch or internal error in go--apply-rcs-patch"))
955 (let ((action (match-string 1))
956 (from (string-to-number (match-string 2)))
957 (len (string-to-number (match-string 3))))
960 (let ((start (point)))
962 (let ((text (buffer-substring start (point))))
963 (with-current-buffer target-buffer
964 (decf line-offset len)
965 (goto-char (point-min))
966 (forward-line (- from len line-offset))
969 (with-current-buffer target-buffer
970 (go--goto-line (- from line-offset))
971 (incf line-offset len)
972 (go--delete-whole-line len)))
974 (error "invalid rcs patch or internal error in go--apply-rcs-patch")))))))))
977 "Format the current buffer according to the gofmt tool."
979 (let ((tmpfile (make-temp-file "gofmt" nil ".go"))
980 (patchbuf (get-buffer-create "*Gofmt patch*"))
981 (errbuf (if gofmt-show-errors (get-buffer-create "*Gofmt Errors*")))
982 (coding-system-for-read 'utf-8)
983 (coding-system-for-write 'utf-8))
988 (with-current-buffer errbuf
989 (setq buffer-read-only nil)
991 (with-current-buffer patchbuf
994 (write-region nil nil tmpfile)
996 ;; We're using errbuf for the mixed stdout and stderr output. This
997 ;; is not an issue because gofmt -w does not produce any stdout
998 ;; output in case of success.
999 (if (zerop (call-process gofmt-command nil errbuf nil "-w" tmpfile))
1001 (if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
1002 (message "Buffer is already gofmted")
1003 (go--apply-rcs-patch patchbuf)
1004 (message "Applied gofmt"))
1005 (if errbuf (gofmt--kill-error-buffer errbuf)))
1006 (message "Could not apply gofmt")
1007 (if errbuf (gofmt--process-errors (buffer-file-name) tmpfile errbuf)))
1009 (kill-buffer patchbuf)
1010 (delete-file tmpfile))))
1013 (defun gofmt--process-errors (filename tmpfile errbuf)
1014 (with-current-buffer errbuf
1015 (if (eq gofmt-show-errors 'echo)
1017 (message "%s" (buffer-string))
1018 (gofmt--kill-error-buffer errbuf))
1019 ;; Convert the gofmt stderr to something understood by the compilation mode.
1020 (goto-char (point-min))
1021 (insert "gofmt errors:\n")
1022 (while (search-forward-regexp (concat "^\\(" (regexp-quote tmpfile) "\\):") nil t)
1023 (replace-match (file-name-nondirectory filename) t t nil 1))
1025 (display-buffer errbuf))))
1027 (defun gofmt--kill-error-buffer (errbuf)
1028 (let ((win (get-buffer-window errbuf)))
1031 (kill-buffer errbuf))))
1034 (defun gofmt-before-save ()
1035 "Add this to .emacs to run gofmt on the current buffer when saving:
1036 (add-hook 'before-save-hook 'gofmt-before-save).
1038 Note that this will cause go-mode to get loaded the first time
1039 you save any file, kind of defeating the point of autoloading."
1042 (when (eq major-mode 'go-mode) (gofmt)))
1044 (defun godoc--read-query ()
1045 "Read a godoc query from the minibuffer."
1046 ;; Compute the default query as the symbol under the cursor.
1047 ;; TODO: This does the wrong thing for e.g. multipart.NewReader (it only grabs
1048 ;; half) but I see no way to disambiguate that from e.g. foobar.SomeMethod.
1049 (let* ((bounds (bounds-of-thing-at-point 'symbol))
1051 (buffer-substring-no-properties (car bounds)
1053 (completing-read (if symbol
1054 (format "godoc (default %s): " symbol)
1056 (go--old-completion-list-style (go-packages)) nil nil nil 'go-godoc-history symbol)))
1058 (defun godoc--get-buffer (query)
1059 "Get an empty buffer for a godoc query."
1060 (let* ((buffer-name (concat "*godoc " query "*"))
1061 (buffer (get-buffer buffer-name)))
1062 ;; Kill the existing buffer if it already exists.
1063 (when buffer (kill-buffer buffer))
1064 (get-buffer-create buffer-name)))
1066 (defun godoc--buffer-sentinel (proc event)
1067 "Sentinel function run when godoc command completes."
1068 (with-current-buffer (process-buffer proc)
1069 (cond ((string= event "finished\n") ;; Successful exit.
1070 (goto-char (point-min))
1072 (display-buffer (current-buffer) t))
1073 ((/= (process-exit-status proc) 0) ;; Error exit.
1074 (let ((output (buffer-string)))
1075 (kill-buffer (current-buffer))
1076 (message (concat "godoc: " output)))))))
1078 (define-derived-mode godoc-mode special-mode "Godoc"
1079 "Major mode for showing Go documentation."
1083 (defun godoc (query)
1084 "Show Go documentation for QUERY, much like M-x man."
1085 (interactive (list (godoc--read-query)))
1086 (unless (string= query "")
1087 (set-process-sentinel
1088 (start-process-shell-command "godoc" (godoc--get-buffer query)
1089 (concat "godoc " query))
1090 'godoc--buffer-sentinel)
1093 (defun godoc-at-point (point)
1094 "Show Go documentation for the identifier at POINT.
1096 `godoc-at-point' requires godef to work.
1098 Due to a limitation in godoc, it is not possible to differentiate
1099 between functions and methods, which may cause `godoc-at-point'
1100 to display more documentation than desired."
1101 ;; TODO(dominikh): Support executing godoc-at-point on a package
1105 (let* ((output (godef--call point))
1107 (name-parts (split-string (cadr output) " "))
1108 (first (car name-parts)))
1109 (if (not (godef--successful-p file))
1110 (message "%s" (godef--error file))
1111 (godoc (format "%s %s"
1112 (file-name-directory file)
1113 (if (or (string= first "type") (string= first "const"))
1115 (car name-parts))))))
1116 (file-error (message "Could not run godef binary"))))
1118 (defun go-goto-imports ()
1119 "Move point to the block of imports.
1128 it will move point directly behind the last import.
1135 it will move point to the next line after the last import.
1137 If no imports can be found, point will be moved after the package
1140 ;; FIXME if there's a block-commented import before the real
1141 ;; imports, we'll jump to that one.
1143 ;; Generally, this function isn't very forgiving. it'll bark on
1144 ;; extra whitespace. It works well for clean code.
1145 (let ((old-point (point)))
1146 (goto-char (point-min))
1148 ((re-search-forward "^import ()" nil t)
1151 ((re-search-forward "^import ([^)]+)" nil t)
1154 ((re-search-forward "\\(^import \\([^\"]+ \\)?\"[^\"]+\"\n?\\)+" nil t)
1156 ((re-search-forward "^[[:space:]\n]*package .+?\n" nil t)
1157 (message "No imports found, moving point after package declaration")
1160 (goto-char old-point)
1161 (message "No imports or package declaration found. Is this really a Go file?")
1164 (defun go-play-buffer ()
1165 "Like `go-play-region', but acts on the entire buffer."
1167 (go-play-region (point-min) (point-max)))
1169 (defun go-play-region (start end)
1170 "Send the region to the Playground.
1171 If non-nil `go-play-browse-function' is called with the
1174 (let* ((url-request-method "POST")
1175 (url-request-extra-headers
1176 '(("Content-Type" . "application/x-www-form-urlencoded")))
1178 (encode-coding-string
1179 (buffer-substring-no-properties start end)
1181 (content-buf (url-retrieve
1182 "http://play.golang.org/share"
1185 ((equal :error (car arg))
1186 (signal 'go-play-error (cdr arg)))
1188 (re-search-forward "\n\n")
1189 (let ((url (format "http://play.golang.org/p/%s"
1190 (buffer-substring (point) (point-max)))))
1191 (when go-play-browse-function
1192 (funcall go-play-browse-function url)))))))))))
1195 (defun go-download-play (url)
1196 "Download a paste from the playground and insert it in a Go buffer.
1197 Tries to look for a URL at point."
1198 (interactive (list (read-from-minibuffer "Playground URL: " (ffap-url-p (ffap-string-at-point 'url)))))
1199 (with-current-buffer
1200 (let ((url-request-method "GET") url-request-data url-request-extra-headers)
1201 (url-retrieve-synchronously (concat url ".go")))
1202 (let ((buffer (generate-new-buffer (concat (car (last (split-string url "/"))) ".go"))))
1203 (goto-char (point-min))
1204 (re-search-forward "\n\n")
1205 (copy-to-buffer buffer (point) (point-max))
1207 (with-current-buffer buffer
1209 (switch-to-buffer buffer)))))
1211 (defun go-propertize-syntax (start end)
1214 (while (search-forward "\\" end t)
1215 (put-text-property (1- (point)) (point) 'syntax-table (if (= (char-after) ?`) '(1) '(9))))))
1217 (defun go-import-add (arg import)
1218 "Add a new IMPORT to the list of imports.
1220 When called with a prefix ARG asks for an alternative name to
1221 import the package as.
1223 If no list exists yet, one will be created if possible.
1225 If an identical import has been commented, it will be
1226 uncommented, otherwise a new import will be added."
1228 ;; - If there's a matching `// import "foo"`, uncomment it
1229 ;; - If we're in an import() block and there's a matching `"foo"`, uncomment it
1230 ;; - Otherwise add a new import, with the appropriate syntax
1234 (replace-regexp-in-string "^[\"']\\|[\"']$" "" (completing-read "Package: " (go--old-completion-list-style (go-packages))))))
1236 (let (as line import-start)
1238 (setq as (read-from-minibuffer "Import as: ")))
1240 (setq line (format "%s \"%s\"" as import))
1241 (setq line (format "\"%s\"" import)))
1243 (goto-char (point-min))
1244 (if (re-search-forward (concat "^[[:space:]]*//[[:space:]]*import " line "$") nil t)
1245 (uncomment-region (line-beginning-position) (line-end-position))
1246 (case (go-goto-imports)
1247 ('fail (message "Could not find a place to add import."))
1249 (insert "\n\t" line "\n"))
1252 (re-search-backward "^import (")
1253 (setq import-start (point)))
1254 (if (re-search-backward (concat "^[[:space:]]*//[[:space:]]*" line "$") import-start t)
1255 (uncomment-region (line-beginning-position) (line-end-position))
1256 (insert "\n\t" line)))
1257 ('single (insert "import " line "\n"))
1258 ('none (insert "\nimport (\n\t" line "\n)\n")))))))
1260 (defun go-root-and-paths ()
1261 (let* ((output (split-string (shell-command-to-string (concat go-command " env GOROOT GOPATH"))
1264 (paths (split-string (cadr output) path-separator)))
1265 (append (list root) paths)))
1267 (defun go--string-prefix-p (s1 s2 &optional ignore-case)
1268 "Return non-nil if S1 is a prefix of S2.
1269 If IGNORE-CASE is non-nil, the comparison is case-insensitive."
1270 (eq t (compare-strings s1 nil nil
1271 s2 0 (length s1) ignore-case)))
1273 (defun go--directory-dirs (dir)
1274 "Recursively return all subdirectories in DIR."
1275 (if (file-directory-p dir)
1276 (let ((dir (directory-file-name dir))
1278 (files (directory-files dir nil nil t)))
1279 (dolist (file files)
1280 (unless (member file '("." ".."))
1281 (let ((file (concat dir "/" file)))
1282 (if (file-directory-p file)
1283 (setq dirs (append (cons file
1284 (go--directory-dirs file))
1290 (defun go-packages ()
1295 (let ((pkgdir (concat topdir "/pkg/")))
1296 (mapcan (lambda (dir)
1297 (mapcar (lambda (file)
1298 (let ((sub (substring file (length pkgdir) -2)))
1299 (unless (or (go--string-prefix-p "obj/" sub) (go--string-prefix-p "tool/" sub))
1300 (mapconcat #'identity (cdr (split-string sub "/")) "/"))))
1301 (if (file-directory-p dir)
1302 (directory-files dir t "\\.a$"))))
1303 (if (file-directory-p pkgdir)
1304 (go--directory-dirs pkgdir)))))
1305 (go-root-and-paths)))
1308 (defun go-unused-imports-lines ()
1309 ;; FIXME Technically, -o /dev/null fails in quite some cases (on
1310 ;; Windows, when compiling from within GOPATH). Practically,
1311 ;; however, it has the same end result: There won't be a
1312 ;; compiled binary/archive, and we'll get our import errors when
1314 (reverse (remove nil
1317 (when (string-match "^\\(.+\\):\\([[:digit:]]+\\): imported and not used: \".+\".*$" line)
1318 (let ((error-file-name (match-string 1 line))
1319 (error-line-num (match-string 2 line)))
1320 (if (string= (file-truename error-file-name) (file-truename buffer-file-name))
1321 (string-to-number error-line-num)))))
1322 (split-string (shell-command-to-string
1324 (if (string-match "_test\.go$" buffer-file-truename)
1326 " build -o /dev/null"))) "\n")))))
1328 (defun go-remove-unused-imports (arg)
1329 "Remove all unused imports.
1330 If ARG is non-nil, unused imports will be commented, otherwise
1331 they will be removed completely."
1334 (let ((cur-buffer (current-buffer)) flymake-state lines)
1335 (when (boundp 'flymake-mode)
1336 (setq flymake-state flymake-mode)
1338 (save-some-buffers nil (lambda () (equal cur-buffer (current-buffer))))
1339 (if (buffer-modified-p)
1340 (message "Cannot operate on unsaved buffer")
1341 (setq lines (go-unused-imports-lines))
1342 (dolist (import lines)
1343 (go--goto-line import)
1346 (comment-region (line-beginning-position) (line-end-position))
1347 (go--delete-whole-line)))
1348 (message "Removed %d imports" (length lines)))
1349 (if flymake-state (flymake-mode-on)))))
1351 (defun godef--find-file-line-column (specifier other-window)
1352 "Given a file name in the format of `filename:line:column',
1353 visit FILENAME and go to line LINE and column COLUMN."
1354 (if (not (string-match "\\(.+\\):\\([0-9]+\\):\\([0-9]+\\)" specifier))
1355 ;; We've only been given a directory name
1356 (funcall (if other-window #'find-file-other-window #'find-file) specifier)
1357 (let ((filename (match-string 1 specifier))
1358 (line (string-to-number (match-string 2 specifier)))
1359 (column (string-to-number (match-string 3 specifier))))
1360 (funcall (if other-window #'find-file-other-window #'find-file) filename)
1361 (go--goto-line line)
1363 (forward-char (1- column))
1364 (if (buffer-modified-p)
1365 (message "Buffer is modified, file position might not have been correct")))))
1367 (defun godef--call (point)
1368 "Call godef, acquiring definition position and expression
1369 description at POINT."
1371 (error "godef does not reliably work in XEmacs, expect bad results"))
1372 (if (not (buffer-file-name (go--coverage-origin-buffer)))
1373 (error "Cannot use godef on a buffer without a file name")
1374 (let ((outbuf (get-buffer-create "*godef*"))
1375 (coding-system-for-read 'utf-8)
1376 (coding-system-for-write 'utf-8))
1377 (with-current-buffer outbuf
1379 (call-process-region (point-min)
1388 (file-truename (buffer-file-name (go--coverage-origin-buffer)))
1390 (number-to-string (go--position-bytes point)))
1391 (with-current-buffer outbuf
1392 (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n")))))
1394 (defun godef--successful-p (output)
1395 (not (or (string= "-" output)
1396 (string= "godef: no identifier found" output)
1397 (go--string-prefix-p "godef: no declaration found for " output)
1398 (go--string-prefix-p "error finding import path for " output))))
1400 (defun godef--error (output)
1402 ((godef--successful-p output)
1404 ((string= "-" output)
1405 "godef: expression is not defined anywhere")
1409 (defun godef-describe (point)
1410 "Describe the expression at POINT."
1413 (let ((description (cdr (butlast (godef--call point) 1))))
1414 (if (not description)
1415 (message "No description found for expression at point")
1416 (message "%s" (mapconcat #'identity description "\n"))))
1417 (file-error (message "Could not run godef binary"))))
1419 (defun godef-jump (point &optional other-window)
1420 "Jump to the definition of the expression at POINT."
1423 (let ((file (car (godef--call point))))
1424 (if (not (godef--successful-p file))
1425 (message "%s" (godef--error file))
1427 (ring-insert find-tag-marker-ring (point-marker))
1428 (godef--find-file-line-column file other-window)))
1429 (file-error (message "Could not run godef binary"))))
1431 (defun godef-jump-other-window (point)
1433 (godef-jump point t))
1435 (defun go--goto-line (line)
1436 (goto-char (point-min))
1437 (forward-line (1- line)))
1439 (defun go--line-column-to-point (line column)
1441 (go--goto-line line)
1442 (forward-char (1- column))
1445 (defstruct go--covered
1446 start-line start-column end-line end-column covered count)
1448 (defun go--coverage-file ()
1449 "Return the coverage file to use, either by reading it from the
1450 current coverage buffer or by prompting for it."
1451 (if (boundp 'go--coverage-current-file-name)
1452 go--coverage-current-file-name
1453 (read-file-name "Coverage file: " nil nil t)))
1455 (defun go--coverage-origin-buffer ()
1456 "Return the buffer to base the coverage on."
1457 (or (buffer-base-buffer) (current-buffer)))
1459 (defun go--coverage-face (count divisor)
1460 "Return the intensity face for COUNT when using DIVISOR
1461 to scale it to a range [0,10].
1463 DIVISOR scales the absolute cover count to values from 0 to 10.
1464 For DIVISOR = 0 the count will always translate to 8."
1467 -0.1) ;; Uncovered code, set to -0.1 so n becomes 0.
1469 0.8) ;; covermode=set, set to 0.8 so n becomes 8.
1471 (/ (log count) divisor))))
1472 (n (1+ (floor (* norm 9))))) ;; Convert normalized count [0,1] to intensity [0,10]
1473 (concat "go-coverage-" (number-to-string n))))
1475 (defun go--coverage-make-overlay (range divisor)
1476 "Create a coverage overlay for a RANGE of covered/uncovered code.
1477 Use DIVISOR to scale absolute counts to a [0,10] scale."
1478 (let* ((count (go--covered-count range))
1479 (face (go--coverage-face count divisor))
1480 (ov (make-overlay (go--line-column-to-point (go--covered-start-line range)
1481 (go--covered-start-column range))
1482 (go--line-column-to-point (go--covered-end-line range)
1483 (go--covered-end-column range)))))
1485 (overlay-put ov 'face face)
1486 (overlay-put ov 'help-echo (format "Count: %d" count))))
1488 (defun go--coverage-clear-overlays ()
1489 "Remove existing overlays and put a single untracked overlay
1490 over the entire buffer."
1492 (overlay-put (make-overlay (point-min) (point-max))
1494 'go-coverage-untracked))
1496 (defun go--coverage-parse-file (coverage-file file-name)
1497 "Parse COVERAGE-FILE and extract coverage information and
1498 divisor for FILE-NAME."
1502 (insert-file-contents coverage-file)
1503 (go--goto-line 2) ;; Skip over mode
1505 (let* ((parts (split-string (buffer-substring (point-at-bol) (point-at-eol)) ":"))
1507 (rest (split-string (nth 1 parts) "[., ]")))
1510 (start-line start-column end-line end-column num count)
1511 (mapcar #'string-to-number rest)
1513 (when (string= (file-name-nondirectory file) file-name)
1514 (if (> count max-count)
1515 (setq max-count count))
1516 (push (make-go--covered :start-line start-line
1517 :start-column start-column
1519 :end-column end-column
1520 :covered (/= count 0)
1526 (list ranges (if (> max-count 0) (log max-count) 0)))))
1528 (defun go-coverage (&optional coverage-file)
1529 "Open a clone of the current buffer and overlay it with
1530 coverage information gathered via go test -coverprofile=COVERAGE-FILE.
1532 If COVERAGE-FILE is nil, it will either be inferred from the
1533 current buffer if it's already a coverage buffer, or be prompted
1536 (let* ((cur-buffer (current-buffer))
1537 (origin-buffer (go--coverage-origin-buffer))
1538 (gocov-buffer-name (concat (buffer-name origin-buffer) "<gocov>"))
1539 (coverage-file (or coverage-file (go--coverage-file)))
1540 (ranges-and-divisor (go--coverage-parse-file
1542 (file-name-nondirectory (buffer-file-name origin-buffer))))
1543 (cov-mtime (nth 5 (file-attributes coverage-file)))
1544 (cur-mtime (nth 5 (file-attributes (buffer-file-name origin-buffer)))))
1546 (if (< (float-time cov-mtime) (float-time cur-mtime))
1547 (message "Coverage file is older than the source file."))
1549 (with-current-buffer (or (get-buffer gocov-buffer-name)
1550 (make-indirect-buffer origin-buffer gocov-buffer-name t))
1551 (set (make-local-variable 'go--coverage-current-file-name) coverage-file)
1554 (go--coverage-clear-overlays)
1555 (dolist (range (car ranges-and-divisor))
1556 (go--coverage-make-overlay range (cadr ranges-and-divisor))))
1558 (if (not (eq cur-buffer (current-buffer)))
1559 (display-buffer (current-buffer) `(,go-coverage-display-buffer-func))))))
1563 ;;; go-mode.el ends here