├── 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 | --------------------------------------------------------------------------------