Left: | ||
Right: |
OLD | NEW |
---|---|
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) |
OLD | NEW |