1
1
;;; go-mode.el --- Major mode for the Go programming language
5
;; For installation instructions, see go-mode-load.el
9
;; * Indentation is *almost* identical to gofmt
10
;; ** We think struct literal keys are labels and outdent them
11
;; ** We disagree on the indentation of function literals in arguments
12
;; ** There are bugs with the close brace of struct literals
13
;; * Highlight identifiers according to their syntactic context: type,
14
;; variable, function call, or tag
15
;; * Command for adding an import
16
;; ** Check if it's already there
17
;; ** Factor/unfactor the import line
19
;; * Remove unused imports
20
;; ** This is hard, since I have to be aware of shadowing to do it
22
;; * Format region using gofmt
26
(eval-when-compile (require 'cl))
3
;; Copyright 2013 The Go Authors. All rights reserved.
4
;; Use of this source code is governed by a BSD-style
5
;; license that can be found in the LICENSE file.
11
;; XEmacs compatibility guidelines
12
;; - Minimum required version of XEmacs: 21.5.32
13
;; - Feature that cannot be backported: POSIX character classes in
14
;; regular expressions
15
;; - Functions that could be backported but won't because 21.5.32
16
;; covers them: plenty.
17
;; - Features that are still partly broken:
18
;; - godef will not work correctly if multibyte characters are
20
;; - Fontification will not handle unicode correctly
22
;; - Do not use \_< and \_> regexp delimiters directly; use
23
;; go--regexp-enclose-in-symbol
25
;; - The character `_` must not be a symbol constituent but a
26
;; character constituent
28
;; - Do not use process-lines
30
;; - Use go--old-completion-list-style when using a plain list as the
31
;; collection for completing-read
33
;; - Use go--kill-whole-line instead of kill-whole-line (called
34
;; kill-entire-line in XEmacs)
36
;; - Use go--position-bytes instead of position-bytes
37
(defmacro go--xemacs-p ()
40
(defalias 'go--kill-whole-line
41
(if (fboundp 'kill-whole-line)
45
;; XEmacs unfortunately does not offer position-bytes. We can fall
46
;; back to just using (point), but it will be incorrect as soon as
47
;; multibyte characters are being used.
48
(if (fboundp 'position-bytes)
49
(defalias 'go--position-bytes 'position-bytes)
50
(defun go--position-bytes (point) point))
52
(defun go--old-completion-list-style (list)
53
(mapcar (lambda (x) (cons x nil)) list))
55
;; GNU Emacs 24 has prog-mode, older GNU Emacs and XEmacs do not.
56
;; Ideally we'd use defalias instead, but that breaks in XEmacs.
58
;; TODO: If XEmacs decides to add prog-mode, change this to use
59
;; defalias to alias prog-mode or fundamental-mode to go--prog-mode
60
;; and use that in define-derived-mode.
61
(if (not (fboundp 'prog-mode))
62
(define-derived-mode prog-mode fundamental-mode "" ""))
64
(defun go--regexp-enclose-in-symbol (s)
65
;; XEmacs does not support \_<, GNU Emacs does. In GNU Emacs we make
66
;; extensive use of \_< to support unicode in identifiers. Until we
67
;; come up with a better solution for XEmacs, this solution will
68
;; break fontification in XEmacs for identifiers such as "typeµ".
69
;; XEmacs will consider "type" a keyword, GNU Emacs won't.
72
(concat "\\<" s "\\>")
73
(concat "\\_<" s "\\_>")))
75
(defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]")
76
(defconst go-identifier-regexp "[[:word:][:multibyte:]]+")
77
(defconst go-label-regexp go-identifier-regexp)
78
(defconst go-type-regexp "[[:word:][:multibyte:]*]+")
79
(defconst go-func-regexp (concat (go--regexp-enclose-in-symbol "func") "\\s *\\(" go-identifier-regexp "\\)"))
80
(defconst go-func-meth-regexp (concat (go--regexp-enclose-in-symbol "func") "\\s *\\(?:(\\s *" go-identifier-regexp "\\s +" go-type-regexp "\\s *)\\s *\\)?\\(" go-identifier-regexp "\\)("))
82
'("append" "cap" "close" "complex" "copy"
83
"delete" "imag" "len" "make" "new"
84
"panic" "print" "println" "real" "recover")
85
"All built-in functions in the Go language. Used for font locking.")
87
(defconst go-mode-keywords
88
'("break" "default" "func" "interface" "select"
89
"case" "defer" "go" "map" "struct"
90
"chan" "else" "goto" "package" "switch"
91
"const" "fallthrough" "if" "range" "type"
92
"continue" "for" "import" "return" "var")
93
"All keywords in the Go language. Used for font locking.")
95
(defconst go-constants '("nil" "true" "false" "iota"))
96
(defconst go-type-name-regexp (concat "\\(?:[*(]\\)*\\(?:" go-identifier-regexp "\\.\\)?\\(" go-identifier-regexp "\\)"))
98
(defvar go-dangling-cache)
99
(defvar go-godoc-history nil)
102
"Major mode for editing Go code"
105
(defcustom go-fontify-function-calls t
106
"Fontify function and method calls if this is non-nil."
28
110
(defvar go-mode-syntax-table
29
111
(let ((st (make-syntax-table)))
30
;; Add _ to :word: character class
31
(modify-syntax-entry ?_ "w" st)
33
;; Operators (punctuation)
34
112
(modify-syntax-entry ?+ "." st)
35
113
(modify-syntax-entry ?- "." st)
36
(modify-syntax-entry ?* ". 23" st) ; also part of comments
37
(modify-syntax-entry ?/ (if (featurep 'xemacs) ". 1456" ". 124b") st) ; ditto
38
114
(modify-syntax-entry ?% "." st)
39
115
(modify-syntax-entry ?& "." st)
40
116
(modify-syntax-entry ?| "." st)
43
119
(modify-syntax-entry ?= "." st)
44
120
(modify-syntax-entry ?< "." st)
45
121
(modify-syntax-entry ?> "." st)
47
;; Strings and comments are font-locked separately.
48
(modify-syntax-entry ?\" "." st)
49
(modify-syntax-entry ?\' "." st)
50
(modify-syntax-entry ?` "." st)
51
(modify-syntax-entry ?\\ "." st)
53
;; Newline is a comment-ender.
122
(modify-syntax-entry ?/ (if (go--xemacs-p) ". 1456" ". 124b") st)
123
(modify-syntax-entry ?* ". 23" st)
54
124
(modify-syntax-entry ?\n "> b" st)
125
(modify-syntax-entry ?\" "\"" st)
126
(modify-syntax-entry ?\' "\"" st)
127
(modify-syntax-entry ?` "\"" st)
128
(modify-syntax-entry ?\\ "\\" st)
129
;; It would be nicer to have _ as a symbol constituent, but that
130
;; would trip up XEmacs, which does not support the \_< anchor
131
(modify-syntax-entry ?_ "w" st)
57
134
"Syntax table for Go mode.")
59
(defvar go-mode-keywords
60
'("break" "default" "func" "interface" "select"
61
"case" "defer" "go" "map" "struct"
62
"chan" "else" "goto" "package" "switch"
63
"const" "fallthrough" "if" "range" "type"
64
"continue" "for" "import" "return" "var")
65
"All keywords in the Go language. Used for font locking and
66
some syntax analysis.")
68
(defvar go-mode-font-lock-keywords
69
(let ((builtins '("append" "cap" "close" "complex" "copy" "delete" "imag" "len"
70
"make" "new" "panic" "print" "println" "real" "recover"))
71
(constants '("nil" "true" "false" "iota"))
72
(type-name "\\s *\\(?:[*(]\\s *\\)*\\(?:\\w+\\s *\\.\\s *\\)?\\(\\w+\\)")
74
`((go-mode-font-lock-cs-comment 0 font-lock-comment-face t)
75
(go-mode-font-lock-cs-string 0 font-lock-string-face t)
76
(,(regexp-opt go-mode-keywords 'words) . font-lock-keyword-face)
77
(,(regexp-opt builtins 'words) . font-lock-builtin-face)
78
(,(regexp-opt constants 'words) . font-lock-constant-face)
79
;; Function names in declarations
80
("\\<func\\>\\s *\\(\\w+\\)" 1 font-lock-function-name-face)
81
;; Function names in methods are handled by function call pattern
82
;; Function names in calls
83
;; XXX Doesn't match if function name is surrounded by parens
84
("\\(\\w+\\)\\s *(" 1 font-lock-function-name-face)
86
("\\<type\\>\\s *\\(\\w+\\)" 1 font-lock-type-face)
87
(,(concat "\\<type\\>\\s *\\w+\\s *" type-name) 1 font-lock-type-face)
88
;; Arrays/slices/map value type
89
;; XXX Wrong. Marks 0 in expression "foo[0] * x"
90
;; (,(concat "]" type-name) 1 font-lock-type-face)
92
(,(concat "\\<map\\s *\\[" type-name) 1 font-lock-type-face)
94
(,(concat "\\<chan\\>\\s *\\(?:<-\\)?" type-name) 1 font-lock-type-face)
96
(,(concat "\\<\\(?:new\\|make\\)\\>\\(?:\\s \\|)\\)*(" type-name) 1 font-lock-type-face)
98
(,(concat "\\.\\s *(" type-name) 1 font-lock-type-face)
99
;; Method receiver type
100
(,(concat "\\<func\\>\\s *(\\w+\\s +" type-name) 1 font-lock-type-face)
102
;; XXX Not quite right. Also marks compound literal fields.
103
("^\\s *\\(\\w+\\)\\s *:\\(\\S.\\|$\\)" 1 font-lock-constant-face)
104
("\\<\\(goto\\|break\\|continue\\)\\>\\s *\\(\\w+\\)" 2 font-lock-constant-face)))
105
"Basic font lock keywords for Go mode. Highlights keywords,
106
built-ins, functions, and some types.")
108
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
136
(defun go--build-font-lock-keywords ()
137
;; we cannot use 'symbols in regexp-opt because emacs <24 doesn't
140
`((,(go--regexp-enclose-in-symbol (regexp-opt go-mode-keywords t)) . font-lock-keyword-face)
141
(,(go--regexp-enclose-in-symbol (regexp-opt go-builtins t)) . font-lock-builtin-face)
142
(,(go--regexp-enclose-in-symbol (regexp-opt go-constants t)) . font-lock-constant-face)
143
(,go-func-regexp 1 font-lock-function-name-face)) ;; function (not method) name
145
(if go-fontify-function-calls
146
`((,(concat "\\(" go-identifier-regexp "\\)[[:space:]]*(") 1 font-lock-function-name-face) ;; function call/method name
147
(,(concat "(\\(" go-identifier-regexp "\\))[[:space:]]*(") 1 font-lock-function-name-face)) ;; bracketed function call
148
`((,go-func-meth-regexp 1 font-lock-function-name-face))) ;; method name
151
(,(concat (go--regexp-enclose-in-symbol "type") "[[:space:]]*\\([^[:space:]]+\\)") 1 font-lock-type-face) ;; types
152
(,(concat (go--regexp-enclose-in-symbol "type") "[[:space:]]*" go-identifier-regexp "[[:space:]]*" go-type-name-regexp) 1 font-lock-type-face) ;; types
153
(,(concat "[^[:word:][:multibyte:]]\\[\\([[:digit:]]+\\|\\.\\.\\.\\)?\\]" go-type-name-regexp) 2 font-lock-type-face) ;; Arrays/slices
154
(,(concat "\\(" go-identifier-regexp "\\)" "{") 1 font-lock-type-face)
155
(,(concat (go--regexp-enclose-in-symbol "map") "\\[[^]]+\\]" go-type-name-regexp) 1 font-lock-type-face) ;; map value type
156
(,(concat (go--regexp-enclose-in-symbol "map") "\\[" go-type-name-regexp) 1 font-lock-type-face) ;; map key type
157
(,(concat (go--regexp-enclose-in-symbol "chan") "[[:space:]]*\\(?:<-\\)?" go-type-name-regexp) 1 font-lock-type-face) ;; channel type
158
(,(concat (go--regexp-enclose-in-symbol "\\(?:new\\|make\\)") "\\(?:[[:space:]]\\|)\\)*(" go-type-name-regexp) 1 font-lock-type-face) ;; new/make type
159
;; TODO do we actually need this one or isn't it just a function call?
160
(,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face) ;; Type conversion
161
(,(concat (go--regexp-enclose-in-symbol "func") "[[:space:]]+(" go-identifier-regexp "[[:space:]]+" go-type-name-regexp ")") 1 font-lock-type-face) ;; Method receiver
162
;; Like the original go-mode this also marks compound literal
163
;; fields. There, it was marked as to fix, but I grew quite
164
;; accustomed to it, so it'll stay for now.
165
(,(concat "^[[:space:]]*\\(" go-label-regexp "\\)[[:space:]]*:\\(\\S.\\|$\\)") 1 font-lock-constant-face) ;; Labels and compound literal fields
166
(,(concat (go--regexp-enclose-in-symbol "\\(goto\\|break\\|continue\\)") "[[:space:]]*\\(" go-label-regexp "\\)") 2 font-lock-constant-face)))) ;; labels in goto/break/continue
112
168
(defvar go-mode-map
113
169
(let ((m (make-sparse-keymap)))
114
(define-key m "}" #'go-mode-insert-and-indent)
115
(define-key m ")" #'go-mode-insert-and-indent)
116
(define-key m "," #'go-mode-insert-and-indent)
117
(define-key m ":" #'go-mode-delayed-electric)
118
;; In case we get : indentation wrong, correct ourselves
119
(define-key m "=" #'go-mode-insert-and-indent)
170
(define-key m "}" 'go-mode-insert-and-indent)
171
(define-key m ")" 'go-mode-insert-and-indent)
172
(define-key m "," 'go-mode-insert-and-indent)
173
(define-key m ":" 'go-mode-insert-and-indent)
174
(define-key m "=" 'go-mode-insert-and-indent)
175
(define-key m (kbd "C-c C-a") 'go-import-add)
176
(define-key m (kbd "C-c C-j") 'godef-jump)
177
(define-key m (kbd "C-c C-d") 'godef-describe)
121
179
"Keymap used by Go mode to implement electric keys.")
127
185
(call-interactively (lookup-key (current-global-map) key))
128
186
(indent-according-to-mode))
130
(defvar go-mode-delayed-point nil
131
"The point following the previous insertion if the insertion
132
was a delayed electric key. Used to communicate between
133
`go-mode-delayed-electric' and `go-mode-delayed-electric-hook'.")
134
(make-variable-buffer-local 'go-mode-delayed-point)
136
(defun go-mode-delayed-electric (p)
137
"Perform electric insertion, but delayed by one event.
139
This inserts P into the buffer, as usual, then waits for another key.
140
If that second key causes a buffer modification starting at the
141
point after the insertion of P, reindents the line containing P."
144
(self-insert-command p)
145
(setq go-mode-delayed-point (point)))
147
(defun go-mode-delayed-electric-hook (b e l)
148
"An after-change-function that implements `go-mode-delayed-electric'."
150
(when (and go-mode-delayed-point
151
(= go-mode-delayed-point b))
154
(goto-char go-mode-delayed-point)
155
(indent-according-to-mode))))
156
(setq go-mode-delayed-point nil))
158
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
162
(defvar go-mode-mark-cs-end 1
163
"The point at which the comment/string cache ends. The buffer
164
will be marked from the beginning up to this point (that is, up
165
to and including character (1- go-mode-mark-cs-end)).")
166
(make-variable-buffer-local 'go-mode-mark-cs-end)
168
(defvar go-mode-mark-string-end 1
169
"The point at which the string cache ends. The buffer
170
will be marked from the beginning up to this point (that is, up
171
to and including character (1- go-mode-mark-string-end)).")
172
(make-variable-buffer-local 'go-mode-mark-string-end)
174
(defvar go-mode-mark-comment-end 1
175
"The point at which the comment cache ends. The buffer
176
will be marked from the beginning up to this point (that is, up
177
to and including character (1- go-mode-mark-comment-end)).")
178
(make-variable-buffer-local 'go-mode-mark-comment-end)
180
(defvar go-mode-mark-nesting-end 1
181
"The point at which the nesting cache ends. The buffer will be
182
marked from the beginning up to this point.")
183
(make-variable-buffer-local 'go-mode-mark-nesting-end)
185
(defun go-mode-mark-clear-cs (b e l)
186
"An after-change-function that removes the go-mode-cs text property"
187
(remove-text-properties b e '(go-mode-cs)))
189
(defun go-mode-mark-clear-cache (b e)
190
"A before-change-function that clears the comment/string and
191
nesting caches from the modified point on."
195
(when (<= b go-mode-mark-cs-end)
196
;; Remove the property adjacent to the change position.
197
;; It may contain positions pointing beyond the new end mark.
198
(let ((b (let ((cs (get-text-property (max 1 (1- b)) 'go-mode-cs)))
199
(if cs (car cs) b))))
200
(remove-text-properties
201
b (min go-mode-mark-cs-end (point-max)) '(go-mode-cs nil))
202
(setq go-mode-mark-cs-end b)))
204
(when (<= b go-mode-mark-string-end)
205
;; Remove the property adjacent to the change position.
206
;; It may contain positions pointing beyond the new end mark.
207
(let ((b (let ((cs (get-text-property (max 1 (1- b)) 'go-mode-string)))
208
(if cs (car cs) b))))
209
(remove-text-properties
210
b (min go-mode-mark-string-end (point-max)) '(go-mode-string nil))
211
(setq go-mode-mark-string-end b)))
212
(when (<= b go-mode-mark-comment-end)
213
;; Remove the property adjacent to the change position.
214
;; It may contain positions pointing beyond the new end mark.
215
(let ((b (let ((cs (get-text-property (max 1 (1- b)) 'go-mode-comment)))
216
(if cs (car cs) b))))
217
(remove-text-properties
218
b (min go-mode-mark-string-end (point-max)) '(go-mode-comment nil))
219
(setq go-mode-mark-comment-end b)))
221
(when (< b go-mode-mark-nesting-end)
222
(remove-text-properties b (min go-mode-mark-nesting-end (point-max)) '(go-mode-nesting nil))
223
(setq go-mode-mark-nesting-end b))))
225
(defmacro go-mode-parser (&rest body)
226
"Evaluate BODY in an environment set up for parsers that use
227
text properties to mark text. This inhibits changes to the undo
228
list or the buffer's modification status and inhibits calls to
229
the modification hooks. It also saves the excursion and
230
restriction and widens the buffer, since most parsers are
233
(let ((modified-var (make-symbol "modified")))
234
`(let ((buffer-undo-list t)
235
(,modified-var (buffer-modified-p))
236
(inhibit-modification-hooks t)
237
(inhibit-read-only t))
243
(set-buffer-modified-p ,modified-var)))))))
245
(defun go-mode-cs (&optional pos)
246
"Return the comment/string state at point POS. If point is
247
inside a comment or string (including the delimiters), this
248
returns a pair (START . END) indicating the extents of the
253
(when (>= pos go-mode-mark-cs-end)
254
(go-mode-mark-cs (1+ pos)))
255
(get-text-property pos 'go-mode-cs))
257
(defun go-mode-mark-cs (end)
258
"Mark comments and strings up to point END. Don't call this
259
directly; use `go-mode-cs'."
260
(setq end (min end (point-max)))
264
;; Back up to the last known state.
266
(and (> go-mode-mark-cs-end 1)
267
(get-text-property (1- go-mode-mark-cs-end)
271
(max 1 (1- go-mode-mark-cs-end))))))
274
(let ((cs-end ; end of the text property
280
(goto-char (+ pos 2))
281
(if (search-forward "*/" (1+ end) t)
286
(if (looking-at "[^\"\n\\\\]*\\(\\\\.[^\"\n\\\\]*\\)*\"")
292
(if (looking-at "[^'\n\\\\]*\\(\\\\.[^'\n\\\\]*\\)*'")
298
(while (if (search-forward "`" end t)
299
(if (eq (char-after) ?`)
300
(goto-char (1+ (point))))
306
(put-text-property pos cs-end 'go-mode-cs (cons pos cs-end))
308
((re-search-forward "[\"'`]\\|/[/*]" end t)
309
(setq pos (match-beginning 0)))
312
(setq go-mode-mark-cs-end pos)))))
314
(defun go-mode-in-comment (&optional pos)
315
"Return the comment/string state at point POS. If point is
316
inside a comment (including the delimiters), this
317
returns a pair (START . END) indicating the extents of the
322
(when (> pos go-mode-mark-comment-end)
323
(go-mode-mark-comment pos))
324
(get-text-property pos 'go-mode-comment))
326
(defun go-mode-mark-comment (end)
327
"Mark comments up to point END. Don't call this directly; use `go-mode-in-comment'."
328
(setq end (min end (point-max)))
332
;; Back up to the last known state.
334
(and (> go-mode-mark-comment-end 1)
335
(get-text-property (1- go-mode-mark-comment-end)
339
(max 1 (1- go-mode-mark-comment-end))))))
342
(let ((comment-end ; end of the text property
348
(goto-char (+ pos 2))
349
(if (search-forward "*/" (1+ end) t)
354
(put-text-property pos comment-end 'go-mode-comment (cons pos comment-end))
355
(setq pos comment-end))
356
((re-search-forward "/[/*]" end t)
357
(setq pos (match-beginning 0)))
360
(setq go-mode-mark-comment-end pos)))))
362
(defun go-mode-in-string (&optional pos)
363
"Return the string state at point POS. If point is
364
inside a string (including the delimiters), this
365
returns a pair (START . END) indicating the extents of the
370
(when (> pos go-mode-mark-string-end)
371
(go-mode-mark-string pos))
372
(get-text-property pos 'go-mode-string))
374
(defun go-mode-mark-string (end)
375
"Mark strings up to point END. Don't call this
376
directly; use `go-mode-in-string'."
377
(setq end (min end (point-max)))
381
;; Back up to the last known state.
383
(and (> go-mode-mark-string-end 1)
384
(get-text-property (1- go-mode-mark-string-end)
388
(max 1 (1- go-mode-mark-string-end))))))
391
(let ((cs-end ; end of the text property
395
(if (looking-at "[^\"\n\\\\]*\\(\\\\.[^\"\n\\\\]*\\)*\"")
401
(if (looking-at "[^'\n\\\\]*\\(\\\\.[^'\n\\\\]*\\)*'")
407
(while (if (search-forward "`" end t)
408
(if (eq (char-after) ?`)
409
(goto-char (1+ (point))))
415
(put-text-property pos cs-end 'go-mode-string (cons pos cs-end))
417
((re-search-forward "[\"'`]" end t)
418
(setq pos (match-beginning 0)))
421
(setq go-mode-mark-string-end pos)))))
423
(defun go-mode-font-lock-cs (limit comment)
424
"Helper function for highlighting comment/strings. If COMMENT is t,
425
set match data to the next comment after point, and advance point
426
after it. If COMMENT is nil, use the next string. Returns nil
427
if no further tokens of the type exist."
428
;; Ensures that `next-single-property-change' below will work properly.
430
(let (cs next (result 'scan))
431
(while (eq result 'scan)
432
(if (or (>= (point) limit) (eobp))
434
(setq cs (go-mode-cs))
436
(if (eq (= (char-after (car cs)) ?/) comment)
437
;; If inside the expected comment/string, highlight it.
439
;; If the match includes a "\n", we have a
440
;; multi-line construct. Mark it as such.
442
(when (search-forward "\n" (cdr cs) t)
444
(car cs) (cdr cs) 'font-lock-multline t))
445
(set-match-data (list (car cs) (copy-marker (cdr cs))))
448
;; Wrong type. Look for next comment/string after this one.
449
(goto-char (cdr cs)))
450
;; Not inside comment/string. Search for next comment/string.
451
(setq next (next-single-property-change
452
(point) 'go-mode-cs nil limit))
453
(if (and next (< next limit))
455
(setq result nil)))))
458
(defun go-mode-font-lock-cs-string (limit)
459
"Font-lock iterator for strings."
460
(go-mode-font-lock-cs limit nil))
462
(defun go-mode-font-lock-cs-comment (limit)
463
"Font-lock iterator for comments."
464
(go-mode-font-lock-cs limit t))
466
(defsubst go-mode-nesting (&optional pos)
467
"Return the nesting at point POS. The nesting is a list
468
of (START . END) pairs for all braces, parens, and brackets
469
surrounding POS, starting at the inner-most nesting. START is
470
the location of the open character. END is the location of the
471
close character or nil if the nesting scanner has not yet
472
encountered the close character."
478
(when (> pos go-mode-mark-nesting-end)
479
(go-mode-mark-nesting pos))
480
(get-text-property (- pos 1) 'go-mode-nesting)))
482
(defun go-mode-mark-nesting (pos)
483
"Mark nesting up to point END. Don't call this directly; use
489
(goto-char go-mode-mark-nesting-end)
490
(let ((nesting (go-mode-nesting))
493
;; Find the next depth-changing character
494
(skip-chars-forward "^(){}[]" pos)
495
;; Mark everything up to this character with the current
497
(put-text-property last (point) 'go-mode-nesting nesting)
499
(let ((depth (length nesting)))
500
(put-text-property last (point) 'face
502
,(format "gray%d" (* depth 10)))))))
506
(let ((ch (unless (go-mode-cs) (char-after))))
510
(setq nesting (cons (cons (- (point) 1) nil)
514
(setcdr (car nesting) (- (point) 1))
515
(setq nesting (cdr nesting))))))))
517
(setq go-mode-mark-nesting-end last))))
519
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
523
(defvar go-mode-non-terminating-keywords-regexp
524
(let* ((kws go-mode-keywords)
525
(kws (remove "break" kws))
526
(kws (remove "continue" kws))
527
(kws (remove "fallthrough" kws))
528
(kws (remove "return" kws)))
529
(regexp-opt kws 'words))
530
"Regular expression matching all Go keywords that *do not*
531
implicitly terminate a statement.")
533
(defun go-mode-semicolon-p ()
534
"True iff point immediately follows either an explicit or
535
implicit semicolon. Point should immediately follow the last
543
;; One of the operators and delimiters ++, --, ), ], or }
544
((?+) (eq (char-before (1- (point))) ?+))
545
((?-) (eq (char-before (1- (point))) ?-))
547
;; An identifier or one of the keywords break, continue,
548
;; fallthrough, or return or a numeric literal
551
(when (/= (skip-chars-backward "[:word:]_") 0)
552
(not (looking-at go-mode-non-terminating-keywords-regexp)))))))
554
(defun go-mode-whitespace-p (char)
555
"Is newline, or char whitespace in the syntax table for go."
557
(= (char-syntax char) ?\ )))
559
(defun go-mode-backward-skip-comments ()
560
"Skip backward over comments and whitespace."
561
;; only proceed if point is in a comment or white space
562
(if (or (go-mode-in-comment)
563
(go-mode-whitespace-p (char-after (point))))
564
(let ((loop-guard t))
569
(cond ((go-mode-whitespace-p (char-after (point)))
570
;; moves point back over any whitespace
571
(re-search-backward "[^[:space:]]"))
573
((go-mode-in-comment)
574
;; move point to char preceeding current comment
575
(goto-char (1- (car (go-mode-in-comment)))))
577
;; not in a comment or whitespace? we must be done.
578
(t (setq loop-guard nil)
579
(forward-char 1)))))))
581
(defun go-mode-indentation ()
582
"Compute the ideal indentation level of the current line.
584
To the first order, this is the brace depth of the current line,
585
plus parens that follow certain keywords. case, default, and
586
labels are outdented one level, and continuation lines are
590
(back-to-indentation)
591
(let ((cs (go-mode-cs)))
592
;; Treat comments and strings differently only if the beginning
593
;; of the line is contained within them
594
(when (and cs (= (point) (car cs)))
596
;; What type of context am I in?
188
(defmacro go-paren-level ()
189
`(car (syntax-ppss)))
191
(defmacro go-in-string-or-comment-p ()
192
`(nth 8 (syntax-ppss)))
194
(defmacro go-in-string-p ()
195
`(nth 3 (syntax-ppss)))
197
(defmacro go-in-comment-p ()
198
`(nth 4 (syntax-ppss)))
200
(defmacro go-goto-beginning-of-string-or-comment ()
201
`(goto-char (nth 8 (syntax-ppss))))
203
(defun go--backward-irrelevant (&optional stop-at-string)
204
"Skips backwards over any characters that are irrelevant for
205
indentation and related tasks.
207
It skips over whitespace, comments, cases and labels and, if
208
STOP-AT-STRING is not true, over strings."
210
(let (pos (start-pos (point)))
211
(skip-chars-backward "\n\s\t")
212
(if (and (save-excursion (beginning-of-line) (go-in-string-p)) (looking-back "`") (not stop-at-string))
214
(if (and (go-in-string-p) (not stop-at-string))
215
(go-goto-beginning-of-string-or-comment))
216
(if (looking-back "\\*/")
218
(if (go-in-comment-p)
219
(go-goto-beginning-of-string-or-comment))
222
(if (or (looking-at (concat "^" go-label-regexp ":")) (looking-at "^[[:space:]]*\\(case .+\\|default\\):"))
225
(if (/= start-pos (point))
226
(go--backward-irrelevant stop-at-string))
227
(/= start-pos (point))))
229
(defun go--buffer-narrowed-p ()
230
"Return non-nil if the current buffer is narrowed."
235
(defun go-previous-line-has-dangling-op-p ()
236
"Returns non-nil if the current line is a continuation line."
237
(let* ((cur-line (line-number-at-pos))
238
(val (gethash cur-line go-dangling-cache 'nope)))
239
(if (or (go--buffer-narrowed-p) (equal val 'nope))
242
(go--backward-irrelevant t)
243
(setq val (looking-back go-dangling-operators-regexp))
244
(if (not (go--buffer-narrowed-p))
245
(puthash cur-line val go-dangling-cache))))
248
(defun go--at-function-definition ()
249
"Return non-nil if point is on the opening curly brace of a
252
We do this by first calling (beginning-of-defun), which will take
253
us to the start of *some* function. We then look for the opening
254
curly brace of that function and compare its position against the
255
curly brace we are checking. If they match, we return non-nil."
256
(if (= (char-after) ?\{)
258
(let ((old-point (point))
261
(when (looking-at "func ")
262
(setq start-nesting (go-paren-level))
263
(skip-chars-forward "^{")
264
(while (> (go-paren-level) start-nesting)
266
(skip-chars-forward "^{") 0)
267
(if (and (= (go-paren-level) start-nesting) (= old-point (point)))
270
(defun go-goto-opening-parenthesis (&optional char)
271
(let ((start-nesting (go-paren-level)))
272
(while (and (not (bobp))
273
(>= (go-paren-level) start-nesting))
274
(if (zerop (skip-chars-backward
276
(case char (?\] "^[") (?\} "^{") (?\) "^("))
278
(if (go-in-string-or-comment-p)
279
(go-goto-beginning-of-string-or-comment)
282
(defun go--indentation-for-opening-parenthesis ()
283
"Return the semantic indentation for the current opening parenthesis.
285
If point is on an opening curly brace and said curly brace
286
belongs to a function declaration, the indentation of the func
287
keyword will be returned. Otherwise the indentation of the
288
current line will be returned."
290
(if (go--at-function-definition)
293
(current-indentation))
294
(current-indentation))))
296
(defun go-indentation-at-point ()
298
(let (start-nesting (outindent 0))
299
(back-to-indentation)
300
(setq start-nesting (go-paren-level))
598
((and cs (save-excursion
601
;; Inside a multi-line string. Don't mess with indentation.
604
;; Inside a general comment
304
(current-indentation))
305
((looking-at "[])}]")
306
(go-goto-opening-parenthesis (char-after))
307
(if (go-previous-line-has-dangling-op-p)
308
(- (current-indentation) tab-width)
309
(go--indentation-for-opening-parenthesis)))
310
((progn (go--backward-irrelevant t) (looking-back go-dangling-operators-regexp))
311
;; only one nesting for all dangling operators in one operation
312
(if (go-previous-line-has-dangling-op-p)
313
(current-indentation)
314
(+ (current-indentation) tab-width)))
315
((zerop (go-paren-level))
317
((progn (go-goto-opening-parenthesis) (< (go-paren-level) start-nesting))
318
(if (go-previous-line-has-dangling-op-p)
319
(current-indentation)
320
(+ (go--indentation-for-opening-parenthesis) tab-width)))
609
;; Not in a multi-line string or comment
611
(inside-indenting-paren nil))
612
;; Count every enclosing brace, plus parens that follow
613
;; import, const, var, or type and indent according to
614
;; depth. This simple rule does quite well, but also has a
615
;; very large extent. It would be better if we could mimic
616
;; some nearby indentation.
618
(skip-chars-forward "})")
620
(dolist (nest (go-mode-nesting))
621
(case (char-after (car nest))
623
(incf indent tab-width))
625
(goto-char (car nest))
626
(go-mode-backward-skip-comments)
628
;; Really just want the token before
629
(when (looking-back "\\<import\\|const\\|var\\|type\\|package"
630
(max (- (point) 7) (point-min)))
631
(incf indent tab-width)
633
(setq inside-indenting-paren t)))))
636
;; case, default, and labels are outdented 1 level
637
(when (looking-at "\\<case\\>\\|\\<default\\>\\|\\w+\\s *:\\(\\S.\\|$\\)")
638
(decf indent tab-width))
640
(when (looking-at "\\w+\\s *:.+,\\s *$")
641
(incf indent tab-width))
643
;; Continuation lines are indented 1 level
644
(beginning-of-line) ; back up to end of previous line
646
(go-mode-backward-skip-comments) ; back up past any comments
647
(when (case (char-before)
649
;; At the beginning of a block or the statement
650
;; following a label.
653
;; Usually a continuation line in an expression,
654
;; unless this paren is part of a factored
656
(not inside-indenting-paren))
658
;; Could be inside a literal. We're a little
659
;; conservative here and consider any comma within
660
;; curly braces (as opposed to parens) to be a
661
;; literal separator. This will fail to recognize
662
;; line-breaks in parallel assignments as
663
;; continuation lines.
664
(let ((depth (go-mode-nesting)))
666
(not (eq (char-after (caar depth)) ?\{)))))
668
;; We're in the middle of a block. Did the
669
;; previous line end with an implicit or explicit
671
(not (go-mode-semicolon-p))))
672
(incf indent tab-width))
322
(current-indentation))))))
676
324
(defun go-mode-indent-line ()
677
"Indent the current line according to `go-mode-indentation'."
680
;; turn off case folding to distinguish keywords from identifiers
681
;; e.g. "default" is a keyword; "Default" can be a variable name.
682
(let ((case-fold-search nil))
683
(let ((col (go-mode-indentation)))
685
(let ((offset (- (current-column) (current-indentation))))
688
(forward-char offset)))))))
690
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
329
(pos (- (point-max) (point)))
331
(beg (line-beginning-position)))
332
(back-to-indentation)
333
(if (go-in-string-or-comment-p)
335
(setq indent (go-indentation-at-point))
336
(if (looking-at (concat go-label-regexp ":\\([[:space:]]*/.+\\)?$\\|case .+:\\|default:"))
337
(decf indent tab-width))
338
(setq shift-amt (- indent (current-column)))
339
(if (zerop shift-amt)
341
(delete-region beg (point))
343
;; If initial point was within line's indentation,
344
;; position after the indentation. Else stay at same point in text.
345
(if (> (- (point-max) pos) (point))
346
(goto-char (- (point-max) pos))))))
348
(defun go-beginning-of-defun (&optional count)
349
(unless count (setq count 1))
350
(let ((first t) failure)
351
(dotimes (i (abs count))
352
(while (and (not failure)
353
(or first (go-in-string-or-comment-p)))
356
(go--backward-irrelevant)
357
(if (not (re-search-backward go-func-meth-regexp nil t))
359
(if (looking-at go-func-meth-regexp)
361
(if (not (re-search-forward go-func-meth-regexp nil t))
368
(defun go-end-of-defun ()
370
;; It can happen that we're not placed before a function by emacs
371
(if (not (looking-at "func"))
372
(go-beginning-of-defun -1))
373
(skip-chars-forward "^{")
375
(setq orig-level (go-paren-level))
376
(while (>= (go-paren-level) orig-level)
377
(skip-chars-forward "^}")
695
(define-derived-mode go-mode nil "Go"
381
(define-derived-mode go-mode prog-mode "Go"
696
382
"Major mode for editing Go source text.
698
This provides basic syntax highlighting for keywords, built-ins,
699
functions, and some types. It also provides indentation that is
700
\(almost) identical to gofmt."
384
This mode provides (not just) basic editing capabilities for
385
working with Go code. It offers almost complete syntax
386
highlighting, indentation that is almost identical to gofmt and
387
proper parsing of the buffer content to allow features such as
388
navigation by function, manipulation of comments or detection of
391
In addition to these core features, it offers various features to
392
help with writing Go code. You can directly run buffer content
393
through gofmt, read godoc documentation from within Emacs, modify
394
and clean up the list of package imports or interact with the
395
Playground (uploading and downloading pastes).
397
The following extra functions are defined:
402
- `go-remove-unused-imports'
404
- `go-play-buffer' and `go-play-region'
406
- `godef-describe' and `godef-jump'
408
If you want to automatically run `gofmt' before saving a file,
409
add the following hook to your emacs configuration:
411
\(add-hook 'before-save-hook 'gofmt-before-save)
413
If you want to use `godef-jump' instead of etags (or similar),
414
consider binding godef-jump to `M-.', which is the default key
417
\(add-hook 'go-mode-hook (lambda ()
418
(local-set-key (kbd \"M-.\") 'godef-jump)))
420
Please note that godef is an external dependency. You can install
423
go get code.google.com/p/rog-go/exp/cmd/godef
426
If you're looking for even more integration with Go, namely
427
on-the-fly syntax checking, auto-completion and snippets, it is
428
recommended that you look at goflymake
429
\(https://github.com/dougm/goflymake), gocode
430
\(https://github.com/nsf/gocode) and yasnippet-go
431
\(https://github.com/dominikh/yasnippet-go)"
703
434
(set (make-local-variable 'font-lock-defaults)
704
'(go-mode-font-lock-keywords nil nil nil nil))
706
;; Remove stale text properties
709
(let ((modified (buffer-modified-p)))
710
(remove-text-properties 1 (point-max)
711
'(go-mode-cs nil go-mode-nesting nil))
712
;; remove-text-properties marks the buffer modified. undo that if it
713
;; wasn't originally marked modified.
714
(set-buffer-modified-p modified)))
716
;; Reset the syntax mark caches
717
(setq go-mode-mark-cs-end 1
718
go-mode-mark-nesting-end 1)
719
(add-hook 'before-change-functions #'go-mode-mark-clear-cache nil t)
720
(add-hook 'after-change-functions #'go-mode-mark-clear-cs nil t)
435
'(go--build-font-lock-keywords))
723
(set (make-local-variable 'indent-line-function)
724
#'go-mode-indent-line)
725
(add-hook 'after-change-functions #'go-mode-delayed-electric-hook nil t)
438
(set (make-local-variable 'indent-line-function) 'go-mode-indent-line)
728
441
(set (make-local-variable 'comment-start) "// ")
729
442
(set (make-local-variable 'comment-end) "")
443
(set (make-local-variable 'comment-use-syntax) t)
444
(set (make-local-variable 'comment-start-skip) "\\(//+\\|/\\*+\\)\\s *")
446
(set (make-local-variable 'beginning-of-defun-function) 'go-beginning-of-defun)
447
(set (make-local-variable 'end-of-defun-function) 'go-end-of-defun)
449
(set (make-local-variable 'parse-sexp-lookup-properties) t)
450
(if (boundp 'syntax-propertize-function)
451
(set (make-local-variable 'syntax-propertize-function) 'go-propertize-syntax))
453
(set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql))
454
(add-hook 'before-change-functions (lambda (x y) (setq go-dangling-cache (make-hash-table :test 'eql))) t t)
457
(setq imenu-generic-expression
458
'(("type" "^type *\\([^ \t\n\r\f]*\\)" 1)
459
("func" "^func *\\(.*\\) {" 1)))
460
(imenu-add-to-menubar "Index")
732
463
(setq indent-tabs-mode t)
739
470
;; those alists are traversed in *reverse* order:
740
471
;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2001-12/msg00674.html
741
472
(when (and (boundp 'compilation-error-regexp-alist)
742
(boundp 'compilation-error-regexp-alist-alist))
743
(add-to-list 'compilation-error-regexp-alist 'go-test t)
744
(add-to-list 'compilation-error-regexp-alist-alist
745
'(go-test . ("^\t+\\([^()\t\n]+\\):\\([0-9]+\\):? .*$" 1 2)) t)))
748
(add-to-list 'auto-mode-alist (cons "\\.go$" #'go-mode))
750
(defun go-mode-reload ()
751
"Reload go-mode.el and put the current buffer into Go mode.
752
Useful for development work."
755
(unload-feature 'go-mode)
473
(boundp 'compilation-error-regexp-alist-alist))
474
(add-to-list 'compilation-error-regexp-alist 'go-test t)
475
(add-to-list 'compilation-error-regexp-alist-alist
476
'(go-test . ("^\t+\\([^()\t\n]+\\):\\([0-9]+\\):? .*$" 1 2)) t)))
479
(add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode))
481
(defun go--apply-rcs-patch (patch-buffer)
482
"Apply an RCS-formatted diff from PATCH-BUFFER to the current
484
(let ((target-buffer (current-buffer))
485
;; Relative offset between buffer line numbers and line numbers
488
;; Line numbers in the patch are based on the source file, so
489
;; we have to keep an offset when making changes to the
492
;; Appending lines decrements the offset (possibly making it
493
;; negative), deleting lines increments it. This order
494
;; simplifies the forward-line invocations.
497
(with-current-buffer patch-buffer
498
(goto-char (point-min))
500
(unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
501
(error "invalid rcs patch or internal error in go--apply-rcs-patch"))
503
(let ((action (match-string 1))
504
(from (string-to-number (match-string 2)))
505
(len (string-to-number (match-string 3))))
508
(let ((start (point)))
510
(let ((text (buffer-substring start (point))))
511
(with-current-buffer target-buffer
512
(decf line-offset len)
513
(goto-char (point-min))
514
(forward-line (- from len line-offset))
517
(with-current-buffer target-buffer
518
(goto-char (point-min))
519
(forward-line (- from line-offset 1))
520
(incf line-offset len)
521
(go--kill-whole-line len)))
523
(error "invalid rcs patch or internal error in go--apply-rcs-patch")))))))))
761
"Pipe the current buffer through the external tool `gofmt`.
762
Replace the current buffer on success; display errors on failure."
526
"Formats the current buffer according to the gofmt tool."
765
(let ((currconf (current-window-configuration)))
766
(let ((srcbuf (current-buffer))
767
(filename buffer-file-name)
768
(patchbuf (get-buffer-create "*Gofmt patch*")))
769
(with-current-buffer patchbuf
770
(let ((errbuf (get-buffer-create "*Gofmt Errors*"))
771
(coding-system-for-read 'utf-8) ;; use utf-8 with subprocesses
772
(coding-system-for-write 'utf-8))
773
(with-current-buffer errbuf
776
(with-current-buffer srcbuf
778
(let (deactivate-mark)
780
; If this is a new file, diff-mode can't apply a
781
; patch to a non-exisiting file, so replace the buffer
782
; completely with the output of 'gofmt'.
783
; If the file exists, patch it to keep the 'undo' list happy.
784
(let* ((newfile (not (file-exists-p filename)))
785
(flag (if newfile "" " -d")))
786
(if (= 0 (shell-command-on-region (point-min) (point-max)
787
(concat "gofmt" flag)
788
patchbuf nil errbuf))
789
; gofmt succeeded: replace buffer or apply patch hunks.
790
(let ((old-point (point))
794
; New file, replace it (diff-mode won't work)
795
(gofmt-replace-buffer srcbuf patchbuf)
796
; Existing file, patch it
797
(gofmt-apply-patch filename srcbuf patchbuf))
798
(goto-char (min old-point (point-max)))
799
;; Restore the mark and point
800
(if old-mark (push-mark (min old-mark (point-max)) t))
801
(set-window-configuration currconf))
803
;; gofmt failed: display the errors
804
(gofmt-process-errors filename errbuf))))))
806
;; Collapse any window opened on outbuf if shell-command-on-region
808
(delete-windows-on patchbuf)))
809
(kill-buffer patchbuf))))
811
(defun gofmt-replace-buffer (srcbuf patchbuf)
812
(with-current-buffer srcbuf
814
(insert-buffer-substring patchbuf)))
816
(defconst gofmt-stdin-tag "<standard input>")
818
(defun gofmt-apply-patch (filename srcbuf patchbuf)
820
;; apply all the patch hunks
821
(with-current-buffer patchbuf
822
(replace-regexp "^--- /tmp/gofmt[0-9]*" (concat "--- " filename)
823
nil (point-min) (point-max))
828
;; When there's no more hunks, diff-hunk-next signals an error, ignore it
831
(defun gofmt-process-errors (filename errbuf)
529
(let ((tmpfile (make-temp-file "gofmt" nil ".go"))
530
(patchbuf (get-buffer-create "*Gofmt patch*"))
531
(errbuf (get-buffer-create "*Gofmt Errors*"))
532
(coding-system-for-read 'utf-8)
533
(coding-system-for-write 'utf-8))
535
(with-current-buffer errbuf
536
(setq buffer-read-only nil)
538
(with-current-buffer patchbuf
541
(write-region nil nil tmpfile)
543
;; We're using errbuf for the mixed stdout and stderr output. This
544
;; is not an issue because gofmt -w does not produce any stdout
545
;; output in case of success.
546
(if (zerop (call-process "gofmt" nil errbuf nil "-w" tmpfile))
547
(if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
550
(message "Buffer is already gofmted"))
551
(go--apply-rcs-patch patchbuf)
553
(message "Applied gofmt"))
554
(message "Could not apply gofmt. Check errors for details")
555
(gofmt--process-errors (buffer-file-name) tmpfile errbuf))
557
(kill-buffer patchbuf)
558
(delete-file tmpfile)))
561
(defun gofmt--process-errors (filename tmpfile errbuf)
832
562
;; Convert the gofmt stderr to something understood by the compilation mode.
833
563
(with-current-buffer errbuf
834
(beginning-of-buffer)
564
(goto-char (point-min))
835
565
(insert "gofmt errors:\n")
836
(replace-string gofmt-stdin-tag (file-name-nondirectory filename) nil (point-min) (point-max))
837
(display-buffer errbuf)
566
(while (search-forward-regexp (concat "^\\(" (regexp-quote tmpfile) "\\):") nil t)
567
(replace-match (file-name-nondirectory filename) t t nil 1))
569
(display-buffer errbuf)))
841
572
(defun gofmt-before-save ()
842
573
"Add this to .emacs to run gofmt on the current buffer when saving:
843
(add-hook 'before-save-hook #'gofmt-before-save)"
574
(add-hook 'before-save-hook 'gofmt-before-save).
576
Note that this will cause go-mode to get loaded the first time
577
you save any file, kind of defeating the point of autoloading."
846
580
(when (eq major-mode 'go-mode) (gofmt)))
848
(defun godoc-read-query ()
582
(defun godoc--read-query ()
849
583
"Read a godoc query from the minibuffer."
850
584
;; Compute the default query as the symbol under the cursor.
851
585
;; TODO: This does the wrong thing for e.g. multipart.NewReader (it only grabs
882
617
(defun godoc (query)
883
618
"Show go documentation for a query, much like M-x man."
884
(interactive (list (godoc-read-query)))
619
(interactive (list (godoc--read-query)))
885
620
(unless (string= query "")
886
621
(set-process-sentinel
887
(start-process-shell-command "godoc" (godoc-get-buffer query)
622
(start-process-shell-command "godoc" (godoc--get-buffer query)
888
623
(concat "godoc " query))
889
'godoc-buffer-sentinel)
624
'godoc--buffer-sentinel)
627
(defun go-goto-imports ()
628
"Move point to the block of imports.
637
it will move point directly behind the last import.
644
it will move point to the next line after the last import.
646
If no imports can be found, point will be moved after the package
649
;; FIXME if there's a block-commented import before the real
650
;; imports, we'll jump to that one.
652
;; Generally, this function isn't very forgiving. it'll bark on
653
;; extra whitespace. It works well for clean code.
654
(let ((old-point (point)))
655
(goto-char (point-min))
657
((re-search-forward "^import ([^)]+)" nil t)
660
((re-search-forward "\\(^import \\([^\"]+ \\)?\"[^\"]+\"\n?\\)+" nil t)
662
((re-search-forward "^[[:space:]\n]*package .+?\n" nil t)
663
(message "No imports found, moving point after package declaration")
666
(goto-char old-point)
667
(message "No imports or package declaration found. Is this really a Go file?")
670
(defun go-play-buffer ()
671
"Like `go-play-region', but acts on the entire buffer."
673
(go-play-region (point-min) (point-max)))
675
(defun go-play-region (start end)
676
"Send the region to the Playground and stores the resulting
677
link in the kill ring."
679
(let* ((url-request-method "POST")
680
(url-request-extra-headers
681
'(("Content-Type" . "application/x-www-form-urlencoded")))
682
(url-request-data (buffer-substring-no-properties start end))
683
(content-buf (url-retrieve
684
"http://play.golang.org/share"
687
((equal :error (car arg))
688
(signal 'go-play-error (cdr arg)))
690
(re-search-forward "\n\n")
691
(kill-new (format "http://play.golang.org/p/%s" (buffer-substring (point) (point-max))))
692
(message "http://play.golang.org/p/%s" (buffer-substring (point) (point-max)))))))))))
695
(defun go-download-play (url)
696
"Downloads a paste from the playground and inserts it in a Go
697
buffer. Tries to look for a URL at point."
698
(interactive (list (read-from-minibuffer "Playground URL: " (ffap-url-p (ffap-string-at-point 'url)))))
700
(let ((url-request-method "GET") url-request-data url-request-extra-headers)
701
(url-retrieve-synchronously (concat url ".go")))
702
(let ((buffer (generate-new-buffer (concat (car (last (split-string url "/"))) ".go"))))
703
(goto-char (point-min))
704
(re-search-forward "\n\n")
705
(copy-to-buffer buffer (point) (point-max))
707
(with-current-buffer buffer
709
(switch-to-buffer buffer)))))
711
(defun go-propertize-syntax (start end)
714
(while (search-forward "\\" end t)
715
(put-text-property (1- (point)) (point) 'syntax-table (if (= (char-after) ?`) '(1) '(9))))))
717
;; ;; Commented until we actually make use of this function
718
;; (defun go--common-prefix (sequences)
719
;; ;; mismatch and reduce are cl
720
;; (assert sequences)
721
;; (flet ((common-prefix (s1 s2)
722
;; (let ((diff-pos (mismatch s1 s2)))
723
;; (if diff-pos (subseq s1 0 diff-pos) s1))))
724
;; (reduce #'common-prefix sequences)))
726
(defun go-import-add (arg import)
727
"Add a new import to the list of imports.
729
When called with a prefix argument asks for an alternative name
730
to import the package as.
732
If no list exists yet, one will be created if possible.
734
If an identical import has been commented, it will be
735
uncommented, otherwise a new import will be added."
737
;; - If there's a matching `// import "foo"`, uncomment it
738
;; - If we're in an import() block and there's a matching `"foo"`, uncomment it
739
;; - Otherwise add a new import, with the appropriate syntax
743
(replace-regexp-in-string "^[\"']\\|[\"']$" "" (completing-read "Package: " (go--old-completion-list-style (go-packages))))))
745
(let (as line import-start)
747
(setq as (read-from-minibuffer "Import as: ")))
749
(setq line (format "%s \"%s\"" as import))
750
(setq line (format "\"%s\"" import)))
752
(goto-char (point-min))
753
(if (re-search-forward (concat "^[[:space:]]*//[[:space:]]*import " line "$") nil t)
754
(uncomment-region (line-beginning-position) (line-end-position))
755
(case (go-goto-imports)
756
('fail (message "Could not find a place to add import."))
759
(re-search-backward "^import (")
760
(setq import-start (point)))
761
(if (re-search-backward (concat "^[[:space:]]*//[[:space:]]*" line "$") import-start t)
762
(uncomment-region (line-beginning-position) (line-end-position))
763
(insert "\n\t" line)))
764
('single (insert "import " line "\n"))
765
('none (insert "\nimport (\n\t" line "\n)\n")))))))
767
(defun go-root-and-paths ()
768
(let* ((output (split-string (shell-command-to-string "go env GOROOT GOPATH") "\n"))
770
(paths (split-string (cadr output) ":")))
771
(append (list root) paths)))
773
(defun go--string-prefix-p (s1 s2 &optional ignore-case)
774
"Return non-nil if S1 is a prefix of S2.
775
If IGNORE-CASE is non-nil, the comparison is case-insensitive."
776
(eq t (compare-strings s1 nil nil
777
s2 0 (length s1) ignore-case)))
779
(defun go--directory-dirs (dir)
780
"Recursively return all subdirectories in DIR."
781
(if (file-directory-p dir)
782
(let ((dir (directory-file-name dir))
784
(files (directory-files dir nil nil t)))
786
(unless (member file '("." ".."))
787
(let ((file (concat dir "/" file)))
788
(if (file-directory-p file)
789
(setq dirs (append (cons file
790
(go--directory-dirs file))
796
(defun go-packages ()
801
(let ((pkgdir (concat topdir "/pkg/")))
802
(mapcan (lambda (dir)
803
(mapcar (lambda (file)
804
(let ((sub (substring file (length pkgdir) -2)))
805
(unless (or (go--string-prefix-p "obj/" sub) (go--string-prefix-p "tool/" sub))
806
(mapconcat 'identity (cdr (split-string sub "/")) "/"))))
807
(if (file-directory-p dir)
808
(directory-files dir t "\\.a$"))))
809
(if (file-directory-p pkgdir)
810
(go--directory-dirs pkgdir)))))
811
(go-root-and-paths)))
814
(defun go-unused-imports-lines ()
815
;; FIXME Technically, -o /dev/null fails in quite some cases (on
816
;; Windows, when compiling from within GOPATH). Practically,
817
;; however, it has the same end result: There won't be a
818
;; compiled binary/archive, and we'll get our import errors when
823
(if (string-match "^\\(.+\\):\\([[:digit:]]+\\): imported and not used: \".+\"$" line)
824
(if (string= (file-truename (match-string 1 line)) (file-truename buffer-file-name))
825
(string-to-number (match-string 2 line)))))
826
(split-string (shell-command-to-string
827
(if (string-match "_test\.go$" buffer-file-truename)
829
"go build -o /dev/null")) "\n")))))
831
(defun go-remove-unused-imports (arg)
832
"Removes all unused imports. If ARG is non-nil, unused imports
833
will be commented, otherwise they will be removed completely."
836
(let ((cur-buffer (current-buffer)) flymake-state lines)
837
(when (boundp 'flymake-mode)
838
(setq flymake-state flymake-mode)
840
(save-some-buffers nil (lambda () (equal cur-buffer (current-buffer))))
841
(if (buffer-modified-p)
842
(message "Cannot operate on unsaved buffer")
843
(setq lines (go-unused-imports-lines))
844
(dolist (import lines)
845
(goto-char (point-min))
846
(forward-line (1- import))
849
(comment-region (line-beginning-position) (line-end-position))
850
(go--kill-whole-line)))
851
(message "Removed %d imports" (length lines)))
852
(if flymake-state (flymake-mode-on)))))
854
(defun godef--find-file-line-column (specifier)
855
"Given a file name in the format of `filename:line:column',
856
visit FILENAME and go to line LINE and column COLUMN."
857
(let* ((components (split-string specifier ":"))
858
(line (string-to-number (nth 1 components)))
859
(column (string-to-number (nth 2 components))))
860
(with-current-buffer (find-file (car components))
861
(goto-char (point-min))
862
(forward-line (1- line))
864
(forward-char (1- column))
865
(if (buffer-modified-p)
866
(message "Buffer is modified, file position might not have been correct")))))
868
(defun godef--call (point)
869
"Call godef, acquiring definition position and expression
870
description at POINT."
872
(message "godef does not reliably work in XEmacs, expect bad results"))
873
(if (not buffer-file-name)
874
(message "Cannot use godef on a buffer without a file name")
875
(let ((outbuf (get-buffer-create "*godef*")))
876
(with-current-buffer outbuf
878
(call-process-region (point-min) (point-max) "godef" nil outbuf nil "-i" "-t" "-f" (file-truename buffer-file-name) "-o" (number-to-string (go--position-bytes (point))))
879
(with-current-buffer outbuf
880
(split-string (buffer-substring-no-properties (point-min) (point-max)) "\n")))))
882
(defun godef-describe (point)
883
"Describe the expression at POINT."
886
(let ((description (nth 1 (godef--call point))))
887
(if (string= "" description)
888
(message "No description found for expression at point")
889
(message "%s" description)))
890
(file-error (message "Could not run godef binary"))))
892
(defun godef-jump (point)
893
"Jump to the definition of the expression at POINT."
896
(let ((file (car (godef--call point))))
899
(message "godef: expression is not defined anywhere"))
900
((string= "godef: no identifier found" file)
902
((go--string-prefix-p "godef: no declaration found for " file)
906
(godef--find-file-line-column file))))
907
(file-error (message "Could not run godef binary"))))
892
909
(provide 'go-mode)