github.com/varialus/godfly@v0.0.0-20130904042352-1934f9f095ab/misc/emacs/go-mode.el (about)

     1  ;;; go-mode.el --- Major mode for the Go programming language
     2  
     3  ;; Copyright 2013 The Go 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.
     6  
     7  (require 'cl)
     8  (require 'etags)
     9  (require 'ffap)
    10  (require 'ring)
    11  (require 'url)
    12  
    13  ;; XEmacs compatibility guidelines
    14  ;; - Minimum required version of XEmacs: 21.5.32
    15  ;;   - Feature that cannot be backported: POSIX character classes in
    16  ;;     regular expressions
    17  ;;   - Functions that could be backported but won't because 21.5.32
    18  ;;     covers them: plenty.
    19  ;;   - Features that are still partly broken:
    20  ;;     - godef will not work correctly if multibyte characters are
    21  ;;       being used
    22  ;;     - Fontification will not handle unicode correctly
    23  ;;
    24  ;; - Do not use \_< and \_> regexp delimiters directly; use
    25  ;;   go--regexp-enclose-in-symbol
    26  ;;
    27  ;; - The character `_` must not be a symbol constituent but a
    28  ;;   character constituent
    29  ;;
    30  ;; - Do not use process-lines
    31  ;;
    32  ;; - Use go--old-completion-list-style when using a plain list as the
    33  ;;   collection for completing-read
    34  ;;
    35  ;; - Use go--kill-whole-line instead of kill-whole-line (called
    36  ;;   kill-entire-line in XEmacs)
    37  ;;
    38  ;; - Use go--position-bytes instead of position-bytes
    39  (defmacro go--xemacs-p ()
    40    `(featurep 'xemacs))
    41  
    42  (defalias 'go--kill-whole-line
    43    (if (fboundp 'kill-whole-line)
    44        'kill-whole-line
    45      'kill-entire-line))
    46  
    47  ;; Delete the current line without putting it in the kill-ring.
    48  (defun go--delete-whole-line (&optional arg)
    49    ;; Emacs uses both kill-region and kill-new, Xemacs only uses
    50    ;; kill-region. In both cases we turn them into operations that do
    51    ;; not modify the kill ring. This solution does depend on the
    52    ;; implementation of kill-line, but it's the only viable solution
    53    ;; that does not require to write kill-line from scratch.
    54    (flet ((kill-region (beg end)
    55                        (delete-region beg end))
    56           (kill-new (s) ()))
    57      (go--kill-whole-line arg)))
    58  
    59  
    60  ;; XEmacs unfortunately does not offer position-bytes. We can fall
    61  ;; back to just using (point), but it will be incorrect as soon as
    62  ;; multibyte characters are being used.
    63  (if (fboundp 'position-bytes)
    64      (defalias 'go--position-bytes 'position-bytes)
    65    (defun go--position-bytes (point) point))
    66  
    67  (defun go--old-completion-list-style (list)
    68    (mapcar (lambda (x) (cons x nil)) list))
    69  
    70  ;; GNU Emacs 24 has prog-mode, older GNU Emacs and XEmacs do not, so
    71  ;; copy its definition for those.
    72  (if (not (fboundp 'prog-mode))
    73      (define-derived-mode prog-mode fundamental-mode "Prog"
    74        "Major mode for editing source code."
    75        (set (make-local-variable 'require-final-newline) mode-require-final-newline)
    76        (set (make-local-variable 'parse-sexp-ignore-comments) t)
    77        (setq bidi-paragraph-direction 'left-to-right)))
    78  
    79  (defun go--regexp-enclose-in-symbol (s)
    80    ;; XEmacs does not support \_<, GNU Emacs does. In GNU Emacs we make
    81    ;; extensive use of \_< to support unicode in identifiers. Until we
    82    ;; come up with a better solution for XEmacs, this solution will
    83    ;; break fontification in XEmacs for identifiers such as "typeµ".
    84    ;; XEmacs will consider "type" a keyword, GNU Emacs won't.
    85  
    86    (if (go--xemacs-p)
    87        (concat "\\<" s "\\>")
    88      (concat "\\_<" s "\\_>")))
    89  
    90  ;; Move up one level of parentheses.
    91  (defun go-goto-opening-parenthesis (&optional legacy-unused)
    92    ;; The old implementation of go-goto-opening-parenthesis had an
    93    ;; optional argument to speed up the function. It didn't change the
    94    ;; function's outcome.
    95  
    96    ;; Silently fail if there's no matching opening parenthesis.
    97    (condition-case nil
    98        (backward-up-list)
    99      (scan-error nil)))
   100  
   101  
   102  (defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]")
   103  (defconst go-identifier-regexp "[[:word:][:multibyte:]]+")
   104  (defconst go-label-regexp go-identifier-regexp)
   105  (defconst go-type-regexp "[[:word:][:multibyte:]*]+")
   106  (defconst go-func-regexp (concat (go--regexp-enclose-in-symbol "func") "\\s *\\(" go-identifier-regexp "\\)"))
   107  (defconst go-func-meth-regexp (concat
   108                                 (go--regexp-enclose-in-symbol "func") "\\s *\\(?:(\\s *"
   109                                 "\\(" go-identifier-regexp "\\s +\\)?" go-type-regexp
   110                                 "\\s *)\\s *\\)?\\("
   111                                 go-identifier-regexp
   112                                 "\\)("))
   113  (defconst go-builtins
   114    '("append" "cap"   "close"   "complex" "copy"
   115      "delete" "imag"  "len"     "make"    "new"
   116      "panic"  "print" "println" "real"    "recover")
   117    "All built-in functions in the Go language. Used for font locking.")
   118  
   119  (defconst go-mode-keywords
   120    '("break"    "default"     "func"   "interface" "select"
   121      "case"     "defer"       "go"     "map"       "struct"
   122      "chan"     "else"        "goto"   "package"   "switch"
   123      "const"    "fallthrough" "if"     "range"     "type"
   124      "continue" "for"         "import" "return"    "var")
   125    "All keywords in the Go language.  Used for font locking.")
   126  
   127  (defconst go-constants '("nil" "true" "false" "iota"))
   128  (defconst go-type-name-regexp (concat "\\(?:[*(]\\)*\\(?:" go-identifier-regexp "\\.\\)?\\(" go-identifier-regexp "\\)"))
   129  
   130  (defvar go-dangling-cache)
   131  (defvar go-godoc-history nil)
   132  (defvar go--coverage-origin-buffer)
   133  (defvar go--coverage-current-file-name)
   134  
   135  (defgroup go nil
   136    "Major mode for editing Go code"
   137    :group 'languages)
   138  
   139  (defgroup go-cover nil
   140    "Options specific to `cover`"
   141    :group 'go)
   142  
   143  (defcustom go-fontify-function-calls t
   144    "Fontify function and method calls if this is non-nil."
   145    :type 'boolean
   146    :group 'go)
   147  
   148  (defcustom go-mode-hook nil
   149    "Hook called by `go-mode'."
   150    :type 'hook
   151    :group 'go)
   152  
   153  (defcustom go-command "go"
   154    "The 'go' command.  Some users have multiple Go development
   155  trees and invoke the 'go' tool via a wrapper that sets GOROOT and
   156  GOPATH based on the current directory.  Such users should
   157  customize this variable to point to the wrapper script."
   158    :type 'string
   159    :group 'go)
   160  
   161  (defface go-coverage-untracked
   162    '((t (:foreground "#505050")))
   163    "Coverage color of untracked code."
   164    :group 'go-cover)
   165  
   166  (defface go-coverage-0
   167    '((t (:foreground "#c00000")))
   168    "Coverage color for uncovered code."
   169    :group 'go-cover)
   170  (defface go-coverage-1
   171    '((t (:foreground "#808080")))
   172    "Coverage color for covered code with weight 1."
   173    :group 'go-cover)
   174  (defface go-coverage-2
   175    '((t (:foreground "#748c83")))
   176    "Coverage color for covered code with weight 2."
   177    :group 'go-cover)
   178  (defface go-coverage-3
   179    '((t (:foreground "#689886")))
   180    "Coverage color for covered code with weight 3."
   181    :group 'go-cover)
   182  (defface go-coverage-4
   183    '((t (:foreground "#5ca489")))
   184    "Coverage color for covered code with weight 4."
   185    :group 'go-cover)
   186  (defface go-coverage-5
   187    '((t (:foreground "#50b08c")))
   188    "Coverage color for covered code with weight 5."
   189    :group 'go-cover)
   190  (defface go-coverage-6
   191    '((t (:foreground "#44bc8f")))
   192    "Coverage color for covered code with weight 6."
   193    :group 'go-cover)
   194  (defface go-coverage-7
   195    '((t (:foreground "#38c892")))
   196    "Coverage color for covered code with weight 7."
   197    :group 'go-cover)
   198  (defface go-coverage-8
   199    '((t (:foreground "#2cd495")))
   200    "Coverage color for covered code with weight 8.
   201  For mode=set, all covered lines will have this weight."
   202    :group 'go-cover)
   203  (defface go-coverage-9
   204    '((t (:foreground "#20e098")))
   205    "Coverage color for covered code with weight 9."
   206    :group 'go-cover)
   207  (defface go-coverage-10
   208    '((t (:foreground "#14ec9b")))
   209    "Coverage color for covered code with weight 10."
   210    :group 'go-cover)
   211  (defface go-coverage-covered
   212    '((t (:foreground "#2cd495")))
   213    "Coverage color of covered code."
   214    :group 'go-cover)
   215  
   216  (defvar go-mode-syntax-table
   217    (let ((st (make-syntax-table)))
   218      (modify-syntax-entry ?+  "." st)
   219      (modify-syntax-entry ?-  "." st)
   220      (modify-syntax-entry ?%  "." st)
   221      (modify-syntax-entry ?&  "." st)
   222      (modify-syntax-entry ?|  "." st)
   223      (modify-syntax-entry ?^  "." st)
   224      (modify-syntax-entry ?!  "." st)
   225      (modify-syntax-entry ?=  "." st)
   226      (modify-syntax-entry ?<  "." st)
   227      (modify-syntax-entry ?>  "." st)
   228      (modify-syntax-entry ?/ (if (go--xemacs-p) ". 1456" ". 124b") st)
   229      (modify-syntax-entry ?*  ". 23" st)
   230      (modify-syntax-entry ?\n "> b" st)
   231      (modify-syntax-entry ?\" "\"" st)
   232      (modify-syntax-entry ?\' "\"" st)
   233      (modify-syntax-entry ?`  "\"" st)
   234      (modify-syntax-entry ?\\ "\\" st)
   235      ;; It would be nicer to have _ as a symbol constituent, but that
   236      ;; would trip up XEmacs, which does not support the \_< anchor
   237      (modify-syntax-entry ?_  "w" st)
   238  
   239      st)
   240    "Syntax table for Go mode.")
   241  
   242  (defun go--build-font-lock-keywords ()
   243    ;; we cannot use 'symbols in regexp-opt because emacs <24 doesn't
   244    ;; understand that
   245    (append
   246     `((,(go--regexp-enclose-in-symbol (regexp-opt go-mode-keywords t)) . font-lock-keyword-face)
   247       (,(go--regexp-enclose-in-symbol (regexp-opt go-builtins t)) . font-lock-builtin-face)
   248       (,(go--regexp-enclose-in-symbol (regexp-opt go-constants t)) . font-lock-constant-face)
   249       (,go-func-regexp 1 font-lock-function-name-face)) ;; function (not method) name
   250  
   251     (if go-fontify-function-calls
   252         `((,(concat "\\(" go-identifier-regexp "\\)[[:space:]]*(") 1 font-lock-function-name-face) ;; function call/method name
   253           (,(concat "(\\(" go-identifier-regexp "\\))[[:space:]]*(") 1 font-lock-function-name-face)) ;; bracketed function call
   254       `((,go-func-meth-regexp 1 font-lock-function-name-face))) ;; method name
   255  
   256     `(
   257       (,(concat (go--regexp-enclose-in-symbol "type") "[[:space:]]*\\([^[:space:]]+\\)") 1 font-lock-type-face) ;; types
   258       (,(concat (go--regexp-enclose-in-symbol "type") "[[:space:]]*" go-identifier-regexp "[[:space:]]*" go-type-name-regexp) 1 font-lock-type-face) ;; types
   259       (,(concat "[^[:word:][:multibyte:]]\\[\\([[:digit:]]+\\|\\.\\.\\.\\)?\\]" go-type-name-regexp) 2 font-lock-type-face) ;; Arrays/slices
   260       (,(concat "\\(" go-identifier-regexp "\\)" "{") 1 font-lock-type-face)
   261       (,(concat (go--regexp-enclose-in-symbol "map") "\\[[^]]+\\]" go-type-name-regexp) 1 font-lock-type-face) ;; map value type
   262       (,(concat (go--regexp-enclose-in-symbol "map") "\\[" go-type-name-regexp) 1 font-lock-type-face) ;; map key type
   263       (,(concat (go--regexp-enclose-in-symbol "chan") "[[:space:]]*\\(?:<-\\)?" go-type-name-regexp) 1 font-lock-type-face) ;; channel type
   264       (,(concat (go--regexp-enclose-in-symbol "\\(?:new\\|make\\)") "\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp) 1 font-lock-type-face) ;; new/make type
   265       ;; TODO do we actually need this one or isn't it just a function call?
   266       (,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face) ;; Type conversion
   267       (,(concat (go--regexp-enclose-in-symbol "func") "[[:space:]]+(" go-identifier-regexp "[[:space:]]+" go-type-name-regexp ")") 1 font-lock-type-face) ;; Method receiver
   268       (,(concat (go--regexp-enclose-in-symbol "func") "[[:space:]]+(" go-type-name-regexp ")") 1 font-lock-type-face) ;; Method receiver without variable name
   269       ;; Like the original go-mode this also marks compound literal
   270       ;; fields. There, it was marked as to fix, but I grew quite
   271       ;; accustomed to it, so it'll stay for now.
   272       (,(concat "^[[:space:]]*\\(" go-label-regexp "\\)[[:space:]]*:\\(\\S.\\|$\\)") 1 font-lock-constant-face) ;; Labels and compound literal fields
   273       (,(concat (go--regexp-enclose-in-symbol "\\(goto\\|break\\|continue\\)") "[[:space:]]*\\(" go-label-regexp "\\)") 2 font-lock-constant-face)))) ;; labels in goto/break/continue
   274  
   275  (defvar go-mode-map
   276    (let ((m (make-sparse-keymap)))
   277      (define-key m "}" 'go-mode-insert-and-indent)
   278      (define-key m ")" 'go-mode-insert-and-indent)
   279      (define-key m "," 'go-mode-insert-and-indent)
   280      (define-key m ":" 'go-mode-insert-and-indent)
   281      (define-key m "=" 'go-mode-insert-and-indent)
   282      (define-key m (kbd "C-c C-a") 'go-import-add)
   283      (define-key m (kbd "C-c C-j") 'godef-jump)
   284      (define-key m (kbd "C-x 4 C-c C-j") 'godef-jump-other-window)
   285      (define-key m (kbd "C-c C-d") 'godef-describe)
   286      m)
   287    "Keymap used by Go mode to implement electric keys.")
   288  
   289  (defun go-mode-insert-and-indent (key)
   290    "Invoke the global binding of KEY, then reindent the line."
   291  
   292    (interactive (list (this-command-keys)))
   293    (call-interactively (lookup-key (current-global-map) key))
   294    (indent-according-to-mode))
   295  
   296  (defmacro go-paren-level ()
   297    `(car (syntax-ppss)))
   298  
   299  (defmacro go-in-string-or-comment-p ()
   300    `(nth 8 (syntax-ppss)))
   301  
   302  (defmacro go-in-string-p ()
   303    `(nth 3 (syntax-ppss)))
   304  
   305  (defmacro go-in-comment-p ()
   306    `(nth 4 (syntax-ppss)))
   307  
   308  (defmacro go-goto-beginning-of-string-or-comment ()
   309    `(goto-char (nth 8 (syntax-ppss))))
   310  
   311  (defun go--backward-irrelevant (&optional stop-at-string)
   312    "Skips backwards over any characters that are irrelevant for
   313  indentation and related tasks.
   314  
   315  It skips over whitespace, comments, cases and labels and, if
   316  STOP-AT-STRING is not true, over strings."
   317  
   318    (let (pos (start-pos (point)))
   319      (skip-chars-backward "\n\s\t")
   320      (if (and (save-excursion (beginning-of-line) (go-in-string-p)) (looking-back "`") (not stop-at-string))
   321          (backward-char))
   322      (if (and (go-in-string-p) (not stop-at-string))
   323          (go-goto-beginning-of-string-or-comment))
   324      (if (looking-back "\\*/")
   325          (backward-char))
   326      (if (go-in-comment-p)
   327          (go-goto-beginning-of-string-or-comment))
   328      (setq pos (point))
   329      (beginning-of-line)
   330      (if (or (looking-at (concat "^" go-label-regexp ":")) (looking-at "^[[:space:]]*\\(case .+\\|default\\):"))
   331          (end-of-line 0)
   332        (goto-char pos))
   333      (if (/= start-pos (point))
   334          (go--backward-irrelevant stop-at-string))
   335      (/= start-pos (point))))
   336  
   337  (defun go--buffer-narrowed-p ()
   338    "Return non-nil if the current buffer is narrowed."
   339    (/= (buffer-size)
   340        (- (point-max)
   341           (point-min))))
   342  
   343  (defun go-previous-line-has-dangling-op-p ()
   344    "Returns non-nil if the current line is a continuation line."
   345    (let* ((cur-line (line-number-at-pos))
   346           (val (gethash cur-line go-dangling-cache 'nope)))
   347      (if (or (go--buffer-narrowed-p) (equal val 'nope))
   348          (save-excursion
   349            (beginning-of-line)
   350            (go--backward-irrelevant t)
   351            (setq val (looking-back go-dangling-operators-regexp))
   352            (if (not (go--buffer-narrowed-p))
   353                (puthash cur-line val go-dangling-cache))))
   354      val))
   355  
   356  (defun go--at-function-definition ()
   357    "Return non-nil if point is on the opening curly brace of a
   358  function definition.
   359  
   360  We do this by first calling (beginning-of-defun), which will take
   361  us to the start of *some* function. We then look for the opening
   362  curly brace of that function and compare its position against the
   363  curly brace we are checking. If they match, we return non-nil."
   364    (if (= (char-after) ?\{)
   365        (save-excursion
   366          (let ((old-point (point))
   367                start-nesting)
   368            (beginning-of-defun)
   369            (when (looking-at "func ")
   370              (setq start-nesting (go-paren-level))
   371              (skip-chars-forward "^{")
   372              (while (> (go-paren-level) start-nesting)
   373                (forward-char)
   374                (skip-chars-forward "^{") 0)
   375              (if (and (= (go-paren-level) start-nesting) (= old-point (point)))
   376                  t))))))
   377  
   378  (defun go--indentation-for-opening-parenthesis ()
   379    "Return the semantic indentation for the current opening parenthesis.
   380  
   381  If point is on an opening curly brace and said curly brace
   382  belongs to a function declaration, the indentation of the func
   383  keyword will be returned. Otherwise the indentation of the
   384  current line will be returned."
   385    (save-excursion
   386      (if (go--at-function-definition)
   387          (progn
   388            (beginning-of-defun)
   389            (current-indentation))
   390        (current-indentation))))
   391  
   392  (defun go-indentation-at-point ()
   393    (save-excursion
   394      (let (start-nesting (outindent 0))
   395        (back-to-indentation)
   396        (setq start-nesting (go-paren-level))
   397  
   398        (cond
   399         ((go-in-string-p)
   400          (current-indentation))
   401         ((looking-at "[])}]")
   402          (go-goto-opening-parenthesis)
   403          (if (go-previous-line-has-dangling-op-p)
   404              (- (current-indentation) tab-width)
   405            (go--indentation-for-opening-parenthesis)))
   406         ((progn (go--backward-irrelevant t) (looking-back go-dangling-operators-regexp))
   407          ;; only one nesting for all dangling operators in one operation
   408          (if (go-previous-line-has-dangling-op-p)
   409              (current-indentation)
   410            (+ (current-indentation) tab-width)))
   411         ((zerop (go-paren-level))
   412          0)
   413         ((progn (go-goto-opening-parenthesis) (< (go-paren-level) start-nesting))
   414          (if (go-previous-line-has-dangling-op-p)
   415              (current-indentation)
   416            (+ (go--indentation-for-opening-parenthesis) tab-width)))
   417         (t
   418          (current-indentation))))))
   419  
   420  (defun go-mode-indent-line ()
   421    (interactive)
   422    (let (indent
   423          shift-amt
   424          end
   425          (pos (- (point-max) (point)))
   426          (point (point))
   427          (beg (line-beginning-position)))
   428      (back-to-indentation)
   429      (if (go-in-string-or-comment-p)
   430          (goto-char point)
   431        (setq indent (go-indentation-at-point))
   432        (if (looking-at (concat go-label-regexp ":\\([[:space:]]*/.+\\)?$\\|case .+:\\|default:"))
   433            (decf indent tab-width))
   434        (setq shift-amt (- indent (current-column)))
   435        (if (zerop shift-amt)
   436            nil
   437          (delete-region beg (point))
   438          (indent-to indent))
   439        ;; If initial point was within line's indentation,
   440        ;; position after the indentation.  Else stay at same point in text.
   441        (if (> (- (point-max) pos) (point))
   442            (goto-char (- (point-max) pos))))))
   443  
   444  (defun go-beginning-of-defun (&optional count)
   445    (unless count (setq count 1))
   446    (let ((first t) failure)
   447      (dotimes (i (abs count))
   448        (while (and (not failure)
   449                    (or first (go-in-string-or-comment-p)))
   450          (if (>= count 0)
   451              (progn
   452                (go--backward-irrelevant)
   453                (if (not (re-search-backward go-func-meth-regexp nil t))
   454                    (setq failure t)))
   455            (if (looking-at go-func-meth-regexp)
   456                (forward-char))
   457            (if (not (re-search-forward go-func-meth-regexp nil t))
   458                (setq failure t)))
   459          (setq first nil)))
   460      (if (< count 0)
   461          (beginning-of-line))
   462      (not failure)))
   463  
   464  (defun go-end-of-defun ()
   465    (let (orig-level)
   466      ;; It can happen that we're not placed before a function by emacs
   467      (if (not (looking-at "func"))
   468          (go-beginning-of-defun -1))
   469      (skip-chars-forward "^{")
   470      (forward-char)
   471      (setq orig-level (go-paren-level))
   472      (while (>= (go-paren-level) orig-level)
   473        (skip-chars-forward "^}")
   474        (forward-char))))
   475  
   476  ;;;###autoload
   477  (define-derived-mode go-mode prog-mode "Go"
   478    "Major mode for editing Go source text.
   479  
   480  This mode provides (not just) basic editing capabilities for
   481  working with Go code. It offers almost complete syntax
   482  highlighting, indentation that is almost identical to gofmt and
   483  proper parsing of the buffer content to allow features such as
   484  navigation by function, manipulation of comments or detection of
   485  strings.
   486  
   487  In addition to these core features, it offers various features to
   488  help with writing Go code. You can directly run buffer content
   489  through gofmt, read godoc documentation from within Emacs, modify
   490  and clean up the list of package imports or interact with the
   491  Playground (uploading and downloading pastes).
   492  
   493  The following extra functions are defined:
   494  
   495  - `gofmt'
   496  - `godoc'
   497  - `go-import-add'
   498  - `go-remove-unused-imports'
   499  - `go-goto-imports'
   500  - `go-play-buffer' and `go-play-region'
   501  - `go-download-play'
   502  - `godef-describe' and `godef-jump'
   503  - `go-coverage'
   504  
   505  If you want to automatically run `gofmt' before saving a file,
   506  add the following hook to your emacs configuration:
   507  
   508  \(add-hook 'before-save-hook 'gofmt-before-save)
   509  
   510  If you want to use `godef-jump' instead of etags (or similar),
   511  consider binding godef-jump to `M-.', which is the default key
   512  for `find-tag':
   513  
   514  \(add-hook 'go-mode-hook (lambda ()
   515                            (local-set-key (kbd \"M-.\") 'godef-jump)))
   516  
   517  Please note that godef is an external dependency. You can install
   518  it with
   519  
   520  go get code.google.com/p/rog-go/exp/cmd/godef
   521  
   522  
   523  If you're looking for even more integration with Go, namely
   524  on-the-fly syntax checking, auto-completion and snippets, it is
   525  recommended that you look at goflymake
   526  \(https://github.com/dougm/goflymake), gocode
   527  \(https://github.com/nsf/gocode) and yasnippet-go
   528  \(https://github.com/dominikh/yasnippet-go)"
   529  
   530    ;; Font lock
   531    (set (make-local-variable 'font-lock-defaults)
   532         '(go--build-font-lock-keywords))
   533  
   534    ;; Indentation
   535    (set (make-local-variable 'indent-line-function) 'go-mode-indent-line)
   536  
   537    ;; Comments
   538    (set (make-local-variable 'comment-start) "// ")
   539    (set (make-local-variable 'comment-end)   "")
   540    (set (make-local-variable 'comment-use-syntax) t)
   541    (set (make-local-variable 'comment-start-skip) "\\(//+\\|/\\*+\\)\\s *")
   542  
   543    (set (make-local-variable 'beginning-of-defun-function) 'go-beginning-of-defun)
   544    (set (make-local-variable 'end-of-defun-function) 'go-end-of-defun)
   545  
   546    (set (make-local-variable 'parse-sexp-lookup-properties) t)
   547    (if (boundp 'syntax-propertize-function)
   548        (set (make-local-variable 'syntax-propertize-function) 'go-propertize-syntax))
   549  
   550    (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql))
   551    (add-hook 'before-change-functions (lambda (x y) (setq go-dangling-cache (make-hash-table :test 'eql))) t t)
   552  
   553  
   554    (setq imenu-generic-expression
   555          '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1)
   556            ("func" "^func *\\(.*\\) {" 1)))
   557    (imenu-add-to-menubar "Index")
   558  
   559    ;; Go style
   560    (setq indent-tabs-mode t)
   561  
   562    ;; Handle unit test failure output in compilation-mode
   563    ;;
   564    ;; Note the final t argument to add-to-list for append, ie put these at the
   565    ;; *ends* of compilation-error-regexp-alist[-alist]. We want go-test to be
   566    ;; handled first, otherwise other elements will match that don't work, and
   567    ;; those alists are traversed in *reverse* order:
   568    ;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2001-12/msg00674.html
   569    (when (and (boundp 'compilation-error-regexp-alist)
   570               (boundp 'compilation-error-regexp-alist-alist))
   571      (add-to-list 'compilation-error-regexp-alist 'go-test t)
   572      (add-to-list 'compilation-error-regexp-alist-alist
   573                   '(go-test . ("^\t+\\([^()\t\n]+\\):\\([0-9]+\\):? .*$" 1 2)) t)))
   574  
   575  ;;;###autoload
   576  (add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode))
   577  
   578  (defun go--apply-rcs-patch (patch-buffer)
   579    "Apply an RCS-formatted diff from PATCH-BUFFER to the current
   580  buffer."
   581    (let ((target-buffer (current-buffer))
   582          ;; Relative offset between buffer line numbers and line numbers
   583          ;; in patch.
   584          ;;
   585          ;; Line numbers in the patch are based on the source file, so
   586          ;; we have to keep an offset when making changes to the
   587          ;; buffer.
   588          ;;
   589          ;; Appending lines decrements the offset (possibly making it
   590          ;; negative), deleting lines increments it. This order
   591          ;; simplifies the forward-line invocations.
   592          (line-offset 0))
   593      (save-excursion
   594        (with-current-buffer patch-buffer
   595          (goto-char (point-min))
   596          (while (not (eobp))
   597            (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
   598              (error "invalid rcs patch or internal error in go--apply-rcs-patch"))
   599            (forward-line)
   600            (let ((action (match-string 1))
   601                  (from (string-to-number (match-string 2)))
   602                  (len  (string-to-number (match-string 3))))
   603              (cond
   604               ((equal action "a")
   605                (let ((start (point)))
   606                  (forward-line len)
   607                  (let ((text (buffer-substring start (point))))
   608                    (with-current-buffer target-buffer
   609                      (decf line-offset len)
   610                      (goto-char (point-min))
   611                      (forward-line (- from len line-offset))
   612                      (insert text)))))
   613               ((equal action "d")
   614                (with-current-buffer target-buffer
   615                  (go--goto-line (- from line-offset))
   616                  (incf line-offset len)
   617                  (go--delete-whole-line len)))
   618               (t
   619                (error "invalid rcs patch or internal error in go--apply-rcs-patch")))))))))
   620  
   621  (defun gofmt ()
   622    "Formats the current buffer according to the gofmt tool."
   623  
   624    (interactive)
   625    (let ((tmpfile (make-temp-file "gofmt" nil ".go"))
   626          (patchbuf (get-buffer-create "*Gofmt patch*"))
   627          (errbuf (get-buffer-create "*Gofmt Errors*"))
   628          (coding-system-for-read 'utf-8)
   629          (coding-system-for-write 'utf-8))
   630  
   631      (with-current-buffer errbuf
   632        (setq buffer-read-only nil)
   633        (erase-buffer))
   634      (with-current-buffer patchbuf
   635        (erase-buffer))
   636  
   637      (write-region nil nil tmpfile)
   638  
   639      ;; We're using errbuf for the mixed stdout and stderr output. This
   640      ;; is not an issue because gofmt -w does not produce any stdout
   641      ;; output in case of success.
   642      (if (zerop (call-process "gofmt" nil errbuf nil "-w" tmpfile))
   643          (if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
   644              (progn
   645                (kill-buffer errbuf)
   646                (message "Buffer is already gofmted"))
   647            (go--apply-rcs-patch patchbuf)
   648            (kill-buffer errbuf)
   649            (message "Applied gofmt"))
   650        (message "Could not apply gofmt. Check errors for details")
   651        (gofmt--process-errors (buffer-file-name) tmpfile errbuf))
   652  
   653      (kill-buffer patchbuf)
   654      (delete-file tmpfile)))
   655  
   656  
   657  (defun gofmt--process-errors (filename tmpfile errbuf)
   658    ;; Convert the gofmt stderr to something understood by the compilation mode.
   659    (with-current-buffer errbuf
   660      (goto-char (point-min))
   661      (insert "gofmt errors:\n")
   662      (while (search-forward-regexp (concat "^\\(" (regexp-quote tmpfile) "\\):") nil t)
   663        (replace-match (file-name-nondirectory filename) t t nil 1))
   664      (compilation-mode)
   665      (display-buffer errbuf)))
   666  
   667  ;;;###autoload
   668  (defun gofmt-before-save ()
   669    "Add this to .emacs to run gofmt on the current buffer when saving:
   670   (add-hook 'before-save-hook 'gofmt-before-save).
   671  
   672  Note that this will cause go-mode to get loaded the first time
   673  you save any file, kind of defeating the point of autoloading."
   674  
   675    (interactive)
   676    (when (eq major-mode 'go-mode) (gofmt)))
   677  
   678  (defun godoc--read-query ()
   679    "Read a godoc query from the minibuffer."
   680    ;; Compute the default query as the symbol under the cursor.
   681    ;; TODO: This does the wrong thing for e.g. multipart.NewReader (it only grabs
   682    ;; half) but I see no way to disambiguate that from e.g. foobar.SomeMethod.
   683    (let* ((bounds (bounds-of-thing-at-point 'symbol))
   684           (symbol (if bounds
   685                       (buffer-substring-no-properties (car bounds)
   686                                                       (cdr bounds)))))
   687      (completing-read (if symbol
   688                           (format "godoc (default %s): " symbol)
   689                         "godoc: ")
   690                       (go--old-completion-list-style (go-packages)) nil nil nil 'go-godoc-history symbol)))
   691  
   692  (defun godoc--get-buffer (query)
   693    "Get an empty buffer for a godoc query."
   694    (let* ((buffer-name (concat "*godoc " query "*"))
   695           (buffer (get-buffer buffer-name)))
   696      ;; Kill the existing buffer if it already exists.
   697      (when buffer (kill-buffer buffer))
   698      (get-buffer-create buffer-name)))
   699  
   700  (defun godoc--buffer-sentinel (proc event)
   701    "Sentinel function run when godoc command completes."
   702    (with-current-buffer (process-buffer proc)
   703      (cond ((string= event "finished\n")  ;; Successful exit.
   704             (goto-char (point-min))
   705             (view-mode 1)
   706             (display-buffer (current-buffer) t))
   707            ((/= (process-exit-status proc) 0)  ;; Error exit.
   708             (let ((output (buffer-string)))
   709               (kill-buffer (current-buffer))
   710               (message (concat "godoc: " output)))))))
   711  
   712  ;;;###autoload
   713  (defun godoc (query)
   714    "Show go documentation for a query, much like M-x man."
   715    (interactive (list (godoc--read-query)))
   716    (unless (string= query "")
   717      (set-process-sentinel
   718       (start-process-shell-command "godoc" (godoc--get-buffer query)
   719                                    (concat "godoc " query))
   720       'godoc--buffer-sentinel)
   721      nil))
   722  
   723  (defun go-goto-imports ()
   724    "Move point to the block of imports.
   725  
   726  If using
   727  
   728    import (
   729      \"foo\"
   730      \"bar\"
   731    )
   732  
   733  it will move point directly behind the last import.
   734  
   735  If using
   736  
   737    import \"foo\"
   738    import \"bar\"
   739  
   740  it will move point to the next line after the last import.
   741  
   742  If no imports can be found, point will be moved after the package
   743  declaration."
   744    (interactive)
   745    ;; FIXME if there's a block-commented import before the real
   746    ;; imports, we'll jump to that one.
   747  
   748    ;; Generally, this function isn't very forgiving. it'll bark on
   749    ;; extra whitespace. It works well for clean code.
   750    (let ((old-point (point)))
   751      (goto-char (point-min))
   752      (cond
   753       ((re-search-forward "^import ([^)]+)" nil t)
   754        (backward-char 2)
   755        'block)
   756       ((re-search-forward "\\(^import \\([^\"]+ \\)?\"[^\"]+\"\n?\\)+" nil t)
   757        'single)
   758       ((re-search-forward "^[[:space:]\n]*package .+?\n" nil t)
   759        (message "No imports found, moving point after package declaration")
   760        'none)
   761       (t
   762        (goto-char old-point)
   763        (message "No imports or package declaration found. Is this really a Go file?")
   764        'fail))))
   765  
   766  (defun go-play-buffer ()
   767    "Like `go-play-region', but acts on the entire buffer."
   768    (interactive)
   769    (go-play-region (point-min) (point-max)))
   770  
   771  (defun go-play-region (start end)
   772    "Send the region to the Playground and stores the resulting
   773  link in the kill ring."
   774    (interactive "r")
   775    (let* ((url-request-method "POST")
   776           (url-request-extra-headers
   777            '(("Content-Type" . "application/x-www-form-urlencoded")))
   778           (url-request-data (buffer-substring-no-properties start end))
   779           (content-buf (url-retrieve
   780                         "http://play.golang.org/share"
   781                         (lambda (arg)
   782                           (cond
   783                            ((equal :error (car arg))
   784                             (signal 'go-play-error (cdr arg)))
   785                            (t
   786                             (re-search-forward "\n\n")
   787                             (kill-new (format "http://play.golang.org/p/%s" (buffer-substring (point) (point-max))))
   788                             (message "http://play.golang.org/p/%s" (buffer-substring (point) (point-max)))))))))))
   789  
   790  ;;;###autoload
   791  (defun go-download-play (url)
   792    "Downloads a paste from the playground and inserts it in a Go
   793  buffer. Tries to look for a URL at point."
   794    (interactive (list (read-from-minibuffer "Playground URL: " (ffap-url-p (ffap-string-at-point 'url)))))
   795    (with-current-buffer
   796        (let ((url-request-method "GET") url-request-data url-request-extra-headers)
   797          (url-retrieve-synchronously (concat url ".go")))
   798      (let ((buffer (generate-new-buffer (concat (car (last (split-string url "/"))) ".go"))))
   799        (goto-char (point-min))
   800        (re-search-forward "\n\n")
   801        (copy-to-buffer buffer (point) (point-max))
   802        (kill-buffer)
   803        (with-current-buffer buffer
   804          (go-mode)
   805          (switch-to-buffer buffer)))))
   806  
   807  (defun go-propertize-syntax (start end)
   808    (save-excursion
   809      (goto-char start)
   810      (while (search-forward "\\" end t)
   811        (put-text-property (1- (point)) (point) 'syntax-table (if (= (char-after) ?`) '(1) '(9))))))
   812  
   813  (defun go-import-add (arg import)
   814    "Add a new import to the list of imports.
   815  
   816  When called with a prefix argument asks for an alternative name
   817  to import the package as.
   818  
   819  If no list exists yet, one will be created if possible.
   820  
   821  If an identical import has been commented, it will be
   822  uncommented, otherwise a new import will be added."
   823  
   824    ;; - If there's a matching `// import "foo"`, uncomment it
   825    ;; - If we're in an import() block and there's a matching `"foo"`, uncomment it
   826    ;; - Otherwise add a new import, with the appropriate syntax
   827    (interactive
   828     (list
   829      current-prefix-arg
   830      (replace-regexp-in-string "^[\"']\\|[\"']$" "" (completing-read "Package: " (go--old-completion-list-style (go-packages))))))
   831    (save-excursion
   832      (let (as line import-start)
   833        (if arg
   834            (setq as (read-from-minibuffer "Import as: ")))
   835        (if as
   836            (setq line (format "%s \"%s\"" as import))
   837          (setq line (format "\"%s\"" import)))
   838  
   839        (goto-char (point-min))
   840        (if (re-search-forward (concat "^[[:space:]]*//[[:space:]]*import " line "$") nil t)
   841            (uncomment-region (line-beginning-position) (line-end-position))
   842          (case (go-goto-imports)
   843            ('fail (message "Could not find a place to add import."))
   844            ('block
   845                (save-excursion
   846                  (re-search-backward "^import (")
   847                  (setq import-start (point)))
   848              (if (re-search-backward (concat "^[[:space:]]*//[[:space:]]*" line "$")  import-start t)
   849                  (uncomment-region (line-beginning-position) (line-end-position))
   850                (insert "\n\t" line)))
   851            ('single (insert "import " line "\n"))
   852            ('none (insert "\nimport (\n\t" line "\n)\n")))))))
   853  
   854  (defun go-root-and-paths ()
   855    (let* ((output (split-string (shell-command-to-string (concat go-command " env GOROOT GOPATH"))
   856                                 "\n"))
   857           (root (car output))
   858           (paths (split-string (cadr output) ":")))
   859      (append (list root) paths)))
   860  
   861  (defun go--string-prefix-p (s1 s2 &optional ignore-case)
   862    "Return non-nil if S1 is a prefix of S2.
   863  If IGNORE-CASE is non-nil, the comparison is case-insensitive."
   864    (eq t (compare-strings s1 nil nil
   865                           s2 0 (length s1) ignore-case)))
   866  
   867  (defun go--directory-dirs (dir)
   868    "Recursively return all subdirectories in DIR."
   869    (if (file-directory-p dir)
   870        (let ((dir (directory-file-name dir))
   871              (dirs '())
   872              (files (directory-files dir nil nil t)))
   873          (dolist (file files)
   874            (unless (member file '("." ".."))
   875              (let ((file (concat dir "/" file)))
   876                (if (file-directory-p file)
   877                    (setq dirs (append (cons file
   878                                             (go--directory-dirs file))
   879                                       dirs))))))
   880          dirs)
   881      '()))
   882  
   883  
   884  (defun go-packages ()
   885    (sort
   886     (delete-dups
   887      (mapcan
   888       (lambda (topdir)
   889         (let ((pkgdir (concat topdir "/pkg/")))
   890           (mapcan (lambda (dir)
   891                     (mapcar (lambda (file)
   892                               (let ((sub (substring file (length pkgdir) -2)))
   893                                 (unless (or (go--string-prefix-p "obj/" sub) (go--string-prefix-p "tool/" sub))
   894                                   (mapconcat 'identity (cdr (split-string sub "/")) "/"))))
   895                             (if (file-directory-p dir)
   896                                 (directory-files dir t "\\.a$"))))
   897                   (if (file-directory-p pkgdir)
   898                       (go--directory-dirs pkgdir)))))
   899       (go-root-and-paths)))
   900     'string<))
   901  
   902  (defun go-unused-imports-lines ()
   903    ;; FIXME Technically, -o /dev/null fails in quite some cases (on
   904    ;; Windows, when compiling from within GOPATH). Practically,
   905    ;; however, it has the same end result: There won't be a
   906    ;; compiled binary/archive, and we'll get our import errors when
   907    ;; there are any.
   908    (reverse (remove nil
   909                     (mapcar
   910                      (lambda (line)
   911                        (if (string-match "^\\(.+\\):\\([[:digit:]]+\\): imported and not used: \".+\"$" line)
   912                            (if (string= (file-truename (match-string 1 line)) (file-truename buffer-file-name))
   913                                (string-to-number (match-string 2 line)))))
   914                      (split-string (shell-command-to-string
   915                                     (concat go-command
   916                                             (if (string-match "_test\.go$" buffer-file-truename)
   917                                                 " test -c"
   918                                               " build -o /dev/null"))) "\n")))))
   919  
   920  (defun go-remove-unused-imports (arg)
   921    "Removes all unused imports. If ARG is non-nil, unused imports
   922  will be commented, otherwise they will be removed completely."
   923    (interactive "P")
   924    (save-excursion
   925      (let ((cur-buffer (current-buffer)) flymake-state lines)
   926        (when (boundp 'flymake-mode)
   927          (setq flymake-state flymake-mode)
   928          (flymake-mode-off))
   929        (save-some-buffers nil (lambda () (equal cur-buffer (current-buffer))))
   930        (if (buffer-modified-p)
   931            (message "Cannot operate on unsaved buffer")
   932          (setq lines (go-unused-imports-lines))
   933          (dolist (import lines)
   934            (go--goto-line import)
   935            (beginning-of-line)
   936            (if arg
   937                (comment-region (line-beginning-position) (line-end-position))
   938              (go--delete-whole-line)))
   939          (message "Removed %d imports" (length lines)))
   940        (if flymake-state (flymake-mode-on)))))
   941  
   942  (defun godef--find-file-line-column (specifier other-window)
   943    "Given a file name in the format of `filename:line:column',
   944  visit FILENAME and go to line LINE and column COLUMN."
   945    (if (not (string-match "\\(.+\\):\\([0-9]+\\):\\([0-9]+\\)" specifier))
   946        (error "Unexpected godef output: %s" specifier)
   947      (let ((filename (match-string 1 specifier))
   948            (line (string-to-number (match-string 2 specifier)))
   949            (column (string-to-number (match-string 3 specifier))))
   950        (with-current-buffer (funcall (if other-window 'find-file-other-window 'find-file) filename)
   951          (go--goto-line line)
   952          (beginning-of-line)
   953          (forward-char (1- column))
   954          (if (buffer-modified-p)
   955              (message "Buffer is modified, file position might not have been correct"))))))
   956  
   957  (defun godef--call (point)
   958    "Call godef, acquiring definition position and expression
   959  description at POINT."
   960    (if (go--xemacs-p)
   961        (error "godef does not reliably work in XEmacs, expect bad results"))
   962    (if (not (buffer-file-name (go--coverage-origin-buffer)))
   963        (error "Cannot use godef on a buffer without a file name")
   964      (let ((outbuf (get-buffer-create "*godef*")))
   965        (with-current-buffer outbuf
   966          (erase-buffer))
   967        (call-process-region (point-min)
   968                             (point-max)
   969                             "godef"
   970                             nil
   971                             outbuf
   972                             nil
   973                             "-i"
   974                             "-t"
   975                             "-f"
   976                             (file-truename (buffer-file-name (go--coverage-origin-buffer)))
   977                             "-o"
   978                             (number-to-string (go--position-bytes (point))))
   979        (with-current-buffer outbuf
   980          (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n")))))
   981  
   982  (defun godef-describe (point)
   983    "Describe the expression at POINT."
   984    (interactive "d")
   985    (condition-case nil
   986        (let ((description (cdr (butlast (godef--call point) 1))))
   987          (if (not description)
   988              (message "No description found for expression at point")
   989            (message "%s" (mapconcat 'identity description "\n"))))
   990      (file-error (message "Could not run godef binary"))))
   991  
   992  (defun godef-jump (point &optional other-window)
   993    "Jump to the definition of the expression at POINT."
   994    (interactive "d")
   995    (condition-case nil
   996        (let ((file (car (godef--call point))))
   997          (cond
   998           ((string= "-" file)
   999            (message "godef: expression is not defined anywhere"))
  1000           ((string= "godef: no identifier found" file)
  1001            (message "%s" file))
  1002           ((go--string-prefix-p "godef: no declaration found for " file)
  1003            (message "%s" file))
  1004           (t
  1005            (push-mark)
  1006            (ring-insert find-tag-marker-ring (point-marker))
  1007            (godef--find-file-line-column file other-window))))
  1008      (file-error (message "Could not run godef binary"))))
  1009  
  1010  (defun godef-jump-other-window (point)
  1011    (interactive "d")
  1012    (godef-jump point t))
  1013  
  1014  (defun go--goto-line (line)
  1015    (goto-char (point-min))
  1016    (forward-line (1- line)))
  1017  
  1018  (defun go--line-column-to-point (line column)
  1019    (save-excursion
  1020      (go--goto-line line)
  1021      (forward-char (1- column))
  1022      (point)))
  1023  
  1024  (defstruct go--covered
  1025    start-line start-column end-line end-column covered count)
  1026  
  1027  (defun go--coverage-file ()
  1028    "Return the coverage file to use, either by reading it from the
  1029  current coverage buffer or by prompting for it."
  1030    (if (boundp 'go--coverage-current-file-name)
  1031        go--coverage-current-file-name
  1032      (read-file-name "Coverage file: " nil nil t)))
  1033  
  1034  (defun go--coverage-origin-buffer ()
  1035    "Return the buffer to base the coverage on."
  1036    (if (boundp 'go--coverage-origin-buffer)
  1037        go--coverage-origin-buffer
  1038      (current-buffer)))
  1039  
  1040  (defun go--coverage-face (count divisor)
  1041    "Return the intensity face for COUNT when using DIVISOR
  1042  to scale it to a range [0,10].
  1043  
  1044  DIVISOR scales the absolute cover count to values from 0 to 10.
  1045  For DIVISOR = 0 the count will always translate to 8."
  1046    (let* ((norm (cond
  1047                  ((= count 0)
  1048                   -0.1) ;; Uncovered code, set to -0.1 so n becomes 0.
  1049                  ((= divisor 0)
  1050                   0.8) ;; covermode=set, set to 0.8 so n becomes 8.
  1051                  (t
  1052                   (/ (log count) divisor))))
  1053           (n (1+ (floor (* norm 9))))) ;; Convert normalized count [0,1] to intensity [0,10]
  1054      (concat "go-coverage-" (number-to-string n))))
  1055  
  1056  (defun go--coverage-make-overlay (range divisor)
  1057    "Create a coverage overlay for a RANGE of covered/uncovered
  1058  code. Uses DIVISOR to scale absolute counts to a [0,10] scale."
  1059    (let* ((count (go--covered-count range))
  1060           (face (go--coverage-face count divisor))
  1061           (ov (make-overlay (go--line-column-to-point (go--covered-start-line range)
  1062                                                       (go--covered-start-column range))
  1063                             (go--line-column-to-point (go--covered-end-line range)
  1064                                                       (go--covered-end-column range)))))
  1065  
  1066      (overlay-put ov 'face face)
  1067      (overlay-put ov 'help-echo (format "Count: %d" count))))
  1068  
  1069  (defun go--coverage-clear-overlays ()
  1070    "Remove existing overlays and put a single untracked overlay
  1071  over the entire buffer."
  1072    (remove-overlays)
  1073    (overlay-put (make-overlay (point-min) (point-max))
  1074                 'face
  1075                 'go-coverage-untracked))
  1076  
  1077  (defun go--coverage-parse-file (coverage-file file-name)
  1078    "Parse COVERAGE-FILE and extract coverage information and
  1079  divisor for FILE-NAME."
  1080    (let (ranges
  1081          (max-count 0))
  1082      (with-temp-buffer
  1083        (insert-file-contents coverage-file)
  1084        (go--goto-line 2) ;; Skip over mode
  1085        (while (not (eobp))
  1086          (let* ((parts (split-string (buffer-substring (point-at-bol) (point-at-eol)) ":"))
  1087                 (file (car parts))
  1088                 (rest (split-string (nth 1 parts) "[., ]")))
  1089  
  1090            (destructuring-bind
  1091                (start-line start-column end-line end-column num count)
  1092                (mapcar #'string-to-number rest)
  1093  
  1094              (when (and (string= (file-name-nondirectory file) file-name))
  1095                (if (> count max-count)
  1096                    (setq max-count count))
  1097                (push (make-go--covered :start-line start-line
  1098                                        :start-column start-column
  1099                                        :end-line end-line
  1100                                        :end-column end-column
  1101                                        :covered (/= count 0)
  1102                                        :count count)
  1103                      ranges)))
  1104  
  1105            (forward-line)))
  1106  
  1107        (list ranges (if (> max-count 0) (log max-count) 0)))))
  1108  
  1109  (defun go-coverage (&optional coverage-file)
  1110    "Open a clone of the current buffer and overlay it with
  1111  coverage information gathered via go test -coverprofile=COVERAGE-FILE.
  1112  
  1113  If COVERAGE-FILE is nil, it will either be infered from the
  1114  current buffer if it's already a coverage buffer, or be prompted
  1115  for."
  1116    (interactive)
  1117    (let* ((cur-buffer (current-buffer))
  1118           (origin-buffer (go--coverage-origin-buffer))
  1119           (gocov-buffer-name (concat (buffer-name origin-buffer) "<gocov>"))
  1120           (coverage-file (or coverage-file (go--coverage-file)))
  1121           (ranges-and-divisor (go--coverage-parse-file
  1122                                coverage-file
  1123                                (file-name-nondirectory (buffer-file-name origin-buffer))))
  1124           (cov-mtime (nth 5 (file-attributes coverage-file)))
  1125           (cur-mtime (nth 5 (file-attributes (buffer-file-name origin-buffer)))))
  1126  
  1127      (if (< (float-time cov-mtime) (float-time cur-mtime))
  1128          (message "Coverage file is older than the source file."))
  1129  
  1130      (with-current-buffer (or (get-buffer gocov-buffer-name)
  1131                               (make-indirect-buffer origin-buffer gocov-buffer-name t))
  1132        (set (make-local-variable 'go--coverage-origin-buffer) origin-buffer)
  1133        (set (make-local-variable 'go--coverage-current-file-name) coverage-file)
  1134  
  1135        (save-excursion
  1136          (go--coverage-clear-overlays)
  1137          (dolist (range (car ranges-and-divisor))
  1138            (go--coverage-make-overlay range (cadr ranges-and-divisor))))
  1139  
  1140        (if (not (eq cur-buffer (current-buffer)))
  1141            (display-buffer (current-buffer) 'display-buffer-reuse-window)))))
  1142  
  1143  (provide 'go-mode)