Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(636)

Side by Side Diff: misc/emacs/go-mode.el

Issue 7314113: code review 7314113: misc/emacs: Greatly improve go-mode for Emacs. (Closed)
Patch Set: diff -r e93de8482d59 https://code.google.com/p/go Created 11 years, 1 month ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | misc/emacs/go-mode-load.el » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 ;;; go-mode.el --- Major mode for the Go programming language 1 ;;; go-mode.el --- Major mode for the Go programming language
2 2
3 ;;; Commentary: 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.
4 6
5 ;; For installation instructions, see go-mode-load.el 7 (require 'cl)
8 (require 'diff-mode)
9 (require 'ffap)
10 (require 'find-lisp)
11 (require 'url)
6 12
7 ;;; To do: 13 (defconst go-dangling-operators-regexp "[^-]-\\|[^+]\\+\\|[/*&><.=|^]")
14 (defconst gofmt-stdin-tag "<standard input>")
15 (defconst go-identifier-regexp "[[:word:][:multibyte:]_]+")
16 (defconst go-type-regexp "[[:word:][:multibyte:]_*]+")
17 (defconst go-func-regexp (concat "\\<func\\>\\s *\\(" go-identifier-regexp "\\)" ))
18 (defconst go-func-meth-regexp (concat "\\<func\\>\\s *\\(?:(\\s *" go-identifier -regexp "\\s +" go-type-regexp "\\s *)\\s *\\)?\\(" go-identifier-regexp "\\)(") )
19 (defconst go-builtins
20 '("append" "cap" "close" "complex" "copy"
21 "delete" "imag" "len" "make" "new"
22 "panic" "print" "println" "real" "recover")
23 "All built-in functions in the Go language. Used for font locking.")
8 24
9 ;; * Indentation is *almost* identical to gofmt 25 (defconst go-mode-keywords
10 ;; ** We think struct literal keys are labels and outdent them 26 '("break" "default" "func" "interface" "select"
11 ;; ** We disagree on the indentation of function literals in arguments 27 "case" "defer" "go" "map" "struct"
12 ;; ** There are bugs with the close brace of struct literals 28 "chan" "else" "goto" "package" "switch"
13 ;; * Highlight identifiers according to their syntactic context: type, 29 "const" "fallthrough" "if" "range" "type"
14 ;; variable, function call, or tag 30 "continue" "for" "import" "return" "var")
15 ;; * Command for adding an import 31 "All keywords in the Go language. Used for font locking.")
16 ;; ** Check if it's already there
17 ;; ** Factor/unfactor the import line
18 ;; ** Alphabetize
19 ;; * Remove unused imports
20 ;; ** This is hard, since I have to be aware of shadowing to do it
21 ;; right
22 ;; * Format region using gofmt
23 32
24 ;;; Code: 33 (defconst go-constants '("nil" "true" "false" "iota"))
34 (defconst go-type-name-regexp (concat "\\(?:[*(]\\)*\\(?:" go-identifier-regexp "\\.\\)?\\(" go-identifier-regexp "\\)"))
25 35
26 (eval-when-compile (require 'cl)) 36 (defvar go-dangling-cache)
37
38 (defgroup go nil
39 "Major mode for editing Go code"
40 :group 'languages)
41
42 (defcustom go-fontify-function-calls t
43 "Fontify function and method calls if this is non-nil."
44 :type 'boolean
45 :group 'go)
27 46
28 (defvar go-mode-syntax-table 47 (defvar go-mode-syntax-table
29 (let ((st (make-syntax-table))) 48 (let ((st (make-syntax-table)))
30 ;; Add _ to :word: character class
31 (modify-syntax-entry ?_ "w" st)
32
33 ;; Operators (punctuation)
34 (modify-syntax-entry ?+ "." st) 49 (modify-syntax-entry ?+ "." st)
35 (modify-syntax-entry ?- "." st) 50 (modify-syntax-entry ?- "." st)
36 (modify-syntax-entry ?* "." st)
37 (modify-syntax-entry ?/ "." st)
38 (modify-syntax-entry ?% "." st) 51 (modify-syntax-entry ?% "." st)
39 (modify-syntax-entry ?& "." st) 52 (modify-syntax-entry ?& "." st)
40 (modify-syntax-entry ?| "." st) 53 (modify-syntax-entry ?| "." st)
41 (modify-syntax-entry ?^ "." st) 54 (modify-syntax-entry ?^ "." st)
42 (modify-syntax-entry ?! "." st) 55 (modify-syntax-entry ?! "." st)
43 (modify-syntax-entry ?= "." st) 56 (modify-syntax-entry ?= "." st)
44 (modify-syntax-entry ?< "." st) 57 (modify-syntax-entry ?< "." st)
45 (modify-syntax-entry ?> "." st) 58 (modify-syntax-entry ?> "." st)
46 59 (modify-syntax-entry ?/ ". 124b" st)
47 ;; Strings and comments are font-locked separately. 60 (modify-syntax-entry ?* ". 23" st)
48 (modify-syntax-entry ?\" "." st) 61 (modify-syntax-entry ?\n "> b" st)
49 (modify-syntax-entry ?\' "." st) 62 (modify-syntax-entry ?\" "\"" st)
50 (modify-syntax-entry ?` "." st) 63 (modify-syntax-entry ?\' "\"" st)
51 (modify-syntax-entry ?\\ "." st) 64 (modify-syntax-entry ?` "\"" st)
65 (modify-syntax-entry ?\\ "\\" st)
66 (modify-syntax-entry ?_ "_" st)
52 67
53 st) 68 st)
54 "Syntax table for Go mode.") 69 "Syntax table for Go mode.")
55 70
56 (defvar go-mode-keywords 71 (defun go--build-font-lock-keywords ()
57 '("break" "default" "func" "interface" "select" 72 (append
58 "case" "defer" "go" "map" "struct" 73 `((,(regexp-opt go-mode-keywords 'symbols) . font-lock-keyword-face)
59 "chan" "else" "goto" "package" "switch" 74 (,(regexp-opt go-builtins 'symbols) . font-lock-builtin-face)
60 "const" "fallthrough" "if" "range" "type" 75 (,(regexp-opt go-constants 'symbols) . font-lock-constant-face)
61 "continue" "for" "import" "return" "var") 76 (,go-func-regexp 1 font-lock-function-name-face)) ;; function (not method) name
62 "All keywords in the Go language. Used for font locking and
63 some syntax analysis.")
64 77
65 (defvar go-mode-font-lock-keywords 78 (if go-fontify-function-calls
66 (let ((builtins '("append" "cap" "close" "complex" "copy" "delete" "imag" "len " 79 `((,(concat "\\(" go-identifier-regexp "\\)[[:space:]]*(") 1 font-lock-fu nction-name-face) ;; function call/method name
67 "make" "new" "panic" "print" "println" "real" "recover")) 80 (,(concat "(\\(" go-identifier-regexp "\\))[[:space:]]*(") 1 font-lock- function-name-face)) ;; bracketed function call
68 (constants '("nil" "true" "false" "iota")) 81 `((,go-func-meth-regexp 1 font-lock-function-name-face))) ;; method name
69 (type-name "\\s *\\(?:[*(]\\s *\\)*\\(?:\\w+\\s *\\.\\s *\\)?\\(\\w+\\)" )
70 )
71 `((go-mode-font-lock-cs-comment 0 font-lock-comment-face t)
72 (go-mode-font-lock-cs-string 0 font-lock-string-face t)
73 (,(regexp-opt go-mode-keywords 'words) . font-lock-keyword-face)
74 (,(regexp-opt builtins 'words) . font-lock-builtin-face)
75 (,(regexp-opt constants 'words) . font-lock-constant-face)
76 ;; Function names in declarations
77 ("\\<func\\>\\s *\\(\\w+\\)" 1 font-lock-function-name-face)
78 ;; Function names in methods are handled by function call pattern
79 ;; Function names in calls
80 ;; XXX Doesn't match if function name is surrounded by parens
81 ("\\(\\w+\\)\\s *(" 1 font-lock-function-name-face)
82 ;; Type names
83 ("\\<type\\>\\s *\\(\\w+\\)" 1 font-lock-type-face)
84 (,(concat "\\<type\\>\\s *\\w+\\s *" type-name) 1 font-lock-type-face)
85 ;; Arrays/slices/map value type
86 ;; XXX Wrong. Marks 0 in expression "foo[0] * x"
87 ;; (,(concat "]" type-name) 1 font-lock-type-face)
88 ;; Map key type
89 (,(concat "\\<map\\s *\\[" type-name) 1 font-lock-type-face)
90 ;; Channel value type
91 (,(concat "\\<chan\\>\\s *\\(?:<-\\)?" type-name) 1 font-lock-type-face)
92 ;; new/make type
93 (,(concat "\\<\\(?:new\\|make\\)\\>\\(?:\\s \\|)\\)*(" type-name) 1 font-l ock-type-face)
94 ;; Type conversion
95 (,(concat "\\.\\s *(" type-name) 1 font-lock-type-face)
96 ;; Method receiver type
97 (,(concat "\\<func\\>\\s *(\\w+\\s +" type-name) 1 font-lock-type-face)
98 ;; Labels
99 ;; XXX Not quite right. Also marks compound literal fields.
100 ("^\\s *\\(\\w+\\)\\s *:\\(\\S.\\|$\\)" 1 font-lock-constant-face)
101 ("\\<\\(goto\\|break\\|continue\\)\\>\\s *\\(\\w+\\)" 2 font-lock-constant -face)))
102 "Basic font lock keywords for Go mode. Highlights keywords,
103 built-ins, functions, and some types.")
104 82
105 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 83 `(
106 ;; Key map 84 ("\\<type\\>[[:space:]]*\\([^[:space:]]+\\)" 1 font-lock-type-face) ;; type s
107 ;; 85 (,(concat "\\<type\\>[[:space:]]*" go-identifier-regexp "[[:space:]]*" go-t ype-name-regexp) 1 font-lock-type-face) ;; types
86 (,(concat "\\(?:[[:space:]]+\\|\\]\\)\\[\\([[:digit:]]+\\|\\.\\.\\.\\)?\\]" go-type-name-regexp) 2 font-lock-type-face) ;; Arrays/slices
87 (,(concat "map\\[[^]]+\\]" go-type-name-regexp) 1 font-lock-type-face) ;; m ap value type
88 (,(concat "\\(" go-identifier-regexp "\\)" "{") 1 font-lock-type-face)
89 (,(concat "\\<map\\[" go-type-name-regexp) 1 font-lock-type-face) ;; map ke y type
90 (,(concat "\\<chan\\>[[:space:]]*\\(?:<-\\)?" go-type-name-regexp) 1 font-l ock-type-face) ;; channel type
91 (,(concat "\\<\\(?:new\\|make\\)\\>\\(?:[[:space:]]\\|)\\)*(" go-type-name- regexp) 1 font-lock-type-face) ;; new/make type
92 ;; TODO do we actually need this one or isn't it just a function call?
93 (,(concat "\\.\\s *(" go-type-name-regexp) 1 font-lock-type-face) ;; Type c onversion
94 (,(concat "\\<func\\>[[:space:]]+(" go-identifier-regexp "[[:space:]]+" go- type-name-regexp ")") 1 font-lock-type-face) ;; Method receiver
95 ;; Like the original go-mode this also marks compound literal
96 ;; fields. There, it was marked as to fix, but I grew quite
97 ;; accustomed to it, so it'll stay for now.
98 (,(concat "^[[:space:]]*\\(" go-identifier-regexp "\\)[[:space:]]*:\\(\\S.\ \|$\\)") 1 font-lock-constant-face) ;; Labels and compound literal fields
99 ("\\<\\(goto\\|break\\|continue\\)\\>[[:space:]]*\\(\\w+\\)" 2 font-lock-co nstant-face)))) ;; labels in goto/break/continue
108 100
109 (defvar go-mode-map 101 (defvar go-mode-map
110 (let ((m (make-sparse-keymap))) 102 (let ((m (make-sparse-keymap)))
111 (define-key m "}" #'go-mode-insert-and-indent) 103 (define-key m "}" 'go-mode-insert-and-indent)
adonovan 2013/02/21 18:45:49 Though #'foo is functionally equivalent to 'foo in
Dominik Honnef 2013/02/21 19:08:34 I actually wasn't too sure about the meaning of #'
adonovan 2013/02/21 19:14:40 Just as the reader expands 'x to (quote x), it exp
Dominik Honnef 2013/02/21 19:27:38 Ah. Thanks for explaining that, that cleared it up
112 (define-key m ")" #'go-mode-insert-and-indent) 104 (define-key m ")" 'go-mode-insert-and-indent)
113 (define-key m "," #'go-mode-insert-and-indent) 105 (define-key m "," 'go-mode-insert-and-indent)
114 (define-key m ":" #'go-mode-delayed-electric) 106 (define-key m ":" 'go-mode-insert-and-indent)
115 ;; In case we get : indentation wrong, correct ourselves 107 (define-key m "=" 'go-mode-insert-and-indent)
116 (define-key m "=" #'go-mode-insert-and-indent) 108 (define-key m (kbd "C-c C-a") 'go-import-add)
117 m) 109 m)
118 "Keymap used by Go mode to implement electric keys.") 110 "Keymap used by Go mode to implement electric keys.")
119 111
120 (defun go-mode-insert-and-indent (key) 112 (defun go-mode-insert-and-indent (key)
121 "Invoke the global binding of KEY, then reindent the line." 113 "Invoke the global binding of KEY, then reindent the line."
122 114
123 (interactive (list (this-command-keys))) 115 (interactive (list (this-command-keys)))
124 (call-interactively (lookup-key (current-global-map) key)) 116 (call-interactively (lookup-key (current-global-map) key))
125 (indent-according-to-mode)) 117 (indent-according-to-mode))
126 118
127 (defvar go-mode-delayed-point nil 119 (defmacro go-paren-level ()
128 "The point following the previous insertion if the insertion 120 `(car (syntax-ppss)))
adonovan 2013/02/21 18:45:49 Glad to see you're using ppss; I never understood
Dominik Honnef 2013/02/21 19:08:34 Heh, two reasons, I'll start from the back: They d
adonovan 2013/02/21 19:14:40 Ah. And the reviewer was afraid to mention it aft
Dominik Honnef 2013/02/21 19:27:38 Well, even the original version of the old mode im
129 was a delayed electric key. Used to communicate between 121
130 `go-mode-delayed-electric' and `go-mode-delayed-electric-hook'.") 122 (defmacro go-in-string-or-comment-p ()
131 (make-variable-buffer-local 'go-mode-delayed-point) 123 `(nth 8 (syntax-ppss)))
132 124
133 (defun go-mode-delayed-electric (p) 125 (defmacro go-in-string-p ()
134 "Perform electric insertion, but delayed by one event. 126 `(nth 3 (syntax-ppss)))
135 127
136 This inserts P into the buffer, as usual, then waits for another key. 128 (defmacro go-in-comment-p ()
137 If that second key causes a buffer modification starting at the 129 `(nth 4 (syntax-ppss)))
138 point after the insertion of P, reindents the line containing P." 130
139 131 (defmacro go-goto-beginning-of-string-or-comment ()
140 (interactive "p") 132 `(goto-char (nth 8 (syntax-ppss))))
141 (self-insert-command p) 133
142 (setq go-mode-delayed-point (point))) 134 (defun go--backward-irrelevant (&optional stop-at-string)
adonovan 2013/02/21 18:45:49 What's the significance of two dashes in go--foo?
Dominik Honnef 2013/02/21 19:08:34 Yup, private.
143 135 "Skips backwards over any characters that are irrelevant for
144 (defun go-mode-delayed-electric-hook (b e l) 136 indentation and related tasks.
145 "An after-change-function that implements `go-mode-delayed-electric'." 137
146 138 It skips over whitespace, comments, cases and labels and, if
147 (when (and go-mode-delayed-point 139 STOP-AT-STRING is not true, over strings."
148 (= go-mode-delayed-point b)) 140
149 (save-excursion 141 (let (pos (start-pos (point)))
150 (save-match-data 142 (skip-chars-backward "\n[:blank:]")
151 (goto-char go-mode-delayed-point) 143 (if (and (save-excursion (beginning-of-line) (go-in-string-p)) (looking-back "`") (not stop-at-string))
152 (indent-according-to-mode)))) 144 (backward-char))
153 (setq go-mode-delayed-point nil)) 145 (if (and (go-in-string-p) (not stop-at-string))
154 146 (go-goto-beginning-of-string-or-comment))
155 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 147 (if (looking-back "\\*/")
156 ;; Parser 148 (backward-char))
157 ;; 149 (if (go-in-comment-p)
158 150 (go-goto-beginning-of-string-or-comment))
159 (defvar go-mode-mark-cs-end 1 151 (setq pos (point))
160 "The point at which the comment/string cache ends. The buffer 152 (beginning-of-line)
161 will be marked from the beginning up to this point (that is, up 153 (if (or (looking-at "^[[:word:]]+:") (looking-at "^[[:space:]]*\\(case .+\\| default\\):"))
162 to and including character (1- go-mode-mark-cs-end)).") 154 (end-of-line 0)
163 (make-variable-buffer-local 'go-mode-mark-cs-end) 155 (goto-char pos))
164 156 (if (/= start-pos (point))
165 (defvar go-mode-mark-string-end 1 157 (go--backward-irrelevant stop-at-string))
166 "The point at which the string cache ends. The buffer 158 (/= start-pos (point))))
167 will be marked from the beginning up to this point (that is, up 159
168 to and including character (1- go-mode-mark-string-end)).") 160 (defun go--buffer-narrowed-p ()
169 (make-variable-buffer-local 'go-mode-mark-string-end) 161 "Return non-nil if the current buffer is narrowed."
170 162 (/= (buffer-size)
171 (defvar go-mode-mark-comment-end 1 163 (- (point-max)
172 "The point at which the comment cache ends. The buffer 164 (point-min))))
173 will be marked from the beginning up to this point (that is, up 165
174 to and including character (1- go-mode-mark-comment-end)).") 166 (defun go-previous-line-has-dangling-op-p ()
175 (make-variable-buffer-local 'go-mode-mark-comment-end) 167 "Returns non-nil if the current line is a continuation line."
176 168 (let* ((cur-line (line-number-at-pos))
177 (defvar go-mode-mark-nesting-end 1 169 (val (gethash cur-line go-dangling-cache 'nope)))
178 "The point at which the nesting cache ends. The buffer will be 170 (if (or (go--buffer-narrowed-p) (equal val 'nope))
179 marked from the beginning up to this point.") 171 (save-excursion
180 (make-variable-buffer-local 'go-mode-mark-nesting-end) 172 (beginning-of-line)
181 173 (go--backward-irrelevant t)
182 (defun go-mode-mark-clear-cs (b e l) 174 (setq val (looking-back go-dangling-operators-regexp))
183 "An after-change-function that removes the go-mode-cs text property" 175 (if (not (go--buffer-narrowed-p))
184 (remove-text-properties b e '(go-mode-cs))) 176 (puthash cur-line val go-dangling-cache))))
185 177 val))
186 (defun go-mode-mark-clear-cache (b e) 178
187 "A before-change-function that clears the comment/string and 179 (defun go-goto-opening-parenthesis (&optional char)
188 nesting caches from the modified point on." 180 (let ((start-nesting (go-paren-level)) group)
189 181 (if char
190 (save-restriction 182 (setq group (case char (?\] "^[") (?\} "^{") (?\) "^(")))
191 (widen) 183 (setq group "^[{("))
192 (when (<= b go-mode-mark-cs-end) 184 (while (and (not (bobp))
193 ;; Remove the property adjacent to the change position. 185 (>= (go-paren-level) start-nesting))
194 ;; It may contain positions pointing beyond the new end mark. 186 (if (zerop (skip-chars-backward group))
195 (let ((b (let ((cs (get-text-property (max 1 (1- b)) 'go-mode-cs))) 187 (if (go-in-string-or-comment-p)
196 (if cs (car cs) b)))) 188 (go-goto-beginning-of-string-or-comment)
197 (remove-text-properties 189 (backward-char))))))
198 b (min go-mode-mark-cs-end (point-max)) '(go-mode-cs nil)) 190
199 (setq go-mode-mark-cs-end b))) 191 (defun go-indentation-at-point ()
200
201 (when (<= b go-mode-mark-string-end)
202 ;; Remove the property adjacent to the change position.
203 ;; It may contain positions pointing beyond the new end mark.
204 (let ((b (let ((cs (get-text-property (max 1 (1- b)) 'go-mode-string)))
205 (if cs (car cs) b))))
206 (remove-text-properties
207 b (min go-mode-mark-string-end (point-max)) '(go-mode-string nil))
208 (setq go-mode-mark-string-end b)))
209 (when (<= b go-mode-mark-comment-end)
210 ;; Remove the property adjacent to the change position.
211 ;; It may contain positions pointing beyond the new end mark.
212 (let ((b (let ((cs (get-text-property (max 1 (1- b)) 'go-mode-comment)))
213 (if cs (car cs) b))))
214 (remove-text-properties
215 b (min go-mode-mark-string-end (point-max)) '(go-mode-comment nil))
216 (setq go-mode-mark-comment-end b)))
217
218 (when (< b go-mode-mark-nesting-end)
219 (remove-text-properties b (min go-mode-mark-nesting-end (point-max)) '(go- mode-nesting nil))
220 (setq go-mode-mark-nesting-end b))))
221
222 (defmacro go-mode-parser (&rest body)
223 "Evaluate BODY in an environment set up for parsers that use
224 text properties to mark text. This inhibits changes to the undo
225 list or the buffer's modification status and inhibits calls to
226 the modification hooks. It also saves the excursion and
227 restriction and widens the buffer, since most parsers are
228 context-sensitive."
229
230 (let ((modified-var (make-symbol "modified")))
231 `(let ((buffer-undo-list t)
232 (,modified-var (buffer-modified-p))
233 (inhibit-modification-hooks t)
234 (inhibit-read-only t))
235 (save-excursion
236 (save-restriction
237 (widen)
238 (unwind-protect
239 (progn ,@body)
240 (set-buffer-modified-p ,modified-var)))))))
241
242 (defun go-mode-cs (&optional pos)
243 "Return the comment/string state at point POS. If point is
244 inside a comment or string (including the delimiters), this
245 returns a pair (START . END) indicating the extents of the
246 comment or string."
247
248 (unless pos
249 (setq pos (point)))
250 (when (>= pos go-mode-mark-cs-end)
251 (go-mode-mark-cs (1+ pos)))
252 (get-text-property pos 'go-mode-cs))
253
254 (defun go-mode-mark-cs (end)
255 "Mark comments and strings up to point END. Don't call this
256 directly; use `go-mode-cs'."
257 (setq end (min end (point-max)))
258 (go-mode-parser
259 (save-match-data
260 (let ((pos
261 ;; Back up to the last known state.
262 (let ((last-cs
263 (and (> go-mode-mark-cs-end 1)
264 (get-text-property (1- go-mode-mark-cs-end)
265 'go-mode-cs))))
266 (if last-cs
267 (car last-cs)
268 (max 1 (1- go-mode-mark-cs-end))))))
269 (while (< pos end)
270 (goto-char pos)
271 (let ((cs-end ; end of the text property
272 (cond
273 ((looking-at "//")
274 (end-of-line)
275 (1+ (point)))
276 ((looking-at "/\\*")
277 (goto-char (+ pos 2))
278 (if (search-forward "*/" (1+ end) t)
279 (point)
280 end))
281 ((looking-at "\"")
282 (goto-char (1+ pos))
283 (if (looking-at "[^\"\n\\\\]*\\(\\\\.[^\"\n\\\\]*\\)*\"")
284 (match-end 0)
285 (end-of-line)
286 (point)))
287 ((looking-at "'")
288 (goto-char (1+ pos))
289 (if (looking-at "[^'\n\\\\]*\\(\\\\.[^'\n\\\\]*\\)*'")
290 (match-end 0)
291 (end-of-line)
292 (point)))
293 ((looking-at "`")
294 (goto-char (1+ pos))
295 (while (if (search-forward "`" end t)
296 (if (eq (char-after) ?`)
297 (goto-char (1+ (point))))
298 (goto-char end)
299 nil))
300 (point)))))
301 (cond
302 (cs-end
303 (put-text-property pos cs-end 'go-mode-cs (cons pos cs-end))
304 (setq pos cs-end))
305 ((re-search-forward "[\"'`]\\|/[/*]" end t)
306 (setq pos (match-beginning 0)))
307 (t
308 (setq pos end)))))
309 (setq go-mode-mark-cs-end pos)))))
310
311 (defun go-mode-in-comment (&optional pos)
312 "Return the comment/string state at point POS. If point is
313 inside a comment (including the delimiters), this
314 returns a pair (START . END) indicating the extents of the
315 comment or string."
316
317 (unless pos
318 (setq pos (point)))
319 (when (> pos go-mode-mark-comment-end)
320 (go-mode-mark-comment pos))
321 (get-text-property pos 'go-mode-comment))
322
323 (defun go-mode-mark-comment (end)
324 "Mark comments up to point END. Don't call this directly; use `go-mode-in-com ment'."
325 (setq end (min end (point-max)))
326 (go-mode-parser
327 (save-match-data
328 (let ((pos
329 ;; Back up to the last known state.
330 (let ((last-comment
331 (and (> go-mode-mark-comment-end 1)
332 (get-text-property (1- go-mode-mark-comment-end)
333 'go-mode-comment))))
334 (if last-comment
335 (car last-comment)
336 (max 1 (1- go-mode-mark-comment-end))))))
337 (while (< pos end)
338 (goto-char pos)
339 (let ((comment-end ; end of the text property
340 (cond
341 ((looking-at "//")
342 (end-of-line)
343 (1+ (point)))
344 ((looking-at "/\\*")
345 (goto-char (+ pos 2))
346 (if (search-forward "*/" (1+ end) t)
347 (point)
348 end)))))
349 (cond
350 (comment-end
351 (put-text-property pos comment-end 'go-mode-comment (cons pos comme nt-end))
352 (setq pos comment-end))
353 ((re-search-forward "/[/*]" end t)
354 (setq pos (match-beginning 0)))
355 (t
356 (setq pos end)))))
357 (setq go-mode-mark-comment-end pos)))))
358
359 (defun go-mode-in-string (&optional pos)
360 "Return the string state at point POS. If point is
361 inside a string (including the delimiters), this
362 returns a pair (START . END) indicating the extents of the
363 comment or string."
364
365 (unless pos
366 (setq pos (point)))
367 (when (> pos go-mode-mark-string-end)
368 (go-mode-mark-string pos))
369 (get-text-property pos 'go-mode-string))
370
371 (defun go-mode-mark-string (end)
372 "Mark strings up to point END. Don't call this
373 directly; use `go-mode-in-string'."
374 (setq end (min end (point-max)))
375 (go-mode-parser
376 (save-match-data
377 (let ((pos
378 ;; Back up to the last known state.
379 (let ((last-cs
380 (and (> go-mode-mark-string-end 1)
381 (get-text-property (1- go-mode-mark-string-end)
382 'go-mode-string))))
383 (if last-cs
384 (car last-cs)
385 (max 1 (1- go-mode-mark-string-end))))))
386 (while (< pos end)
387 (goto-char pos)
388 (let ((cs-end ; end of the text property
389 (cond
390 ((looking-at "\"")
391 (goto-char (1+ pos))
392 (if (looking-at "[^\"\n\\\\]*\\(\\\\.[^\"\n\\\\]*\\)*\"")
393 (match-end 0)
394 (end-of-line)
395 (point)))
396 ((looking-at "'")
397 (goto-char (1+ pos))
398 (if (looking-at "[^'\n\\\\]*\\(\\\\.[^'\n\\\\]*\\)*'")
399 (match-end 0)
400 (end-of-line)
401 (point)))
402 ((looking-at "`")
403 (goto-char (1+ pos))
404 (while (if (search-forward "`" end t)
405 (if (eq (char-after) ?`)
406 (goto-char (1+ (point))))
407 (goto-char end)
408 nil))
409 (point)))))
410 (cond
411 (cs-end
412 (put-text-property pos cs-end 'go-mode-string (cons pos cs-end))
413 (setq pos cs-end))
414 ((re-search-forward "[\"'`]" end t)
415 (setq pos (match-beginning 0)))
416 (t
417 (setq pos end)))))
418 (setq go-mode-mark-string-end pos)))))
419
420 (defun go-mode-font-lock-cs (limit comment)
421 "Helper function for highlighting comment/strings. If COMMENT is t,
422 set match data to the next comment after point, and advance point
423 after it. If COMMENT is nil, use the next string. Returns nil
424 if no further tokens of the type exist."
425 ;; Ensures that `next-single-property-change' below will work properly.
426 (go-mode-cs limit)
427 (let (cs next (result 'scan))
428 (while (eq result 'scan)
429 (if (or (>= (point) limit) (eobp))
430 (setq result nil)
431 (setq cs (go-mode-cs))
432 (if (and cs (>= (car cs) (point)))
433 (if (eq (= (char-after (car cs)) ?/) comment)
434 ;; If inside the expected comment/string, highlight it.
435 (progn
436 ;; If the match includes a "\n", we have a
437 ;; multi-line construct. Mark it as such.
438 (goto-char (car cs))
439 (when (search-forward "\n" (cdr cs) t)
440 (put-text-property
441 (car cs) (cdr cs) 'font-lock-multline t))
442 (set-match-data (list (car cs) (copy-marker (cdr cs))))
443 (goto-char (cdr cs))
444 (setq result t))
445 ;; Wrong type. Look for next comment/string after this one.
446 (goto-char (cdr cs)))
447 ;; Not inside comment/string. Search for next comment/string.
448 (setq next (next-single-property-change
449 (point) 'go-mode-cs nil limit))
450 (if (and next (< next limit))
451 (goto-char next)
452 (setq result nil)))))
453 result))
454
455 (defun go-mode-font-lock-cs-string (limit)
456 "Font-lock iterator for strings."
457 (go-mode-font-lock-cs limit nil))
458
459 (defun go-mode-font-lock-cs-comment (limit)
460 "Font-lock iterator for comments."
461 (go-mode-font-lock-cs limit t))
462
463 (defsubst go-mode-nesting (&optional pos)
464 "Return the nesting at point POS. The nesting is a list
465 of (START . END) pairs for all braces, parens, and brackets
466 surrounding POS, starting at the inner-most nesting. START is
467 the location of the open character. END is the location of the
468 close character or nil if the nesting scanner has not yet
469 encountered the close character."
470
471 (unless pos
472 (setq pos (point)))
473 (if (= pos 1)
474 '()
475 (when (> pos go-mode-mark-nesting-end)
476 (go-mode-mark-nesting pos))
477 (get-text-property (- pos 1) 'go-mode-nesting)))
478
479 (defun go-mode-mark-nesting (pos)
480 "Mark nesting up to point END. Don't call this directly; use
481 `go-mode-nesting'."
482
483 (go-mode-cs pos)
484 (go-mode-parser
485 ;; Mark depth
486 (goto-char go-mode-mark-nesting-end)
487 (let ((nesting (go-mode-nesting))
488 (last (point)))
489 (while (< last pos)
490 ;; Find the next depth-changing character
491 (skip-chars-forward "^(){}[]" pos)
492 ;; Mark everything up to this character with the current
493 ;; nesting
494 (put-text-property last (point) 'go-mode-nesting nesting)
495 (when nil
496 (let ((depth (length nesting)))
497 (put-text-property last (point) 'face
498 `((:background
499 ,(format "gray%d" (* depth 10)))))))
500 (setq last (point))
501 ;; Update nesting
502 (unless (eobp)
503 (let ((ch (unless (go-mode-cs) (char-after))))
504 (forward-char 1)
505 (case ch
506 ((?\( ?\{ ?\[)
507 (setq nesting (cons (cons (- (point) 1) nil)
508 nesting)))
509 ((?\) ?\} ?\])
510 (when nesting
511 (setcdr (car nesting) (- (point) 1))
512 (setq nesting (cdr nesting))))))))
513 ;; Update state
514 (setq go-mode-mark-nesting-end last))))
515
516 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
517 ;; Indentation
518 ;;
519
520 (defvar go-mode-non-terminating-keywords-regexp
521 (let* ((kws go-mode-keywords)
522 (kws (remove "break" kws))
523 (kws (remove "continue" kws))
524 (kws (remove "fallthrough" kws))
525 (kws (remove "return" kws)))
526 (regexp-opt kws 'words))
527 "Regular expression matching all Go keywords that *do not*
528 implicitly terminate a statement.")
529
530 (defun go-mode-semicolon-p ()
531 "True iff point immediately follows either an explicit or
532 implicit semicolon. Point should immediately follow the last
533 token on the line."
534
535 ;; #Semicolons
536 (case (char-before)
537 ((?\;) t)
538 ;; String literal
539 ((?' ?\" ?`) t)
540 ;; One of the operators and delimiters ++, --, ), ], or }
541 ((?+) (eq (char-before (1- (point))) ?+))
542 ((?-) (eq (char-before (1- (point))) ?-))
543 ((?\) ?\] ?\}) t)
544 ;; An identifier or one of the keywords break, continue,
545 ;; fallthrough, or return or a numeric literal
546 (otherwise
547 (save-excursion
548 (when (/= (skip-chars-backward "[:word:]_") 0)
549 (not (looking-at go-mode-non-terminating-keywords-regexp)))))))
550
551 (defun go-mode-whitespace-p (char)
552 "Is char whitespace in the syntax table for go."
553 (eq 32 (char-syntax char)))
554
555 (defun go-mode-backward-skip-comments ()
556 "Skip backward over comments and whitespace."
557 ;; only proceed if point is in a comment or white space
558 (if (or (go-mode-in-comment)
559 (go-mode-whitespace-p (char-after (point))))
560 (let ((loop-guard t))
561 (while (and
562 loop-guard
563 (not (bobp)))
564
565 (cond ((go-mode-whitespace-p (char-after (point)))
566 ;; moves point back over any whitespace
567 (re-search-backward "[^[:space:]]"))
568
569 ((go-mode-in-comment)
570 ;; move point to char preceeding current comment
571 (goto-char (1- (car (go-mode-in-comment)))))
572
573 ;; not in a comment or whitespace? we must be done.
574 (t (setq loop-guard nil)
575 (forward-char 1)))))))
576
577 (defun go-mode-indentation ()
578 "Compute the ideal indentation level of the current line.
579
580 To the first order, this is the brace depth of the current line,
581 plus parens that follow certain keywords. case, default, and
582 labels are outdented one level, and continuation lines are
583 indented one level."
584
585 (save-excursion 192 (save-excursion
193 (let (start-nesting (outindent 0))
194 (back-to-indentation)
195 (setq start-nesting (go-paren-level))
196
197 (cond
198 ((go-in-string-p)
199 (current-indentation))
200 ((looking-at "[])}]")
201 (go-goto-opening-parenthesis (char-after))
202 (if (go-previous-line-has-dangling-op-p)
203 (- (current-indentation) tab-width)
204 (current-indentation)))
205 ((progn (go--backward-irrelevant t) (looking-back go-dangling-operators-r egexp))
206 ;; only one nesting for all dangling operators in one operation
207 (if (go-previous-line-has-dangling-op-p)
208 (current-indentation)
209 (+ (current-indentation) tab-width)))
210 ((zerop (go-paren-level))
211 0)
212 ((progn (go-goto-opening-parenthesis) (< (go-paren-level) start-nesting))
213 (if (go-previous-line-has-dangling-op-p)
214 (current-indentation)
215 (+ (current-indentation) tab-width)))
216 (t
217 (current-indentation))))))
218
219 (defun go-mode-indent-line ()
220 (interactive)
221 (let (indent
222 shift-amt
223 end
224 (pos (- (point-max) (point)))
225 (point (point))
226 (beg (line-beginning-position)))
586 (back-to-indentation) 227 (back-to-indentation)
587 (let ((cs (go-mode-cs))) 228 (if (go-in-string-or-comment-p)
588 ;; Treat comments and strings differently only if the beginning 229 (goto-char point)
589 ;; of the line is contained within them 230 (setq indent (go-indentation-at-point))
590 (when (and cs (= (point) (car cs))) 231 (if (looking-at "[[:word:]]+:\\([[:space:]]*/.+\\)?$\\|case .+:\\|default: ")
591 (setq cs nil)) 232 (decf indent tab-width))
592 ;; What type of context am I in? 233 (setq shift-amt (- indent (current-column)))
593 (cond 234 (if (zerop shift-amt)
594 ((and cs (save-excursion 235 nil
595 (goto-char (car cs)) 236 (delete-region beg (point))
596 (looking-at "`"))) 237 (indent-to indent))
597 ;; Inside a multi-line string. Don't mess with indentation. 238 ;; If initial point was within line's indentation,
598 nil) 239 ;; position after the indentation. Else stay at same point in text.
599 (cs 240 (if (> (- (point-max) pos) (point))
600 ;; Inside a general comment 241 (goto-char (- (point-max) pos))))))
601 (goto-char (car cs)) 242
602 (forward-char 1) 243 (defun go-beginning-of-defun (&optional count)
603 (current-column)) 244 (unless count (setq count 1))
604 (t 245 (let ((first t) failure)
605 ;; Not in a multi-line string or comment 246 (dotimes (i (abs count))
606 (let ((indent 0) 247 (while (and (not failure)
607 (inside-indenting-paren nil)) 248 (or first (go-in-string-or-comment-p)))
608 ;; Count every enclosing brace, plus parens that follow 249 (if (>= count 0)
609 ;; import, const, var, or type and indent according to 250 (progn
610 ;; depth. This simple rule does quite well, but also has a 251 (go--backward-irrelevant)
611 ;; very large extent. It would be better if we could mimic 252 (if (not (re-search-backward go-func-meth-regexp nil t))
612 ;; some nearby indentation. 253 (setq failure t)))
613 (save-excursion 254 (if (looking-at go-func-meth-regexp)
614 (skip-chars-forward "})") 255 (forward-char))
615 (let ((first t)) 256 (if (not (re-search-forward go-func-meth-regexp nil t))
616 (dolist (nest (go-mode-nesting)) 257 (setq failure t)))
617 (case (char-after (car nest)) 258 (setq first nil)))
618 ((?\{) 259 (if (< count 0)
619 (incf indent tab-width)) 260 (beginning-of-line))
620 ((?\() 261 (not failure)))
621 (goto-char (car nest)) 262
622 (go-mode-backward-skip-comments) 263 (defun go-end-of-defun ()
623 (backward-char) 264 (let (orig-level)
624 ;; Really just want the token before 265 ;; It can happen that we're not placed before a function by emacs
625 (when (looking-back "\\<import\\|const\\|var\\|type\\|package " 266 (if (not (looking-at "func"))
626 (max (- (point) 7) (point-min))) 267 (go-beginning-of-defun -1))
627 (incf indent tab-width) 268 (skip-chars-forward "^{")
628 (when first 269 (forward-char)
629 (setq inside-indenting-paren t))))) 270 (setq orig-level (go-paren-level))
630 (setq first nil)))) 271 (while (>= (go-paren-level) orig-level)
631 272 (skip-chars-forward "^}")
632 ;; case, default, and labels are outdented 1 level 273 (forward-char))))
633 (when (looking-at "\\<case\\>\\|\\<default\\>\\|\\w+\\s *:\\(\\S.\\|$\ \)")
634 (decf indent tab-width))
635
636 » (when (looking-at "\\w+\\s *:.+,\\s *$")
637 » (incf indent tab-width))
638
639 ;; Continuation lines are indented 1 level
640 (beginning-of-line)» » ; back up to end of previous line
641 » (backward-char)
642 (go-mode-backward-skip-comments) ; back up past any comments
643 (when (case (char-before)
644 ((nil ?\{ ?:)
645 ;; At the beginning of a block or the statement
646 ;; following a label.
647 nil)
648 ((?\()
649 ;; Usually a continuation line in an expression,
650 ;; unless this paren is part of a factored
651 ;; declaration.
652 (not inside-indenting-paren))
653 ((?,)
654 ;; Could be inside a literal. We're a little
655 ;; conservative here and consider any comma within
656 ;; curly braces (as opposed to parens) to be a
657 ;; literal separator. This will fail to recognize
658 ;; line-breaks in parallel assignments as
659 ;; continuation lines.
660 (let ((depth (go-mode-nesting)))
661 (and depth
662 (not (eq (char-after (caar depth)) ?\{)))))
663 (t
664 ;; We're in the middle of a block. Did the
665 ;; previous line end with an implicit or explicit
666 ;; semicolon?
667 (not (go-mode-semicolon-p))))
668 (incf indent tab-width))
669
670 (max indent 0)))))))
671
672 (defun go-mode-indent-line ()
673 "Indent the current line according to `go-mode-indentation'."
674 (interactive)
675
676 ;; turn off case folding to distinguish keywords from identifiers
677 ;; e.g. "default" is a keyword; "Default" can be a variable name.
678 (let ((case-fold-search nil))
679 (let ((col (go-mode-indentation)))
680 (when col
681 » (let ((offset (- (current-column) (current-indentation))))
682 » (indent-line-to col)
683 » (when (> offset 0)
684 » (forward-char offset)))))))
685
686 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
687 ;; Go mode
688 ;;
689 274
690 ;;;###autoload 275 ;;;###autoload
691 (define-derived-mode go-mode nil "Go" 276 (define-derived-mode go-mode fundamental-mode "Go"
692 "Major mode for editing Go source text. 277 "Major mode for editing Go source text.
693 278
694 This provides basic syntax highlighting for keywords, built-ins, 279 This mode provides (not just) basic editing capabilities for
695 functions, and some types. It also provides indentation that is 280 working with Go code. It offers almost complete syntax
696 \(almost) identical to gofmt." 281 highlighting, indentation that is almost identical to gofmt and
282 proper parsing of the buffer content to allow features such as
283 navigation by function, manipulation of comments or detection of
284 strings.
285
286 In addition to these core features, it offers various features to
287 help with writing Go code. You can directly run buffer content
288 through gofmt, read godoc documentation from within Emacs, modify
289 and clean up the list of package imports or interact with the
290 Playground (uploading and downloading pastes).
291
292 The following extra functions are defined:
293
294 - `gofmt'
295 - `godoc'
296 - `go-import-add'
297 - `go-remove-unused-imports'
298 - `go-goto-imports'
299 - `go-play-buffer' and `go-play-region'
300 - `go-download-play'
301
302 If you want to automatically run `gofmt' before saving a file,
303 add the following hook to your emacs configuration:
304
305 \(add-hook 'before-save-hook 'gofmt-before-save)
306
307 If you're looking for even more integration with Go, namely
308 on-the-fly syntax checking, auto-completion and snippets, it is
309 recommended that you look at goflymake
310 \(https://github.com/dougm/goflymake), gocode
311 \(https://github.com/nsf/gocode) and yasnippet-go
312 \(https://github.com/dominikh/yasnippet-go)"
697 313
698 ;; Font lock 314 ;; Font lock
699 (set (make-local-variable 'font-lock-defaults) 315 (set (make-local-variable 'font-lock-defaults)
700 '(go-mode-font-lock-keywords nil nil nil nil)) 316 '(go--build-font-lock-keywords))
701
702 ;; Remove stale text properties
703 (save-restriction
704 (widen)
705 (let ((modified (buffer-modified-p)))
706 (remove-text-properties 1 (point-max)
707 '(go-mode-cs nil go-mode-nesting nil))
708 ;; remove-text-properties marks the buffer modified. undo that if it
709 ;; wasn't originally marked modified.
710 (set-buffer-modified-p modified)))
711
712 ;; Reset the syntax mark caches
713 (setq go-mode-mark-cs-end 1
714 go-mode-mark-nesting-end 1)
715 (add-hook 'before-change-functions #'go-mode-mark-clear-cache nil t)
716 (add-hook 'after-change-functions #'go-mode-mark-clear-cs nil t)
717 317
718 ;; Indentation 318 ;; Indentation
719 (set (make-local-variable 'indent-line-function) 319 (set (make-local-variable 'indent-line-function) 'go-mode-indent-line)
720 #'go-mode-indent-line)
721 (add-hook 'after-change-functions #'go-mode-delayed-electric-hook nil t)
722 320
723 ;; Comments 321 ;; Comments
724 (set (make-local-variable 'comment-start) "// ") 322 (set (make-local-variable 'comment-start) "// ")
725 (set (make-local-variable 'comment-end) "") 323 (set (make-local-variable 'comment-end) "")
726 (set (make-local-variable 'comment-use-syntax) nil) 324 (set (make-local-variable 'comment-use-syntax) t)
727 (set (make-local-variable 'comment-start-skip) "\\([ \t]*\\)// ") 325 (set (make-local-variable 'comment-start-skip) "\\(//+\\|/\\*+\\)\\s *")
326
327 (set (make-local-variable 'beginning-of-defun-function) 'go-beginning-of-defun )
328 (set (make-local-variable 'end-of-defun-function) 'go-end-of-defun)
329
330 (set (make-local-variable 'parse-sexp-lookup-properties) t)
331 (if (boundp 'syntax-propertize-function)
332 (set (make-local-variable 'syntax-propertize-function) 'go-propertize-synt ax))
333
334 (set (make-local-variable 'go-dangling-cache) (make-hash-table :test 'eql))
335 (add-hook 'before-change-functions (lambda (x y) (setq go-dangling-cache (make -hash-table :test 'eql))) t t)
336
337
338 (setq imenu-generic-expression
339 '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1)
340 ("func" "^func *\\(.*\\) {" 1)))
341 (imenu-add-to-menubar "Index")
728 342
729 ;; Go style 343 ;; Go style
730 (setq indent-tabs-mode t) 344 (setq indent-tabs-mode t)
731 345
732 ;; Handle unit test failure output in compilation-mode 346 ;; Handle unit test failure output in compilation-mode
733 ;; 347 ;;
734 ;; Note the final t argument to add-to-list for append, ie put these at the 348 ;; Note the final t argument to add-to-list for append, ie put these at the
735 ;; *ends* of compilation-error-regexp-alist[-alist]. We want go-test to be 349 ;; *ends* of compilation-error-regexp-alist[-alist]. We want go-test to be
736 ;; handled first, otherwise other elements will match that don't work, and 350 ;; handled first, otherwise other elements will match that don't work, and
737 ;; those alists are traversed in *reverse* order: 351 ;; those alists are traversed in *reverse* order:
738 ;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2001-12/msg00674.html 352 ;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2001-12/msg00674.html
739 (when (and (boundp 'compilation-error-regexp-alist) 353 (when (and (boundp 'compilation-error-regexp-alist)
740 (boundp 'compilation-error-regexp-alist-alist)) 354 (boundp 'compilation-error-regexp-alist-alist))
741 (add-to-list 'compilation-error-regexp-alist 'go-test t) 355 (add-to-list 'compilation-error-regexp-alist 'go-test t)
742 (add-to-list 'compilation-error-regexp-alist-alist 356 (add-to-list 'compilation-error-regexp-alist-alist
743 '(go-test . ("^\t+\\([^()\t\n]+\\):\\([0-9]+\\):? .*$" 1 2)) t))) 357 '(go-test . ("^\t+\\([^()\t\n]+\\):\\([0-9]+\\):? .*$" 1 2)) t) ))
744 358
745 ;;;###autoload 359 ;;;###autoload
746 (add-to-list 'auto-mode-alist (cons "\\.go$" #'go-mode)) 360 (add-to-list 'auto-mode-alist (cons "\\.go\\'" 'go-mode))
747 361
748 (defun go-mode-reload ()
749 "Reload go-mode.el and put the current buffer into Go mode.
750 Useful for development work."
751
752 (interactive)
753 (unload-feature 'go-mode)
754 (require 'go-mode)
755 (go-mode))
756
757 ;;;###autoload
758 (defun gofmt () 362 (defun gofmt ()
759 "Pipe the current buffer through the external tool `gofmt`. 363 "Pipe the current buffer through the external tool `gofmt`.
760 Replace the current buffer on success; display errors on failure." 364 Replace the current buffer on success; display errors on failure."
761 365
762 (interactive) 366 (interactive)
763 (let ((currconf (current-window-configuration))) 367 (let ((currconf (current-window-configuration)))
764 (let ((srcbuf (current-buffer)) 368 (let ((srcbuf (current-buffer))
765 (filename buffer-file-name) 369 (filename buffer-file-name)
766 (patchbuf (get-buffer-create "*Gofmt patch*"))) 370 (patchbuf (get-buffer-create "*Gofmt patch*")))
767 (with-current-buffer patchbuf 371 (with-current-buffer patchbuf
768 (let ((errbuf (get-buffer-create "*Gofmt Errors*")) 372 (let ((errbuf (get-buffer-create "*Gofmt Errors*"))
769 (coding-system-for-read 'utf-8) ;; use utf-8 with subprocesses 373 ;; use utf-8 with subprocesses
374 (coding-system-for-read 'utf-8)
770 (coding-system-for-write 'utf-8)) 375 (coding-system-for-write 'utf-8))
771 (with-current-buffer errbuf 376 (with-current-buffer errbuf
772 (toggle-read-only 0) 377 (let ((inhibit-read-only t))
773 (erase-buffer)) 378 (erase-buffer)))
774 (with-current-buffer srcbuf 379 (with-current-buffer srcbuf
775 (save-restriction 380 (save-restriction
776 (let (deactivate-mark) 381 (let (deactivate-mark)
777 (widen) 382 (widen)
778 ; If this is a new file, diff-mode can't apply a 383 ;; If this is a new file, diff-mode can't apply a
779 ; patch to a non-exisiting file, so replace the buffer 384 ;; patch to a non-exisiting file, so replace the buffer
780 ; completely with the output of 'gofmt'. 385 ;; completely with the output of 'gofmt'.
781 ; If the file exists, patch it to keep the 'undo' list happy. 386 ;; If the file exists, patch it to keep the 'undo' list happy.
782 (let* ((newfile (not (file-exists-p filename))) 387 (let* ((newfile (not (file-exists-p filename)))
783 (flag (if newfile "" " -d"))) 388 (flag (if newfile "" " -d")))
784 (if (= 0 (shell-command-on-region (point-min) (point-max) 389
785 (concat "gofmt" flag) 390 ;; diff-mode doesn't work too well with missing
786 patchbuf nil errbuf)) 391 ;; end-of-file newline, so add one
787 ; gofmt succeeded: replace buffer or apply patch hunks. 392 (if (/= (char-after (1- (point-max))) ?\n)
393 (save-excursion
394 (goto-char (point-max))
395 (insert ?\n)))
396
397 (if (zerop (shell-command-on-region (point-min) (point-max)
398 (concat "gofmt" flag)
399 patchbuf nil errbuf))
400 ;; gofmt succeeded: replace buffer or apply patch hunks.
788 (let ((old-point (point)) 401 (let ((old-point (point))
789 (old-mark (mark t))) 402 (old-mark (mark t)))
790 (kill-buffer errbuf) 403 (kill-buffer errbuf)
791 (if newfile 404 (if newfile
792 ; New file, replace it (diff-mode won't work) 405 ;; New file, replace it (diff-mode won't work)
793 (gofmt-replace-buffer srcbuf patchbuf) 406 (gofmt--replace-buffer srcbuf patchbuf)
794 ; Existing file, patch it 407 ;; Existing file, patch it
795 (gofmt-apply-patch filename srcbuf patchbuf)) 408 (gofmt--apply-patch filename srcbuf patchbuf))
796 (goto-char (min old-point (point-max))) 409 (goto-char (min old-point (point-max)))
797 ;; Restore the mark and point 410 ;; Restore the mark and point
798 (if old-mark (push-mark (min old-mark (point-max)) t)) 411 (if old-mark (push-mark (min old-mark (point-max)) t))
799 (set-window-configuration currconf)) 412 (set-window-configuration currconf))
800 413
801 ;; gofmt failed: display the errors 414 ;; gofmt failed: display the errors
802 (gofmt-process-errors filename errbuf)))))) 415 (message "Could not apply gofmt. Check errors for details")
416 (gofmt--process-errors filename errbuf))))))
803 417
804 ;; Collapse any window opened on outbuf if shell-command-on-region 418 ;; Collapse any window opened on outbuf if shell-command-on-region
805 ;; displayed it. 419 ;; displayed it.
806 (delete-windows-on patchbuf))) 420 (delete-windows-on patchbuf)))
807 (kill-buffer patchbuf)))) 421 (kill-buffer patchbuf))))
808 422
809 (defun gofmt-replace-buffer (srcbuf patchbuf) 423 (defun gofmt--replace-buffer (srcbuf patchbuf)
810 (with-current-buffer srcbuf 424 (with-current-buffer srcbuf
811 (erase-buffer) 425 (erase-buffer)
812 (insert-buffer-substring patchbuf))) 426 (insert-buffer-substring patchbuf))
427 (message "Applied gofmt"))
813 428
814 (defconst gofmt-stdin-tag "<standard input>") 429 (defun gofmt--apply-patch (filename srcbuf patchbuf)
430 ;; apply all the patch hunks
431 (let (changed)
432 (with-current-buffer patchbuf
433 (goto-char (point-min))
434 ;; The .* is for TMPDIR, but to avoid dealing with TMPDIR
435 ;; having a trailing / or not, it's easier to just search for .*
436 ;; especially as we're only replacing the first instance.
437 (if (re-search-forward "^--- \\(.*/gofmt[0-9]*\\)" nil t)
438 (replace-match filename nil nil nil 1))
439 (condition-case nil
440 (while t
441 (diff-hunk-next)
442 (diff-apply-hunk)
443 (setq changed t))
444 ;; When there's no more hunks, diff-hunk-next signals an error, ignore i t
445 (error nil)))
446 (if changed (message "Applied gofmt") (message "Buffer was already gofmted") )))
815 447
816 (defun gofmt-apply-patch (filename srcbuf patchbuf) 448 (defun gofmt--process-errors (filename errbuf)
817 (require 'diff-mode)
818 ;; apply all the patch hunks
819 (with-current-buffer patchbuf
820 (goto-char (point-min))
821 ;; The .* is for TMPDIR, but to avoid dealing with TMPDIR
822 ;; having a trailing / or not, it's easier to just search for .*
823 ;; especially as we're only replacing the first instance.
824 (if (re-search-forward "^--- \\(.*/gofmt[0-9]*\\)" nil t)
825 (replace-match filename nil nil nil 1))
826 (condition-case nil
827 (while t
828 (diff-hunk-next)
829 (diff-apply-hunk))
830 ;; When there's no more hunks, diff-hunk-next signals an error, ignore it
831 (error nil))))
832
833 (defun gofmt-process-errors (filename errbuf)
834 ;; Convert the gofmt stderr to something understood by the compilation mode. 449 ;; Convert the gofmt stderr to something understood by the compilation mode.
835 (with-current-buffer errbuf 450 (with-current-buffer errbuf
836 (goto-char (point-min)) 451 (goto-char (point-min))
837 (insert "gofmt errors:\n") 452 (insert "gofmt errors:\n")
838 (if (search-forward gofmt-stdin-tag nil t) 453 (if (search-forward gofmt-stdin-tag nil t)
839 (replace-match (file-name-nondirectory filename) nil t)) 454 (replace-match (file-name-nondirectory filename) nil t))
840 (display-buffer errbuf) 455 (display-buffer errbuf)
841 (compilation-mode))) 456 (compilation-mode)))
842 457
843 ;;;###autoload 458 ;;;###autoload
844 (defun gofmt-before-save () 459 (defun gofmt-before-save ()
845 "Add this to .emacs to run gofmt on the current buffer when saving: 460 "Add this to .emacs to run gofmt on the current buffer when saving:
846 (add-hook 'before-save-hook #'gofmt-before-save)" 461 (add-hook 'before-save-hook 'gofmt-before-save).
462
463 Note that this will cause go-mode to get loaded the first time
464 you save any file, kind of defeating the point of autoloading."
847 465
848 (interactive) 466 (interactive)
849 (when (eq major-mode 'go-mode) (gofmt))) 467 (when (eq major-mode 'go-mode) (gofmt)))
850 468
851 (defun godoc-read-query () 469 (defun godoc--read-query ()
852 "Read a godoc query from the minibuffer." 470 "Read a godoc query from the minibuffer."
853 ;; Compute the default query as the symbol under the cursor. 471 ;; Compute the default query as the symbol under the cursor.
854 ;; TODO: This does the wrong thing for e.g. multipart.NewReader (it only grabs 472 ;; TODO: This does the wrong thing for e.g. multipart.NewReader (it only grabs
855 ;; half) but I see no way to disambiguate that from e.g. foobar.SomeMethod. 473 ;; half) but I see no way to disambiguate that from e.g. foobar.SomeMethod.
856 (let* ((bounds (bounds-of-thing-at-point 'symbol)) 474 (let* ((bounds (bounds-of-thing-at-point 'symbol))
857 (symbol (if bounds 475 (symbol (if bounds
858 (buffer-substring-no-properties (car bounds) 476 (buffer-substring-no-properties (car bounds)
859 (cdr bounds))))) 477 (cdr bounds)))))
860 (read-string (if symbol 478 (read-string (if symbol
861 (format "godoc (default %s): " symbol) 479 (format "godoc (default %s): " symbol)
862 "godoc: ") 480 "godoc: ")
863 nil nil symbol))) 481 nil nil symbol)))
864 482
865 (defun godoc-get-buffer (query) 483 (defun godoc--get-buffer (query)
866 "Get an empty buffer for a godoc query." 484 "Get an empty buffer for a godoc query."
867 (let* ((buffer-name (concat "*godoc " query "*")) 485 (let* ((buffer-name (concat "*godoc " query "*"))
868 (buffer (get-buffer buffer-name))) 486 (buffer (get-buffer buffer-name)))
869 ;; Kill the existing buffer if it already exists. 487 ;; Kill the existing buffer if it already exists.
870 (when buffer (kill-buffer buffer)) 488 (when buffer (kill-buffer buffer))
871 (get-buffer-create buffer-name))) 489 (get-buffer-create buffer-name)))
872 490
873 (defun godoc-buffer-sentinel (proc event) 491 (defun godoc--buffer-sentinel (proc event)
874 "Sentinel function run when godoc command completes." 492 "Sentinel function run when godoc command completes."
875 (with-current-buffer (process-buffer proc) 493 (with-current-buffer (process-buffer proc)
876 (cond ((string= event "finished\n") ;; Successful exit. 494 (cond ((string= event "finished\n") ;; Successful exit.
877 (goto-char (point-min)) 495 (goto-char (point-min))
878 (display-buffer (current-buffer) 'not-this-window)) 496 (view-mode 1)
879 ((not (= (process-exit-status proc) 0)) ;; Error exit. 497 (display-buffer (current-buffer) t))
498 ((/= (process-exit-status proc) 0) ;; Error exit.
880 (let ((output (buffer-string))) 499 (let ((output (buffer-string)))
881 (kill-buffer (current-buffer)) 500 (kill-buffer (current-buffer))
882 (message (concat "godoc: " output))))))) 501 (message (concat "godoc: " output)))))))
883 502
884 ;;;###autoload 503 ;;;###autoload
885 (defun godoc (query) 504 (defun godoc (query)
886 "Show go documentation for a query, much like M-x man." 505 "Show go documentation for a query, much like M-x man."
887 (interactive (list (godoc-read-query))) 506 (interactive (list (godoc--read-query)))
888 (unless (string= query "") 507 (unless (string= query "")
889 (set-process-sentinel 508 (set-process-sentinel
890 (start-process-shell-command "godoc" (godoc-get-buffer query) 509 (start-process-shell-command "godoc" (godoc--get-buffer query)
891 (concat "godoc " query)) 510 (concat "godoc " query))
892 'godoc-buffer-sentinel) 511 'godoc--buffer-sentinel)
893 nil)) 512 nil))
894 513
514 (defun go-goto-imports ()
515 "Move point to the block of imports.
516
517 If using
518
519 import (
520 \"foo\"
521 \"bar\"
522 )
523
524 it will move point directly behind the last import.
525
526 If using
527
528 import \"foo\"
529 import \"bar\"
530
531 it will move point to the next line after the last import.
532
533 If no imports can be found, point will be moved after the package
534 declaration."
535 (interactive)
536 ;; FIXME if there's a block-commented import before the real
537 ;; imports, we'll jump to that one.
538
539 ;; Generally, this function isn't very forgiving. it'll bark on
540 ;; extra whitespace. It works well for clean code.
541 (let ((old-point (point)))
542 (goto-char (point-min))
543 (cond
544 ((re-search-forward "^import ([^)]+)" nil t)
545 (backward-char 2)
546 'block)
547 ((re-search-forward "\\(^import \\([^\"]+ \\)?\"[^\"]+\"\n?\\)+" nil t)
548 'single)
549 ((re-search-forward "^[[:space:]\n]*package .+?\n" nil t)
550 (message "No imports found, moving point after package declaration")
551 'none)
552 (t
553 (goto-char old-point)
554 (message "No imports or package declaration found. Is this really a Go fil e?")
555 'fail))))
556
557 (defun go-play-buffer ()
558 "Like `go-play-region', but acts on the entire buffer."
559 (interactive)
560 (go-play-region (point-min) (point-max)))
561
562 (defun go-play-region (start end)
563 "Send the region to the Playground and stores the resulting
564 link in the kill ring."
565 (interactive "r")
566 (let* ((url-request-method "POST")
567 (url-request-extra-headers
568 '(("Content-Type" . "application/x-www-form-urlencoded")))
569 (url-request-data (buffer-substring-no-properties start end))
570 (content-buf (url-retrieve
571 "http://play.golang.org/share"
572 (lambda (arg)
573 (cond
574 ((equal :error (car arg))
575 (signal 'go-play-error (cdr arg)))
576 (t
577 (re-search-forward "\n\n")
578 (kill-new (format "http://play.golang.org/p/%s" (buff er-substring (point) (point-max))))
579 (message "http://play.golang.org/p/%s" (buffer-substr ing (point) (point-max)))))))))))
580
581 ;;;###autoload
582 (defun go-download-play (url)
583 "Downloads a paste from the playground and inserts it in a Go
584 buffer. Tries to look for a URL at point."
585 (interactive (list (read-from-minibuffer "Playground URL: " (ffap-url-p (ffap- string-at-point 'url)))))
586 (with-current-buffer
587 (let ((url-request-method "GET") url-request-data url-request-extra-header s)
588 (url-retrieve-synchronously (concat url ".go")))
589 (let ((buffer (generate-new-buffer (concat (car (last (split-string url "/") )) ".go"))))
590 (goto-char (point-min))
591 (re-search-forward "\n\n")
592 (copy-to-buffer buffer (point) (point-max))
593 (kill-buffer)
594 (with-current-buffer buffer
595 (go-mode)
596 (switch-to-buffer buffer)))))
597
598 (defun go-propertize-syntax (start end)
599 (save-excursion
600 (goto-char start)
601 (while (search-forward "\\" end t)
602 (put-text-property (1- (point)) (point) 'syntax-table (if (= (char-after) ?`) '(1) '(9))))))
603
604 ;; ;; Commented until we actually make use of this function
605 ;; (defun go--common-prefix (sequences)
606 ;; ;; mismatch and reduce are cl
607 ;; (assert sequences)
608 ;; (flet ((common-prefix (s1 s2)
609 ;; (let ((diff-pos (mismatch s1 s2)))
610 ;; (if diff-pos (subseq s1 0 diff-pos) s1))))
611 ;; (reduce #'common-prefix sequences)))
612
613 (defun go-import-add (arg import)
614 "Add a new import to the list of imports.
615
616 When called with a prefix argument asks for an alternative name
617 to import the package as.
618
619 If no list exists yet, one will be created if possible.
620
621 If an identical import has been commented, it will be
622 uncommented, otherwise a new import will be added."
623
624 ;; - If there's a matching `// import "foo"`, uncomment it
625 ;; - If we're in an import() block and there's a matching `"foo"`, uncomment i t
626 ;; - Otherwise add a new import, with the appropriate syntax
627 (interactive
628 (list
629 current-prefix-arg
630 (replace-regexp-in-string "^[\"']\\|[\"']$" "" (completing-read "Package: " (go-packages)))))
631 (save-excursion
632 (let (as line import-start)
633 (if arg
634 (setq as (read-from-minibuffer "Import as: ")))
635 (if as
636 (setq line (format "%s \"%s\"" as import))
637 (setq line (format "\"%s\"" import)))
638
639 (goto-char (point-min))
640 (if (re-search-forward (concat "^[[:space:]]*//[[:space:]]*import " line " $") nil t)
641 (uncomment-region (line-beginning-position) (line-end-position))
642 (case (go-goto-imports)
643 ('fail (message "Could not find a place to add import."))
644 ('block
645 (save-excursion
646 (re-search-backward "^import (")
647 (setq import-start (point)))
648 (if (re-search-backward (concat "^[[:space:]]*//[[:space:]]*" line " $") import-start t)
649 (uncomment-region (line-beginning-position) (line-end-position))
650 (insert "\n\t" line)))
651 ('single (insert "import " line "\n"))
652 ('none (insert "\nimport (\n\t" line "\n)\n")))))))
653
654 (defun go-root-and-paths ()
adonovan 2013/02/21 18:45:49 I recommend adding docstrings even for short inter
Dominik Honnef 2013/02/21 19:08:34 I'll sneak that in in a future CL. I really want t
655 (let* ((output (process-lines "go" "env" "GOROOT" "GOPATH"))
656 (root (car output))
657 (paths (split-string (car (cdr output)) ":")))
658 (append (list root) paths)))
659
660 (defun go-packages ()
661 (sort
662 (delete-dups
663 (mapcan
664 (lambda (topdir)
665 (let ((pkgdir (concat topdir "/pkg/")))
666 (mapcan (lambda (dir)
667 (mapcar (lambda (file)
668 (let ((sub (substring file (length pkgdir) -2)))
669 (unless (or (string-prefix-p "obj/" sub) (string- prefix-p "tool/" sub))
670 (mapconcat 'identity (cdr (split-string sub "/" )) "/"))))
671 (if (file-directory-p dir)
672 (directory-files dir t "\\.a$"))))
673 (if (file-directory-p pkgdir)
674 (find-lisp-find-files-internal pkgdir 'find-lisp-file-predi cate-is-directory 'find-lisp-default-directory-predicate)))))
675 (go-root-and-paths)))
676 'string<))
677
678 (defun go-unused-imports-lines ()
679 ;; FIXME Technically, -o /dev/null fails in quite some cases (on
adonovan 2013/02/21 18:45:49 Prefer "TODO($USER): fix: ..." to "FIXME: ...".
Dominik Honnef 2013/02/21 19:08:34 Same as above, in a later CL.
680 ;; Windows, when compiling from within GOPATH). Practically,
681 ;; however, it has the same end result: There won't be a
682 ;; compiled binary/archive, and we'll get our import errors when
683 ;; there are any.
684 (reverse (remove nil
685 (mapcar
686 (lambda (line)
687 (if (string-match "^\\(.+\\):\\([[:digit:]]+\\): imported and not used: \".+\"$" line)
688 (if (string= (file-truename (match-string 1 line)) (fi le-truename buffer-file-name))
689 (string-to-number (match-string 2 line)))))
690 (split-string (shell-command-to-string
691 (if (string-match "_test\.go$" buffer-file-tr uename)
692 "go test -c"
693 "go build -o /dev/null")) "\n")))))
694
695 (defun go-remove-unused-imports (arg)
696 "Removes all unused imports. If ARG is non-nil, unused imports
697 will be commented, otherwise they will be removed completely."
698 (interactive "P")
699 (save-excursion
700 (let ((cur-buffer (current-buffer)) flymake-state lines)
701 (when (boundp 'flymake-mode)
702 (setq flymake-state flymake-mode)
703 (flymake-mode-off))
704 (save-some-buffers nil (lambda () (equal cur-buffer (current-buffer))))
705 (if (buffer-modified-p)
706 (message "Cannot operate on unsaved buffer")
707 (setq lines (go-unused-imports-lines))
708 (dolist (import lines)
709 (goto-char (point-min))
710 (forward-line (1- import))
711 (beginning-of-line)
712 (if arg
713 (comment-region (line-beginning-position) (line-end-position))
714 (let ((kill-whole-line t))
715 (kill-line))))
716 (message "Removed %d imports" (length lines)))
717 (if flymake-state (flymake-mode-on)))))
718
895 (provide 'go-mode) 719 (provide 'go-mode)
OLDNEW
« no previous file with comments | « no previous file | misc/emacs/go-mode-load.el » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b