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