go-mode.el (112537B)
1 ;;; go-mode.el --- Major mode for the Go programming language 2 3 ;;; Commentary: 4 5 ;; Copyright 2013 The go-mode Authors. All rights reserved. 6 ;; Use of this source code is governed by a BSD-style 7 ;; license that can be found in the LICENSE file. 8 9 ;; Author: The go-mode Authors 10 ;; Version: 1.6.0 11 ;; Keywords: languages go 12 ;; Package-Requires: ((emacs "26.1")) 13 ;; URL: https://github.com/dominikh/go-mode.el 14 ;; 15 ;; This file is not part of GNU Emacs. 16 17 ;;; Code: 18 19 (require 'cl-lib) 20 (require 'compile) 21 (require 'etags) 22 (require 'ffap) 23 (require 'find-file) 24 (require 'ring) 25 (require 'url) 26 (require 'xref) 27 28 29 (eval-when-compile 30 (defmacro go--forward-word (&optional arg) 31 (if (fboundp 'forward-word-strictly) 32 `(forward-word-strictly ,arg) 33 `(forward-word ,arg)))) 34 35 (defun go--delete-whole-line (&optional arg) 36 "Delete the current line without putting it in the `kill-ring'. 37 Derived from function `kill-whole-line'. ARG is defined as for that 38 function." 39 (setq arg (or arg 1)) 40 (if (and (> arg 0) 41 (eobp) 42 (save-excursion (forward-visible-line 0) (eobp))) 43 (signal 'end-of-buffer nil)) 44 (if (and (< arg 0) 45 (bobp) 46 (save-excursion (end-of-visible-line) (bobp))) 47 (signal 'beginning-of-buffer nil)) 48 (cond ((zerop arg) 49 (delete-region (progn (forward-visible-line 0) (point)) 50 (progn (end-of-visible-line) (point)))) 51 ((< arg 0) 52 (delete-region (progn (end-of-visible-line) (point)) 53 (progn (forward-visible-line (1+ arg)) 54 (unless (bobp) 55 (backward-char)) 56 (point)))) 57 (t 58 (delete-region (progn (forward-visible-line 0) (point)) 59 (progn (forward-visible-line arg) (point)))))) 60 61 (defun go-goto-opening-parenthesis (&optional _legacy-unused) 62 "Move up one level of parentheses. 63 64 Return non-nil if there was a paren to move up to." 65 ;; The old implementation of go-goto-opening-parenthesis had an 66 ;; optional argument to speed up the function. It didn't change the 67 ;; function's outcome. 68 69 ;; Silently fail if there's no matching opening parenthesis. 70 (let ((open-char (nth 1 (syntax-ppss)))) 71 (when open-char 72 (goto-char open-char)))) 73 74 75 (defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]") 76 (defconst go--max-dangling-operator-length 2 77 "The maximum length of dangling operators. 78 This must be at least the length of the longest string matched by 79 ‘go-dangling-operators-regexp’ and must be updated whenever that 80 constant is changed.") 81 82 (defconst go-identifier-regexp "[[:word:][:multibyte:]]+") 83 (defconst go-type-name-no-prefix-regexp "\\(?:[[:word:][:multibyte:]]+\\.\\)?[[:word:][:multibyte:]]+") 84 (defconst go-qualified-identifier-regexp (concat go-identifier-regexp "\\." go-identifier-regexp)) 85 (defconst go-label-regexp go-identifier-regexp) 86 (defconst go-type-regexp "[[:word:][:multibyte:]*]+") 87 (defconst go-func-regexp (concat "\\_<func\\_>\\s *\\(" go-identifier-regexp "\\)")) 88 (defconst go-func-meth-regexp (concat 89 "\\_<func\\_>\\s *\\(?:(\\s *" 90 "\\(" go-identifier-regexp "\\s +\\)?" go-type-regexp 91 "\\s *)\\s *\\)?\\(" 92 go-identifier-regexp 93 "\\)(")) 94 95 (defconst go--comment-start-regexp "[[:space:]]*\\(?:/[/*]\\)") 96 (defconst go--case-regexp "\\([[:space:]]*case\\([[:space:]]\\|$\\)\\)") 97 (defconst go--case-or-default-regexp (concat "\\(" go--case-regexp "\\|" "[[:space:]]*default:\\)")) 98 99 (defconst go-builtins 100 '("append" "cap" "close" "complex" "copy" 101 "delete" "imag" "len" "make" "new" 102 "panic" "print" "println" "real" "recover") 103 "All built-in functions in the Go language. Used for font locking.") 104 105 (defconst go-mode-keywords 106 '("break" "default" "func" "interface" "select" 107 "case" "defer" "go" "map" "struct" 108 "chan" "else" "goto" "package" "switch" 109 "const" "fallthrough" "if" "range" "type" 110 "continue" "for" "import" "return" "var") 111 "All keywords in the Go language. Used for font locking.") 112 113 (defconst go-constants '("nil" "true" "false" "iota")) 114 (defconst go-type-name-regexp (concat "\\**\\(\\(?:" go-identifier-regexp "\\.\\)?" go-identifier-regexp "\\)")) 115 116 (defvar go-dangling-cache) 117 (defvar go-godoc-history nil) 118 (defvar go--coverage-current-file-name) 119 120 (defgroup go nil 121 "Major mode for editing Go code." 122 :link '(url-link "https://github.com/dominikh/go-mode.el") 123 :group 'languages) 124 125 (defgroup go-cover nil 126 "Options specific to `cover`." 127 :group 'go) 128 129 (defgroup godoc nil 130 "Options specific to `godoc'." 131 :group 'go) 132 133 (defcustom go-fontify-function-calls t 134 "Fontify function and method calls if this is non-nil." 135 :type 'boolean 136 :group 'go) 137 138 (defcustom go-fontify-variables t 139 "Fontify variable declarations if this is non-nil." 140 :type 'boolean 141 :group 'go) 142 143 (defcustom go-mode-hook nil 144 "Hook called by `go-mode'." 145 :type 'hook 146 :group 'go) 147 148 (defcustom go-command "go" 149 "The 'go' command. 150 Some users have multiple Go development trees and invoke the 'go' 151 tool via a wrapper that sets GOROOT and GOPATH based on the 152 current directory. Such users should customize this variable to 153 point to the wrapper script." 154 :type 'string 155 :group 'go) 156 157 (defcustom gofmt-command "gofmt" 158 "The 'gofmt' command. 159 Some users may replace this with 'goimports' 160 from https://golang.org/x/tools/cmd/goimports." 161 :type 'string 162 :group 'go) 163 164 (defcustom gofmt-args nil 165 "Additional arguments to pass to gofmt." 166 :type '(repeat string) 167 :group 'go) 168 169 (defcustom gofmt-show-errors 'buffer 170 "Where to display gofmt error output. 171 It can either be displayed in its own buffer, in the echo area, or not at all. 172 173 Please note that Emacs outputs to the echo area when writing 174 files and will overwrite gofmt's echo output if used from inside 175 a `before-save-hook'." 176 :type '(choice 177 (const :tag "Own buffer" buffer) 178 (const :tag "Echo area" echo) 179 (const :tag "None" nil)) 180 :group 'go) 181 182 (defcustom godef-command "godef" 183 "The 'godef' command." 184 :type 'string 185 :group 'go) 186 187 (defcustom go-other-file-alist 188 '(("_test\\.go\\'" (".go")) 189 ("\\.go\\'" ("_test.go"))) 190 "See the documentation of `ff-other-file-alist' for details." 191 :type '(repeat (list regexp (choice (repeat string) function))) 192 :group 'go) 193 194 (defcustom go-packages-function 'go-packages-go-list 195 "Function called by `go-packages' to determine the list of available packages. 196 This is used in e.g. tab completion in `go-import-add'. 197 198 This package provides two functions: `go-packages-go-list' uses 199 'go list all' to determine all Go packages. `go-packages-native' uses 200 elisp to find all .a files in all /pkg/ directories. 201 `go-packages-native' is obsolete as it doesn't behave correctly with 202 the Go build cache or Go modules." 203 :type 'function 204 :package-version '(go-mode . 1.4.0) 205 :group 'go) 206 207 (defcustom go-guess-gopath-functions (list #'go-plain-gopath) 208 "Functions to call in sequence to detect a project's GOPATH. 209 210 The functions in this list will be called one after another, 211 until a function returns non-nil. The order of the functions in 212 this list is important, as some project layouts may superficially 213 look like others." 214 :type '(repeat function) 215 :group 'go) 216 217 (defcustom go-confirm-playground-uploads t 218 "Ask before uploading code to the public Go Playground. 219 220 Set this to nil to upload without prompting. 221 " 222 :type 'boolean 223 :group 'go) 224 225 (defcustom godoc-command "go doc" 226 "Which executable to use for `godoc'. 227 This can be either an absolute path or an executable in PATH." 228 :type 'string 229 :group 'go) 230 231 (defcustom godoc-and-godef-command "go doc" 232 "Which executable to use for `godoc-and-godef'. 233 This can be either an absolute path or an executable in PATH." 234 :type 'string 235 :group 'go) 236 237 (defcustom godoc-use-completing-read nil 238 "Provide auto-completion for godoc. 239 Only really desirable when using `godoc' instead of `go doc'." 240 :type 'boolean 241 :group 'godoc) 242 243 (defcustom godoc-reuse-buffer nil 244 "Reuse a single *godoc* buffer to display godoc-at-point calls. 245 The default behavior is to open a separate buffer for each call." 246 :type 'boolean 247 :group 'godoc) 248 249 (defcustom godoc-at-point-function #'godoc-and-godef 250 "Function to call to display the documentation for an 251 identifier at a given position. 252 253 This package provides two functions: `godoc-and-godef' uses a 254 combination of godef and godoc to find the documentation. This 255 approach has several caveats. See its documentation for more 256 information. The second function, `godoc-gogetdoc' uses an 257 additional tool that correctly determines the documentation for 258 any identifier. It provides better results than 259 `godoc-and-godef'." 260 :type 'function 261 :group 'godoc) 262 263 (defun godoc-and-godef (point) 264 "Use a combination of godef and godoc to guess the documentation at POINT. 265 266 Due to a limitation in godoc, it is not possible to differentiate 267 between functions and methods, which may cause `godoc-at-point' 268 to display more documentation than desired. Furthermore, it 269 doesn't work on package names or variables. 270 271 Consider using ‘godoc-gogetdoc’ instead for more accurate results." 272 (condition-case nil 273 (let* ((output (godef--call point)) 274 (file (car output)) 275 (name-parts (split-string (cadr output) " ")) 276 (first (car name-parts))) 277 (if (not (godef--successful-p file)) 278 (message "%s" (godef--error file)) 279 (go--godoc (format "%s %s" 280 (file-name-directory file) 281 (if (or (string= first "type") (string= first "const")) 282 (cadr name-parts) 283 (car name-parts))) 284 godoc-and-godef-command))) 285 (file-error (message "Could not run godef binary")))) 286 287 (defun godoc-gogetdoc (point) 288 "Use the gogetdoc tool to find the documentation for an identifier at POINT. 289 290 You can install gogetdoc with 'go get -u github.com/zmb3/gogetdoc'." 291 (if (not (buffer-file-name (go--coverage-origin-buffer))) 292 ;; TODO: gogetdoc supports unsaved files, but not introducing 293 ;; new artificial files, so this limitation will stay for now. 294 (error "Cannot use gogetdoc on a buffer without a file name")) 295 (let ((posn (format "%s:#%d" (file-truename buffer-file-name) (1- (position-bytes point)))) 296 (out (godoc--get-buffer "<at point>"))) 297 (with-temp-buffer 298 (go--insert-modified-files) 299 (call-process-region (point-min) (point-max) "gogetdoc" nil out nil 300 "-modified" 301 (format "-pos=%s" posn))) 302 (with-current-buffer out 303 (goto-char (point-min)) 304 (godoc-mode) 305 (display-buffer (current-buffer) t)))) 306 307 (defun go--kill-new-message (url) 308 "Make URL the latest kill and print a message." 309 (kill-new url) 310 (message "%s" url)) 311 312 (defcustom go-play-browse-function 'go--kill-new-message 313 "Function to call with the Playground URL. 314 See `go-play-region' for more details." 315 :type '(choice 316 (const :tag "Nothing" nil) 317 (const :tag "Kill + Message" go--kill-new-message) 318 (const :tag "Browse URL" browse-url) 319 (function :tag "Call function")) 320 :group 'go) 321 322 (defcustom go-coverage-display-buffer-func 'display-buffer-reuse-window 323 "How `go-coverage' should display the coverage buffer. 324 See `display-buffer' for a list of possible functions." 325 :type 'function 326 :group 'go-cover) 327 328 (defface go-coverage-untracked 329 '((t (:foreground "#505050"))) 330 "Coverage color of untracked code." 331 :group 'go-cover) 332 333 (defface go-coverage-0 334 '((t (:foreground "#c00000"))) 335 "Coverage color for uncovered code." 336 :group 'go-cover) 337 (defface go-coverage-1 338 '((t (:foreground "#808080"))) 339 "Coverage color for covered code with weight 1." 340 :group 'go-cover) 341 (defface go-coverage-2 342 '((t (:foreground "#748c83"))) 343 "Coverage color for covered code with weight 2." 344 :group 'go-cover) 345 (defface go-coverage-3 346 '((t (:foreground "#689886"))) 347 "Coverage color for covered code with weight 3." 348 :group 'go-cover) 349 (defface go-coverage-4 350 '((t (:foreground "#5ca489"))) 351 "Coverage color for covered code with weight 4." 352 :group 'go-cover) 353 (defface go-coverage-5 354 '((t (:foreground "#50b08c"))) 355 "Coverage color for covered code with weight 5." 356 :group 'go-cover) 357 (defface go-coverage-6 358 '((t (:foreground "#44bc8f"))) 359 "Coverage color for covered code with weight 6." 360 :group 'go-cover) 361 (defface go-coverage-7 362 '((t (:foreground "#38c892"))) 363 "Coverage color for covered code with weight 7." 364 :group 'go-cover) 365 (defface go-coverage-8 366 '((t (:foreground "#2cd495"))) 367 "Coverage color for covered code with weight 8. 368 For mode=set, all covered lines will have this weight." 369 :group 'go-cover) 370 (defface go-coverage-9 371 '((t (:foreground "#20e098"))) 372 "Coverage color for covered code with weight 9." 373 :group 'go-cover) 374 (defface go-coverage-10 375 '((t (:foreground "#14ec9b"))) 376 "Coverage color for covered code with weight 10." 377 :group 'go-cover) 378 (defface go-coverage-covered 379 '((t (:foreground "#2cd495"))) 380 "Coverage color of covered code." 381 :group 'go-cover) 382 383 (defvar go-mode-syntax-table 384 (let ((st (make-syntax-table))) 385 (modify-syntax-entry ?+ "." st) 386 (modify-syntax-entry ?- "." st) 387 (modify-syntax-entry ?% "." st) 388 (modify-syntax-entry ?& "." st) 389 (modify-syntax-entry ?| "." st) 390 (modify-syntax-entry ?^ "." st) 391 (modify-syntax-entry ?! "." st) 392 (modify-syntax-entry ?= "." st) 393 (modify-syntax-entry ?< "." st) 394 (modify-syntax-entry ?> "." st) 395 (modify-syntax-entry ?/ ". 124b" st) 396 (modify-syntax-entry ?* ". 23" st) 397 (modify-syntax-entry ?\n "> b" st) 398 (modify-syntax-entry ?\" "\"" st) 399 (modify-syntax-entry ?\' "\"" st) 400 (modify-syntax-entry ?` "\"" st) 401 (modify-syntax-entry ?\\ "\\" st) 402 ;; TODO make _ a symbol constituent now that xemacs is gone 403 (modify-syntax-entry ?_ "w" st) 404 405 st) 406 "Syntax table for Go mode.") 407 408 (defun go--fontify-type-switch-case-pre () 409 "Move point to line following the end of case statement. 410 411 This is used as an anchored font lock keyword PRE-MATCH-FORM. We 412 expand the font lock region to include multiline type switch case 413 statements." 414 (save-excursion 415 (beginning-of-line) 416 (while (or (looking-at "[[:space:]]*\\($\\|//\\)") (go--line-suffix-p ",")) 417 (forward-line)) 418 (when (go--line-suffix-p ":") 419 (forward-line)) 420 (point))) 421 422 (defun go--build-font-lock-keywords () 423 ;; we cannot use 'symbols in regexp-opt because GNU Emacs <24 424 ;; doesn't understand that 425 (append 426 `( 427 ;; Match param lists in func signatures. This uses the 428 ;; MATCH-ANCHORED format (see `font-lock-keywords' docs). 429 ;; 430 ;; Parent/anchor match. It matches the param list opening "(". 431 (go--match-param-start 432 ;; Sub-matcher that matches individual params in the param list. 433 (go--fontify-param 434 ;; Pre-match form that runs before the first sub-match. 435 (go--fontify-param-pre) 436 ;; Post-match form that runs after last sub-match. 437 (go--fontify-param-post) 438 ;; Subexp 1 is the param variable name, if any. 439 (1 font-lock-variable-name-face nil t) 440 ;; Subexp 2 is the param type name, if any. We set the LAXMATCH 441 ;; flag to allow optional regex groups. 442 (2 font-lock-type-face nil t))) 443 444 ;; Special case to match non-parenthesized function results. For 445 ;; example, "func(i int) string". 446 (go--match-single-func-result 1 font-lock-type-face) 447 448 ;; Match name+type pairs, such as "foo bar" in "var foo bar". 449 (go--match-ident-type-pair 2 font-lock-type-face) 450 451 ;; An anchored matcher for type switch case clauses. 452 (go--match-type-switch-case 453 (go--fontify-type-switch-case 454 (go--fontify-type-switch-case-pre) 455 nil 456 (1 font-lock-type-face))) 457 458 ;; Match variable names in var decls, constant names in const 459 ;; decls, and type names in type decls. 460 (go--match-decl 461 (1 font-lock-variable-name-face nil t) 462 (2 font-lock-constant-face nil t) 463 (3 font-lock-type-face nil t)) 464 465 (,(concat "\\_<" (regexp-opt go-mode-keywords t) "\\_>") . font-lock-keyword-face) 466 (,(concat "\\(\\_<" (regexp-opt go-builtins t) "\\_>\\)[[:space:]]*(") 1 font-lock-builtin-face) 467 (,(concat "\\_<" (regexp-opt go-constants t) "\\_>") . font-lock-constant-face) 468 469 ;; Function (not method) name 470 (,go-func-regexp 1 font-lock-function-name-face)) 471 472 (if go-fontify-function-calls 473 ;; Function call/method name 474 `((,(concat "\\(" go-identifier-regexp "\\)[[:space:]]*(") 1 font-lock-function-name-face) 475 ;; Bracketed function call 476 (,(concat "[^[:word:][:multibyte:]](\\(" go-identifier-regexp "\\))[[:space:]]*(") 1 font-lock-function-name-face)) 477 ;; Method name 478 `((,go-func-meth-regexp 2 font-lock-function-name-face))) 479 480 `( 481 ;; Raw string literal, needed for font-lock-syntactic-keywords 482 ("\\(`[^`]*`\\)" 1 font-lock-multiline) 483 484 ;; RHS of type alias. 485 (go--match-type-alias 2 font-lock-type-face) 486 487 ;; Arrays/slices: []<type> | [123]<type> | [some.Const]<type> | [someConst]<type> | [...]<type> 488 (,(concat "\\(?:^\\|[^[:word:][:multibyte:]]\\)\\[\\(?:[[:digit:]]+\\|" go-qualified-identifier-regexp "\\|" go-identifier-regexp "\\|\\.\\.\\.\\)?\\]" go-type-name-regexp) 1 font-lock-type-face) 489 490 ;; Unary "!" 491 ("\\(!\\)[^=]" 1 font-lock-negation-char-face) 492 493 ;; Composite literal type 494 (,(concat go-type-name-regexp "{") 1 font-lock-type-face) 495 496 ;; Map value type 497 (go--match-map-value 1 font-lock-type-face) 498 499 ;; Map key type 500 (,(concat "\\_<map\\_>\\[" go-type-name-regexp) 1 font-lock-type-face) 501 502 ;; Channel type 503 (,(concat "\\_<chan\\_>[[:space:]]*\\(?:<-[[:space:]]*\\)?" go-type-name-regexp) 1 font-lock-type-face) 504 505 ;; "new()"/"make()" type 506 (,(concat "\\_<\\(?:new\\|make\\)\\_>\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp) 1 font-lock-type-face) 507 508 ;; Type assertion 509 (,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face) 510 511 ;; Composite literal field names and label definitions. 512 (go--match-ident-colon 1 font-lock-constant-face) 513 514 ;; Labels in goto/break/continue 515 (,(concat "\\_<\\(?:goto\\|break\\|continue\\)\\_>[[:space:]]*\\(" go-label-regexp "\\)") 1 font-lock-constant-face)))) 516 517 (let ((m (define-prefix-command 'go-goto-map))) 518 (define-key m "a" #'go-goto-arguments) 519 (define-key m "d" #'go-goto-docstring) 520 (define-key m "f" #'go-goto-function) 521 (define-key m "i" #'go-goto-imports) 522 (define-key m "m" #'go-goto-method-receiver) 523 (define-key m "n" #'go-goto-function-name) 524 (define-key m "r" #'go-goto-return-values)) 525 526 (defvar go-mode-map 527 (let ((m (make-sparse-keymap))) 528 (unless (boundp 'electric-indent-chars) 529 (define-key m "}" #'go-mode-insert-and-indent) 530 (define-key m ")" #'go-mode-insert-and-indent)) 531 (define-key m (kbd "C-c C-a") #'go-import-add) 532 (define-key m (kbd "C-c C-j") #'godef-jump) 533 (define-key m (kbd "C-x 4 C-c C-j") #'godef-jump-other-window) 534 (define-key m (kbd "C-c C-d") #'godef-describe) 535 (define-key m (kbd "C-c C-f") 'go-goto-map) 536 m) 537 "Keymap used by ‘go-mode’.") 538 539 (easy-menu-define go-mode-menu go-mode-map 540 "Menu for Go mode." 541 '("Go" 542 ["Describe Expression" godef-describe t] 543 ["Jump to Definition" godef-jump t] 544 "---" 545 ["Add Import" go-import-add t] 546 ["Remove Unused Imports" go-remove-unused-imports t] 547 ["Go to Imports" go-goto-imports t] 548 "---" 549 ("Playground" 550 ["Send Buffer" go-play-buffer t] 551 ["Send Region" go-play-region t] 552 ["Download" go-download-play t]) 553 "---" 554 ["Coverage" go-coverage t] 555 ["Gofmt" gofmt t] 556 ["Godoc" godoc t] 557 "---" 558 ["Customize Mode" (customize-group 'go) t])) 559 560 (defun go-mode-insert-and-indent (key) 561 "Invoke the global binding of KEY, then reindent the line." 562 563 (interactive (list (this-command-keys))) 564 (call-interactively (lookup-key (current-global-map) key)) 565 (indent-according-to-mode)) 566 567 (defmacro go-paren-level () 568 `(car (syntax-ppss))) 569 570 (defmacro go-in-string-or-comment-p () 571 `(nth 8 (syntax-ppss))) 572 573 (defmacro go-in-string-p () 574 `(nth 3 (syntax-ppss))) 575 576 (defmacro go-in-comment-p () 577 `(nth 4 (syntax-ppss))) 578 579 (defmacro go-goto-beginning-of-string-or-comment () 580 `(goto-char (nth 8 (syntax-ppss)))) 581 582 (defun go--backward-irrelevant (&optional stop-at-string) 583 "Skip backwards over any characters that are irrelevant for 584 indentation and related tasks. 585 586 It skips over whitespace, comments, cases and labels and, if 587 STOP-AT-STRING is not true, over strings." 588 589 (let (pos (start-pos (point))) 590 (skip-chars-backward "\n\s\t") 591 (if (and (save-excursion (beginning-of-line) (go-in-string-p)) 592 (= (char-before) ?`) 593 (not stop-at-string)) 594 (backward-char)) 595 (if (and (go-in-string-p) 596 (not stop-at-string)) 597 (go-goto-beginning-of-string-or-comment)) 598 (if (looking-back "\\*/" (line-beginning-position)) 599 (backward-char)) 600 (if (go-in-comment-p) 601 (go-goto-beginning-of-string-or-comment)) 602 (setq pos (point)) 603 (beginning-of-line) 604 (if (or (looking-at (concat "^" go-label-regexp ":")) 605 (looking-at "^[[:space:]]*\\(case .+\\|default\\):")) 606 (end-of-line 0) 607 (goto-char pos)) 608 (if (/= start-pos (point)) 609 (go--backward-irrelevant stop-at-string)) 610 (/= start-pos (point)))) 611 612 (defun go--buffer-narrowed-p () 613 "Return non-nil if the current buffer is narrowed." 614 (/= (buffer-size) 615 (- (point-max) 616 (point-min)))) 617 618 (defun go-previous-line-has-dangling-op-p () 619 "Return non-nil if the current line is a continuation line. 620 The return value is cached based on the current `line-beginning-position'." 621 (let* ((line-begin (line-beginning-position)) 622 (val (gethash line-begin go-dangling-cache 'nope))) 623 (when (or (go--buffer-narrowed-p) (equal val 'nope)) 624 (save-excursion 625 (go--forward-line -1) 626 (if (go--current-line-has-dangling-op-p) 627 (setq val (line-end-position)) 628 (setq val nil)) 629 630 (if (not (go--buffer-narrowed-p)) 631 (puthash line-begin val go-dangling-cache)))) 632 val)) 633 634 (defun go--current-line-has-dangling-op-p () 635 "Return non-nil if current line ends in a dangling operator. 636 The return value is not cached." 637 (or 638 (and 639 (go--line-suffix-p go-dangling-operators-regexp) 640 641 ;; "=" does not behave like a dangling operator in decl statements. 642 (not (go--line-suffix-p "\\(?:var\\|type\\|const\\)[[:space:]].*=")) 643 644 ;; Don't mistake "1234." for a dangling operator. 645 (not (go--line-suffix-p "[[:space:]]-?[[:digit:]][_0-9]*\\."))) 646 647 ;; treat comma as dangling operator in certain cases 648 (and 649 (go--line-suffix-p ",") 650 (save-excursion (end-of-line) (go--commas-indent-p))))) 651 652 653 (defun go--commas-indent-p () 654 "Return non-nil if in a context where dangling commas indent next line." 655 (not (or 656 (go--open-paren-position) 657 (go--in-composite-literal-p) 658 (go--in-case-clause-list-p) 659 (go--in-struct-definition-p)))) 660 661 (defun go--in-case-clause-list-p () 662 "Return non-nil if inside a multi-line case cause list. 663 664 This function is only concerned with list items on lines after the 665 case keyword. It returns nil for the case line itself." 666 (save-excursion 667 (beginning-of-line) 668 (when (not (looking-at go--case-or-default-regexp)) 669 (let (saw-colon) 670 ;; optionally skip line with the colon 671 (when (go--line-suffix-p ":") 672 (setq saw-colon t) 673 (forward-line -1)) 674 675 ;; go backwards while at a comment or a line ending in comma 676 (while (and 677 (or 678 (go--boring-line-p) 679 (go--line-suffix-p ",")) 680 (not (looking-at go--case-regexp)) 681 (go--forward-line -1))) 682 683 (and 684 (looking-at-p go--case-regexp) 685 ;; we weren't in case list if first line ended in colon 686 ;; and the "case" line ended in colon 687 (not (and saw-colon (looking-at ".*:[[:space:]]*$")))))))) 688 689 (defun go--in-composite-literal-p () 690 "Return non-nil if point is in a composite literal." 691 (save-excursion 692 (save-match-data 693 (and 694 (go-goto-opening-parenthesis) 695 696 ;; Opening paren-like character is a curly. 697 (eq (char-after) ?{) 698 699 (or 700 ;; Curly is preceded by non space (e.g. "Foo{"), definitely 701 ;; composite literal. 702 (zerop (skip-syntax-backward " ")) 703 704 ;; Curly preceded by comma or semicolon. This is a composite 705 ;; literal with implicit type name. 706 (looking-back "[,:]" (1- (point))) 707 708 ;; If we made it to the beginning of line we are either a naked 709 ;; block or a composite literal with implicit type name. If we 710 ;; are the latter, we must be contained in another composite 711 ;; literal. 712 (and (bolp) (go--in-composite-literal-p))))))) 713 714 (defun go--in-paren-with-prefix-p (paren prefix) 715 (save-excursion 716 (and 717 (go-goto-opening-parenthesis) 718 (eq (char-after) paren) 719 (skip-syntax-backward " ") 720 (> (point) (length prefix)) 721 (string= prefix (buffer-substring (- (point) (length prefix)) (point)))))) 722 723 (defun go--in-struct-definition-p () 724 "Return non-nil if point is inside a struct definition." 725 (go--in-paren-with-prefix-p ?{ "struct")) 726 727 (defun go--in-interface-p () 728 "Return non-nil if point is inside an interface definition." 729 (go--in-paren-with-prefix-p ?{ "interface")) 730 731 732 (defun go--in-type-switch-p () 733 "Return non-nil if point is inside a type switch statement." 734 (go--in-paren-with-prefix-p ?{ ".(type)")) 735 736 (defun go--fill-prefix () 737 "Return fill prefix for following comment paragraph." 738 (save-excursion 739 (beginning-of-line) 740 741 ;; Skip over empty lines and empty comment openers/closers. 742 (while (and 743 (or (go--empty-line-p) (go--boring-comment-p)) 744 (zerop (forward-line 1)))) 745 746 ;; If we are in a block comment, set prefix based on first line 747 ;; with content. 748 (if (go-in-comment-p) 749 (progn 750 (looking-at "[[:space:]]*") 751 (match-string-no-properties 0)) 752 753 ;; Else if we are looking at the start of an interesting comment, our 754 ;; prefix is the comment opener and any space following. 755 (if (looking-at (concat go--comment-start-regexp "[[:space:]]*")) 756 ;; Replace "/*" opener with spaces so following lines don't 757 ;; get "/*" prefix. 758 (replace-regexp-in-string "/\\*" " " 759 (match-string-no-properties 0)))))) 760 761 (defun go--open-paren-position () 762 "Return non-nil if point is between '(' and ')'. 763 764 The return value is the position of the opening paren." 765 (save-excursion 766 (let ((start-paren-level (go-paren-level))) 767 (and 768 (go-goto-opening-parenthesis) 769 770 ;; opening paren-like character is actually a paren 771 (eq (char-after) ?\() 772 773 ;; point is before the closing paren 774 (< (go-paren-level) start-paren-level) 775 776 (point))))) 777 778 (defun go-indentation-at-point () 779 "Return the appropriate indentation for the current line." 780 (save-excursion 781 (beginning-of-line) 782 783 (if (go-in-comment-p) 784 (go--multiline-comment-indent) 785 (go--indentation-at-point)))) 786 787 ;; It's unfortunate that the user cannot reindent the current line to 788 ;; align with the previous line; however, if they could, then people 789 ;; who use reindent-then-newline-and-indent wouldn't be able to 790 ;; explicitly indent lines inside comments. 791 (defun go--multiline-comment-indent () 792 "Return the appropriate indent inside multiline comment. 793 794 Assumes point is at beginning of line within comment. This 795 function has basic logic to indent as you add new lines to a 796 multiline comment, and to line up all the `*' if each line starts 797 with `*'. The gofmt behavior for multiline comments is 798 surprisingly complex and strange/buggy, so we just aim to do 799 something simple rather than encode all the subtle behavior." 800 (let* (;; Indent of current line. 801 (indent (current-indentation)) 802 ;; Indent of opening "/*". 803 start-indent 804 ;; Default indent to use based on preceding context. 805 natural-indent 806 ;; Non-nil means keep existing indent and give up calculating indent. 807 give-up 808 ;; Whether all comment lines (except first) begin with "*". 809 (all-star t)) 810 811 (save-excursion 812 (go-goto-beginning-of-string-or-comment) 813 814 (setq start-indent (current-indentation)) 815 816 ;; If other stuff precedes start of multiline comment, give up. 817 (setq give-up (/= (current-column) start-indent)) 818 819 ;; Skip "/*". 820 (forward-char 2) 821 822 (skip-syntax-forward " ") 823 824 (if (not (eolp)) 825 ;; If we aren't at EOL, we have content on the first line. 826 ;; Base our natural indent on that. 827 (setq natural-indent (current-column)) 828 ;; Otherwise default to 1 space beyond "/*". 829 (setq natural-indent (+ start-indent 3))) 830 831 (let (done) 832 (while (not done) 833 (setq done (or (looking-at ".*\\*/") (not (zerop (forward-line))))) 834 (setq all-star (and all-star (looking-at "[[:space:]]*\\*")))))) 835 836 ;; If previous line has comment content, use its indent as our 837 ;; natural indent. 838 (save-excursion 839 (when (zerop (forward-line -1)) 840 (beginning-of-line) 841 (when (and (go-in-comment-p) (> (current-indentation) 0)) 842 (setq natural-indent (current-indentation))))) 843 844 (cond 845 (give-up indent) 846 847 (all-star (1+ start-indent)) 848 849 ;; Closing "*/" with no preceding content always lines up with "/*". 850 ((looking-at "[[:space:]]*\\*/") start-indent) 851 852 ;; If the line is already indented, leave it. 853 (t (if (zerop indent) natural-indent indent))))) 854 855 (defun go--indentation-at-point () 856 "Return the appropriate indentation for the current non-comment line. 857 858 This function works by walking a line's characters backwards. When it 859 encounters a closing paren or brace it bounces to the corresponding 860 opener. If it arrives at the beginning of the line you are indenting, 861 it moves to the end of the previous line if the current line is a 862 continuation line, else it moves to the containing opening paren or 863 brace. If it arrives at the beginning of a line other than the line 864 you are indenting, it will continue to the previous dangling line if 865 the line you are indenting was not a continuation line, otherwise it 866 is done." 867 (save-excursion 868 (beginning-of-line) 869 870 (let ( 871 ;; Beginning of our starting line. 872 (start-line (point)) 873 874 ;; Whether this is our first iteration of the outer while loop. 875 (first t) 876 877 ;; Whether we start in a block (i.e. our first line is not a 878 ;; continuation line and is in an "if", "for", etc. block). 879 (in-block) 880 881 ;; Our desired indent relative to our ending line's indent. 882 (indent 0)) 883 884 ;; Skip leading whitespace. 885 (skip-syntax-forward " ") 886 887 ;; Decrement indent if the first character on the line is a closer. 888 (when (or (eq (char-after) ?\)) (eq (char-after) ?})) 889 (cl-decf indent tab-width)) 890 891 (while (or 892 ;; Always run the first iteration so we process empty lines. 893 first 894 895 ;; Otherwise stop if we are at the start of a line. 896 (not (bolp))) 897 (setq first nil) 898 899 (cl-case (char-before) 900 901 ;; We have found a closer (paren or brace). 902 ((?\) ?}) 903 (backward-char) 904 (let ((bol (line-beginning-position))) 905 906 ;; Jump back to corresponding opener. 907 (go-goto-opening-parenthesis) 908 909 ;; Here we decrement the indent if we are closing an indented 910 ;; expression. In other words, the closer's line was indented 911 ;; relative to the opener's line, and that indent should not 912 ;; be inherited by our starting line. 913 (when (and 914 ;; We care about dangling expressions, not child blocks. 915 (not in-block) 916 917 ;; Opener and closer aren't on same line. 918 (< (point) bol) 919 920 (go-previous-line-has-dangling-op-p) 921 922 ;; Opener is at same paren level as start of line (ignore sub-expressions). 923 (eq (go-paren-level) (save-excursion (beginning-of-line) (go-paren-level))) 924 925 ;; This dangling line opened indent relative to previous dangling line. 926 (go--continuation-line-indents-p)) 927 (cl-decf indent tab-width)))) 928 929 ;; Brackets don't affect indentation, so just skip them. 930 ((?\]) 931 (backward-char))) 932 933 ;; Skip non-closers since we are only interested in closing parens/braces. 934 (skip-syntax-backward "^)" (line-beginning-position)) 935 936 (when (go-in-string-or-comment-p) 937 (go-goto-beginning-of-string-or-comment)) 938 939 ;; At the beginning of the starting line. 940 (when (= start-line (point)) 941 942 ;; We are a continuation line. 943 (if (go-previous-line-has-dangling-op-p) 944 (progn 945 ;; Presume a continuation line always gets an extra indent. 946 ;; We reduce the indent after the loop, if necessary. 947 (cl-incf indent tab-width) 948 949 ;; Go to the end of the dangling line. 950 (goto-char (go-previous-line-has-dangling-op-p))) 951 952 ;; If we aren't a continuation line and we have an enclosing paren 953 ;; or brace, jump to opener and increment our indent. 954 (when (go-goto-opening-parenthesis) 955 (setq in-block (go--flow-block-p)) 956 (cl-incf indent tab-width)))) 957 958 ;; If we started in a child block we must follow dangling lines 959 ;; until they don't dangle anymore. This is to handle cases like: 960 ;; 961 ;; if foo || 962 ;; foo && 963 ;; foo { 964 ;; X 965 ;; 966 ;; There can be an arbitrary number of indents, so we must go back to 967 ;; the "if" to determine the indent of "X". 968 (when (and in-block (bolp) (go-previous-line-has-dangling-op-p)) 969 (goto-char (go-previous-line-has-dangling-op-p)))) 970 971 ;; If our ending line is a continuation line but doesn't open 972 ;; an extra indent, reduce indent. We tentatively gave indents to all 973 ;; dangling lines and all lines inside open parens, so here we take that 974 ;; indent back. 975 ;; 976 ;; 1 + 1 + 977 ;; ending line 1 + foo( 1 + foo( 978 ;; starting line 1, becomes 1, 979 ;; ) ) 980 ;; 981 ;; 982 ;; 1 + 1 + 983 ;; ending line 1 + becomes 1 + 984 ;; starting line 1 1 985 (when (and 986 (go-previous-line-has-dangling-op-p) 987 (not (go--continuation-line-indents-p))) 988 (cl-decf indent tab-width)) 989 990 ;; Apply our computed indent relative to the indent of the 991 ;; ending line, or 0 if we are at the top level. 992 (if (and 993 (= 0 (go-paren-level)) 994 (not (go-previous-line-has-dangling-op-p))) 995 indent 996 (+ indent (current-indentation)))))) 997 998 (defconst go--operator-chars "*/%<>&\\^+\\-|=!,." 999 "Individual characters that appear in operators. 1000 Comma and period are included because they can be dangling operators, so 1001 they need to be considered by `go--continuation-line-indents-p'") 1002 1003 (defun go--operator-precedence (op) 1004 "Go operator precedence (higher binds tighter)." 1005 (cl-case (intern op) 1006 (\. 7) ; "." in "foo.bar", binds tightest 1007 (! 6) 1008 ((* / % << >> & &^) 5) 1009 ((+ - | ^) 4) 1010 ((== != < <= > >=) 3) 1011 (&& 2) 1012 (|| 1) 1013 (t 0))) 1014 1015 (defun go--flow-block-p () 1016 "Return whether looking at a { that opens a control flow block. 1017 1018 We check for a { that is preceded by a space and is not a func 1019 literal opening brace." 1020 (save-excursion 1021 (when (and 1022 (eq (char-after) ?{) 1023 (not (zerop (skip-syntax-backward " ")))) 1024 1025 (let ((eol (line-end-position)) 1026 (level (go-paren-level)) 1027 (found-func-literal)) 1028 1029 (beginning-of-line) 1030 1031 ;; See if we find any "func" keywords on this line at the same paren 1032 ;; level as the curly. 1033 (while (and 1034 (not found-func-literal) 1035 (re-search-forward "\\_<func\\_>" eol t)) 1036 (setq found-func-literal (and 1037 (= level (go-paren-level)) 1038 (not (go-in-string-or-comment-p))))) 1039 (not found-func-literal))))) 1040 1041 (defun go--continuation-line-indents-p () 1042 "Return non-nil if the current continuation line opens an additional indent. 1043 1044 This function works by looking at the Go operators used on the current 1045 line. If all the operators bind tighter than the previous line's 1046 dangling operator and the current line ends in a dangling operator or 1047 open paren, the next line will have an additional indent. 1048 1049 For example: 1050 foo || 1051 foo && // this continuation line opens another indent 1052 foo 1053 " 1054 (save-excursion 1055 (let (prev-op (all-tighter t)) 1056 1057 ;; Record the dangling operator from previous line. 1058 (save-excursion 1059 (goto-char (go-previous-line-has-dangling-op-p)) 1060 (go--end-of-line) 1061 (skip-syntax-backward " ") 1062 (let ((end (point))) 1063 (skip-chars-backward go--operator-chars) 1064 (setq prev-op (buffer-substring-no-properties (point) end)))) 1065 1066 (beginning-of-line) 1067 1068 (when (or 1069 ;; We can only open indent if we have a dangling operator, or 1070 (go--current-line-has-dangling-op-p) 1071 1072 (save-excursion 1073 (go--end-of-line) 1074 (backward-char) 1075 (or 1076 ;; Line ends in a "(" or ",", or 1077 (eq (char-after) ?\() 1078 (eq (char-after) ?,) 1079 1080 ;; Line ends in a "{" that isn't a control block. 1081 (and 1082 (eq (char-after) ?{) 1083 (not (go--flow-block-p)))))) 1084 1085 (let ((prev-precedence (go--operator-precedence prev-op)) 1086 (start-depth (go-paren-level)) 1087 (line-start (line-beginning-position))) 1088 1089 (end-of-line) 1090 1091 ;; While we haven't found a looser operator and are on the starting line... 1092 (while (and all-tighter (> (point) line-start)) 1093 1094 ;; Skip over non-operator characters. 1095 (skip-chars-backward (concat "^" go--operator-chars) line-start) 1096 1097 (let ((end (point))) 1098 (cond 1099 ;; Ignore sub-expressions at different paren levels. 1100 ((/= (go-paren-level) start-depth) 1101 (skip-syntax-backward "^()")) 1102 1103 ((go-in-string-or-comment-p) 1104 (go-goto-beginning-of-string-or-comment)) 1105 1106 ;; We found an operator. Check if it has lower precedence. 1107 ((/= (skip-chars-backward go--operator-chars) 0) 1108 (when (>= 1109 prev-precedence 1110 (go--operator-precedence (buffer-substring (point) end))) 1111 (setq all-tighter nil))))))) 1112 all-tighter)))) 1113 1114 (defun go--end-of-line () 1115 "Move to the end of the code on the current line. 1116 Point will be left before any trailing comments. Point will be left 1117 after the opening backtick of multiline strings." 1118 (end-of-line) 1119 (let ((keep-going t)) 1120 (while keep-going 1121 (skip-syntax-backward " ") 1122 (when (looking-back "\\*/" (- (point) 2)) 1123 ;; back up so we are in the /* comment */ 1124 (backward-char)) 1125 (if (go-in-comment-p) 1126 (go-goto-beginning-of-string-or-comment) 1127 (setq keep-going nil)))) 1128 (when (go-in-string-p) 1129 (go-goto-beginning-of-string-or-comment) 1130 ;; forward one so point is after the opening "`" 1131 (forward-char))) 1132 1133 (defun go--line-suffix-p (re) 1134 "Return non-nil if RE matches the end of the line starting from `point'. 1135 1136 Trailing whitespace, trailing comments and trailing multiline strings are 1137 ignored." 1138 (let ((start (point)) 1139 (end (save-excursion (go--end-of-line) (point)))) 1140 (when (< start end) 1141 (string-match-p 1142 (concat "\\(?:" re "\\)$") 1143 (buffer-substring-no-properties start end))))) 1144 1145 (defun go--boring-line-p () 1146 "Return non-nil if the current line probably doesn't impact indentation. 1147 1148 A boring line is one that starts with a comment, is empty, is part of a 1149 multiline comment, or starts and ends in a multiline string." 1150 (or 1151 (looking-at (concat go--comment-start-regexp "\\|[[:space:]]*$")) 1152 (go-in-comment-p) 1153 (and (go-in-string-p) (save-excursion (end-of-line) (go-in-string-p))))) 1154 1155 (defun go--forward-line (&optional count) 1156 "Like `forward-line' but skip comments and empty lines. 1157 1158 Return non-nil if point changed lines." 1159 (let (moved) 1160 (while (and 1161 (zerop (forward-line count)) 1162 (setq moved t) 1163 (go--boring-line-p)) 1164 (setq count (if (and count (< count 0 )) -1 1))) 1165 moved)) 1166 1167 (defun go--case-comment-p (indent) 1168 "Return non-nil if looking at a comment attached to a case statement. 1169 1170 INDENT is the normal indent of this line, i.e. that of the case body." 1171 (when (and 1172 (> (current-indentation) 0) 1173 (looking-at go--comment-start-regexp)) 1174 1175 (let (switch-before 1176 case-after 1177 has-case-aligned-preceding-comment) 1178 1179 (save-excursion 1180 ;; Search for previous case-aligned comment. 1181 (while (and 1182 (zerop (forward-line -1)) 1183 (cond 1184 ((looking-at "^[[:space:]]*$")) 1185 1186 ((looking-at go--comment-start-regexp) 1187 (when (= (current-indentation) (- indent tab-width)) 1188 (setq has-case-aligned-preceding-comment t)) 1189 t) 1190 1191 ((go-in-comment-p))))) 1192 1193 ;; Record if a switch (or select) precedes us. 1194 (setq switch-before (looking-at "^[[:space:]]*\\(switch\\|select\\)[[:space:]]"))) 1195 1196 ;; Record if first proceeding non-comment line is a case statement. 1197 (save-excursion 1198 (while (and 1199 (zerop (forward-line 1)) 1200 (or 1201 (looking-at go--comment-start-regexp) 1202 (looking-at "^[[:space:]]*$") 1203 (go-in-comment-p)))) 1204 1205 (setq case-after (looking-at go--case-or-default-regexp))) 1206 1207 (and 1208 ;; a "case" statement comes after our comment 1209 case-after 1210 1211 (or 1212 ;; "switch" statement precedes us, always align with "case" 1213 switch-before 1214 1215 ;; a preceding comment is aligned with "case", we should too 1216 has-case-aligned-preceding-comment 1217 1218 ;; other cases are ambiguous, so if comment is currently 1219 ;; aligned with "case", leave it that way 1220 (= (current-indentation) (- indent tab-width))))))) 1221 1222 (defun go-mode-indent-line () 1223 (interactive) 1224 (let (indent 1225 ;; case sensitively match "case", "default", etc. 1226 (case-fold-search nil) 1227 (pos (- (point-max) (point))) 1228 (point (point)) 1229 (beg (line-beginning-position)) 1230 (non-tab-indents 0)) 1231 (back-to-indentation) 1232 (if (go-in-string-p) 1233 (goto-char point) 1234 (setq indent (go-indentation-at-point)) 1235 (when (or 1236 (and 1237 (looking-at (concat go-label-regexp ":\\([[:space:]]*/.+\\)?$\\|" go--case-or-default-regexp)) 1238 ;; don't think last part of multiline case statement is a label 1239 (not (go-previous-line-has-dangling-op-p)) 1240 (not (go--in-case-clause-list-p)) 1241 (not (go--in-composite-literal-p))) 1242 1243 ;; comment attached above a "case" statement 1244 (go--case-comment-p indent)) 1245 (cl-decf indent tab-width)) 1246 1247 ;; Don't do anything if current indent is correct. 1248 (when (/= indent (current-column)) 1249 ;; Don't use tabs for indenting beyond "/*" in multiline 1250 ;; comments. They don't play well with gofmt. 1251 (when (go-in-comment-p) 1252 (save-excursion 1253 (go-goto-beginning-of-string-or-comment) 1254 (when (> indent (current-indentation)) 1255 (setq non-tab-indents (- indent (current-indentation))) 1256 (setq indent (current-indentation))))) 1257 1258 (delete-region beg (point)) 1259 (indent-to indent) 1260 (insert-char ? non-tab-indents)) 1261 1262 ;; If initial point was within line's indentation, 1263 ;; position after the indentation. Else stay at same point in text. 1264 (if (> (- (point-max) pos) (point)) 1265 (goto-char (- (point-max) pos)))))) 1266 1267 (defun go-beginning-of-defun (&optional count) 1268 (unless (bolp) 1269 (end-of-line)) 1270 (setq count (or count 1)) 1271 (let (first failure) 1272 (dotimes (i (abs count)) 1273 (setq first t) 1274 (while (and (not failure) 1275 (or first (go-in-string-or-comment-p))) 1276 (if (>= count 0) 1277 (progn 1278 (go--backward-irrelevant) 1279 (if (not (re-search-backward go-func-meth-regexp nil t)) 1280 (setq failure t))) 1281 (if (looking-at go-func-meth-regexp) 1282 (forward-char)) 1283 (if (not (re-search-forward go-func-meth-regexp nil t)) 1284 (setq failure t))) 1285 (setq first nil))) 1286 (if (< count 0) 1287 (beginning-of-line)) 1288 (not failure))) 1289 1290 (defun go-end-of-defun () 1291 (let (orig-level) 1292 ;; It can happen that we're not placed before a function by emacs 1293 (if (not (looking-at "func")) 1294 (go-beginning-of-defun -1)) 1295 ;; Find the { that starts the function, i.e., the next { that isn't 1296 ;; preceded by struct or interface, or a comment or struct tag. BUG: 1297 ;; breaks if there's a comment between the struct/interface keyword and 1298 ;; bracket, like this: 1299 ;; 1300 ;; struct /* why? */ { 1301 (while (progn 1302 (skip-chars-forward "^{") 1303 (forward-char) 1304 (or (go-in-string-or-comment-p) 1305 (looking-back "\\(struct\\|interface\\)\\s-*{" 1306 (line-beginning-position))))) 1307 (setq orig-level (go-paren-level)) 1308 (while (>= (go-paren-level) orig-level) 1309 (skip-chars-forward "^}") 1310 (forward-char)))) 1311 1312 1313 (defvar go--fontify-param-has-name nil 1314 "Whether the current params list has names. 1315 1316 This is used during fontification of function signatures.") 1317 1318 (defvar go--fontify-param-beg nil 1319 "Position of \"(\" starting param list. 1320 1321 This is used during fontification of function signatures.") 1322 1323 (defun go--fontify-param-pre () 1324 "Set `go--fontify-param-has-name' and `go--fontify-param-beg' appropriately. 1325 1326 This is used as an anchored font lock keyword PRE-MATCH-FORM. We 1327 must set `go--fontify-param-has-name' ahead of time because you 1328 can't know if the param list is types only or names and types 1329 until you see the end. For example: 1330 1331 // types only 1332 func foo(int, string) {} 1333 1334 // names and types (don't know so until you see the \"int\"). 1335 func foo(i, j int) {} 1336 " 1337 (setq go--fontify-param-has-name (eq 1338 (go--parameter-list-type (point-max)) 1339 'present)) 1340 1341 ;; Remember where our match started so we can continue our search 1342 ;; from here. 1343 (setq go--fontify-param-beg (point)) 1344 1345 ;; Return position of closing paren so we process the entire 1346 ;; multiline param list. 1347 (save-excursion 1348 (let ((depth (go-paren-level))) 1349 ;; First check that our paren is closed by the end of the file. This 1350 ;; avoids expanding the fontification region to the entire file when you 1351 ;; have an unclosed paren at file scope. 1352 (when (save-excursion 1353 (goto-char (1+ (buffer-size))) 1354 (< (go-paren-level) depth)) 1355 (while (and 1356 (re-search-forward ")" nil t) 1357 (>= (go-paren-level) depth))))) 1358 (point))) 1359 1360 (defun go--fontify-param-post () 1361 "Move point back to opening paren. 1362 1363 This is used as an anchored font lock keyword POST-MATCH-FORM. We 1364 move point back to the opening \"(\" so we find nested param 1365 lists. 1366 " 1367 (goto-char go--fontify-param-beg)) 1368 1369 (defun go--match-param-start (end) 1370 "Search for the starting of param lists. 1371 1372 Search for the opening `(' of function signature param lists. 1373 This covers the func receiver, params, and results. Interface 1374 declarations are also included." 1375 (let (found-match) 1376 (while (and 1377 (not found-match) 1378 (re-search-forward (concat "\\(\\_<" go-identifier-regexp "\\)?(") end t)) 1379 (when (not (go-in-string-or-comment-p)) 1380 (save-excursion 1381 (goto-char (match-beginning 0)) 1382 1383 (let ((name (match-string 1))) 1384 (when name 1385 ;; We are in a param list if "func" preceded the "(" (i.e. 1386 ;; func literal), or if we are in an interface 1387 ;; declaration, e.g. "interface { foo(i int) }". 1388 (setq found-match (or (string= name "func") (go--in-interface-p)))) 1389 1390 ;; Otherwise we are in a param list if our "(" is preceded 1391 ;; by ") " or "func ". 1392 (when (and (not found-match) (not (zerop (skip-syntax-backward " ")))) 1393 (setq found-match (or 1394 (eq (char-before) ?\)) 1395 (looking-back "\\_<func" (- (point) 4))))))))) 1396 found-match)) 1397 1398 1399 (defconst go--named-param-re 1400 (concat "[[:space:]\n]*\\(" go-identifier-regexp "\\)\\(?:[[:space:]]+\\(?:\\.\\.\\.\\)?" go-type-name-regexp "[[:space:]]*[,)]\\)?") 1401 "Regexp to match named param such as \"s *string\" in: 1402 1403 func(i int, s *string) { }") 1404 1405 (defconst go--unnamed-param-re 1406 (concat "\\(\\)[[:space:]\n]*\\(?:\\.\\.\\.\\)?" go-type-name-regexp "[[:space:]]*[,)]") 1407 "Regexp to match unnamed param such as \"*string\" in: 1408 1409 func(int, *string) { } 1410 1411 We start with an empty subexp since our font lock keyword expects 1412 subexp 1 to a variable name, but we have no variable.") 1413 1414 (defun go--fontify-param (end) 1415 "Match a param within a param list. 1416 1417 Our parent font lock matcher is anchored to the beginning of the 1418 param list. `go--fontify-param-has-name' has been set 1419 appropriately. We match the next param and advance point to after 1420 the next comma or to the closing paren." 1421 (let (found-match done) 1422 ;; We loop until match because there are some params that we can't 1423 ;; handle (but we may need to handle subsequent params). For 1424 ;; example: 1425 ;; 1426 ;; // We don't handle the interface, so we must skip it and handle 1427 ;; // "string". 1428 ;; func(int, interface { foo() }, string) 1429 (while (and (not found-match) (not done)) 1430 (if go--fontify-param-has-name 1431 (when (looking-at go--named-param-re) 1432 (when (not go-fontify-variables) 1433 (let ((md (match-data))) 1434 (setf (nth 2 md) nil (nth 3 md) nil) 1435 (set-match-data md))) 1436 (setq found-match t)) 1437 (when (looking-at go--unnamed-param-re) 1438 (setq found-match t))) 1439 1440 ;; Advance to next comma. We are done if there are no more commas. 1441 (setq done (not (go--search-next-comma end)))) 1442 found-match)) 1443 1444 (defun go--search-next-comma (end) 1445 "Search forward from point for a comma whose nesting level is 1446 the same as point. If it reaches a closing parenthesis before a 1447 comma, it stops at it. Return non-nil if comma was found." 1448 (let ((orig-level (go-paren-level))) 1449 (while (and (< (point) end) 1450 (or (looking-at-p "[^,)]") 1451 (> (go-paren-level) orig-level))) 1452 (forward-char)) 1453 (when (and (looking-at-p ",") 1454 (< (point) (1- end))) 1455 (forward-char) 1456 t))) 1457 1458 (defun go--looking-at-keyword () 1459 (and (looking-at (concat "\\(" go-identifier-regexp "\\)")) 1460 (member (match-string 1) go-mode-keywords))) 1461 1462 (defun go--match-type-switch-case (end) 1463 "Match a \"case\" clause within a type switch." 1464 (let (found-match) 1465 (while (and 1466 (not found-match) 1467 1468 ;; Search for "case" statements. 1469 (re-search-forward "^[[:space:]]*case " end t)) 1470 1471 ;; Make sure we are in a type switch statement. 1472 (setq found-match (go--in-type-switch-p))) 1473 found-match)) 1474 1475 (defun go--fontify-type-switch-case (end) 1476 "Match a single type within a type switch case." 1477 (let (found-match done) 1478 ;; Loop until we find a match because we must skip types we don't 1479 ;; handle, such as "interface { foo() }". 1480 (while (and (not found-match) (not done)) 1481 (when (looking-at (concat "\\(?:[[:space:]]*\\|//.*\\|\n\\)*" go-type-name-regexp "[[:space:]]*[,:]")) 1482 (goto-char (match-end 1)) 1483 (unless (member (match-string 1) go-constants) 1484 (setq found-match t))) 1485 (setq done (not (go--search-next-comma end)))) 1486 found-match)) 1487 1488 (defun go--containing-decl () 1489 "Return containing decl kind var|const|type, if any." 1490 (save-match-data 1491 (or 1492 (save-excursion 1493 (and 1494 (go-goto-opening-parenthesis) 1495 (eq (char-after) ?\() 1496 (skip-syntax-backward " ") 1497 (skip-syntax-backward "w") 1498 (looking-at "\\(var\\|const\\|type\\)[[:space:]]") 1499 (match-string-no-properties 1))) 1500 1501 (save-excursion 1502 (let ((depth (go-paren-level))) 1503 (beginning-of-line) 1504 (and 1505 (= (go-paren-level) depth) 1506 (looking-at "[[:space:]]*\\(var\\|const\\|type\\)[[:space:]]") 1507 (match-string-no-properties 1))))))) 1508 1509 (defconst go--decl-ident-re (concat "\\(?:^\\|[[:space:]]\\)\\(\\(\\(" go-identifier-regexp "\\)\\)\\)\\_>")) 1510 1511 (defun go--match-decl (end) 1512 "Match identifiers in \"var\", \"type\" and \"const\" decls, as 1513 well as \":=\" assignments. 1514 1515 In order to only scan once, the regex has three subexpressions 1516 that match the same identifier. Depending on the kind of 1517 containing decl we zero out the subexpressions so the right one 1518 gets highlighted by the font lock keyword." 1519 (let (found-match decl) 1520 (while (and 1521 (not found-match) 1522 (re-search-forward go--decl-ident-re end t)) 1523 1524 (save-excursion 1525 ;; Skip keywords. 1526 (cond 1527 ((member (match-string 1) go-mode-keywords)) 1528 1529 ((and 1530 ;; We are in a decl of some kind. 1531 (setq decl (go--containing-decl)) 1532 1533 ;; We aren't on right side of equals sign. 1534 (not (go--looking-back-p "="))) 1535 1536 (setq found-match t) 1537 1538 ;; Unset match data subexpressions that don't apply based on 1539 ;; the decl kind. 1540 (let ((md (match-data))) 1541 (cond 1542 ((string= decl "var") 1543 (setf (nth 4 md) nil (nth 5 md) nil (nth 6 md) nil (nth 7 md) nil) 1544 (when (not go-fontify-variables) 1545 (setf (nth 2 md) nil (nth 3 md) nil))) 1546 ((string= decl "const") 1547 (setf (nth 2 md) nil (nth 3 md) nil (nth 6 md) nil (nth 7 md) nil)) 1548 ((string= decl "type") 1549 (setf (nth 2 md) nil (nth 3 md) nil (nth 4 md) nil (nth 5 md) nil))) 1550 (set-match-data md))) 1551 1552 (go-fontify-variables 1553 (save-match-data 1554 ;; Left side of ":=" assignment. 1555 (when (looking-at ".*:=") 1556 (let ((depth (go-paren-level))) 1557 (goto-char (match-end 0)) 1558 ;; Make sure the ":=" isn't in a comment or a sub-block. 1559 (setq found-match (and 1560 (not (go-in-string-or-comment-p)) 1561 (= depth (go-paren-level))))))))))) 1562 found-match)) 1563 1564 (defun go--looking-back-p (re) 1565 "Return non-nil if RE matches beginning of line to point. 1566 1567 RE is not anchored automatically." 1568 (string-match-p 1569 re 1570 (buffer-substring-no-properties (point) (line-beginning-position)))) 1571 1572 1573 (defconst go--ident-type-pair-re (concat "\\_<\\(" go-identifier-regexp "\\)[[:space:]]+" go-type-name-regexp)) 1574 1575 (defun go--match-ident-type-pair (end) 1576 "Search for identifier + type-name pairs. 1577 1578 For example, this looks for the \"foo bar\" in \"var foo bar\", 1579 yielding match-data for \"bar\" since that is a type name to be 1580 fontified. This approach matches type names in var and const 1581 decls, and in struct definitions. Return non-nil if search 1582 succeeds." 1583 (let (found-match) 1584 (while (and 1585 (not found-match) 1586 (re-search-forward go--ident-type-pair-re end t)) 1587 1588 ;; Make sure the neither match is a keyword. 1589 (if (member (match-string 2) go-mode-keywords) 1590 (goto-char (match-end 2)) 1591 (if (member (match-string 1) go-mode-keywords) 1592 (goto-char (match-end 1)) 1593 (setq found-match t)))) 1594 1595 found-match)) 1596 1597 (defconst go--single-func-result-re (concat ")[[:space:]]+" go-type-name-regexp "\\(?:$\\|[[:space:]),]\\)")) 1598 1599 (defun go--match-single-func-result (end) 1600 "Match single result types. 1601 1602 Parenthetical result lists are handled by the param list keyword, 1603 so we need a separate keyword to handle singular result types 1604 such as \"string\" in: 1605 1606 func foo(i int) string" 1607 (let (found-match) 1608 (while (and 1609 (not found-match) 1610 (re-search-forward go--single-func-result-re end t)) 1611 (when (not (member (match-string 1) go-mode-keywords)) 1612 (setq found-match t) 1613 (goto-char (match-end 1)))) 1614 found-match)) 1615 1616 (defconst go--type-alias-re 1617 (concat "^[[:space:]]*\\(type\\)?[[:space:]]*" go-identifier-regexp "[[:space:]]*=[[:space:]]*" go-type-name-regexp)) 1618 1619 (defun go--match-type-alias (end) 1620 "Search for type aliases. 1621 1622 We are looking for the right-hand-side of the type alias" 1623 (let (found-match) 1624 (while (and 1625 (not found-match) 1626 (re-search-forward go--type-alias-re end t)) 1627 ;; Either line started with "type", or we are in a "type" block. 1628 (setq found-match (or 1629 (match-string 1) 1630 (go--in-paren-with-prefix-p ?\( "type")))) 1631 found-match)) 1632 1633 1634 (defconst go--map-value-re 1635 (concat "\\_<map\\_>\\[\\(?:\\[[^]]*\\]\\)*[^]]*\\]" go-type-name-regexp)) 1636 1637 (defun go--match-map-value (end) 1638 "Search for map value types." 1639 (when (re-search-forward go--map-value-re end t) 1640 ;; Move point to beginning of map value in case value itself is 1641 ;; also a map (we will match it next iteration). 1642 (goto-char (match-beginning 1)) 1643 t)) 1644 1645 (defconst go--label-re (concat "\\(" go-label-regexp "\\):")) 1646 1647 (defun go--match-ident-colon (end) 1648 "Search for composite literal field names and label definitions." 1649 (let (found-match) 1650 (while (and 1651 (not found-match) 1652 (re-search-forward go--label-re end t)) 1653 1654 (save-excursion 1655 (goto-char (match-beginning 1)) 1656 (skip-syntax-backward " ") 1657 1658 (setq found-match (or 1659 ;; We are a label/field name if we are at the 1660 ;; beginning of the line. 1661 (bolp) 1662 1663 ;; Composite literal field names, e.g. "Foo{Bar:". Note 1664 ;; that this gives false positives for literal maps, 1665 ;; arrays, and slices. 1666 (and 1667 (or (eq (char-before) ?,) (eq (char-before) ?{)) 1668 (go--in-composite-literal-p)))))) 1669 1670 found-match)) 1671 1672 (defun go--parameter-list-type (end) 1673 "Return `present' if the parameter list has names, or `absent' if not. 1674 Assumes point is at the beginning of a parameter list, just 1675 after '('." 1676 (save-excursion 1677 (skip-chars-forward "[:space:]\n" end) 1678 (cond ((> (point) end) 1679 nil) 1680 ((looking-at (concat go-identifier-regexp "[[:space:]\n]*,")) 1681 (goto-char (match-end 0)) 1682 (go--parameter-list-type end)) 1683 ((or (looking-at go-qualified-identifier-regexp) 1684 (looking-at (concat go-type-name-no-prefix-regexp "[[:space:]\n]*\\(?:)\\|\\'\\)")) 1685 (go--looking-at-keyword) 1686 (looking-at "[*\\[]\\|\\.\\.\\.\\|\\'")) 1687 'absent) 1688 (t 'present)))) 1689 1690 (defun go--reset-dangling-cache-before-change (&optional _beg _end) 1691 "Reset `go-dangling-cache'. 1692 1693 This is intended to be called from `before-change-functions'." 1694 (setq go-dangling-cache (make-hash-table :test 'eql))) 1695 1696 (defun go--electric-indent-function (inserted-char) 1697 (let ((prev (char-before (1- (point))))) 1698 (cond 1699 ;; Indent after starting/ending a comment. This is handy for 1700 ;; comments above "case" statements and closing multiline 1701 ;; comments. 1702 ((or 1703 (and (eq inserted-char ?/) (eq prev ?/)) 1704 (and (eq inserted-char ?/) (eq prev ?*)) 1705 (and (eq inserted-char ?*) (eq prev ?/))) 1706 'do-indent) 1707 1708 ((eq inserted-char ? ) 1709 (and 1710 (eq prev ?e) 1711 (eq (char-before (- (point) 2)) ?s) 1712 (eq (char-before (- (point) 3)) ?a) 1713 (eq (char-before (- (point) 4)) ?c))) 1714 1715 ;; Trick electric-indent-mode into indenting inside multiline 1716 ;; comments. 1717 ((and (eq inserted-char ?\n) (go-in-comment-p)) 1718 'do-indent)))) 1719 1720 (defun go--comment-region (beg end &optional arg) 1721 "Switch to block comment when commenting a partial line." 1722 (save-excursion 1723 (goto-char beg) 1724 (let ((beg-bol (line-beginning-position))) 1725 (goto-char end) 1726 (if (and 1727 ;; beg and end are on the same line 1728 (eq (line-beginning-position) beg-bol) 1729 ;; end is not at end of line 1730 (not (eq end (line-end-position)))) 1731 (let ((comment-start "/* ") 1732 (comment-end " */") 1733 (comment-padding "")) 1734 (comment-region-default beg end arg)) 1735 (comment-region-default beg end arg))))) 1736 1737 ;;;###autoload 1738 (define-derived-mode go-mode prog-mode "Go" 1739 "Major mode for editing Go source text. 1740 1741 This mode provides (not just) basic editing capabilities for 1742 working with Go code. It offers almost complete syntax 1743 highlighting, indentation that is almost identical to gofmt and 1744 proper parsing of the buffer content to allow features such as 1745 navigation by function, manipulation of comments or detection of 1746 strings. 1747 1748 In addition to these core features, it offers various features to 1749 help with writing Go code. You can directly run buffer content 1750 through gofmt, read godoc documentation from within Emacs, modify 1751 and clean up the list of package imports or interact with the 1752 Playground (uploading and downloading pastes). 1753 1754 The following extra functions are defined: 1755 1756 - `gofmt' 1757 - `godoc' and `godoc-at-point' 1758 - `go-import-add' 1759 - `go-remove-unused-imports' 1760 - `go-goto-arguments' 1761 - `go-goto-docstring' 1762 - `go-goto-function' 1763 - `go-goto-function-name' 1764 - `go-goto-imports' 1765 - `go-goto-return-values' 1766 - `go-goto-method-receiver' 1767 - `go-play-buffer' and `go-play-region' 1768 - `go-download-play' 1769 - `godef-describe' and `godef-jump' 1770 - `go-coverage' 1771 - `go-set-project' 1772 - `go-reset-gopath' 1773 1774 If you want to automatically run `gofmt' before saving a file, 1775 add the following hook to your emacs configuration: 1776 1777 \(add-hook 'before-save-hook #'gofmt-before-save) 1778 1779 If you want to use `godef-jump' instead of etags (or similar), 1780 consider binding godef-jump to `M-.', which is the default key 1781 for `find-tag': 1782 1783 \(add-hook 'go-mode-hook (lambda () 1784 (local-set-key (kbd \"M-.\") #'godef-jump))) 1785 1786 Please note that godef is an external dependency. You can install 1787 it with 1788 1789 go get github.com/rogpeppe/godef 1790 1791 1792 If you're looking for even more integration with Go, namely 1793 on-the-fly syntax checking, auto-completion and snippets, it is 1794 recommended that you look at flycheck 1795 \(see URL `https://github.com/flycheck/flycheck') or flymake in combination 1796 with goflymake (see URL `https://github.com/dougm/goflymake'), gocode 1797 \(see URL `https://github.com/nsf/gocode'), go-eldoc 1798 \(see URL `github.com/syohex/emacs-go-eldoc') and yasnippet-go 1799 \(see URL `https://github.com/dominikh/yasnippet-go')" 1800 1801 ;; Font lock 1802 (setq font-lock-defaults '(go--build-font-lock-keywords)) 1803 (setq font-lock-multiline t) 1804 1805 ;; Indentation 1806 (set (make-local-variable 'indent-line-function) #'go-mode-indent-line) 1807 1808 ;; Comments 1809 (set (make-local-variable 'comment-start) "// ") 1810 (set (make-local-variable 'comment-end) "") 1811 (set (make-local-variable 'comment-use-syntax) t) 1812 (set (make-local-variable 'comment-start-skip) "\\(//+\\|/\\*+\\)\\s *") 1813 (set (make-local-variable 'comment-region-function) #'go--comment-region) 1814 ;; Set comment-multi-line to t so that comment-indent-new-line 1815 ;; doesn't use one /* */ per line. Thanks to comment-use-syntax, 1816 ;; Emacs is smart enough to still insert new // for single-line 1817 ;; comments. 1818 (set (make-local-variable 'comment-multi-line) t) 1819 1820 (set (make-local-variable 'beginning-of-defun-function) #'go-beginning-of-defun) 1821 (set (make-local-variable 'end-of-defun-function) #'go-end-of-defun) 1822 (setq-local paragraph-start 1823 (concat "[[:space:]]*\\(?:" 1824 comment-start-skip 1825 "\\|\\*/?[[:space:]]*\\|\\)$")) 1826 (setq-local paragraph-separate paragraph-start) 1827 (setq-local fill-paragraph-function #'go-fill-paragraph) 1828 (setq-local fill-forward-paragraph-function #'go--fill-forward-paragraph) 1829 (setq-local adaptive-fill-function #'go--find-fill-prefix) 1830 (setq-local adaptive-fill-first-line-regexp "") 1831 (setq-local comment-line-break-function #'go--comment-indent-new-line) 1832 1833 (set (make-local-variable 'parse-sexp-lookup-properties) t) 1834 (set (make-local-variable 'syntax-propertize-function) #'go-propertize-syntax) 1835 1836 (when (boundp 'electric-indent-chars) 1837 (set (make-local-variable 'electric-indent-chars) '(?\n ?} ?\) ?:)) 1838 (add-hook 'electric-indent-functions #'go--electric-indent-function nil t)) 1839 1840 (set (make-local-variable 'compilation-error-screen-columns) nil) 1841 1842 (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql)) 1843 (add-hook 'before-change-functions #'go--reset-dangling-cache-before-change t t) 1844 1845 ;; ff-find-other-file 1846 (setq ff-other-file-alist 'go-other-file-alist) 1847 1848 (setq imenu-generic-expression 1849 '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1) 1850 ("func" "^func *\\(.*\\) {" 1))) 1851 (imenu-add-to-menubar "Index") 1852 1853 ;; Go style 1854 (setq indent-tabs-mode t) 1855 1856 ;; Handle unit test failure output in compilation-mode 1857 ;; 1858 ;; Note that we add our entry to the beginning of 1859 ;; compilation-error-regexp-alist. In older versions of Emacs, the 1860 ;; list was processed from the end, and we would've wanted to add 1861 ;; ours last. But at some point this changed, and now the list is 1862 ;; processed from the beginning. It's important that our entry comes 1863 ;; before gnu, because gnu matches go test output, but includes the 1864 ;; leading whitespace in the file name. 1865 ;; 1866 ;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2001-12/msg00674.html 1867 ;; documents the old, reversed order. 1868 (when (and (boundp 'compilation-error-regexp-alist) 1869 (boundp 'compilation-error-regexp-alist-alist)) 1870 (add-to-list 'compilation-error-regexp-alist 'go-test) 1871 (add-to-list 'compilation-error-regexp-alist-alist 1872 '(go-test . ("^\\s-+\\([^()\t\n]+\\):\\([0-9]+\\):? .*$" 1 2)) t))) 1873 1874 ;;;###autoload 1875 (add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode)) 1876 1877 (defun go--apply-rcs-patch (patch-buffer) 1878 "Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer." 1879 (let ((target-buffer (current-buffer)) 1880 ;; Relative offset between buffer line numbers and line numbers 1881 ;; in patch. 1882 ;; 1883 ;; Line numbers in the patch are based on the source file, so 1884 ;; we have to keep an offset when making changes to the 1885 ;; buffer. 1886 ;; 1887 ;; Appending lines decrements the offset (possibly making it 1888 ;; negative), deleting lines increments it. This order 1889 ;; simplifies the forward-line invocations. 1890 (line-offset 0) 1891 (column (current-column))) 1892 (save-excursion 1893 (with-current-buffer patch-buffer 1894 (goto-char (point-min)) 1895 (while (not (eobp)) 1896 (unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)") 1897 (error "Invalid rcs patch or internal error in go--apply-rcs-patch")) 1898 (forward-line) 1899 (let ((action (match-string 1)) 1900 (from (string-to-number (match-string 2))) 1901 (len (string-to-number (match-string 3)))) 1902 (cond 1903 ((equal action "a") 1904 (let ((start (point))) 1905 (forward-line len) 1906 (let ((text (buffer-substring start (point)))) 1907 (with-current-buffer target-buffer 1908 (cl-decf line-offset len) 1909 (goto-char (point-min)) 1910 (forward-line (- from len line-offset)) 1911 (insert text))))) 1912 ((equal action "d") 1913 (with-current-buffer target-buffer 1914 (go--goto-line (- from line-offset)) 1915 (cl-incf line-offset len) 1916 (go--delete-whole-line len))) 1917 (t 1918 (error "Invalid rcs patch or internal error in go--apply-rcs-patch"))))))) 1919 (move-to-column column))) 1920 1921 (defun gofmt--is-goimports-p () 1922 (string-equal (file-name-base gofmt-command) "goimports")) 1923 1924 (defun gofmt () 1925 "Format the current buffer according to the formatting tool. 1926 1927 The tool used can be set via ‘gofmt-command’ (default: gofmt) and additional 1928 arguments can be set as a list via ‘gofmt-args’." 1929 (interactive) 1930 (let ((tmpfile (make-nearby-temp-file "gofmt" nil ".go")) 1931 (patchbuf (get-buffer-create "*Gofmt patch*")) 1932 (errbuf (if gofmt-show-errors (get-buffer-create "*Gofmt Errors*"))) 1933 (coding-system-for-read 'utf-8) 1934 (coding-system-for-write 'utf-8) 1935 our-gofmt-args) 1936 1937 (unwind-protect 1938 (save-restriction 1939 (widen) 1940 (if errbuf 1941 (with-current-buffer errbuf 1942 (setq buffer-read-only nil) 1943 (erase-buffer))) 1944 (with-current-buffer patchbuf 1945 (erase-buffer)) 1946 1947 (write-region nil nil tmpfile) 1948 1949 (when (and (gofmt--is-goimports-p) buffer-file-name) 1950 (setq our-gofmt-args 1951 (append our-gofmt-args 1952 ;; srcdir, despite its name, supports 1953 ;; accepting a full path, and some features 1954 ;; of goimports rely on knowing the full 1955 ;; name. 1956 (list "-srcdir" (file-local-name 1957 (file-truename buffer-file-name)))))) 1958 (setq our-gofmt-args 1959 (append our-gofmt-args gofmt-args 1960 (list "-w" (file-local-name tmpfile)))) 1961 (message "Calling gofmt: %s %s" gofmt-command our-gofmt-args) 1962 ;; We're using errbuf for the mixed stdout and stderr output. This 1963 ;; is not an issue because gofmt -w does not produce any stdout 1964 ;; output in case of success. 1965 (if (zerop (apply #'process-file gofmt-command nil errbuf nil our-gofmt-args)) 1966 (progn 1967 ;; There is no remote variant of ‘call-process-region’, but we 1968 ;; can invoke diff locally, and the results should be the same. 1969 (if (zerop (let ((local-copy (file-local-copy tmpfile))) 1970 (unwind-protect 1971 (call-process-region 1972 (point-min) (point-max) "diff" nil patchbuf 1973 nil "-n" "-" (or local-copy tmpfile)) 1974 (when local-copy (delete-file local-copy))))) 1975 (message "Buffer is already gofmted") 1976 (go--apply-rcs-patch patchbuf) 1977 (message "Applied gofmt")) 1978 (if errbuf (gofmt--kill-error-buffer errbuf))) 1979 (message "Could not apply gofmt") 1980 (if errbuf (gofmt--process-errors (buffer-file-name) tmpfile errbuf)))) 1981 1982 (kill-buffer patchbuf) 1983 (delete-file tmpfile)))) 1984 1985 1986 (defun gofmt--process-errors (filename tmpfile errbuf) 1987 (with-current-buffer errbuf 1988 (if (eq gofmt-show-errors 'echo) 1989 (progn 1990 (message "%s" (buffer-string)) 1991 (gofmt--kill-error-buffer errbuf)) 1992 ;; Convert the gofmt stderr to something understood by the compilation mode. 1993 (goto-char (point-min)) 1994 (if (save-excursion 1995 (save-match-data 1996 (search-forward "flag provided but not defined: -srcdir" nil t))) 1997 (insert "Your version of goimports is too old and doesn't support vendoring. Please update goimports!\n\n")) 1998 (insert "gofmt errors:\n") 1999 (let ((truefile 2000 (if (gofmt--is-goimports-p) 2001 (concat (file-name-directory filename) (file-name-nondirectory tmpfile)) 2002 tmpfile))) 2003 (while (search-forward-regexp 2004 (concat "^\\(" (regexp-quote (file-local-name truefile)) 2005 "\\):") 2006 nil t) 2007 (replace-match (file-name-nondirectory filename) t t nil 1))) 2008 (compilation-mode) 2009 (display-buffer errbuf)))) 2010 2011 (defun gofmt--kill-error-buffer (errbuf) 2012 (let ((win (get-buffer-window errbuf))) 2013 (if win 2014 (quit-window t win) 2015 (kill-buffer errbuf)))) 2016 2017 ;;;###autoload 2018 (defun gofmt-before-save () 2019 "Add this to .emacs to run gofmt on the current buffer when saving: 2020 \(add-hook 'before-save-hook 'gofmt-before-save). 2021 2022 Note that this will cause ‘go-mode’ to get loaded the first time 2023 you save any file, kind of defeating the point of autoloading." 2024 2025 (interactive) 2026 (when (eq major-mode 'go-mode) (gofmt))) 2027 2028 (defun godoc--read-query () 2029 "Read a godoc query from the minibuffer." 2030 (if godoc-use-completing-read 2031 (completing-read "godoc; " 2032 (go-packages) nil nil nil 'go-godoc-history) 2033 (read-from-minibuffer "godoc: " nil nil nil 'go-godoc-history))) 2034 2035 (defun godoc--buffer-name (query) 2036 "Determine the name to use for the output buffer of a given godoc QUERY." 2037 (if godoc-reuse-buffer 2038 "*godoc*" 2039 (concat "*godoc " query "*"))) 2040 2041 (defun godoc--get-buffer (query) 2042 "Get an empty buffer for a godoc QUERY." 2043 (let* ((buffer-name (godoc--buffer-name query)) 2044 (buffer (get-buffer buffer-name))) 2045 ;; Kill the existing buffer if it already exists. 2046 (when buffer (kill-buffer buffer)) 2047 (get-buffer-create buffer-name))) 2048 2049 (defun godoc--buffer-sentinel (proc event) 2050 "Sentinel function run when godoc command completes." 2051 (with-current-buffer (process-buffer proc) 2052 (cond ((string= event "finished\n") ;; Successful exit. 2053 (goto-char (point-min)) 2054 (godoc-mode) 2055 (display-buffer (current-buffer) t)) 2056 ((/= (process-exit-status proc) 0) ;; Error exit. 2057 (let ((output (buffer-string))) 2058 (kill-buffer (current-buffer)) 2059 (message (concat "godoc: " output))))))) 2060 2061 (define-derived-mode godoc-mode special-mode "Godoc" 2062 "Major mode for showing Go documentation." 2063 (view-mode-enter)) 2064 2065 ;;;###autoload 2066 (defun godoc (query) 2067 "Show Go documentation for QUERY, much like \\<go-mode-map>\\[man]." 2068 (interactive (list (godoc--read-query))) 2069 (go--godoc query godoc-command)) 2070 2071 (defun go--godoc (query command) 2072 (unless (string= query "") 2073 (set-process-sentinel 2074 (start-process-shell-command "godoc" (godoc--get-buffer query) 2075 (concat command " " query)) 2076 'godoc--buffer-sentinel) 2077 nil)) 2078 2079 (defun godoc-at-point (point) 2080 "Show Go documentation for the identifier at POINT. 2081 2082 It uses `godoc-at-point-function' to look up the documentation." 2083 (interactive "d") 2084 (funcall godoc-at-point-function point)) 2085 2086 (defun go-goto-imports () 2087 "Move point to the block of imports. 2088 2089 If using 2090 2091 import ( 2092 \"foo\" 2093 \"bar\" 2094 ) 2095 2096 it will move point directly behind the last import. 2097 2098 If using 2099 2100 import \"foo\" 2101 import \"bar\" 2102 2103 it will move point to the next line after the last import. 2104 2105 If no imports can be found, point will be moved after the package 2106 declaration." 2107 (interactive) 2108 ;; FIXME if there's a block-commented import before the real 2109 ;; imports, we'll jump to that one. 2110 2111 ;; Generally, this function isn't very forgiving. it'll bark on 2112 ;; extra whitespace. It works well for clean code. 2113 (let ((old-point (point))) 2114 (goto-char (point-min)) 2115 (cond 2116 ((re-search-forward "^import ()" nil t) 2117 (backward-char 1) 2118 'block-empty) 2119 ((re-search-forward "^import ([^)]+)" nil t) 2120 (backward-char 2) 2121 'block) 2122 ((re-search-forward "\\(^import \\([^\"]+ \\)?\"[^\"]+\"\n?\\)+" nil t) 2123 'single) 2124 ((re-search-forward "^[[:space:]\n]*package .+?\n" nil t) 2125 (message "No imports found, moving point after package declaration") 2126 'none) 2127 (t 2128 (goto-char old-point) 2129 (message "No imports or package declaration found. Is this really a Go file?") 2130 'fail)))) 2131 2132 (defun go-play-buffer () 2133 "Like `go-play-region', but acts on the entire buffer." 2134 (interactive) 2135 (go-play-region (point-min) (point-max))) 2136 2137 (defun go-play-region (start end) 2138 "Send the region between START and END to the Playground. 2139 If non-nil `go-play-browse-function' is called with the 2140 Playground URL. 2141 2142 By default this function will prompt to confirm you want to upload 2143 code to the Playground. You can disable the confirmation by setting 2144 `go-confirm-playground-uploads' to nil. 2145 " 2146 (interactive "r") 2147 (if (and go-confirm-playground-uploads 2148 (not (yes-or-no-p "Upload to public Go Playground? "))) 2149 (message "Upload aborted") 2150 (let* ((url-request-method "POST") 2151 (url-request-extra-headers 2152 '(("Content-Type" . "text/plain; charset=UTF-8"))) 2153 (url-request-data 2154 (encode-coding-string 2155 (buffer-substring-no-properties start end) 2156 'utf-8)) 2157 2158 (content-buf (url-retrieve 2159 "https://play.golang.org/share" 2160 (lambda (arg) 2161 (cond 2162 ((equal :error (car arg)) 2163 (signal 'go-play-error (cdr arg))) 2164 (t 2165 (re-search-forward "\n\n") 2166 (let ((url (format "https://play.golang.org/p/%s" 2167 (buffer-substring (point) (point-max))))) 2168 (when go-play-browse-function 2169 (funcall go-play-browse-function url)))))))))))) 2170 2171 ;;;###autoload 2172 (defun go-download-play (url) 2173 "Download a paste from the playground and insert it in a Go buffer. 2174 Tries to look for a URL at point." 2175 (interactive (list (read-from-minibuffer "Playground URL: " (ffap-url-p (ffap-string-at-point 'url))))) 2176 (with-current-buffer 2177 (let ((url-request-method "GET") url-request-data url-request-extra-headers) 2178 (url-retrieve-synchronously (concat url ".go"))) 2179 (let ((buffer (generate-new-buffer (concat (car (last (split-string url "/"))) ".go")))) 2180 (goto-char (point-min)) 2181 (re-search-forward "\n\n") 2182 (copy-to-buffer buffer (point) (point-max)) 2183 (kill-buffer) 2184 (with-current-buffer buffer 2185 (go-mode) 2186 (switch-to-buffer buffer))))) 2187 2188 (defun go-propertize-syntax (start end) 2189 (save-excursion 2190 (goto-char start) 2191 (while (search-forward "\\" end t) 2192 (put-text-property (1- (point)) (point) 'syntax-table (if (= (char-after) ?`) '(1) '(9)))))) 2193 2194 (defun go-import-add (arg import) 2195 "Add a new IMPORT to the list of imports. 2196 2197 When called with a prefix ARG asks for an alternative name to 2198 import the package as. 2199 2200 If no list exists yet, one will be created if possible. 2201 2202 If an identical import has been commented, it will be 2203 uncommented, otherwise a new import will be added." 2204 2205 ;; - If there's a matching `// import "foo"`, uncomment it 2206 ;; - If we're in an import() block and there's a matching `"foo"`, uncomment it 2207 ;; - Otherwise add a new import, with the appropriate syntax 2208 (interactive 2209 (list 2210 current-prefix-arg 2211 (replace-regexp-in-string "^[\"']\\|[\"']$" "" (completing-read "Package: " (go-packages))))) 2212 (save-excursion 2213 (let (as line import-start) 2214 (if arg 2215 (setq as (read-from-minibuffer "Import as: "))) 2216 (if as 2217 (setq line (format "%s \"%s\"" as import)) 2218 (setq line (format "\"%s\"" import))) 2219 2220 (goto-char (point-min)) 2221 (if (re-search-forward (concat "^[[:space:]]*//[[:space:]]*import " line "$") nil t) 2222 (uncomment-region (line-beginning-position) (line-end-position)) 2223 (cl-case (go-goto-imports) 2224 ('fail (message "Could not find a place to add import.")) 2225 ('block-empty 2226 (insert "\n\t" line "\n")) 2227 ('block 2228 (save-excursion 2229 (re-search-backward "^import (") 2230 (setq import-start (point))) 2231 (if (re-search-backward (concat "^[[:space:]]*//[[:space:]]*" line "$") import-start t) 2232 (uncomment-region (line-beginning-position) (line-end-position)) 2233 (insert "\n\t" line))) 2234 ('single (insert "import " line "\n")) 2235 ('none (insert "\nimport (\n\t" line "\n)\n"))))))) 2236 2237 (defun go-root-and-paths () 2238 (let* ((output (process-lines go-command "env" "GOROOT" "GOPATH")) 2239 (root (car output)) 2240 (paths (split-string (cadr output) path-separator))) 2241 (cons root paths))) 2242 2243 (defun go--string-prefix-p (s1 s2 &optional ignore-case) 2244 "Return non-nil if S1 is a prefix of S2. 2245 If IGNORE-CASE is non-nil, the comparison is case-insensitive." 2246 (eq t (compare-strings s1 nil nil 2247 s2 0 (length s1) ignore-case))) 2248 2249 (defun go--directory-dirs (dir) 2250 "Recursively return all subdirectories in DIR." 2251 (if (file-directory-p dir) 2252 (let ((dir (directory-file-name dir)) 2253 (dirs '()) 2254 (files (directory-files dir nil nil t))) 2255 (dolist (file files) 2256 (unless (member file '("." "..")) 2257 (let ((file (concat dir "/" file))) 2258 (if (and (file-directory-p file) 2259 (not (file-symlink-p file))) 2260 (setq dirs (append (cons file 2261 (go--directory-dirs file)) 2262 dirs)))))) 2263 dirs) 2264 '())) 2265 2266 2267 (defun go-packages () 2268 (funcall go-packages-function)) 2269 2270 (defun go-packages-native () 2271 "Return a list of all installed Go packages. Obsolete. 2272 It looks for archive files in /pkg/. This strategy does not work 2273 well with the Go build cache or Go modules. 2274 2275 You should use `go-packages-go-list' instead." 2276 (sort 2277 (delete-dups 2278 (cl-mapcan 2279 (lambda (topdir) 2280 (let ((pkgdir (concat topdir "/pkg/"))) 2281 (cl-mapcan (lambda (dir) 2282 (mapcar (lambda (file) 2283 (let ((sub (substring file (length pkgdir) -2))) 2284 (unless (or (go--string-prefix-p "obj/" sub) (go--string-prefix-p "tool/" sub)) 2285 (mapconcat #'identity (cdr (split-string sub "/")) "/")))) 2286 (if (file-directory-p dir) 2287 (directory-files dir t "\\.a$")))) 2288 (if (file-directory-p pkgdir) 2289 (go--directory-dirs pkgdir))))) 2290 (go-root-and-paths))) 2291 #'string<)) 2292 2293 (defun go-packages-go-list () 2294 "Return a list of all Go packages, using `go list'." 2295 (process-lines go-command "list" "-e" "all")) 2296 2297 (defun go-unused-imports-lines () 2298 (reverse (remove nil 2299 (mapcar 2300 (lambda (line) 2301 (when (string-match "^\\(.+\\):\\([[:digit:]]+\\):\\([[:digit:]]+\\): imported and not used: \".+\".*$" line) 2302 (let ((error-file-name (match-string 1 line)) 2303 (error-line-num (match-string 2 line))) 2304 (if (string= (file-truename error-file-name) (file-truename buffer-file-name)) 2305 (string-to-number error-line-num))))) 2306 (split-string (shell-command-to-string 2307 (concat go-command 2308 (if (string-match "_test\\.go$" buffer-file-truename) 2309 " test -c" 2310 (concat " build -o " null-device)) 2311 " -gcflags=-e" 2312 " " 2313 (shell-quote-argument (file-truename buffer-file-name)))) "\n"))))) 2314 2315 (defun go-remove-unused-imports (arg) 2316 "Remove all unused imports. 2317 If ARG is non-nil, unused imports will be commented, otherwise 2318 they will be removed completely." 2319 (interactive "P") 2320 (save-excursion 2321 (let ((cur-buffer (current-buffer)) flymake-state lines) 2322 (when (boundp 'flymake-mode) 2323 (setq flymake-state flymake-mode) 2324 (flymake-mode -1)) 2325 (save-some-buffers nil (lambda () (equal cur-buffer (current-buffer)))) 2326 (if (buffer-modified-p) 2327 (message "Cannot operate on unsaved buffer") 2328 (setq lines (go-unused-imports-lines)) 2329 (dolist (import lines) 2330 (go--goto-line import) 2331 (beginning-of-line) 2332 (if arg 2333 (comment-region (line-beginning-position) (line-end-position)) 2334 (go--delete-whole-line))) 2335 (message "Removed %d imports" (length lines))) 2336 (if flymake-state (flymake-mode 1))))) 2337 2338 (defun godef--find-file-line-column (specifier other-window) 2339 "Given a file name in the format of `filename:line:column', 2340 visit FILENAME and go to line LINE and column COLUMN." 2341 (if (not (string-match "\\(.+\\):\\([0-9]+\\):\\([0-9]+\\)" specifier)) 2342 ;; We've only been given a directory name 2343 (funcall (if other-window #'find-file-other-window #'find-file) specifier) 2344 (let ((filename (match-string 1 specifier)) 2345 (line (string-to-number (match-string 2 specifier))) 2346 (column (string-to-number (match-string 3 specifier)))) 2347 (funcall (if other-window #'find-file-other-window #'find-file) filename) 2348 (go--goto-line line) 2349 (beginning-of-line) 2350 (forward-char (1- column)) 2351 (if (buffer-modified-p) 2352 (message "Buffer is modified, file position might not have been correct"))))) 2353 2354 (defun godef--call (point) 2355 "Call godef, acquiring definition position and expression 2356 description at POINT." 2357 (if (not (buffer-file-name (go--coverage-origin-buffer))) 2358 (error "Cannot use godef on a buffer without a file name") 2359 (let ((outbuf (generate-new-buffer "*godef*")) 2360 (coding-system-for-read 'utf-8) 2361 (coding-system-for-write 'utf-8)) 2362 (prog2 2363 (call-process-region (point-min) 2364 (point-max) 2365 godef-command 2366 nil 2367 outbuf 2368 nil 2369 "-i" 2370 "-t" 2371 "-f" 2372 (file-truename (buffer-file-name (go--coverage-origin-buffer))) 2373 "-o" 2374 ;; Emacs point and byte positions are 1-indexed. 2375 (number-to-string (1- (position-bytes point)))) 2376 (with-current-buffer outbuf 2377 (split-string (buffer-substring-no-properties (point-min) (point-max)) "\n")) 2378 (kill-buffer outbuf))))) 2379 2380 (defun godef--successful-p (output) 2381 (not (or (string= "-" output) 2382 (string= "godef: no identifier found" output) 2383 (string= "godef: no object" output) 2384 (go--string-prefix-p "godef: no declaration found for " output) 2385 (go--string-prefix-p "error finding import path for " output)))) 2386 2387 (defun godef--error (output) 2388 (cond 2389 ((godef--successful-p output) 2390 nil) 2391 ((string= "-" output) 2392 "godef: expression is not defined anywhere") 2393 (t 2394 output))) 2395 2396 (defun godef-describe (point) 2397 "Describe the expression at POINT." 2398 (interactive "d") 2399 (condition-case nil 2400 (let ((description (cdr (butlast (godef--call point) 1)))) 2401 (if (not description) 2402 (message "No description found for expression at point") 2403 (message "%s" (mapconcat #'identity description "\n")))) 2404 (file-error (message "Could not run godef binary")))) 2405 2406 (defun godef-jump (point &optional other-window) 2407 "Jump to the definition of the expression at POINT." 2408 (interactive "d") 2409 (condition-case nil 2410 (let ((file (car (godef--call point)))) 2411 (if (not (godef--successful-p file)) 2412 (message "%s" (godef--error file)) 2413 (push-mark) 2414 ;; TODO: Integrate this facility with XRef. 2415 (xref-push-marker-stack) 2416 (godef--find-file-line-column file other-window))) 2417 (file-error (message "Could not run godef binary")))) 2418 2419 (defun godef-jump-other-window (point) 2420 (interactive "d") 2421 (godef-jump point t)) 2422 2423 (defun go--goto-line (line) 2424 (goto-char (point-min)) 2425 (forward-line (1- line))) 2426 2427 (defun go--line-column-to-point (line column) 2428 (save-excursion 2429 (go--goto-line line) 2430 (forward-char (1- column)) 2431 (point))) 2432 2433 (cl-defstruct go--covered 2434 start-line start-column end-line end-column covered count) 2435 2436 (defun go--coverage-file () 2437 "Return the coverage file to use, either by reading it from the 2438 current coverage buffer or by prompting for it." 2439 (if (boundp 'go--coverage-current-file-name) 2440 go--coverage-current-file-name 2441 (read-file-name "Coverage file: " nil nil t))) 2442 2443 (defun go--coverage-origin-buffer () 2444 "Return the buffer to base the coverage on." 2445 (or (buffer-base-buffer) (current-buffer))) 2446 2447 (defun go--coverage-face (count divisor) 2448 "Return the intensity face for COUNT when using DIVISOR 2449 to scale it to a range [0,10]. 2450 2451 DIVISOR scales the absolute cover count to values from 0 to 10. 2452 For DIVISOR = 0 the count will always translate to 8." 2453 (let* ((norm (cond 2454 ((= count 0) 2455 -0.1) ;; Uncovered code, set to -0.1 so n becomes 0. 2456 ((= divisor 0) 2457 0.8) ;; covermode=set, set to 0.8 so n becomes 8. 2458 (t 2459 (/ (log count) divisor)))) 2460 (n (1+ (floor (* norm 9))))) ;; Convert normalized count [0,1] to intensity [0,10] 2461 (concat "go-coverage-" (number-to-string n)))) 2462 2463 (defun go--coverage-make-overlay (range divisor) 2464 "Create a coverage overlay for a RANGE of covered/uncovered code. 2465 Use DIVISOR to scale absolute counts to a [0,10] scale." 2466 (let* ((count (go--covered-count range)) 2467 (face (go--coverage-face count divisor)) 2468 (ov (make-overlay (go--line-column-to-point (go--covered-start-line range) 2469 (go--covered-start-column range)) 2470 (go--line-column-to-point (go--covered-end-line range) 2471 (go--covered-end-column range))))) 2472 2473 (overlay-put ov 'face face) 2474 (overlay-put ov 'help-echo (format "Count: %d" count)))) 2475 2476 (defun go--coverage-clear-overlays () 2477 "Remove existing overlays and put a single untracked overlay 2478 over the entire buffer." 2479 (remove-overlays) 2480 (overlay-put (make-overlay (point-min) (point-max)) 2481 'face 2482 'go-coverage-untracked)) 2483 2484 (defun go--coverage-parse-file (coverage-file file-name) 2485 "Parse COVERAGE-FILE and extract coverage information and 2486 divisor for FILE-NAME." 2487 (let (ranges 2488 (max-count 0)) 2489 (with-temp-buffer 2490 (insert-file-contents coverage-file) 2491 (go--goto-line 2) ;; Skip over mode 2492 (while (not (eobp)) 2493 (let* ((parts (split-string (buffer-substring (point-at-bol) (point-at-eol)) ":")) 2494 (file (car parts)) 2495 (rest (split-string (nth 1 parts) "[., ]"))) 2496 2497 (cl-destructuring-bind 2498 (start-line start-column end-line end-column num count) 2499 (mapcar #'string-to-number rest) 2500 2501 (when (string= (file-name-nondirectory file) file-name) 2502 (if (> count max-count) 2503 (setq max-count count)) 2504 (push (make-go--covered :start-line start-line 2505 :start-column start-column 2506 :end-line end-line 2507 :end-column end-column 2508 :covered (/= count 0) 2509 :count count) 2510 ranges))) 2511 2512 (forward-line))) 2513 2514 (list ranges (if (> max-count 0) (log max-count) 0))))) 2515 2516 (defun go-coverage (&optional coverage-file) 2517 "Open a clone of the current buffer and overlay it with 2518 coverage information gathered via go test -coverprofile=COVERAGE-FILE. 2519 2520 If COVERAGE-FILE is nil, it will either be inferred from the 2521 current buffer if it's already a coverage buffer, or be prompted 2522 for." 2523 (interactive) 2524 (let* ((cur-buffer (current-buffer)) 2525 (origin-buffer (go--coverage-origin-buffer)) 2526 (gocov-buffer-name (concat (buffer-name origin-buffer) "<gocov>")) 2527 (coverage-file (or coverage-file (go--coverage-file))) 2528 (ranges-and-divisor (go--coverage-parse-file 2529 coverage-file 2530 (file-name-nondirectory (buffer-file-name origin-buffer)))) 2531 (cov-mtime (nth 5 (file-attributes coverage-file))) 2532 (cur-mtime (nth 5 (file-attributes (buffer-file-name origin-buffer))))) 2533 2534 (if (< (float-time cov-mtime) (float-time cur-mtime)) 2535 (message "Coverage file is older than the source file.")) 2536 2537 (with-current-buffer (or (get-buffer gocov-buffer-name) 2538 (make-indirect-buffer origin-buffer gocov-buffer-name t)) 2539 (set (make-local-variable 'go--coverage-current-file-name) coverage-file) 2540 2541 (save-excursion 2542 (go--coverage-clear-overlays) 2543 (dolist (range (car ranges-and-divisor)) 2544 (go--coverage-make-overlay range (cadr ranges-and-divisor)))) 2545 2546 (if (not (eq cur-buffer (current-buffer))) 2547 (display-buffer (current-buffer) `(,go-coverage-display-buffer-func)))))) 2548 2549 (defun go-goto-function (&optional arg) 2550 "Go to the function definition (named or anonymous) surrounding point. 2551 2552 If we are on a docstring, follow the docstring down. 2553 If no function is found, assume that we are at the top of a file 2554 and search forward instead. 2555 2556 If point is looking at the func keyword of an anonymous function, 2557 go to the surrounding function. 2558 2559 If ARG is non-nil, anonymous functions are ignored." 2560 (interactive "P") 2561 (let ((p (point))) 2562 (cond 2563 ((save-excursion 2564 (beginning-of-line) 2565 (looking-at "^//")) 2566 ;; In case we are looking at the docstring, move on forward until we are 2567 ;; not anymore 2568 (beginning-of-line) 2569 (while (looking-at "^//") 2570 (forward-line 1)) 2571 ;; If we are still not looking at a function, retry by calling self again. 2572 (when (not (looking-at "\\<func\\>")) 2573 (go-goto-function arg))) 2574 2575 ;; If we're already looking at an anonymous func, look for the 2576 ;; surrounding function. 2577 ((and (looking-at "\\<func\\>") 2578 (not (looking-at "^func\\>"))) 2579 (re-search-backward "\\<func\\>" nil t)) 2580 2581 ((not (looking-at "\\<func\\>")) 2582 ;; If point is on the "func" keyword, step back a word and retry 2583 (if (string= (symbol-name (symbol-at-point)) "func") 2584 (backward-word) 2585 ;; If we are not looking at the beginning of a function line, do a regexp 2586 ;; search backwards 2587 (re-search-backward "\\<func\\>" nil t)) 2588 2589 ;; If nothing is found, assume that we are at the top of the file and 2590 ;; should search forward instead. 2591 (when (not (looking-at "\\<func\\>")) 2592 (re-search-forward "\\<func\\>" nil t) 2593 (go--forward-word -1)) 2594 2595 ;; If we have landed at an anonymous function, it is possible that we 2596 ;; were not inside it but below it. If we were not inside it, we should 2597 ;; go to the containing function. 2598 (while (and (not (go--in-function-p p)) 2599 (not (looking-at "^func\\>"))) 2600 (go-goto-function arg))))) 2601 2602 (cond 2603 ((go-in-comment-p) 2604 ;; If we are still in a comment, redo the call so that we get out of it. 2605 (go-goto-function arg)) 2606 2607 ((and (looking-at "\\<func(") arg) 2608 ;; If we are looking at an anonymous function and a prefix argument has 2609 ;; been supplied, redo the call so that we skip the anonymous function. 2610 (go-goto-function arg)))) 2611 2612 (defun go--goto-opening-curly-brace () 2613 ;; Find the { that starts the function, i.e., the next { that isn't 2614 ;; preceded by struct or interface, or a comment or struct tag. BUG: 2615 ;; breaks if there's a comment between the struct/interface keyword and 2616 ;; bracket, like this: 2617 ;; 2618 ;; struct /* why? */ { 2619 (go--goto-return-values) 2620 (while (progn 2621 (skip-chars-forward "^{") 2622 (forward-char) 2623 (or (go-in-string-or-comment-p) 2624 (looking-back "\\(struct\\|interface\\)\\s-*{" 2625 (line-beginning-position))))) 2626 (backward-char)) 2627 2628 (defun go--in-function-p (compare-point) 2629 "Return t if COMPARE-POINT is inside the function immediately surrounding point." 2630 (save-excursion 2631 (when (not (looking-at "\\<func\\>")) 2632 (go-goto-function)) 2633 (let ((start (point))) 2634 (go--goto-opening-curly-brace) 2635 2636 (unless (looking-at "{") 2637 (error "Expected to be looking at opening curly brace")) 2638 (forward-list 1) 2639 (and (>= compare-point start) 2640 (<= compare-point (point)))))) 2641 2642 (defun go-goto-function-name (&optional arg) 2643 "Go to the name of the current function. 2644 2645 If the function is a test, place point after 'Test'. 2646 If the function is anonymous, place point on the 'func' keyword. 2647 2648 If ARG is non-nil, anonymous functions are skipped." 2649 (interactive "P") 2650 (when (not (looking-at "\\<func\\>")) 2651 (go-goto-function arg)) 2652 ;; If we are looking at func( we are on an anonymous function and 2653 ;; nothing else should be done. 2654 (when (not (looking-at "\\<func(")) 2655 (let ((words 1) 2656 (chars 1)) 2657 (when (looking-at "\\<func (") 2658 (setq words 3 2659 chars 2)) 2660 (go--forward-word words) 2661 (forward-char chars) 2662 (when (looking-at "Test") 2663 (forward-char 4))))) 2664 2665 (defun go-goto-arguments (&optional arg) 2666 "Go to the arguments of the current function. 2667 2668 If ARG is non-nil, anonymous functions are skipped." 2669 (interactive "P") 2670 (go-goto-function-name arg) 2671 (go--forward-word 1) 2672 (forward-char 1)) 2673 2674 (defun go--goto-return-values (&optional arg) 2675 "Go to the declaration of return values for the current function." 2676 (go-goto-arguments arg) 2677 (backward-char) 2678 (forward-list) 2679 (forward-char)) 2680 2681 (defun go-goto-return-values (&optional arg) 2682 "Go to the return value declaration of the current function. 2683 2684 If there are multiple ones contained in a parenthesis, enter the parenthesis. 2685 If there is none, make space for one to be added. 2686 2687 If ARG is non-nil, anonymous functions are skipped." 2688 (interactive "P") 2689 (go--goto-return-values arg) 2690 2691 ;; Opening parenthesis, enter it 2692 (when (looking-at "(") 2693 (forward-char 1)) 2694 2695 ;; No return arguments, add space for adding 2696 (when (looking-at "{") 2697 (insert " ") 2698 (backward-char 1))) 2699 2700 (defun go-goto-method-receiver (&optional arg) 2701 "Go to the receiver of the current method. 2702 2703 If there is none, add parenthesis to add one. 2704 2705 Anonymous functions cannot have method receivers, so when this is called 2706 interactively anonymous functions will be skipped. If called programmatically, 2707 an error is raised unless ARG is non-nil." 2708 (interactive "P") 2709 2710 (when (and (not (called-interactively-p 'interactive)) 2711 (not arg) 2712 (go--in-anonymous-funcion-p)) 2713 (error "Anonymous functions cannot have method receivers")) 2714 2715 (go-goto-function t) ; Always skip anonymous functions 2716 (forward-char 5) 2717 (when (not (looking-at "(")) 2718 (save-excursion 2719 (insert "() "))) 2720 (forward-char 1)) 2721 2722 (defun go-goto-docstring (&optional arg) 2723 "Go to the top of the docstring of the current function. 2724 2725 If there is none, add one beginning with the name of the current function. 2726 2727 Anonymous functions do not have docstrings, so when this is called 2728 interactively anonymous functions will be skipped. If called programmatically, 2729 an error is raised unless ARG is non-nil." 2730 (interactive "P") 2731 2732 (when (and (not (called-interactively-p 'interactive)) 2733 (not arg) 2734 (go--in-anonymous-funcion-p)) 2735 (error "Anonymous functions do not have docstrings")) 2736 2737 (go-goto-function t) 2738 (forward-line -1) 2739 (beginning-of-line) 2740 2741 (while (looking-at "^//") 2742 (forward-line -1)) 2743 (forward-line 1) 2744 (beginning-of-line) 2745 2746 (cond 2747 ;; If we are looking at an empty comment, add a single space in front of it. 2748 ((looking-at "^//$") 2749 (forward-char 2) 2750 (insert (format " %s " (go--function-name t)))) 2751 ;; If we are not looking at the function signature, we are looking at a docstring. 2752 ;; Move to the beginning of the first word of it. 2753 ((not (looking-at "^func")) 2754 (forward-char 3)) 2755 ;; If we are still at the function signature, we should add a new docstring. 2756 (t 2757 (forward-line -1) 2758 (newline) 2759 (insert "// ") 2760 (insert (go--function-name t))))) 2761 2762 (defun go--function-name (&optional arg) 2763 "Return the name of the surrounding function. 2764 2765 If ARG is non-nil, anonymous functions will be ignored and the 2766 name returned will be that of the top-level function. If ARG is 2767 nil and the surrounding function is anonymous, nil will be 2768 returned." 2769 (when (or (not (go--in-anonymous-funcion-p)) 2770 arg) 2771 (save-excursion 2772 (go-goto-function-name t) 2773 (symbol-name (symbol-at-point))))) 2774 2775 (defun go--in-anonymous-funcion-p () 2776 "Return t if point is inside an anonymous function, nil otherwise." 2777 (save-excursion 2778 (go-goto-function) 2779 (looking-at "\\<func("))) 2780 2781 (defun go-guess-gopath (&optional buffer) 2782 "Determine a suitable GOPATH for BUFFER, or the current buffer if BUFFER is nil." 2783 (with-current-buffer (or buffer (current-buffer)) 2784 (let ((gopath (cl-some (lambda (el) (funcall el)) 2785 go-guess-gopath-functions))) 2786 (if gopath 2787 (mapconcat 2788 (lambda (el) (file-truename el)) 2789 gopath 2790 path-separator))))) 2791 2792 (defun go-plain-gopath () 2793 "Detect a normal GOPATH, by looking for the first `src' 2794 directory up the directory tree." 2795 (let ((d (locate-dominating-file buffer-file-name "src"))) 2796 (if d 2797 (list d)))) 2798 2799 (defun go-set-project (&optional buffer) 2800 "Set GOPATH based on `go-guess-gopath' for BUFFER. 2801 Set it to the current buffer if BUFFER is nil. 2802 2803 If go-guess-gopath returns nil, that is if it couldn't determine 2804 a valid value for GOPATH, GOPATH will be set to the initial value 2805 of when Emacs was started. 2806 2807 This function can for example be used as a 2808 projectile-switch-project-hook, or simply be called manually when 2809 switching projects." 2810 (interactive) 2811 (let ((gopath (or (go-guess-gopath buffer) 2812 (go-original-gopath)))) 2813 (setenv "GOPATH" gopath) 2814 (message "Set GOPATH to %s" gopath))) 2815 2816 (defun go-reset-gopath () 2817 "Reset GOPATH to the value it had when Emacs started." 2818 (interactive) 2819 (let ((gopath (go-original-gopath))) 2820 (setenv "GOPATH" gopath) 2821 (message "Set GOPATH to %s" gopath))) 2822 2823 (defun go-original-gopath () 2824 "Return the original value of GOPATH from when Emacs was started." 2825 (let ((process-environment initial-environment)) (getenv "GOPATH"))) 2826 2827 (defun go--insert-modified-files () 2828 "Insert the contents of each modified Go buffer into the 2829 current buffer in the format specified by guru's -modified flag." 2830 (mapc #'(lambda (b) 2831 (and (buffer-modified-p b) 2832 (buffer-file-name b) 2833 (string= (file-name-extension (buffer-file-name b)) "go") 2834 (go--insert-modified-file (buffer-file-name b) b))) 2835 (buffer-list))) 2836 2837 (defun go--insert-modified-file (name buffer) 2838 (insert (format "%s\n%d\n" name (go--buffer-size-bytes buffer))) 2839 (insert-buffer-substring buffer)) 2840 2841 (defun go--buffer-size-bytes (&optional buffer) 2842 (message "buffer; %s" buffer) 2843 "Return the number of bytes in the current buffer. 2844 If BUFFER, return the number of characters in that buffer instead." 2845 (with-current-buffer (or buffer (current-buffer)) 2846 (1- (position-bytes (point-max))))) 2847 2848 (defvar go-dot-mod-mode-map 2849 (let ((map (make-sparse-keymap))) 2850 map) 2851 "Keymap for `go-dot-mod-mode'.") 2852 2853 (defvar go-dot-mod-mode-syntax-table 2854 (let ((st (make-syntax-table))) 2855 ;; handle '//' comment syntax 2856 (modify-syntax-entry ?/ ". 124b" st) 2857 (modify-syntax-entry ?\n "> b" st) 2858 st) 2859 "Syntax table for `go-dot-mod-mode'.") 2860 2861 (defconst go-dot-mod-mode-keywords 2862 '("module" "go" "require" "replace" "exclude") 2863 "All keywords for go.mod files. Used for font locking.") 2864 2865 (defgroup go-dot-mod nil 2866 "Options specific to `go-dot-mod-mode`." 2867 :group 'go) 2868 2869 (defface go-dot-mod-module-name '((t :inherit default)) 2870 "Face for module name in \"require\" list." 2871 :group 'go-dot-mod) 2872 2873 (defface go-dot-mod-module-version '((t :inherit default)) 2874 "Face for module version in \"require\" list." 2875 :group 'go-dot-mod) 2876 2877 (defface go-dot-mod-module-semver '((t :inherit go-dot-mod-module-version)) 2878 "Face for module semver in \"require\" list." 2879 :group 'go-dot-mod) 2880 2881 2882 (defvar go-dot-mod-font-lock-keywords 2883 `( 2884 (,(concat "^\\s-*\\(" (regexp-opt go-dot-mod-mode-keywords t) "\\)\\s-") 1 font-lock-keyword-face) 2885 ("\\(?:^\\|=>\\)\\s-*\\([^[:space:]\n()]+\\)\\(?:\\s-+\\(v[0-9]+\\.[0-9]+\\.[0-9]+\\)\\([^[:space:]\n]*\\)\\)?" (1 'go-dot-mod-module-name) (2 'go-dot-mod-module-semver nil t) (3 'go-dot-mod-module-version nil t))) 2886 "Keyword highlighting specification for `go-dot-mod-mode'.") 2887 2888 ;;;###autoload 2889 (define-derived-mode go-dot-mod-mode fundamental-mode "Go Mod" 2890 "A major mode for editing go.mod files." 2891 :syntax-table go-dot-mod-mode-syntax-table 2892 (set (make-local-variable 'comment-start) "// ") 2893 (set (make-local-variable 'comment-end) "") 2894 (set (make-local-variable 'comment-use-syntax) t) 2895 (set (make-local-variable 'comment-start-skip) "\\(//+\\)\\s *") 2896 2897 (set (make-local-variable 'font-lock-defaults) 2898 '(go-dot-mod-font-lock-keywords)) 2899 (set (make-local-variable 'indent-line-function) 'go-mode-indent-line) 2900 2901 ;; Go style 2902 (setq indent-tabs-mode t) 2903 2904 ;; we borrow the go-mode-indent function so we need this buffer cache 2905 (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql)) 2906 (add-hook 'before-change-functions #'go--reset-dangling-cache-before-change t t)) 2907 2908 ;;;###autoload 2909 (add-to-list 'auto-mode-alist '("go\\.mod\\'" . go-dot-mod-mode)) 2910 2911 ;; The following functions were copied (and modified) from rust-mode.el. 2912 ;; 2913 ;; Copyright (c) 2015 The Rust Project Developers 2914 ;; 2915 ;; Permission is hereby granted, free of charge, to any 2916 ;; person obtaining a copy of this software and associated 2917 ;; documentation files (the "Software"), to deal in the 2918 ;; Software without restriction, including without 2919 ;; limitation the rights to use, copy, modify, merge, 2920 ;; publish, distribute, sublicense, and/or sell copies of 2921 ;; the Software, and to permit persons to whom the Software 2922 ;; is furnished to do so, subject to the following 2923 ;; conditions: 2924 ;; 2925 ;; The above copyright notice and this permission notice 2926 ;; shall be included in all copies or substantial portions 2927 ;; of the Software. 2928 2929 (defun go--fill-prefix-for-comment-start (line-start) 2930 "Determine what to use for `fill-prefix' based on the text at LINE-START." 2931 (let ((result 2932 ;; Replace /* with same number of spaces 2933 (replace-regexp-in-string 2934 "\\(?:/\\*+?\\)[!*]?" 2935 (lambda (s) 2936 (let ((offset (if (eq t 2937 (compare-strings "/*" nil nil 2938 s 2939 (- (length s) 2) 2940 (length s))) 2941 1 2))) 2942 (make-string (1+ (- (length s) offset)) ?\x20))) 2943 line-start))) 2944 ;; Make sure we've got at least one space at the end 2945 (if (not (= (aref result (- (length result) 1)) ?\x20)) 2946 (setq result (concat result " "))) 2947 result)) 2948 2949 (defun go--in-comment-paragraph (body) 2950 ;; We might move the point to fill the next comment, but we don't want it 2951 ;; seeming to jump around on the user 2952 (save-excursion 2953 ;; If we're outside of a comment, with only whitespace and then a comment 2954 ;; in front, jump to the comment and prepare to fill it. 2955 (when (not (go-in-comment-p)) 2956 (beginning-of-line) 2957 (when (looking-at (concat "[[:space:]\n]*" comment-start-skip)) 2958 (goto-char (match-end 0)))) 2959 2960 ;; If we're at the beginning of a comment paragraph with nothing but 2961 ;; whitespace til the next line, jump to the next line so that we use the 2962 ;; existing prefix to figure out what the new prefix should be, rather than 2963 ;; inferring it from the comment start. 2964 (while (save-excursion 2965 (end-of-line) 2966 (and (go-in-comment-p) 2967 (save-excursion 2968 (beginning-of-line) 2969 (looking-at paragraph-start)) 2970 (looking-at "[[:space:]]*$") 2971 (nth 4 (syntax-ppss (line-beginning-position 2))))) 2972 (goto-char (line-beginning-position 2))) 2973 2974 ;; If we're on the last line of a multiline-style comment that started 2975 ;; above, back up one line so we don't mistake the * of the */ that ends 2976 ;; the comment for a prefix. 2977 (when (save-excursion 2978 (and (nth 4 (syntax-ppss (line-beginning-position 1))) 2979 (looking-at "[[:space:]]*\\*/"))) 2980 (goto-char (line-end-position 0))) 2981 (funcall body))) 2982 2983 (defun go--with-comment-fill-prefix (body) 2984 (let* 2985 ((line-string (buffer-substring-no-properties 2986 (line-beginning-position) (line-end-position))) 2987 (line-comment-start 2988 (when (go-in-comment-p) 2989 (cond 2990 ;; If we're inside the comment and see a * prefix, use it 2991 ((string-match "^\\([[:space:]]*\\*+[[:space:]]*\\)" 2992 line-string) 2993 (match-string 1 line-string)) 2994 ;; If we're at the start of a comment, figure out what prefix 2995 ;; to use for the subsequent lines after it 2996 ((string-match (concat "[[:space:]]*" comment-start-skip) line-string) 2997 (go--fill-prefix-for-comment-start 2998 (match-string 0 line-string)))))) 2999 (fill-prefix 3000 (or line-comment-start 3001 fill-prefix))) 3002 (funcall body))) 3003 3004 (defun go--find-fill-prefix () 3005 (go--in-comment-paragraph 3006 (lambda () 3007 (go--with-comment-fill-prefix 3008 (lambda () 3009 fill-prefix))))) 3010 3011 (defun go-fill-paragraph (&rest args) 3012 "Special wrapping for `fill-paragraph'. 3013 This handles multi-line comments with a * prefix on each line." 3014 (go--in-comment-paragraph 3015 (lambda () 3016 (go--with-comment-fill-prefix 3017 (lambda () 3018 (let 3019 ((fill-paragraph-function 3020 (if (not (eq fill-paragraph-function 'go-fill-paragraph)) 3021 fill-paragraph-function)) 3022 (fill-paragraph-handle-comment t)) 3023 (apply 'fill-paragraph args) 3024 t)))))) 3025 3026 (defun go--do-auto-fill (&rest args) 3027 "Special wrapping for `do-auto-fill'. 3028 This handles multi-line comments with a * prefix on each line." 3029 (go--with-comment-fill-prefix 3030 (lambda () 3031 (apply 'do-auto-fill args) 3032 t))) 3033 3034 (defun go--fill-forward-paragraph (arg) 3035 ;; This is to work around some funny behavior when a paragraph separator is 3036 ;; at the very top of the file and there is a fill prefix. 3037 (let ((fill-prefix nil)) (forward-paragraph arg))) 3038 3039 (defun go--comment-indent-new-line (&optional arg) 3040 (go--with-comment-fill-prefix 3041 (lambda () (comment-indent-new-line arg)))) 3042 3043 3044 3045 (provide 'go-mode) 3046 3047 ;;; go-mode.el ends here