├── Context.sublime-menu
├── Default.sublime-commands
├── ExampleProject.sublime-project
├── GoTools.sublime-build
├── GoTools.sublime-settings
├── GoTools.tmLanguage
├── GoTools.tmLanguage.json
├── LICENSE.md
├── Main.sublime-menu
├── README.md
├── gotools_build.py
├── gotools_format.py
├── gotools_goto_def.py
├── gotools_oracle.py
├── gotools_rename.py
├── gotools_settings.py
├── gotools_suggestions.py
└── gotools_util.py
/Context.sublime-menu:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "command": "build",
4 | "caption": "Go Build"
5 | },
6 | {
7 | "command": "build",
8 | "caption": "Go Tests",
9 | "args": {
10 | "variant": "Run Tests"
11 | }
12 | },
13 | {
14 | "command": "build",
15 | "caption": "Go Test at Cursor",
16 | "args": {
17 | "variant": "Run Test at Cursor"
18 | }
19 | },
20 | {
21 | "command": "build",
22 | "caption": "Go Test Current Package",
23 | "args": {
24 | "variant": "Run Current Package Tests"
25 | }
26 | },
27 | {
28 | "command": "build",
29 | "caption": "Go Test Tagged",
30 | "args": {
31 | "variant": "Run Tagged Tests"
32 | }
33 | }
34 | ]
35 |
--------------------------------------------------------------------------------
/Default.sublime-commands:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "GoTools: Go to definition",
4 | "command": "gotools_goto_def"
5 | },
6 | {
7 | "caption": "GoTools: Format",
8 | "command": "gotools_format"
9 | },
10 | {
11 | "caption": "GoTools: Rename",
12 | "command": "gotools_rename"
13 | },
14 | {
15 | "caption": "GoTools: Oracle: Callers",
16 | "command": "gotools_oracle",
17 | "args": {
18 | "command": "callers"
19 | }
20 | },
21 | {
22 | "caption": "GoTools: Oracle: Callees",
23 | "command": "gotools_oracle",
24 | "args": {
25 | "command": "callees"
26 | }
27 | },
28 | {
29 | "caption": "GoTools: Oracle: Callstack",
30 | "command": "gotools_oracle",
31 | "args": {
32 | "command": "callstack"
33 | }
34 | },
35 | {
36 | "caption": "GoTools: Oracle: Describe",
37 | "command": "gotools_oracle",
38 | "args": {
39 | "command": "describe"
40 | }
41 | },
42 | {
43 | "caption": "GoTools: Oracle: Freevars",
44 | "command": "gotools_oracle",
45 | "args": {
46 | "command": "freevars"
47 | }
48 | },
49 | {
50 | "caption": "GoTools: Oracle: Implements",
51 | "command": "gotools_oracle",
52 | "args": {
53 | "command": "implements"
54 | }
55 | },
56 | {
57 | "caption": "GoTools: Oracle: Peers",
58 | "command": "gotools_oracle",
59 | "args": {
60 | "command": "peers"
61 | }
62 | },
63 | {
64 | "caption": "GoTools: Oracle: Referrers",
65 | "command": "gotools_oracle",
66 | "args": {
67 | "command": "referrers"
68 | }
69 | }
70 | ]
--------------------------------------------------------------------------------
/ExampleProject.sublime-project:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [],
3 | "settings": {
4 | "GoTools": {
5 | // A custom GOPATH for this project; ${gopath} is expanded by the global
6 | // settings value.
7 | "gopath": "${gopath}/src/github.com/some/project/Godeps/_workspace:${gopath}",
8 |
9 | // The root package (or namespace) of a project.
10 | "project_package": "github.com/some/project",
11 |
12 | // A list of sub-packages relative to project_packages to be included in
13 | // builds.
14 | "build_packages": ["cmd/myprogram"],
15 |
16 | // A list of sub-packages relative to project_package to be included in
17 | // test discovery.
18 | "test_packages": ["cmd", "lib", "examples"],
19 |
20 | // A list of sub-packages relative to project_packages to be identified
21 | // as tagged tests during test discovery.
22 | "tagged_test_packages": ["test/integration"],
23 |
24 | // The tags to apply to `go test` when running tagged tests.
25 | "tagged_test_tags": ["integration"],
26 |
27 | // Runs `go test -v` for verbose output.
28 | "verbose_tests": true,
29 |
30 | // A go time string; test timeouts are set via the `-timeout` flag.
31 | "test_timeout": "10s"
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/GoTools.sublime-build:
--------------------------------------------------------------------------------
1 | {
2 | "cmd": [],
3 | "working_dir": "",
4 | "target": "gotools_build",
5 | "selector": "source.go",
6 |
7 | "variants": [
8 | {
9 | "name": "Build",
10 | },
11 | {
12 | "name": "Clean Build",
13 | "clean": true
14 | },
15 | {
16 | "name": "Run Tests",
17 | "task": "test_packages"
18 | },
19 | {
20 | "name": "Run Tagged Tests",
21 | "task": "test_tagged_packages"
22 | },
23 | {
24 | "name": "Run Test at Cursor",
25 | "task": "test_at_cursor"
26 | },
27 | {
28 | "name": "Run Current Package Tests",
29 | "task": "test_current_package"
30 | },
31 | {
32 | "name": "Run Last Test",
33 | "task": "test_last"
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/GoTools.sublime-settings:
--------------------------------------------------------------------------------
1 | {
2 | // The GOPATH used for plugin operations. May be overridden and used as a
3 | // substitution value in the gopath project setting. If left blank or
4 | // undefined, the default will be the system GOPATH environment variable, or
5 | // the GOPATH reported by `go env` if the system GOPATH environment variable
6 | // is unset.
7 | "gopath": "",
8 |
9 | // Format source files each time they're saved.
10 | "format_on_save": true,
11 |
12 | // A formatting backend (must be either 'gofmt', 'goimports' or 'both').
13 | // The 'both' option will first run 'goimports' then 'gofmt'
14 | "format_backend": "gofmt",
15 |
16 | // A go-to-definition backend (must be either 'oracle' or 'godef').
17 | "goto_def_backend": "godef",
18 |
19 | // Enable gocode autocompletion.
20 | "autocomplete": true,
21 |
22 | // Enable GoTools debugging output to the Sublime console.
23 | "debug_enabled": false,
24 |
25 | // Use tabs for Go source files by default.
26 | "translate_tabs_to_spaces": false,
27 |
28 | // Use GoTools for all Go source files.
29 | "extensions": ["go"]
30 | }
31 |
--------------------------------------------------------------------------------
/GoTools.tmLanguage:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | comment
6 | Go allows any Unicode character to be used in identifiers, so our identifier regex is: \b([[:alpha:]_]+[[:alnum:]_]*)\b
7 | fileTypes
8 |
9 | go
10 |
11 | firstLineMatch
12 | -[*]-( Mode:)? Go -[*]-
13 | foldingStartMarker
14 | (?x)
15 | /\*\*(?!\*) # opening C-style comment with 2 asterisks but no third later on
16 | | # OR
17 | ^ # start of line...
18 | (?! # ...which does NOT contain...
19 | [^{(]*?// # ...a possible bunch of non-opening-braces, followed by a C++ comment
20 | | # OR
21 | [^{(]*?/\*(?!.*?\*/.*?[{(]) # ...a possible bunch of non-opening-braces, followed by a C comment with no ending
22 | )
23 | .*? # ...any characters (or none)...
24 | [{(]\s* # ...followed by an open brace and zero or more whitespace...
25 | ( # ...followed by...
26 | $ # ...a dollar...
27 | | # OR
28 | // # ...a C++ comment...
29 | | # OR
30 | /\*(?!.*?\*/.*\S) # ...a C comment, so long as no non-whitespace chars follow it..
31 | )
32 |
33 | foldingStopMarker
34 | (?<!\*)\*\*/|^\s*[})]
35 | keyEquivalent
36 | ^~G
37 | name
38 | Go (GoTools)
39 | patterns
40 |
41 |
42 | include
43 | #receiver_function_declaration
44 |
45 |
46 | include
47 | #plain_function_declaration
48 |
49 |
50 | include
51 | #basic_things
52 |
53 |
54 | include
55 | #exported_variables
56 |
57 |
58 | begin
59 | ^\s*(import\s*.+?)\n\n
60 | beginCaptures
61 |
62 | 1
63 |
64 | name
65 | keyword.control.import.go
66 |
67 |
68 | end
69 | (?=(?://|/\*))|$
70 | name
71 | meta.preprocessor.go.import
72 | patterns
73 |
74 |
75 | begin
76 | "
77 | beginCaptures
78 |
79 | 0
80 |
81 | name
82 | punctuation.definition.string.begin.go
83 |
84 |
85 | end
86 | "
87 | endCaptures
88 |
89 | 0
90 |
91 | name
92 | punctuation.definition.string.end.go
93 |
94 |
95 | name
96 | string.quoted.double.import.go
97 |
98 |
99 |
100 |
101 | include
102 | #block
103 |
104 |
105 | include
106 | #root_parens
107 |
108 |
109 | include
110 | #function_calls
111 |
112 |
113 | repository
114 |
115 | access
116 |
117 | match
118 | (?<=\.)[[:alpha:]_][[:alnum:]_]*\b(?!\s*\()
119 | name
120 | variable.other.dot-access.go
121 |
122 | basic_things
123 |
124 | patterns
125 |
126 |
127 | include
128 | #comments
129 |
130 |
131 | include
132 | #initializers
133 |
134 |
135 | include
136 | #access
137 |
138 |
139 | include
140 | #strings
141 |
142 |
143 | include
144 | #keywords
145 |
146 |
147 |
148 | block
149 |
150 | begin
151 | \{
152 | end
153 | \}
154 | name
155 | meta.block.go
156 | patterns
157 |
158 |
159 | include
160 | #block_innards
161 |
162 |
163 |
164 | block_innards
165 |
166 | patterns
167 |
168 |
169 | include
170 | #function_block_innards
171 |
172 |
173 | include
174 | #exported_variables
175 |
176 |
177 |
178 | comments
179 |
180 | patterns
181 |
182 |
183 | captures
184 |
185 | 1
186 |
187 | name
188 | meta.toc-list.banner.block.go
189 |
190 |
191 | match
192 | ^/\* =(\s*.*?)\s*= \*/$\n?
193 | name
194 | comment.block.go
195 |
196 |
197 | begin
198 | /\*
199 | captures
200 |
201 | 0
202 |
203 | name
204 | punctuation.definition.comment.go
205 |
206 |
207 | end
208 | \*/
209 | name
210 | comment.block.go
211 |
212 |
213 | match
214 | \*/.*\n
215 | name
216 | invalid.illegal.stray-commend-end.go
217 |
218 |
219 | captures
220 |
221 | 1
222 |
223 | name
224 | meta.toc-list.banner.line.go
225 |
226 |
227 | match
228 | ^// =(\s*.*?)\s*=\s*$\n?
229 | name
230 | comment.line.double-slash.banner.go
231 |
232 |
233 | begin
234 | //
235 | beginCaptures
236 |
237 | 0
238 |
239 | name
240 | punctuation.definition.comment.go
241 |
242 |
243 | end
244 | $\n?
245 | name
246 | comment.line.double-slash.go
247 | patterns
248 |
249 |
250 | match
251 | (?>\\\s*\n)
252 | name
253 | punctuation.separator.continuation.go
254 |
255 |
256 |
257 |
258 |
259 | exported_variables
260 |
261 | comment
262 | This is kinda hacky, in order to get the 'var' scoped the right way again.
263 | match
264 | (?<=\s|\[\])([[:upper:]][[:alnum:]_]*)(?=\W+)
265 | name
266 | variable.exported.go
267 |
268 | fn_parens
269 |
270 | begin
271 | \(
272 | end
273 | \)
274 | name
275 | meta.parens.go
276 | patterns
277 |
278 |
279 | include
280 | #basic_things
281 |
282 |
283 | include
284 | #function_calls
285 |
286 |
287 |
288 | function_block
289 |
290 | begin
291 | \{
292 | end
293 | \}
294 | name
295 | meta.block.go
296 | patterns
297 |
298 |
299 | include
300 | #function_block_innards
301 |
302 |
303 |
304 | function_block_innards
305 |
306 | patterns
307 |
308 |
309 | include
310 | #basic_things
311 |
312 |
313 | captures
314 |
315 | 1
316 |
317 | name
318 | punctuation.whitespace.support.function.leading.go
319 |
320 | 2
321 |
322 | name
323 | support.function.builtin.go
324 |
325 |
326 | match
327 | (\s*)\b(new|c(lose|ap|opy)|p(anic(ln)?|rint(ln)?)|len|make|delete|re(al|cover)|imag|append)(?:\b|\()
328 |
329 |
330 | include
331 | #function_block
332 |
333 |
334 | include
335 | #function_calls
336 |
337 |
338 | include
339 | #fn_parens
340 |
341 |
342 |
343 | function_calls
344 |
345 | captures
346 |
347 | 1
348 |
349 | name
350 | punctuation.whitespace.function-call.leading.go
351 |
352 | 2
353 |
354 | name
355 | support.function.any-method.go
356 |
357 | 3
358 |
359 | name
360 | punctuation.definition.parameters.go
361 |
362 |
363 | match
364 | (?x)
365 | (?: (?= \s ) (?:(?<=else|new|return) | (?<!\w)) (\s+) )?
366 | (\b
367 | (?!(for|if|else|switch|return)\s*\()
368 | (?:[[:alpha:]_][[:alnum:]_]*+\b) # method name
369 | )
370 | \s*(\()
371 |
372 | name
373 | meta.function-call.go
374 |
375 | initializers
376 |
377 | patterns
378 |
379 |
380 | captures
381 |
382 | 0
383 |
384 | name
385 | variable.other.go
386 |
387 | 1
388 |
389 | name
390 | keyword.control.go
391 |
392 |
393 | comment
394 | This matches the 'var x int = 0' style of variable declaration.
395 | match
396 | ^[[:blank:]]*(var)\s+(?:[[:alpha:]_][[:alnum:]_]*)(?:,\s+[[:alpha:]_][[:alnum:]_]*)*
397 | name
398 | meta.initialization.explicit.go
399 |
400 |
401 | captures
402 |
403 | 0
404 |
405 | name
406 | variable.other.go
407 |
408 | 1
409 |
410 | name
411 | keyword.operator.initialize.go
412 |
413 |
414 | comment
415 | This matches the 'x := 0' style of variable declaration.
416 | match
417 | (?:[[:alpha:]_][[:alnum:]_]*)(?:,\s+[[:alpha:]_][[:alnum:]_]*)*\s*(:=)
418 | name
419 | meta.initialization.short.go
420 |
421 |
422 |
423 | keywords
424 |
425 | patterns
426 |
427 |
428 | match
429 | \b(break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go|goto|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b
430 | name
431 | keyword.control.go
432 |
433 |
434 | match
435 | \b(bool|byte|complex64|complex128|error|float32|float64|int|int8|int16|int32|int64|rune|string|uint|uint8|uint16|uint32|uint64|uintptr)\b
436 | name
437 | storage.type.go
438 |
439 |
440 | match
441 | \b(const|chan)\b
442 | name
443 | storage.modifier.go
444 |
445 |
446 | match
447 | \b(nil|true|false|iota)\b
448 | name
449 | constant.language.go
450 |
451 |
452 | match
453 | \b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)\b
454 | name
455 | constant.numeric.go
456 |
457 |
458 | match
459 | (<-)
460 | name
461 | support.channel-operator.go
462 |
463 |
464 |
465 | plain_function_declaration
466 |
467 | begin
468 | (?x) ^[[:blank:]]*(func)\s*
469 | (?: ([[:alpha:]_][[:alnum:]_]*)? ) # name of function is optional
470 | (?: \( ((?:[\[\]\w\d\s\/,._*&<>-]|(?:interface\{\}))*)? \) ) # required braces for parameters (even if empty)
471 | \s*
472 | (?: \(? ((?:[\[\]\w\d\s,._*&<>-]|(?:interface\{\}))*) \)? )? # optional return types, optionally within braces
473 |
474 | beginCaptures
475 |
476 | 1
477 |
478 | name
479 | keyword.control.go
480 |
481 | 2
482 |
483 | name
484 | entity.name.function.go
485 |
486 | 3
487 |
488 | name
489 | variable.parameters.go
490 |
491 | 4
492 |
493 | name
494 | variable.return-types.go
495 |
496 |
497 | end
498 | (?<=\})
499 | name
500 | meta.function.plain.go
501 | patterns
502 |
503 |
504 | include
505 | #comments
506 |
507 |
508 | include
509 | #function_block
510 |
511 |
512 |
513 | receiver_function_declaration
514 |
515 | begin
516 | (?x)
517 | (func)\s*
518 | (?: \( ((?:[\[\]\w\d\s,._*&<>-]|(?:interface\{\}))*) \)\s+ ) # receiver variable declarations, in brackets
519 | (?: ([[:alpha:]_][[:alnum:]_]*)? ) # name of function is optional
520 | (?: \( ((?:[\[\]\w\d\s,._*&<>-]|(?:interface\{\}))*)? \) ) # required braces for parameters (even if empty)
521 | \s*
522 | (?: \(? ((?:[\[\]\w\d\s,._*&<>-]|(?:interface\{\}))*) \)? )? # optional return types, optionally within braces
523 |
524 | beginCaptures
525 |
526 | 1
527 |
528 | name
529 | keyword.control.go
530 |
531 | 2
532 |
533 | name
534 | variable.receiver.go
535 |
536 | 3
537 |
538 | name
539 | entity.name.function.go
540 |
541 | 4
542 |
543 | name
544 | variable.parameters.go
545 |
546 | 5
547 |
548 | name
549 | variable.return-types.go
550 |
551 |
552 | comment
553 | Version of above with support for declaring a receiver variable.
554 | end
555 | (?<=\})
556 | name
557 | meta.function.receiver.go
558 | patterns
559 |
560 |
561 | include
562 | #comments
563 |
564 |
565 | include
566 | #function_block
567 |
568 |
569 |
570 | root_parens
571 |
572 | begin
573 | \(
574 | end
575 | (?<=\()(\))?|(?:\))
576 | endCaptures
577 |
578 | 1
579 |
580 | name
581 | meta.parens.empty.go
582 |
583 |
584 | name
585 | meta.parens.go
586 | patterns
587 |
588 |
589 | include
590 | #basic_things
591 |
592 |
593 | include
594 | #exported_variables
595 |
596 |
597 | include
598 | #function_calls
599 |
600 |
601 |
602 | string_escaped_char
603 |
604 | patterns
605 |
606 |
607 | match
608 | \\(\\|[abfnrutv'"]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|[0-7]{3})
609 | name
610 | constant.character.escape.go
611 |
612 |
613 | match
614 | \\.
615 | name
616 | invalid.illegal.unknown-escape.go
617 |
618 |
619 |
620 | string_placeholder
621 |
622 | patterns
623 |
624 |
625 | match
626 | (?x)%
627 | (\d+\$)? # field (argument #)
628 | [#0\- +']* # flags
629 | [,;:_]? # separator character (AltiVec)
630 | ((-?\d+)|\*(-?\d+\$)?)? # minimum field width
631 | (\.((-?\d+)|\*(-?\d+\$)?)?)? # precision
632 | [diouxXDOUeEfFgGaAcCsSqpnvtTbyYhHmMzZ%] # conversion type
633 |
634 | name
635 | constant.other.placeholder.go
636 |
637 |
638 | match
639 | %
640 | name
641 | invalid.illegal.placeholder.go
642 |
643 |
644 |
645 | strings
646 |
647 | patterns
648 |
649 |
650 | begin
651 | "
652 | beginCaptures
653 |
654 | 0
655 |
656 | name
657 | punctuation.definition.string.begin.go
658 |
659 |
660 | end
661 | "
662 | endCaptures
663 |
664 | 0
665 |
666 | name
667 | punctuation.definition.string.end.go
668 |
669 |
670 | name
671 | string.quoted.double.go
672 | patterns
673 |
674 |
675 | include
676 | #string_placeholder
677 |
678 |
679 | include
680 | #string_escaped_char
681 |
682 |
683 |
684 |
685 | begin
686 | '
687 | beginCaptures
688 |
689 | 0
690 |
691 | name
692 | punctuation.definition.string.begin.go
693 |
694 |
695 | end
696 | '
697 | endCaptures
698 |
699 | 0
700 |
701 | name
702 | punctuation.definition.string.end.go
703 |
704 |
705 | name
706 | string.quoted.single.go
707 | patterns
708 |
709 |
710 | include
711 | #string_escaped_char
712 |
713 |
714 |
715 |
716 | begin
717 | `
718 | beginCaptures
719 |
720 | 0
721 |
722 | name
723 | punctuation.definition.string.begin.go
724 |
725 |
726 | end
727 | `
728 | endCaptures
729 |
730 | 0
731 |
732 | name
733 | punctuation.definition.string.end.go
734 |
735 |
736 | name
737 | string.quoted.raw.go
738 | patterns
739 |
740 |
741 | include
742 | #string_placeholder
743 |
744 |
745 |
746 |
747 |
748 |
749 | scopeName
750 | source.go
751 | uuid
752 | 57cdab30-4362-11e4-916c-0800200c9a66
753 |
754 |
755 |
--------------------------------------------------------------------------------
/GoTools.tmLanguage.json:
--------------------------------------------------------------------------------
1 | {
2 | "comment": "Go allows any Unicode character to be used in identifiers, so our identifier regex is: \\b([[:alpha:]_]+[[:alnum:]_]*)\\b",
3 | "firstLineMatch": "-[*]-( Mode:)? Go -[*]-",
4 | "name": "Go (GoTools)",
5 | "repository": {
6 | "fn_parens": {
7 | "patterns": [
8 | {
9 | "include": "#basic_things"
10 | },
11 | {
12 | "include": "#function_calls"
13 | }
14 | ],
15 | "begin": "\\(",
16 | "end": "\\)",
17 | "name": "meta.parens.go"
18 | },
19 | "basic_things": {
20 | "patterns": [
21 | {
22 | "include": "#comments"
23 | },
24 | {
25 | "include": "#initializers"
26 | },
27 | {
28 | "include": "#access"
29 | },
30 | {
31 | "include": "#strings"
32 | },
33 | {
34 | "include": "#keywords"
35 | }
36 | ]
37 | },
38 | "plain_function_declaration": {
39 | "patterns": [
40 | {
41 | "include": "#comments"
42 | },
43 | {
44 | "include": "#function_block"
45 | }
46 | ],
47 | "begin": "(?x) \t ^[[:blank:]]*(func)\\s*\n \t (?: ([[:alpha:]_][[:alnum:]_]*)? ) # name of function is optional\n \t (?: \\( ((?:[\\[\\]\\w\\d\\s\\/,._*&<>-]|(?:interface\\{\\}))*)? \\) ) # required braces for parameters (even if empty)\n \t \\s*\n \t (?: \\(? ((?:[\\[\\]\\w\\d\\s,._*&<>-]|(?:interface\\{\\}))*) \\)? )? # optional return types, optionally within braces\n \t ",
48 | "end": "(?<=\\})",
49 | "name": "meta.function.plain.go",
50 | "beginCaptures": {
51 | "1": {
52 | "name": "keyword.control.go"
53 | },
54 | "3": {
55 | "name": "variable.parameters.go"
56 | },
57 | "2": {
58 | "name": "entity.name.function.go"
59 | },
60 | "4": {
61 | "name": "variable.return-types.go"
62 | }
63 | }
64 | },
65 | "exported_variables": {
66 | "comment": "This is kinda hacky, in order to get the 'var' scoped the right way again.",
67 | "name": "variable.exported.go",
68 | "match": "(?<=\\s|\\[\\])([[:upper:]][[:alnum:]_]*)(?=\\W+)"
69 | },
70 | "string_escaped_char": {
71 | "patterns": [
72 | {
73 | "name": "constant.character.escape.go",
74 | "match": "\\\\(\\\\|[abfnrutv'\"]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|[0-7]{3})"
75 | },
76 | {
77 | "name": "invalid.illegal.unknown-escape.go",
78 | "match": "\\\\."
79 | }
80 | ]
81 | },
82 | "comments": {
83 | "patterns": [
84 | {
85 | "captures": {
86 | "1": {
87 | "name": "meta.toc-list.banner.block.go"
88 | }
89 | },
90 | "name": "comment.block.go",
91 | "match": "^/\\* =(\\s*.*?)\\s*= \\*/$\\n?"
92 | },
93 | {
94 | "captures": {
95 | "0": {
96 | "name": "punctuation.definition.comment.go"
97 | }
98 | },
99 | "begin": "/\\*",
100 | "end": "\\*/",
101 | "name": "comment.block.go"
102 | },
103 | {
104 | "name": "invalid.illegal.stray-commend-end.go",
105 | "match": "\\*/.*\\n"
106 | },
107 | {
108 | "captures": {
109 | "1": {
110 | "name": "meta.toc-list.banner.line.go"
111 | }
112 | },
113 | "name": "comment.line.double-slash.banner.go",
114 | "match": "^// =(\\s*.*?)\\s*=\\s*$\\n?"
115 | },
116 | {
117 | "patterns": [
118 | {
119 | "name": "punctuation.separator.continuation.go",
120 | "match": "(?>\\\\\\s*\\n)"
121 | }
122 | ],
123 | "begin": "//",
124 | "end": "$\\n?",
125 | "name": "comment.line.double-slash.go",
126 | "beginCaptures": {
127 | "0": {
128 | "name": "punctuation.definition.comment.go"
129 | }
130 | }
131 | }
132 | ]
133 | },
134 | "string_placeholder": {
135 | "patterns": [
136 | {
137 | "name": "constant.other.placeholder.go",
138 | "match": "(?x)%\n (\\d+\\$)? # field (argument #)\n [#0\\- +']* # flags\n [,;:_]? # separator character (AltiVec)\n ((-?\\d+)|\\*(-?\\d+\\$)?)? # minimum field width\n (\\.((-?\\d+)|\\*(-?\\d+\\$)?)?)? # precision\n [diouxXDOUeEfFgGaAcCsSqpnvtTbyYhHmMzZ%] # conversion type\n "
139 | },
140 | {
141 | "name": "invalid.illegal.placeholder.go",
142 | "match": "%"
143 | }
144 | ]
145 | },
146 | "access": {
147 | "name": "variable.other.dot-access.go",
148 | "match": "(?<=\\.)[[:alpha:]_][[:alnum:]_]*\\b(?!\\s*\\()"
149 | },
150 | "function_block": {
151 | "patterns": [
152 | {
153 | "include": "#function_block_innards"
154 | }
155 | ],
156 | "begin": "\\{",
157 | "end": "\\}",
158 | "name": "meta.block.go"
159 | },
160 | "initializers": {
161 | "patterns": [
162 | {
163 | "comment": "This matches the 'var x int = 0' style of variable declaration.",
164 | "captures": {
165 | "1": {
166 | "name": "keyword.control.go"
167 | },
168 | "0": {
169 | "name": "variable.other.go"
170 | }
171 | },
172 | "name": "meta.initialization.explicit.go",
173 | "match": "^[[:blank:]]*(var)\\s+(?:[[:alpha:]_][[:alnum:]_]*)(?:,\\s+[[:alpha:]_][[:alnum:]_]*)*"
174 | },
175 | {
176 | "comment": "This matches the 'x := 0' style of variable declaration.",
177 | "captures": {
178 | "1": {
179 | "name": "keyword.operator.initialize.go"
180 | },
181 | "0": {
182 | "name": "variable.other.go"
183 | }
184 | },
185 | "name": "meta.initialization.short.go",
186 | "match": "(?:[[:alpha:]_][[:alnum:]_]*)(?:,\\s+[[:alpha:]_][[:alnum:]_]*)*\\s*(:=)"
187 | }
188 | ]
189 | },
190 | "function_calls": {
191 | "captures": {
192 | "1": {
193 | "name": "punctuation.whitespace.function-call.leading.go"
194 | },
195 | "3": {
196 | "name": "punctuation.definition.parameters.go"
197 | },
198 | "2": {
199 | "name": "support.function.any-method.go"
200 | }
201 | },
202 | "name": "meta.function-call.go",
203 | "match": "(?x)\n (?: (?= \\s ) (?:(?<=else|new|return) | (?-]|(?:interface\\{\\}))*) \\)\\s+ ) # receiver variable declarations, in brackets\n \t (?: ([[:alpha:]_][[:alnum:]_]*)? ) # name of function is optional\n \t (?: \\( ((?:[\\[\\]\\w\\d\\s,._*&<>-]|(?:interface\\{\\}))*)? \\) ) # required braces for parameters (even if empty)\n \t \\s*\n \t (?: \\(? ((?:[\\[\\]\\w\\d\\s,._*&<>-]|(?:interface\\{\\}))*) \\)? )? # optional return types, optionally within braces\n \t ",
208 | "end": "(?<=\\})",
209 | "name": "meta.function.receiver.go",
210 | "beginCaptures": {
211 | "1": {
212 | "name": "keyword.control.go"
213 | },
214 | "3": {
215 | "name": "entity.name.function.go"
216 | },
217 | "2": {
218 | "name": "variable.receiver.go"
219 | },
220 | "5": {
221 | "name": "variable.return-types.go"
222 | },
223 | "4": {
224 | "name": "variable.parameters.go"
225 | }
226 | },
227 | "patterns": [
228 | {
229 | "include": "#comments"
230 | },
231 | {
232 | "include": "#function_block"
233 | }
234 | ]
235 | },
236 | "keywords": {
237 | "patterns": [
238 | {
239 | "name": "keyword.control.go",
240 | "match": "\\b(break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go|goto|if|import|interface|map|package|range|return|select|struct|switch|type|var)\\b"
241 | },
242 | {
243 | "name": "storage.type.go",
244 | "match": "\\b(bool|byte|complex64|complex128|error|float32|float64|int|int8|int16|int32|int64|rune|string|uint|uint8|uint16|uint32|uint64|uintptr)\\b"
245 | },
246 | {
247 | "name": "storage.modifier.go",
248 | "match": "\\b(const|chan)\\b"
249 | },
250 | {
251 | "name": "constant.language.go",
252 | "match": "\\b(nil|true|false|iota)\\b"
253 | },
254 | {
255 | "name": "constant.numeric.go",
256 | "match": "\\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\\.?[0-9]*)|(\\.[0-9]+))((e|E)(\\+|-)?[0-9]+)?)\\b"
257 | },
258 | {
259 | "name": "support.channel-operator.go",
260 | "match": "(<-)"
261 | }
262 | ]
263 | },
264 | "block_innards": {
265 | "patterns": [
266 | {
267 | "include": "#function_block_innards"
268 | },
269 | {
270 | "include": "#exported_variables"
271 | }
272 | ]
273 | },
274 | "root_parens": {
275 | "endCaptures": {
276 | "1": {
277 | "name": "meta.parens.empty.go"
278 | }
279 | },
280 | "begin": "\\(",
281 | "end": "(?<=\\()(\\))?|(?:\\))",
282 | "name": "meta.parens.go",
283 | "patterns": [
284 | {
285 | "include": "#basic_things"
286 | },
287 | {
288 | "include": "#exported_variables"
289 | },
290 | {
291 | "include": "#function_calls"
292 | }
293 | ]
294 | },
295 | "strings": {
296 | "patterns": [
297 | {
298 | "begin": "\"",
299 | "end": "\"",
300 | "name": "string.quoted.double.go",
301 | "endCaptures": {
302 | "0": {
303 | "name": "punctuation.definition.string.end.go"
304 | }
305 | },
306 | "beginCaptures": {
307 | "0": {
308 | "name": "punctuation.definition.string.begin.go"
309 | }
310 | },
311 | "patterns": [
312 | {
313 | "include": "#string_placeholder"
314 | },
315 | {
316 | "include": "#string_escaped_char"
317 | }
318 | ]
319 | },
320 | {
321 | "begin": "'",
322 | "end": "'",
323 | "name": "string.quoted.single.go",
324 | "endCaptures": {
325 | "0": {
326 | "name": "punctuation.definition.string.end.go"
327 | }
328 | },
329 | "beginCaptures": {
330 | "0": {
331 | "name": "punctuation.definition.string.begin.go"
332 | }
333 | },
334 | "patterns": [
335 | {
336 | "include": "#string_escaped_char"
337 | }
338 | ]
339 | },
340 | {
341 | "endCaptures": {
342 | "0": {
343 | "name": "punctuation.definition.string.end.go"
344 | }
345 | },
346 | "begin": "`",
347 | "end": "`",
348 | "name": "string.quoted.raw.go",
349 | "beginCaptures": {
350 | "0": {
351 | "name": "punctuation.definition.string.begin.go"
352 | }
353 | },
354 | "patterns": [
355 | {
356 | "include": "#string_placeholder"
357 | }
358 | ]
359 | }
360 | ]
361 | },
362 | "block": {
363 | "patterns": [
364 | {
365 | "include": "#block_innards"
366 | }
367 | ],
368 | "begin": "\\{",
369 | "end": "\\}",
370 | "name": "meta.block.go"
371 | },
372 | "function_block_innards": {
373 | "patterns": [
374 | {
375 | "include": "#basic_things"
376 | },
377 | {
378 | "captures": {
379 | "1": {
380 | "name": "punctuation.whitespace.support.function.leading.go"
381 | },
382 | "2": {
383 | "name": "support.function.builtin.go"
384 | }
385 | },
386 | "match": "(\\s*)\\b(new|c(lose|ap|opy)|p(anic(ln)?|rint(ln)?)|len|make|delete|re(al|cover)|imag|append)(?:\\b|\\()"
387 | },
388 | {
389 | "include": "#function_block"
390 | },
391 | {
392 | "include": "#function_calls"
393 | },
394 | {
395 | "include": "#fn_parens"
396 | }
397 | ]
398 | }
399 | },
400 | "foldingStartMarker": "(?x)\n /\\*\\*(?!\\*) # opening C-style comment with 2 asterisks but no third later on\n | # OR\n ^ # start of line...\n (?! # ...which does NOT contain...\n [^{(]*?// # ...a possible bunch of non-opening-braces, followed by a C++ comment\n | # OR\n [^{(]*?/\\*(?!.*?\\*/.*?[{(]) # ...a possible bunch of non-opening-braces, followed by a C comment with no ending\n )\n .*? # ...any characters (or none)...\n [{(]\\s* # ...followed by an open brace and zero or more whitespace...\n ( # ...followed by...\n $ # ...a dollar...\n | # OR\n // # ...a C++ comment...\n | # OR\n /\\*(?!.*?\\*/.*\\S) # ...a C comment, so long as no non-whitespace chars follow it..\n )\n ",
401 | "scopeName": "source.go",
402 | "keyEquivalent": "^~G",
403 | "patterns": [
404 | {
405 | "include": "#receiver_function_declaration"
406 | },
407 | {
408 | "include": "#plain_function_declaration"
409 | },
410 | {
411 | "include": "#basic_things"
412 | },
413 | {
414 | "include": "#exported_variables"
415 | },
416 | {
417 | "patterns": [
418 | {
419 | "endCaptures": {
420 | "0": {
421 | "name": "punctuation.definition.string.end.go"
422 | }
423 | },
424 | "begin": "\"",
425 | "end": "\"",
426 | "name": "string.quoted.double.import.go",
427 | "beginCaptures": {
428 | "0": {
429 | "name": "punctuation.definition.string.begin.go"
430 | }
431 | }
432 | }
433 | ],
434 | "begin": "^\\s*(import\\s*.+?)\\n\\n",
435 | "end": "(?=(?://|/\\*))|$",
436 | "name": "meta.preprocessor.go.import",
437 | "beginCaptures": {
438 | "1": {
439 | "name": "keyword.control.import.go"
440 | }
441 | }
442 | },
443 | {
444 | "include": "#block"
445 | },
446 | {
447 | "include": "#root_parens"
448 | },
449 | {
450 | "include": "#function_calls"
451 | }
452 | ],
453 | "foldingStopMarker": "(? GoTools -> Settings -> User`.
40 |
41 | [Default settings](GoTools.sublime-settings) are provided and can be accessed through the Sublime Text preferences menu at `Package Settings -> GoTools -> Settings - Default`. Each option is documented in the settings file itself.
42 |
43 | ### Configuring Your Project
44 |
45 | Create a `GoTools` settings key in a Sublime Text `.sublime-project` file (through the menu at `Project -> Edit Project`).
46 |
47 | A documented [example project file](ExampleProject.sublime-project) is provided.
48 |
49 | ## Using GoTools
50 |
51 | **NOTE:** Most GoTools commands are available via the Sublime Text command palette. Open the palette when viewing a Go source file and search for "GoTools" to see what's available.
52 |
53 | Many of the build commands are also available via the context menu.
54 |
55 | #### Format on Save
56 |
57 | GoTools will format Go source buffers each time they're saved. To disable automatic formatting, set `format_on_save` in your [GoTools settings](GoTools.sublime-settings).
58 |
59 | Here's an example key binding which formats a source file when `++f` is pressed:
60 |
61 | ```json
62 | {"keys": ["ctrl+alt+f"], "command": "gotools_format"}
63 | ```
64 |
65 | By default [gofmt](https://golang.org/cmd/gofmt/) is used for formatting. To change the backend, set `format_backend` in your [GoTools settings](GoTools.sublime-settings). [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports) is also available, as well as the option to first run goimports, then gofmt. This third option is useful when you want the automatic import resolution as well as the simplification (`-s`) feature from gofmt at the same time.
66 |
67 | #### Go to Definition
68 |
69 | GoTools provides a `gotools_goto_def` Sublime Text command which will jump to the symbol definition at the cursor.
70 |
71 | Here's an example key binding which will go to a definition when `` is pressed:
72 |
73 | ```json
74 | {"keys": ["ctrl+g"], "command": "gotools_goto_def"}
75 | ```
76 |
77 | Here's an example `sublime-mousemap` entry which will go to a definition using `+`:
78 |
79 | ```json
80 | {"button": "button1", "count": 1, "modifiers": ["ctrl"], "command": "gotools_goto_def"}
81 | ```
82 |
83 | By default [godef](https://github.com/rogpeppe/godef) is used for definition support. To change the backend, set `goto_def_backend` in your [GoTools settings](GoTools.sublime-settings).
84 |
85 | #### Autocomplete
86 |
87 | GoTools integrates the Sublime Text autocompletion engine with [gocode](https://github.com/nsf/gocode).
88 |
89 | Here's an example key binding which autocompletes when `+` is pressed:
90 |
91 | ```json
92 | {"keys": ["ctrl+space"], "command": "auto_complete"}
93 | ```
94 |
95 | When suggestions are available, a specially formatted suggestion list will appear, including type information for each suggestion.
96 |
97 | To disable autocompletion integration, set `autocomplete` in your [GoTools settings](GoTools.sublime-settings).
98 |
99 | #### Builds
100 |
101 | GoTools integrates the Sublime Text build system with `go build`.
102 |
103 | Activate the GoTools build system from the Sublime Text menu by selecting it from `Tools -> Build System`. If the build system is set to `Automatic`, GoTools will be automatically used for builds when editing Go source files.
104 |
105 | There are several ways to perform a build:
106 |
107 | * From the Sublime Text menu at `Tools -> Build`
108 | * A key bound to the `build` command
109 | * The command palette, as `Build: Build`
110 |
111 | A "Clean Build" command variant is also provided which recursively deletes all `GOPATH/pkg` directory contents prior to executing the build as usual.
112 |
113 | Build results are placed in the Sublime Text build output panel which can be toggled with a command such as:
114 |
115 | ```json
116 | { "keys" : ["ctrl+m"], "command" : "show_panel" , "args" : {"panel": "output.exec", "toggle": true}},
117 | ```
118 |
119 | Here's an example key binding which runs a build when `+b` is pressed:
120 |
121 | ```json
122 | { "keys": ["ctrl+b"], "command": "build" },
123 | ```
124 |
125 | Here's an example key binding which runs "Clean Build" when `++b` is pressed:
126 |
127 | ```json
128 | { "keys": ["ctrl+alt+b"], "command": "build", "args": {"variant": "Clean Build"}},
129 | ```
130 |
131 | #### Tests
132 |
133 | GoTools integrates the Sublime Text build system with `go test`.
134 |
135 | GoTools attempts to "do what you mean" depending on context. For instance, when using "Run Test at Cursor" in a test file which requires an `integration` Go build tag, GoTools will notice this and automatically add `-tags integration` to the test execution.
136 |
137 | The following GoTools build variants are available:
138 |
139 | Variant | Description
140 | --------------------------|-------------
141 | Run Tests | Discovers test packages based on the `project_package` and `test_packages` settings relative to the project `gopath` and executes them.
142 | Run Test at Cursor | Runs a single test method at or surrounding the cursor.
143 | Run Current Package Tests | Runs tests for the package containing the current file.
144 | Run Tagged Tests | Like "Run Tests" but for the packages specified in the `tagged_packages` setting.
145 | Run Last Test | Runs the last test variant that was executed.
146 |
147 | Test results are placed in the built-in Sublime Text build output panel which can be toggled with a command such as:
148 |
149 | ```json
150 | { "keys" : ["ctrl+m"], "command" : "show_panel" , "args" : {"panel": "output.exec", "toggle": true}},
151 | ```
152 |
153 | Here's an example key binding which runs the test at the cursor when `++t` is pressed:
154 |
155 | ```json
156 | { "keys": ["ctrl+alt+t"], "command": "build", "args": {"variant": "Run Test at Cursor"}},
157 | ```
158 |
159 | Replace `variant` in the command with any variant name from the preceding table for other bindings.
160 |
161 | #### Oracle Analysis (experimental)
162 |
163 | GoTools integrates Sublime Text with [oracle](https://godoc.org/golang.org/x/tools/oracle). Oracle is invoked with the `gotools_oracle` Sublime Text command.
164 |
165 | Here's an example which runs the oracle "implements" command when `` is pressed:
166 |
167 | ```json
168 | { "keys" : ["ctrl+alt+i"], "command" : "gotools_oracle" , "args" : {"command": "implements"}},
169 | ```
170 |
171 | The following oracle operations are supported as arguments to the `gotools_oracle` command:
172 |
173 | Command | Notes
174 | -------------|------
175 | callers | Slow on large codebases.
176 | callees | Slow on large codebases.
177 | callstack | Slow on large codebases.
178 | describe |
179 | freevars | Requires a selection.
180 | implements |
181 | peers |
182 | referrers |
183 |
184 | Oracle results are placed in a Sublime Text output panel which can be toggled with a command such as:
185 |
186 | ```json
187 | { "keys" : ["ctrl+m"], "command" : "show_panel" , "args" : {"panel": "output.gotools_oracle", "toggle": true}},
188 | ```
189 |
190 | #### Rename (experimental)
191 |
192 | GoTools provides a `gotools_rename` command supported by [gorename](https://godoc.org/golang.org/x/tools/cmd/gorename) which supports type-safe renaming of identifiers.
193 |
194 | When the `gotools_rename` command is executed, an input panel labeled `Go rename:` will appear. Rename results are placed in a Sublime Text output panel which can be toggled with a command such as:
195 |
196 | ```json
197 | { "keys" : ["ctrl+m"], "command" : "show_panel" , "args" : {"panel": "output.gotools_rename", "toggle": true}},
198 | ```
199 |
200 | **Important**: The `gorename` tool writes files in-place with no option for a dry-run. Changes might be destructive, and the tool is known to have bugs.
201 |
202 |
203 | ### Gocode Caveats
204 |
205 | **Important**: Using gocode support will modify the `lib-path` setting in the gocode daemon. The change will affect all clients, including other Sublime Text sessions, Vim instances, etc. Don't use this setting if you're concerned about interoperability with other tools which integrate with gocode.
206 |
207 | Some projects make use of a dependency isolation tool such as [Godep](https://github.com/tools/godep), and many projects use some sort of custom build script. Additionally, gocode uses a client/server architecture, and at present relies on a global server-side setting to resolve Go package paths for suggestion computation. By default, gocode will only search `GOROOT` and `GOPATH/pkg` for packages, which may be insufficient if the project compiles source to multiple `GOPATH` entries (such as `Godeps/_workspace/pkg`).
208 |
209 | With such a project, to get the best suggestions from gocode, it's necessary to configure the gocode daemon prior to client suggestion requests to inform gocode about the locations of compiled packages for the project.
210 |
211 | GoTools will infer the correct gocode `lib-path` by constructing a path which incorporates all project `GOPATH` entries.
212 |
213 | ### GoSublime Caveats
214 |
215 | Installing GoTools alongside GoSublime isn't tested or supported, so YMMV.
216 |
--------------------------------------------------------------------------------
/gotools_build.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import sublime_plugin
3 | import fnmatch
4 | import os
5 | import re
6 | import shutil
7 |
8 | from .gotools_util import Buffers
9 | from .gotools_util import GoBuffers
10 | from .gotools_util import Logger
11 | from .gotools_util import ToolRunner
12 | from .gotools_settings import GoToolsSettings
13 |
14 | class GotoolsBuildCommand(sublime_plugin.WindowCommand):
15 | def run(self, cmd = None, shell_cmd = None, file_regex = "", line_regex = "", working_dir = "",
16 | encoding = "utf-8", env = {}, quiet = False, kill = False,
17 | word_wrap = True, syntax = "Packages/Text/Plain text.tmLanguage",
18 | clean = False, task = "build",
19 | # Catches "path" and "shell"
20 | **kwargs):
21 | if clean:
22 | self.clean()
23 |
24 | if len(file_regex) == 0:
25 | file_regex = "^(.*\\.go):(\\d+):()(.*)$"
26 |
27 | env["GOPATH"] = GoToolsSettings.get().gopath
28 | env["GOROOT"] = GoToolsSettings.get().goroot
29 | env["PATH"] = GoToolsSettings.get().ospath
30 |
31 | exec_opts = {
32 | "cmd": cmd,
33 | "shell_cmd": shell_cmd,
34 | "file_regex": file_regex,
35 | "line_regex": line_regex,
36 | "working_dir": working_dir,
37 | "encoding": encoding,
38 | "env": env,
39 | "quiet": quiet,
40 | "kill": kill,
41 | "word_wrap": word_wrap,
42 | "syntax": syntax,
43 | }
44 |
45 | if task == "build":
46 | self.build(exec_opts)
47 | elif task == "test_packages":
48 | self.test_packages(exec_opts, self.find_test_packages())
49 | elif task == "test_tagged_packages":
50 | pkgs = []
51 | for p in GoToolsSettings.get().tagged_test_packages:
52 | pkgs.append(os.path.join(GoToolsSettings.get().project_package, p))
53 | self.test_packages(exec_opts=exec_opts, packages=pkgs, tags=GoToolsSettings.get().tagged_test_tags)
54 | elif task == "test_at_cursor":
55 | self.test_at_cursor(exec_opts)
56 | elif task == "test_current_package":
57 | self.test_current_package(exec_opts)
58 | elif task == "test_last":
59 | Logger.log("re-running last test")
60 | self.window.run_command("exec", self.last_test_exec_opts)
61 | else:
62 | Logger.log("invalid task: " + task)
63 |
64 | def clean(self):
65 | Logger.log("cleaning build output directories")
66 | for p in GoToolsSettings.get().gopath.split(":"):
67 | pkgdir = os.path.join(p, "pkg", GoToolsSettings.get().goos + "_" + GoToolsSettings.get().goarch)
68 | Logger.log("=> " + pkgdir)
69 | if os.path.exists(pkgdir):
70 | try:
71 | shutil.rmtree(pkgdir)
72 | except Exception as e:
73 | Logger.log("WARNING: couldn't clean directory: " + str(e))
74 |
75 |
76 | def build(self, exec_opts):
77 | build_packages = []
78 | for p in GoToolsSettings.get().build_packages:
79 | build_packages.append(os.path.join(GoToolsSettings.get().project_package, p))
80 |
81 | Logger.log("running build for packages: " + str(build_packages))
82 |
83 | go = GoToolsSettings.get().find_go_binary(GoToolsSettings.get().ospath)
84 | exec_opts["cmd"] = [go, "install"] + build_packages
85 |
86 | self.window.run_command("exec", exec_opts)
87 |
88 | def test_packages(self, exec_opts, packages = [], patterns = [], tags = []):
89 | Logger.log("running tests")
90 |
91 | Logger.log("test packages: " + str(packages))
92 | Logger.log("test patterns: " + str(patterns))
93 |
94 | go = GoToolsSettings.get().find_go_binary(GoToolsSettings.get().ospath)
95 | cmd = [go, "test"]
96 |
97 | if len(tags) > 0:
98 | cmd += ["-tags", ",".join(tags)]
99 |
100 | if GoToolsSettings.get().verbose_tests:
101 | cmd.append("-v")
102 |
103 | if GoToolsSettings.get().test_timeout:
104 | cmd += ["-timeout", GoToolsSettings.get().test_timeout]
105 |
106 | cmd += packages
107 |
108 | for p in patterns:
109 | cmd += ["-run", "^"+p+"$"]
110 |
111 | exec_opts["cmd"] = cmd
112 |
113 | # Cache the execution for easy recall
114 | self.last_test_exec_opts = exec_opts
115 | self.window.run_command("exec", exec_opts)
116 |
117 | def test_current_package(self, exec_opts):
118 | Logger.log("running current package tests")
119 | view = self.window.active_view()
120 | pkg = self.current_file_pkg(view)
121 |
122 | if len(pkg) == 0:
123 | Logger.log("couldn't determine package for current file: " + view.file_name())
124 | return
125 |
126 | tags = self.tags_for_buffer(view)
127 |
128 | Logger.log("running tests for package: " + pkg)
129 | self.test_packages(exec_opts=exec_opts, packages=[pkg], tags=tags)
130 |
131 | def test_at_cursor(self, exec_opts):
132 | Logger.log("running current test under cursor")
133 | view = self.window.active_view()
134 |
135 | func_name = GoBuffers.func_name_at_cursor(view)
136 |
137 | if len(func_name) == 0:
138 | Logger.log("no function found near cursor")
139 | return
140 |
141 | pkg = self.current_file_pkg(view)
142 |
143 | if len(pkg) == 0:
144 | Logger.log("couldn't determine package for current file: " + view.file_name())
145 | return
146 |
147 | tags = self.tags_for_buffer(view)
148 |
149 | Logger.log("running test: " + pkg + "#" + func_name)
150 | self.test_packages(exec_opts=exec_opts, packages=[pkg], patterns=[func_name], tags=tags)
151 |
152 | def current_file_pkg(self, view):
153 | abs_pkg_dir = os.path.dirname(view.file_name())
154 | try:
155 | return abs_pkg_dir[abs_pkg_dir.index(GoToolsSettings.get().project_package):]
156 | except:
157 | return ""
158 |
159 | def find_test_packages(self):
160 | proj_package_dir = None
161 |
162 | for gopath in GoToolsSettings.get().gopath.split(":"):
163 | d = os.path.join(gopath, "src", GoToolsSettings.get().project_package)
164 | if os.path.exists(d):
165 | proj_package_dir = d
166 | break
167 |
168 | if proj_package_dir == None:
169 | Logger.log("ERROR: couldn't find project package dir '"
170 | + GoToolsSettings.get().project_package + "' in GOPATH: " + GoToolsSettings.get().gopath)
171 | return []
172 |
173 | packages = {}
174 |
175 | for pkg_dir in GoToolsSettings.get().test_packages:
176 | abs_pkg_dir = os.path.join(proj_package_dir, pkg_dir)
177 | Logger.log("searching for tests in: " + abs_pkg_dir)
178 | for root, dirnames, filenames in os.walk(abs_pkg_dir):
179 | for filename in fnmatch.filter(filenames, '*_test.go'):
180 | abs_test_file = os.path.join(root, filename)
181 | rel_test_file = os.path.relpath(abs_test_file, proj_package_dir)
182 | test_pkg = os.path.join(GoToolsSettings.get().project_package, os.path.dirname(rel_test_file))
183 | packages[test_pkg] = None
184 |
185 | return list(packages.keys())
186 |
187 | @staticmethod
188 | def tags_for_buffer(view):
189 | # TODO: Use a sane way to get the first line of the buffer
190 | header = Buffers.buffer_text(view).decode("utf-8").splitlines()[0]
191 |
192 | found_tags = []
193 | match = re.match('\/\/\ \+build\ (.*)', header)
194 | if match and match.group(1):
195 | tags = match.group(1)
196 | tags = tags.split(',')
197 | for tag in tags:
198 | if not tag.startswith('!'):
199 | found_tags.append(tag)
200 |
201 | return found_tags
202 |
--------------------------------------------------------------------------------
/gotools_format.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import sublime_plugin
3 | import re
4 |
5 | from .gotools_util import Buffers
6 | from .gotools_util import GoBuffers
7 | from .gotools_util import Logger
8 | from .gotools_util import ToolRunner
9 | from .gotools_settings import GoToolsSettings
10 |
11 | class GotoolsFormatOnSave(sublime_plugin.EventListener):
12 | def on_pre_save(self, view):
13 | if not GoBuffers.is_go_source(view): return
14 | if not GoToolsSettings.get().format_on_save: return
15 | view.run_command('gotools_format')
16 |
17 | class GotoolsFormat(sublime_plugin.TextCommand):
18 | def is_enabled(self):
19 | return GoBuffers.is_go_source(self.view)
20 |
21 | def run(self, edit):
22 | command = ""
23 | args = []
24 | if GoToolsSettings.get().format_backend == "gofmt":
25 | command = "gofmt"
26 | args = ["-e", "-s"]
27 | elif GoToolsSettings.get().format_backend in ["goimports", "both"] :
28 | command = "goimports"
29 | args = ["-e"]
30 |
31 | stdout, stderr, rc = ToolRunner.run(command, args, stdin=Buffers.buffer_text(self.view))
32 |
33 | # Clear previous syntax error marks
34 | self.view.erase_regions("mark")
35 |
36 | if rc == 2:
37 | # Show syntax errors and bail
38 | self.show_syntax_errors(stderr)
39 | return
40 |
41 | if rc != 0:
42 | # Ermmm...
43 | Logger.log("unknown gofmt error (" + str(rc) + ") stderr:\n" + stderr)
44 | return
45 |
46 | if GoToolsSettings.get().format_backend == "both":
47 | command = "gofmt"
48 | args = ["-e", "-s"]
49 | stdout, stderr, rc = ToolRunner.run(command, args, stdin=stdout.encode('utf-8'))
50 |
51 | # Clear previous syntax error marks
52 | self.view.erase_regions("mark")
53 |
54 | if rc == 2:
55 | # Show syntax errors and bail
56 | self.show_syntax_errors(stderr)
57 | return
58 |
59 | if rc != 0:
60 | # Ermmm...
61 | Logger.log("unknown gofmt error (" + str(rc) + ") stderr:\n" + stderr)
62 | return
63 |
64 | # Everything's good, hide the syntax error panel
65 | self.view.window().run_command("hide_panel", {"panel": "output.gotools_syntax_errors"})
66 |
67 | # Remember the viewport position. When replacing the buffer, Sublime likes to jitter the
68 | # viewport around for some reason.
69 | self.prev_viewport_pos = self.view.viewport_position()
70 |
71 | # Replace the buffer with gofmt output.
72 | self.view.replace(edit, sublime.Region(0, self.view.size()), stdout)
73 |
74 | # Restore the viewport on the main GUI thread (which is the only way this works).
75 | sublime.set_timeout(self.restore_viewport, 0)
76 |
77 | def restore_viewport(self):
78 | self.view.set_viewport_position(self.prev_viewport_pos, False)
79 |
80 | # Display an output panel containing the syntax errors, and set gutter marks for each error.
81 | def show_syntax_errors(self, stderr):
82 | output_view = self.view.window().create_output_panel('gotools_syntax_errors')
83 | output_view.set_scratch(True)
84 | output_view.settings().set("result_file_regex","^(.*):(\d+):(\d+):(.*)$")
85 | output_view.run_command("select_all")
86 | output_view.run_command("right_delete")
87 |
88 | syntax_output = stderr.replace("", self.view.file_name())
89 | output_view.run_command('append', {'characters': syntax_output})
90 | self.view.window().run_command("show_panel", {"panel": "output.gotools_syntax_errors"})
91 |
92 | marks = []
93 | for error in stderr.splitlines():
94 | match = re.match("(.*):(\d+):(\d+):", error)
95 | if not match or not match.group(2):
96 | Logger.log("skipping unrecognizable error:\n" + error + "\nmatch:" + str(match))
97 | continue
98 |
99 | row = int(match.group(2))
100 | pt = self.view.text_point(row-1, 0)
101 | Logger.log("adding mark at row " + str(row))
102 | marks.append(sublime.Region(pt))
103 |
104 | if len(marks) > 0:
105 | self.view.add_regions("mark", marks, "mark", "dot", sublime.DRAW_STIPPLED_UNDERLINE | sublime.PERSISTENT)
106 |
--------------------------------------------------------------------------------
/gotools_goto_def.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import sublime_plugin
3 | import os
4 | import json
5 |
6 | from .gotools_util import Buffers
7 | from .gotools_util import GoBuffers
8 | from .gotools_util import Logger
9 | from .gotools_util import ToolRunner
10 | from .gotools_settings import GoToolsSettings
11 |
12 | class GotoolsGotoDef(sublime_plugin.TextCommand):
13 | def is_enabled(self):
14 | return GoBuffers.is_go_source(self.view)
15 |
16 | # Capture mouse events so users can click on a definition.
17 | def want_event(self):
18 | return True
19 |
20 | def run(self, edit, event=None):
21 | sublime.set_timeout_async(lambda: self.godef(event), 0)
22 |
23 | def godef(self, event):
24 | # Find and store the current filename and byte offset at the
25 | # cursor or mouse event location.
26 | if event:
27 | filename, row, col, offset = Buffers.location_for_event(self.view, event)
28 | else:
29 | filename, row, col, offset, offset_end = Buffers.location_at_cursor(self.view)
30 |
31 | backend = GoToolsSettings.get().goto_def_backend if GoToolsSettings.get().goto_def_backend else ""
32 | try:
33 | if backend == "oracle":
34 | file, row, col = self.get_oracle_location(filename, offset)
35 | elif backend == "godef":
36 | file, row, col = self.get_godef_location(filename, offset)
37 | else:
38 | Logger.log("Invalid godef backend '" + backend + "' (supported: godef, oracle)")
39 | Logger.status("Invalid godef configuration; see console log for details")
40 | return
41 | except Exception as e:
42 | Logger.status(str(e))
43 | return
44 |
45 | if not os.path.isfile(file):
46 | Logger.log("WARN: file indicated by godef not found: " + file)
47 | Logger.status("godef failed: Please enable debugging and check console log")
48 | return
49 |
50 | Logger.log("opening definition at " + file + ":" + str(row) + ":" + str(col))
51 | w = self.view.window()
52 | new_view = w.open_file(file + ':' + str(row) + ':' + str(col), sublime.ENCODED_POSITION)
53 | group, index = w.get_view_index(new_view)
54 | if group != -1:
55 | w.focus_group(group)
56 |
57 | def get_oracle_location(self, filename, offset):
58 | args = ["-pos="+filename+":#"+str(offset), "-format=json", "definition"]
59 |
60 | # Build up a package scope contaning all packages the user might have
61 | # configured.
62 | # TODO: put into a utility
63 | package_scope = []
64 | for p in GoToolsSettings.get().build_packages:
65 | package_scope.append(os.path.join(GoToolsSettings.get().project_package, p))
66 | for p in GoToolsSettings.get().test_packages:
67 | package_scope.append(os.path.join(GoToolsSettings.get().project_package, p))
68 | for p in GoToolsSettings.get().tagged_test_packages:
69 | package_scope.append(os.path.join(GoToolsSettings.get().project_package, p))
70 |
71 | if len(package_scope) > 0:
72 | args = args + package_scope
73 |
74 | location, err, rc = ToolRunner.run("oracle", args)
75 | if rc != 0:
76 | raise Exception("no definition found")
77 |
78 | Logger.log("oracle output:\n" + location.rstrip())
79 |
80 | # cut anything prior to the first path separator
81 | location = json.loads(location.rstrip())['definition']['objpos'].rsplit(":", 2)
82 |
83 | if len(location) != 3:
84 | raise Exception("no definition found")
85 |
86 | file = location[0]
87 | row = int(location[1])
88 | col = int(location[2])
89 |
90 | return [file, row, col]
91 |
92 | def get_godef_location(self, filename, offset):
93 | location, err, rc = ToolRunner.run("godef", ["-f", filename, "-o", str(offset)])
94 | if rc != 0:
95 | raise Exception("no definition found")
96 |
97 | Logger.log("godef output:\n" + location.rstrip())
98 |
99 | # godef is sometimes returning this junk as part of the output,
100 | # so just cut anything prior to the first path separator
101 | location = location.rstrip().rsplit(":", 2)
102 |
103 | if len(location) != 3:
104 | raise Exception("no definition found")
105 |
106 | file = location[0]
107 | row = int(location[1])
108 | col = int(location[2])
109 |
110 | return [file, row, col]
111 |
--------------------------------------------------------------------------------
/gotools_oracle.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import sublime_plugin
3 | import os
4 |
5 | from .gotools_util import Buffers
6 | from .gotools_util import GoBuffers
7 | from .gotools_util import Logger
8 | from .gotools_util import ToolRunner
9 | from .gotools_settings import GoToolsSettings
10 |
11 | class GotoolsOracleCommand(sublime_plugin.TextCommand):
12 | def is_enabled(self):
13 | return GoBuffers.is_go_source(self.view)
14 |
15 | def run(self, edit, command=None):
16 | if not command:
17 | Logger.log("command is required")
18 | return
19 |
20 | filename, row, col, offset, offset_end = Buffers.location_at_cursor(self.view)
21 | pos = filename+":#"+str(offset)
22 |
23 | # Build up a package scope contaning all packages the user might have
24 | # configured.
25 | # TODO: put into a utility
26 | package_scope = []
27 | for p in GoToolsSettings.get().build_packages:
28 | package_scope.append(os.path.join(GoToolsSettings.get().project_package, p))
29 | for p in GoToolsSettings.get().test_packages:
30 | package_scope.append(os.path.join(GoToolsSettings.get().project_package, p))
31 | for p in GoToolsSettings.get().tagged_test_packages:
32 | package_scope.append(os.path.join(GoToolsSettings.get().project_package, p))
33 |
34 | sublime.active_window().run_command("hide_panel", {"panel": "output.gotools_oracle"})
35 |
36 | if command == "callees":
37 | sublime.set_timeout_async(lambda: self.do_plain_oracle("callees", pos, package_scope), 0)
38 | if command == "callers":
39 | sublime.set_timeout_async(lambda: self.do_plain_oracle("callers", pos, package_scope), 0)
40 | if command == "callstack":
41 | sublime.set_timeout_async(lambda: self.do_plain_oracle("callstack", pos, package_scope), 0)
42 | if command == "describe":
43 | sublime.set_timeout_async(lambda: self.do_plain_oracle("describe", pos, package_scope), 0)
44 | if command == "freevars":
45 | pos = filename+":#"+str(offset)+","+"#"+str(offset_end)
46 | sublime.set_timeout_async(lambda: self.do_plain_oracle("freevars", pos, package_scope), 0)
47 | if command == "implements":
48 | sublime.set_timeout_async(lambda: self.do_plain_oracle("implements", pos, package_scope), 0)
49 | if command == "peers":
50 | sublime.set_timeout_async(lambda: self.do_plain_oracle("peers", pos, package_scope), 0)
51 | if command == "referrers":
52 | sublime.set_timeout_async(lambda: self.do_plain_oracle("referrers", pos, package_scope), 0)
53 |
54 | def do_plain_oracle(self, mode, pos, package_scope=[], regex="^(.*):(\d+):(\d+):(.*)$"):
55 | Logger.status("running oracle "+mode+"...")
56 | args = ["-pos="+pos, "-format=plain", mode]
57 | if len(package_scope) > 0:
58 | args = args + package_scope
59 | output, err, rc = ToolRunner.run("oracle", args, timeout=60)
60 | Logger.log("oracle "+mode+" output: " + output.rstrip())
61 |
62 | if rc != 0:
63 | Logger.status("oracle call failed (" + str(rc) +")")
64 | return
65 | Logger.status("oracle "+mode+" finished")
66 |
67 | panel = self.view.window().create_output_panel('gotools_oracle')
68 | panel.set_scratch(True)
69 | panel.settings().set("result_file_regex", regex)
70 | panel.run_command("select_all")
71 | panel.run_command("right_delete")
72 | panel.run_command('append', {'characters': output})
73 | self.view.window().run_command("show_panel", {"panel": "output.gotools_oracle"})
74 |
--------------------------------------------------------------------------------
/gotools_rename.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import sublime_plugin
3 | import os
4 |
5 | from .gotools_util import Buffers
6 | from .gotools_util import GoBuffers
7 | from .gotools_util import Logger
8 | from .gotools_util import ToolRunner
9 | from .gotools_settings import GoToolsSettings
10 |
11 | class GotoolsRenameCommand(sublime_plugin.TextCommand):
12 | def is_enabled(self):
13 | return GoBuffers.is_go_source(self.view)
14 |
15 | def run(self, edit):
16 | self.view.window().show_input_panel("Go rename:", "", self.do_rename_async, None, None)
17 |
18 | def do_rename_async(self, name):
19 | sublime.set_timeout_async(lambda: self.do_rename(name), 0)
20 |
21 | def do_rename(self, name):
22 | filename, _row, _col, offset, _offset_end = Buffers.location_at_cursor(self.view)
23 | args = [
24 | "-offset", "{file}:#{offset}".format(file=filename, offset=offset),
25 | "-to", name,
26 | "-v"
27 | ]
28 | output, err, exit = ToolRunner.run("gorename", args, timeout=15)
29 |
30 | if exit != 0:
31 | Logger.status("rename failed ({0}): {1}".format(exit, err))
32 | return
33 | Logger.status("renamed symbol to {name}".format(name=name))
34 |
35 | panel = self.view.window().create_output_panel('gotools_rename')
36 | panel.set_scratch(True)
37 | # TODO: gorename isn't emitting line numbers, so to get clickable
38 | # referenced we'd need to process each line to append ':N' to make the
39 | # sublime regex work properly (line number is a required capture group).
40 | panel.settings().set("result_file_regex", "^\t(.*\.go)$")
41 | panel.run_command("select_all")
42 | panel.run_command("right_delete")
43 | panel.run_command('append', {'characters': err})
44 | self.view.window().run_command("show_panel", {"panel": "output.gotools_rename"})
45 |
--------------------------------------------------------------------------------
/gotools_settings.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import os
3 | import platform
4 | import re
5 | import subprocess
6 | import tempfile
7 | import threading
8 |
9 | class GoToolsSettings():
10 | lock = threading.Lock()
11 | instance = None
12 |
13 | def __init__(self):
14 | # Only load the environment once.
15 | # TODO: Consider doing this during refresh. Environment shouldn't change
16 | # often and the call can be slow if the login shell has a nontrivial
17 | # amount of init (e.g. bashrc).
18 | self.env = self.create_environment()
19 | # Perform the initial plugin settings load.
20 | self.refresh()
21 | # Only refresh plugin settings when they have changed.
22 | self.plugin_settings.add_on_change("gopath", self.refresh)
23 |
24 | @staticmethod
25 | def get():
26 | GoToolsSettings.lock.acquire()
27 | try:
28 | if GoToolsSettings.instance is None:
29 | print("GoTools: initializing settings...")
30 | GoToolsSettings.instance = GoToolsSettings()
31 | print("GoTools: successfully initialized settings")
32 | except Exception as e:
33 | raise Exception("GoTools: ERROR: failed to initialize settings: {0}".format(str(e)))
34 | finally:
35 | GoToolsSettings.lock.release()
36 | return GoToolsSettings.instance
37 |
38 | # There's no direct access to project settings, so load them from the active
39 | # view every time. This doesn't seem ideal.
40 | @property
41 | def project_settings(self):
42 | return sublime.active_window().active_view().settings().get('GoTools', {})
43 |
44 | # Returns setting with key, preferring project settings over plugin settings
45 | # and using default if neither is found. Key values with 0 length when
46 | # converted to a string are treated as None.
47 | def get_setting(self, key, default = None):
48 | val = self.project_settings.get(key, '')
49 | if len(str(val)) > 0:
50 | return val
51 | val = self.plugin_settings.get(key, '')
52 | if len(str(val)) > 0:
53 | return val
54 | return default
55 |
56 | # Reloads the plugin settings file from disk and validates required setings.
57 | def refresh(self):
58 | # Load settings from disk.
59 | self.plugin_settings = sublime.load_settings("GoTools.sublime-settings")
60 |
61 | # Validate properties.
62 | if self.gopath is None or len(self.gopath) == 0:
63 | raise Exception("GoTools requires either the `gopath` setting or the GOPATH environment variable to be s")
64 | if not self.goroot or not self.goarch or not self.goos or not self.go_tools:
65 | raise Exception("GoTools couldn't find Go runtime information")
66 |
67 | print("GoTools: configuration updated:\n\tgopath={0}\n\tgoroot={1}\n\tpath={2}\n\tdebug_enabled={3}".format(self.gopath, self.goroot, self.ospath, self.debug_enabled))
68 |
69 | # Project > Plugin > Shell env > OS env > go env
70 | @property
71 | def gopath(self):
72 | gopath = self.get_setting('gopath', self.env["GOPATH"])
73 | # Support 'gopath' expansion in project settings.
74 | if 'gopath' in self.project_settings:
75 | sub = self.plugin_settings.get('gopath', '')
76 | if len(sub) == 0:
77 | sub = self.env['GOPATH']
78 | gopath = self.project_settings['gopath'].replace('${gopath}', sub)
79 | return gopath
80 |
81 | @property
82 | def goroot(self):
83 | return self.get_setting('goroot', self.env["GOROOT"])
84 |
85 | @property
86 | def ospath(self):
87 | return self.get_setting('path', self.env["PATH"])
88 |
89 | @property
90 | def goarch(self):
91 | return self.env["GOHOSTARCH"]
92 |
93 | @property
94 | def goos(self):
95 | return self.env["GOHOSTOS"]
96 |
97 | @property
98 | def go_tools(self):
99 | return self.env["GOTOOLDIR"]
100 |
101 | @property
102 | def gorootbin(self):
103 | # The GOROOT bin directory is namespaced with the GOOS and GOARCH.
104 | return os.path.join(self.goroot, "bin", self.gohostosarch)
105 |
106 | @property
107 | def golibpath(self):
108 | libpath = []
109 | arch = "{0}_{1}".format(self.goos, self.goarch)
110 | libpath.append(os.path.join(self.goroot, "pkg", self.gohostosarch))
111 | for p in self.gopath.split(":"):
112 | libpath.append(os.path.join(p, "pkg", arch))
113 | return ":".join(libpath)
114 |
115 | @property
116 | def gohostosarch(self):
117 | return "{0}_{1}".format(self.goos, self.goarch)
118 |
119 | @property
120 | def debug_enabled(self):
121 | return self.get_setting("debug_enabled")
122 |
123 | @property
124 | def format_on_save(self):
125 | return self.get_setting("format_on_save")
126 |
127 | @property
128 | def format_backend(self):
129 | return self.get_setting("format_backend")
130 |
131 | @property
132 | def autocomplete(self):
133 | return self.get_setting("autocomplete")
134 |
135 | @property
136 | def goto_def_backend(self):
137 | return self.get_setting("goto_def_backend")
138 |
139 | @property
140 | def project_package(self):
141 | return self.get_setting("project_package")
142 |
143 | @property
144 | def build_packages(self):
145 | return self.get_setting("build_packages", [])
146 |
147 | @property
148 | def test_packages(self):
149 | return self.get_setting("test_packages", [])
150 |
151 | @property
152 | def tagged_test_tags(self):
153 | return self.get_setting("tagged_test_tags", [])
154 |
155 | @property
156 | def tagged_test_packages(self):
157 | return self.get_setting("tagged_test_packages", [])
158 |
159 | @property
160 | def verbose_tests(self):
161 | return self.get_setting("verbose_tests", False)
162 |
163 | @property
164 | def test_timeout(self):
165 | return self.get_setting("test_timeout", None)
166 |
167 | # Load PATH, GOPATH, GOROOT, and anything `go env` can provide. Use the
168 | # precedence order: Login shell > OS env > go env. The environment is
169 | # returned as a dict.
170 | #
171 | # Raises an exception if PATH can't be resolved or if `go env` fails.
172 | @staticmethod
173 | def create_environment():
174 | special_keys = ['PATH', 'GOPATH', 'GOROOT']
175 | env = {}
176 |
177 | # Gather up keys from the OS environment.
178 | for k in special_keys:
179 | env[k] = os.getenv(k, '')
180 |
181 | # Hide popups on Windows
182 | si = None
183 | if platform.system() == "Windows":
184 | si = subprocess.STARTUPINFO()
185 | si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
186 |
187 | # For non-Windows platforms, use a login shell to get environment. Write the
188 | # values to a tempfile; relying on stdout is brittle because of things like
189 | # ANSI color codes which can come over stdout when .profile/.bashrc are
190 | # sourced.
191 | if platform.system() != "Windows":
192 | for k in special_keys:
193 | tempf = tempfile.NamedTemporaryFile()
194 | cmd = [os.getenv("SHELL"), "-l", "-c", "sh -c -l 'echo ${0}>{1}'".format(k, tempf.name)]
195 | try:
196 | subprocess.check_output(cmd)
197 | val = tempf.read().decode("utf-8").rstrip()
198 | if len(val) > 0:
199 | env[k] = val
200 | except subprocess.CalledProcessError as e:
201 | raise Exception("couldn't resolve environment variable '{0}': {1}".format(k, str(e)))
202 |
203 | if len(env['PATH']) == 0:
204 | raise Exception("couldn't resolve PATH via system environment or login shell")
205 |
206 | # Resolve the go binary.
207 | gobinary = GoToolsSettings.find_go_binary(env['PATH'])
208 |
209 | # Gather up the Go environment using `go env`, but only keep keys which
210 | # aren't already set from the shell or OS environment.
211 | cmdenv = os.environ.copy()
212 | for k in env:
213 | cmdenv[k] = env[k]
214 | goenv, stderr = subprocess.Popen([gobinary, 'env'],
215 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, startupinfo=si, env=cmdenv).communicate()
216 | if stderr and len(stderr) > 0:
217 | raise Exception("'{0} env' returned an error: {1}".format(gobinary, stderr.decode()))
218 |
219 | for name in goenv.decode().splitlines():
220 | match = re.match('(.*)=\"(.*)\"', name)
221 | if platform.system() == "Windows":
222 | match = re.match('(?:set\s)(.*)=(.*)', name)
223 | if match and match.group(1) and match.group(2):
224 | k = match.group(1)
225 | v = match.group(2)
226 | if not k in env or len(env[k]) == 0:
227 | env[k] = v
228 |
229 | print("GoTools: using environment: {0}".format(str(env)))
230 | return env
231 |
232 | # Returns the absolute path to the go binary found on path. Raises an
233 | # exception if go can't be found.
234 | @staticmethod
235 | def find_go_binary(path=""):
236 | goname = "go"
237 | if platform.system() == "Windows":
238 | goname = "go.exe"
239 | for segment in path.split(os.pathsep):
240 | candidate = os.path.join(segment, goname)
241 | if os.path.isfile(candidate):
242 | return candidate
243 | raise Exception("couldn't find the go binary in path: {0}".format(path))
244 |
--------------------------------------------------------------------------------
/gotools_suggestions.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import sublime_plugin
3 | import json
4 | import os
5 |
6 | from .gotools_util import Buffers
7 | from .gotools_util import GoBuffers
8 | from .gotools_util import Logger
9 | from .gotools_util import ToolRunner
10 | from .gotools_settings import GoToolsSettings
11 |
12 | class GotoolsSuggestions(sublime_plugin.EventListener):
13 | CLASS_SYMBOLS = {
14 | "func": "ƒ",
15 | "var": "ν",
16 | "type": "ʈ",
17 | "package": "ρ"
18 | }
19 |
20 | def on_query_completions(self, view, prefix, locations):
21 | if not GoBuffers.is_go_source(view): return
22 | if not GoToolsSettings.get().autocomplete: return
23 |
24 | # set the lib-path for gocode's lookups
25 | _, _, rc = ToolRunner.run("gocode", ["set", "lib-path", GoToolsSettings.get().golibpath])
26 |
27 | suggestionsJsonStr, stderr, rc = ToolRunner.run("gocode", ["-f=json", "autocomplete",
28 | str(Buffers.offset_at_cursor(view)[0])], stdin=Buffers.buffer_text(view))
29 |
30 | # TODO: restore gocode's lib-path
31 |
32 | suggestionsJson = json.loads(suggestionsJsonStr)
33 |
34 | Logger.log("DEBUG: gocode output: " + suggestionsJsonStr)
35 |
36 | if rc != 0:
37 | Logger.status("no completions found: " + str(e))
38 | return []
39 |
40 | if len(suggestionsJson) > 0:
41 | return ([GotoolsSuggestions.build_suggestion(j) for j in suggestionsJson[1]], sublime.INHIBIT_WORD_COMPLETIONS)
42 | else:
43 | return []
44 |
45 | @staticmethod
46 | def build_suggestion(json):
47 | label = '{0: <30.30} {1: <40.40} {2}'.format(
48 | json["name"],
49 | json["type"],
50 | GotoolsSuggestions.CLASS_SYMBOLS.get(json["class"], "?"))
51 | return (label, json["name"])
52 |
--------------------------------------------------------------------------------
/gotools_util.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | import os
3 | import re
4 | import platform
5 | import subprocess
6 | import time
7 |
8 | from .gotools_settings import GoToolsSettings
9 |
10 | class Buffers():
11 | @staticmethod
12 | def offset_at_row_col(view, row, col):
13 | point = view.text_point(row, col)
14 | select_region = sublime.Region(0, point)
15 | string_region = view.substr(select_region)
16 | buffer_region = bytearray(string_region, encoding="utf8")
17 | offset = len(buffer_region)
18 | return offset
19 |
20 | @staticmethod
21 | def buffer_text(view):
22 | file_text = sublime.Region(0, view.size())
23 | return view.substr(file_text).encode('utf-8')
24 |
25 | @staticmethod
26 | def offset_at_cursor(view):
27 | begin_row, begin_col = view.rowcol(view.sel()[0].begin())
28 | end_row, end_col = view.rowcol(view.sel()[0].end())
29 |
30 | return (Buffers.offset_at_row_col(view, begin_row, begin_col), Buffers.offset_at_row_col(view, end_row, end_col))
31 |
32 | @staticmethod
33 | def location_at_cursor(view):
34 | row, col = view.rowcol(view.sel()[0].begin())
35 | offsets = Buffers.offset_at_cursor(view)
36 | return (view.file_name(), row, col, offsets[0], offsets[1])
37 |
38 | @staticmethod
39 | def location_for_event(view, event):
40 | pt = view.window_to_text((event["x"], event["y"]))
41 | row, col = view.rowcol(pt)
42 | offset = Buffers.offset_at_row_col(view, row, col)
43 | return (view.file_name(), row, col, offset)
44 |
45 | class GoBuffers():
46 | @staticmethod
47 | def func_name_at_cursor(view):
48 | func_regions = view.find_by_selector('meta.function')
49 |
50 | func_name = ""
51 | for r in func_regions:
52 | if r.contains(Buffers.offset_at_cursor(view)[0]):
53 | lines = view.substr(r).splitlines()
54 | match = re.match('func.*(Test.+)\(', lines[0])
55 | if match and match.group(1):
56 | func_name = match.group(1)
57 | break
58 |
59 | return func_name
60 |
61 | @staticmethod
62 | def is_go_source(view):
63 | return view.score_selector(0, 'source.go') != 0
64 |
65 | class Logger():
66 | @staticmethod
67 | def log(msg):
68 | if GoToolsSettings.get().debug_enabled:
69 | print("GoTools: DEBUG: {0}".format(msg))
70 |
71 | @staticmethod
72 | def error(msg):
73 | print("GoTools: ERROR: {0}".format(msg))
74 |
75 | @staticmethod
76 | def status(msg):
77 | sublime.status_message("GoTools: " + msg)
78 |
79 | class ToolRunner():
80 | @staticmethod
81 | def run(tool, args=[], stdin=None, timeout=5):
82 | toolpath = None
83 | searchpaths = list(map(lambda x: os.path.join(x, 'bin'), GoToolsSettings.get().gopath.split(os.pathsep)))
84 | for p in GoToolsSettings.get().ospath.split(os.pathsep):
85 | searchpaths.append(p)
86 | searchpaths.append(os.path.join(GoToolsSettings.get().goroot, 'bin'))
87 | searchpaths.append(GoToolsSettings.get().gorootbin)
88 |
89 | if platform.system() == "Windows":
90 | tool = tool + ".exe"
91 |
92 | for path in searchpaths:
93 | candidate = os.path.join(path, tool)
94 | if os.path.isfile(candidate):
95 | toolpath = candidate
96 | break
97 |
98 | if not toolpath:
99 | Logger.log("Couldn't find Go tool '{0}' in:\n{1}".format(tool, "\n".join(searchpaths)))
100 | raise Exception("Error running Go tool '{0}'; check the console logs for details".format(tool))
101 |
102 | cmd = [toolpath] + args
103 | try:
104 | Logger.log("spawning process...")
105 |
106 | env = os.environ.copy()
107 | env["PATH"] = GoToolsSettings.get().ospath
108 | env["GOPATH"] = GoToolsSettings.get().gopath
109 | env["GOROOT"] = GoToolsSettings.get().goroot
110 |
111 | Logger.log("\tcommand: " + " ".join(cmd))
112 | Logger.log("\tenvironment: " + str(env))
113 |
114 | # Hide popups on Windows
115 | si = None
116 | if platform.system() == "Windows":
117 | si = subprocess.STARTUPINFO()
118 | si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
119 |
120 | start = time.time()
121 | p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, startupinfo=si)
122 | stdout, stderr = p.communicate(input=stdin, timeout=timeout)
123 | p.wait(timeout=timeout)
124 | elapsed = round(time.time() - start)
125 | Logger.log("process returned ({0}) in {1} seconds".format(str(p.returncode), str(elapsed)))
126 | stderr = stderr.decode("utf-8")
127 | if len(stderr) > 0:
128 | Logger.log("stderr:\n{0}".format(stderr))
129 | return stdout.decode("utf-8"), stderr, p.returncode
130 | except subprocess.CalledProcessError as e:
131 | raise
132 |
--------------------------------------------------------------------------------