~ubuntu-branches/ubuntu/vivid/golang/vivid

« back to all changes in this revision

Viewing changes to misc/emacs/go-mode.el

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2013-08-20 14:06:23 UTC
  • mfrom: (14.1.23 saucy-proposed)
  • Revision ID: package-import@ubuntu.com-20130820140623-b414jfxi3m0qkmrq
Tags: 2:1.1.2-2ubuntu1
* Merge from Debian unstable (LP: #1211749, #1202027). Remaining changes:
  - 016-armhf-elf-header.patch: Use correct ELF header for armhf binaries.
  - d/control,control.cross: Update Breaks/Replaces for Ubuntu
    versions to ensure smooth upgrades, regenerate control file.

Show diffs side-by-side

added added

removed removed

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