├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature---enhancement-request.md ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── GodotAddinVS.sln ├── GodotAddinVS.sln.DotSettings ├── GodotAddinVS ├── Commands │ ├── CommandResetGodot.cs │ └── CommandRunGodot.cs ├── Debugging │ ├── GodotDebugTarget.cs │ ├── GodotDebugTargetSelection.cs │ ├── GodotDebuggableProjectCfg.cs │ ├── GodotDebuggerSession.cs │ └── GodotStartInfo.cs ├── GeneralOptionsPage.cs ├── GodotAddinVS.csproj ├── GodotFlavoredProject.cs ├── GodotFlavoredProjectFactory.cs ├── GodotMessaging │ └── MessageHandler.cs ├── GodotPackage.cs ├── GodotPackage.vsct ├── GodotSolutionEventsListener.cs ├── GodotVSLogger.cs ├── GodotVariant.cs ├── GodotVsProviderContext.cs ├── LICENSE.txt ├── NuGet.Config ├── Properties │ └── AssemblyInfo.cs ├── SolutionEventsListener.cs ├── icon.png └── source.extension.vsixmanifest ├── GodotCompletionProviders.Test ├── GodotCompletionProviders.Test.csproj ├── InputActionTests.cs ├── NodePathTests.cs ├── Properties │ └── AssemblyInfo.cs ├── ResourcePathTests.cs ├── ScenePathTests.cs ├── SignalNameTests.cs ├── TestsBase.cs ├── Utils.cs └── packages.config ├── GodotCompletionProviders ├── BaseCompletionProvider.cs ├── CompletionKind.cs ├── GodotCompletionProviders.csproj ├── ILogger.cs ├── IProviderContext.cs ├── InputActionCompletionProvider.cs ├── NodePathCompletionProvider.cs ├── ResourcePathCompletionProvider.cs ├── RoslynUtils.cs ├── ScenePathCompletionProvider.cs ├── SignalNameCompletionProvider.cs └── SpecificInvocationCompletionProvider.cs ├── LICENSE ├── NuGet.Config ├── README.md └── format.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: godotengine 2 | custom: https://godotengine.org/donate 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug with the extension. 4 | title: '' 5 | labels: bug 6 | assignees: neikeq 7 | 8 | --- 9 | 10 | 16 | 17 | **OS/device including version:** 18 | 19 | 20 | 21 | **Issue description:** 22 | 23 | 24 | 25 | **Screenshots of issue:** 26 | 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature---enhancement-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature / Enhancement Request 3 | about: Adding new features or improving existing ones. 4 | title: '' 5 | labels: enhancement 6 | assignees: neikeq 7 | 8 | --- 9 | 10 | 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-20.04 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v2 10 | 11 | - name: Lint extension 12 | run: | 13 | sudo apt-get update -qq 14 | sudo apt-get install -qq dos2unix recode 15 | bash ./format.sh 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rider 2 | .idea/ 3 | 4 | # Visual Studio Code 5 | .vscode/ 6 | 7 | ## Ignore Visual Studio temporary files, build results, and 8 | ## files generated by popular Visual Studio add-ons. 9 | ## 10 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 11 | 12 | # User-specific files 13 | *.rsuser 14 | *.suo 15 | *.user 16 | *.userosscache 17 | *.sln.docstates 18 | 19 | # User-specific files (MonoDevelop/Xamarin Studio) 20 | *.userprefs 21 | 22 | # Mono auto generated files 23 | mono_crash.* 24 | 25 | # Build results 26 | [Dd]ebug/ 27 | [Dd]ebugPublic/ 28 | [Rr]elease/ 29 | [Rr]eleases/ 30 | x64/ 31 | x86/ 32 | [Aa][Rr][Mm]/ 33 | [Aa][Rr][Mm]64/ 34 | bld/ 35 | [Bb]in/ 36 | [Oo]bj/ 37 | [Ll]og/ 38 | 39 | # Visual Studio 2015/2017 cache/options directory 40 | .vs/ 41 | # Uncomment if you have tasks that create the project's static files in wwwroot 42 | #wwwroot/ 43 | 44 | # Visual Studio 2017 auto generated files 45 | Generated\ Files/ 46 | 47 | # MSTest test Results 48 | [Tt]est[Rr]esult*/ 49 | [Bb]uild[Ll]og.* 50 | 51 | # NUnit 52 | *.VisualState.xml 53 | TestResult.xml 54 | nunit-*.xml 55 | 56 | # Build Results of an ATL Project 57 | [Dd]ebugPS/ 58 | [Rr]eleasePS/ 59 | dlldata.c 60 | 61 | # Benchmark Results 62 | BenchmarkDotNet.Artifacts/ 63 | 64 | # .NET Core 65 | project.lock.json 66 | project.fragment.lock.json 67 | artifacts/ 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # JustCode is a .NET coding add-in 136 | .JustCode 137 | 138 | # TeamCity is a build add-in 139 | _TeamCity* 140 | 141 | # DotCover is a Code Coverage Tool 142 | *.dotCover 143 | 144 | # AxoCover is a Code Coverage Tool 145 | .axoCover/* 146 | !.axoCover/settings.json 147 | 148 | # Visual Studio code coverage results 149 | *.coverage 150 | *.coveragexml 151 | 152 | # NCrunch 153 | _NCrunch_* 154 | .*crunch*.local.xml 155 | nCrunchTemp_* 156 | 157 | # MightyMoose 158 | *.mm.* 159 | AutoTest.Net/ 160 | 161 | # Web workbench (sass) 162 | .sass-cache/ 163 | 164 | # Installshield output folder 165 | [Ee]xpress/ 166 | 167 | # DocProject is a documentation generator add-in 168 | DocProject/buildhelp/ 169 | DocProject/Help/*.HxT 170 | DocProject/Help/*.HxC 171 | DocProject/Help/*.hhc 172 | DocProject/Help/*.hhk 173 | DocProject/Help/*.hhp 174 | DocProject/Help/Html2 175 | DocProject/Help/html 176 | 177 | # Click-Once directory 178 | publish/ 179 | 180 | # Publish Web Output 181 | *.[Pp]ublish.xml 182 | *.azurePubxml 183 | # Note: Comment the next line if you want to checkin your web deploy settings, 184 | # but database connection strings (with potential passwords) will be unencrypted 185 | *.pubxml 186 | *.publishproj 187 | 188 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 189 | # checkin your Azure Web App publish settings, but sensitive information contained 190 | # in these scripts will be unencrypted 191 | PublishScripts/ 192 | 193 | # NuGet Packages 194 | *.nupkg 195 | # NuGet Symbol Packages 196 | *.snupkg 197 | # The packages folder can be ignored because of Package Restore 198 | **/[Pp]ackages/* 199 | # except build/, which is used as an MSBuild target. 200 | !**/[Pp]ackages/build/ 201 | # Uncomment if necessary however generally it will be regenerated when needed 202 | #!**/[Pp]ackages/repositories.config 203 | # NuGet v3's project.json files produces more ignorable files 204 | *.nuget.props 205 | *.nuget.targets 206 | 207 | # Microsoft Azure Build Output 208 | csx/ 209 | *.build.csdef 210 | 211 | # Microsoft Azure Emulator 212 | ecf/ 213 | rcf/ 214 | 215 | # Windows Store app package directories and files 216 | AppPackages/ 217 | BundleArtifacts/ 218 | Package.StoreAssociation.xml 219 | _pkginfo.txt 220 | *.appx 221 | *.appxbundle 222 | *.appxupload 223 | 224 | # Visual Studio cache files 225 | # files ending in .cache can be ignored 226 | *.[Cc]ache 227 | # but keep track of directories ending in .cache 228 | !?*.[Cc]ache/ 229 | 230 | # Others 231 | ClientBin/ 232 | ~$* 233 | *~ 234 | *.dbmdl 235 | *.dbproj.schemaview 236 | *.jfm 237 | *.pfx 238 | *.publishsettings 239 | orleans.codegen.cs 240 | 241 | # Including strong name files can present a security risk 242 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 243 | #*.snk 244 | 245 | # Since there are multiple workflows, uncomment next line to ignore bower_components 246 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 247 | #bower_components/ 248 | 249 | # RIA/Silverlight projects 250 | Generated_Code/ 251 | 252 | # Backup & report files from converting an old project file 253 | # to a newer Visual Studio version. Backup files are not needed, 254 | # because we have git ;-) 255 | _UpgradeReport_Files/ 256 | Backup*/ 257 | UpgradeLog*.XML 258 | UpgradeLog*.htm 259 | ServiceFabricBackup/ 260 | *.rptproj.bak 261 | 262 | # SQL Server files 263 | *.mdf 264 | *.ldf 265 | *.ndf 266 | 267 | # Business Intelligence projects 268 | *.rdl.data 269 | *.bim.layout 270 | *.bim_*.settings 271 | *.rptproj.rsuser 272 | *- [Bb]ackup.rdl 273 | *- [Bb]ackup ([0-9]).rdl 274 | *- [Bb]ackup ([0-9][0-9]).rdl 275 | 276 | # Microsoft Fakes 277 | FakesAssemblies/ 278 | 279 | # GhostDoc plugin setting file 280 | *.GhostDoc.xml 281 | 282 | # Node.js Tools for Visual Studio 283 | .ntvs_analysis.dat 284 | node_modules/ 285 | 286 | # Visual Studio 6 build log 287 | *.plg 288 | 289 | # Visual Studio 6 workspace options file 290 | *.opt 291 | 292 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 293 | *.vbw 294 | 295 | # Visual Studio LightSwitch build output 296 | **/*.HTMLClient/GeneratedArtifacts 297 | **/*.DesktopClient/GeneratedArtifacts 298 | **/*.DesktopClient/ModelManifest.xml 299 | **/*.Server/GeneratedArtifacts 300 | **/*.Server/ModelManifest.xml 301 | _Pvt_Extensions 302 | 303 | # Paket dependency manager 304 | .paket/paket.exe 305 | paket-files/ 306 | 307 | # FAKE - F# Make 308 | .fake/ 309 | 310 | # CodeRush personal settings 311 | .cr/personal 312 | 313 | # Python Tools for Visual Studio (PTVS) 314 | __pycache__/ 315 | *.pyc 316 | 317 | # Cake - Uncomment if you are using it 318 | # tools/** 319 | # !tools/packages.config 320 | 321 | # Tabs Studio 322 | *.tss 323 | 324 | # Telerik's JustMock configuration file 325 | *.jmconfig 326 | 327 | # BizTalk build output 328 | *.btp.cs 329 | *.btm.cs 330 | *.odx.cs 331 | *.xsd.cs 332 | 333 | # OpenCover UI analysis results 334 | OpenCover/ 335 | 336 | # Azure Stream Analytics local run output 337 | ASALocalRun/ 338 | 339 | # MSBuild Binary and Structured Log 340 | *.binlog 341 | 342 | # NVidia Nsight GPU debugger configuration file 343 | *.nvuser 344 | 345 | # MFractors (Xamarin productivity tool) working folder 346 | .mfractor/ 347 | 348 | # Local History for Visual Studio 349 | .localhistory/ 350 | 351 | # BeatPulse healthcheck temp database 352 | healthchecksdb 353 | 354 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 355 | MigrationBackup/ 356 | 357 | # Rider 358 | .idea/ 359 | 360 | # Visual Studio Code 361 | .vscode/ 362 | 363 | ## Ignore Visual Studio temporary files, build results, and 364 | ## files generated by popular Visual Studio add-ons. 365 | ## 366 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 367 | 368 | # User-specific files 369 | *.rsuser 370 | *.suo 371 | *.user 372 | *.userosscache 373 | *.sln.docstates 374 | 375 | # User-specific files (MonoDevelop/Xamarin Studio) 376 | *.userprefs 377 | 378 | # Mono auto generated files 379 | mono_crash.* 380 | 381 | # Build results 382 | [Dd]ebug/ 383 | [Dd]ebugPublic/ 384 | [Rr]elease/ 385 | [Rr]eleases/ 386 | x64/ 387 | x86/ 388 | [Aa][Rr][Mm]/ 389 | [Aa][Rr][Mm]64/ 390 | bld/ 391 | [Bb]in/ 392 | [Oo]bj/ 393 | [Ll]og/ 394 | 395 | # Visual Studio 2015/2017 cache/options directory 396 | .vs/ 397 | # Uncomment if you have tasks that create the project's static files in wwwroot 398 | #wwwroot/ 399 | 400 | # Visual Studio 2017 auto generated files 401 | Generated\ Files/ 402 | 403 | # MSTest test Results 404 | [Tt]est[Rr]esult*/ 405 | [Bb]uild[Ll]og.* 406 | 407 | # NUnit 408 | *.VisualState.xml 409 | TestResult.xml 410 | nunit-*.xml 411 | 412 | # Build Results of an ATL Project 413 | [Dd]ebugPS/ 414 | [Rr]eleasePS/ 415 | dlldata.c 416 | 417 | # Benchmark Results 418 | BenchmarkDotNet.Artifacts/ 419 | 420 | # .NET Core 421 | project.lock.json 422 | project.fragment.lock.json 423 | artifacts/ 424 | 425 | # StyleCop 426 | StyleCopReport.xml 427 | 428 | # Files built by Visual Studio 429 | *_i.c 430 | *_p.c 431 | *_h.h 432 | *.ilk 433 | *.meta 434 | *.obj 435 | *.iobj 436 | *.pch 437 | *.pdb 438 | *.ipdb 439 | *.pgc 440 | *.pgd 441 | *.rsp 442 | *.sbr 443 | *.tlb 444 | *.tli 445 | *.tlh 446 | *.tmp 447 | *.tmp_proj 448 | *_wpftmp.csproj 449 | *.log 450 | *.vspscc 451 | *.vssscc 452 | .builds 453 | *.pidb 454 | *.svclog 455 | *.scc 456 | 457 | # Chutzpah Test files 458 | _Chutzpah* 459 | 460 | # Visual C++ cache files 461 | ipch/ 462 | *.aps 463 | *.ncb 464 | *.opendb 465 | *.opensdf 466 | *.sdf 467 | *.cachefile 468 | *.VC.db 469 | *.VC.VC.opendb 470 | 471 | # Visual Studio profiler 472 | *.psess 473 | *.vsp 474 | *.vspx 475 | *.sap 476 | 477 | # Visual Studio Trace Files 478 | *.e2e 479 | 480 | # TFS 2012 Local Workspace 481 | $tf/ 482 | 483 | # Guidance Automation Toolkit 484 | *.gpState 485 | 486 | # ReSharper is a .NET coding add-in 487 | _ReSharper*/ 488 | *.[Rr]e[Ss]harper 489 | *.DotSettings.user 490 | 491 | # JustCode is a .NET coding add-in 492 | .JustCode 493 | 494 | # TeamCity is a build add-in 495 | _TeamCity* 496 | 497 | # DotCover is a Code Coverage Tool 498 | *.dotCover 499 | 500 | # AxoCover is a Code Coverage Tool 501 | .axoCover/* 502 | !.axoCover/settings.json 503 | 504 | # Visual Studio code coverage results 505 | *.coverage 506 | *.coveragexml 507 | 508 | # NCrunch 509 | _NCrunch_* 510 | .*crunch*.local.xml 511 | nCrunchTemp_* 512 | 513 | # MightyMoose 514 | *.mm.* 515 | AutoTest.Net/ 516 | 517 | # Web workbench (sass) 518 | .sass-cache/ 519 | 520 | # Installshield output folder 521 | [Ee]xpress/ 522 | 523 | # DocProject is a documentation generator add-in 524 | DocProject/buildhelp/ 525 | DocProject/Help/*.HxT 526 | DocProject/Help/*.HxC 527 | DocProject/Help/*.hhc 528 | DocProject/Help/*.hhk 529 | DocProject/Help/*.hhp 530 | DocProject/Help/Html2 531 | DocProject/Help/html 532 | 533 | # Click-Once directory 534 | publish/ 535 | 536 | # Publish Web Output 537 | *.[Pp]ublish.xml 538 | *.azurePubxml 539 | # Note: Comment the next line if you want to checkin your web deploy settings, 540 | # but database connection strings (with potential passwords) will be unencrypted 541 | *.pubxml 542 | *.publishproj 543 | 544 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 545 | # checkin your Azure Web App publish settings, but sensitive information contained 546 | # in these scripts will be unencrypted 547 | PublishScripts/ 548 | 549 | # NuGet Packages 550 | *.nupkg 551 | # NuGet Symbol Packages 552 | *.snupkg 553 | # The packages folder can be ignored because of Package Restore 554 | **/[Pp]ackages/* 555 | # except build/, which is used as an MSBuild target. 556 | !**/[Pp]ackages/build/ 557 | # Uncomment if necessary however generally it will be regenerated when needed 558 | #!**/[Pp]ackages/repositories.config 559 | # NuGet v3's project.json files produces more ignorable files 560 | *.nuget.props 561 | *.nuget.targets 562 | 563 | # Microsoft Azure Build Output 564 | csx/ 565 | *.build.csdef 566 | 567 | # Microsoft Azure Emulator 568 | ecf/ 569 | rcf/ 570 | 571 | # Windows Store app package directories and files 572 | AppPackages/ 573 | BundleArtifacts/ 574 | Package.StoreAssociation.xml 575 | _pkginfo.txt 576 | *.appx 577 | *.appxbundle 578 | *.appxupload 579 | 580 | # Visual Studio cache files 581 | # files ending in .cache can be ignored 582 | *.[Cc]ache 583 | # but keep track of directories ending in .cache 584 | !?*.[Cc]ache/ 585 | 586 | # Others 587 | ClientBin/ 588 | ~$* 589 | *~ 590 | *.dbmdl 591 | *.dbproj.schemaview 592 | *.jfm 593 | *.pfx 594 | *.publishsettings 595 | orleans.codegen.cs 596 | 597 | # Including strong name files can present a security risk 598 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 599 | #*.snk 600 | 601 | # Since there are multiple workflows, uncomment next line to ignore bower_components 602 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 603 | #bower_components/ 604 | 605 | # RIA/Silverlight projects 606 | Generated_Code/ 607 | 608 | # Backup & report files from converting an old project file 609 | # to a newer Visual Studio version. Backup files are not needed, 610 | # because we have git ;-) 611 | _UpgradeReport_Files/ 612 | Backup*/ 613 | UpgradeLog*.XML 614 | UpgradeLog*.htm 615 | ServiceFabricBackup/ 616 | *.rptproj.bak 617 | 618 | # SQL Server files 619 | *.mdf 620 | *.ldf 621 | *.ndf 622 | 623 | # Business Intelligence projects 624 | *.rdl.data 625 | *.bim.layout 626 | *.bim_*.settings 627 | *.rptproj.rsuser 628 | *- [Bb]ackup.rdl 629 | *- [Bb]ackup ([0-9]).rdl 630 | *- [Bb]ackup ([0-9][0-9]).rdl 631 | 632 | # Microsoft Fakes 633 | FakesAssemblies/ 634 | 635 | # GhostDoc plugin setting file 636 | *.GhostDoc.xml 637 | 638 | # Node.js Tools for Visual Studio 639 | .ntvs_analysis.dat 640 | node_modules/ 641 | 642 | # Visual Studio 6 build log 643 | *.plg 644 | 645 | # Visual Studio 6 workspace options file 646 | *.opt 647 | 648 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 649 | *.vbw 650 | 651 | # Visual Studio LightSwitch build output 652 | **/*.HTMLClient/GeneratedArtifacts 653 | **/*.DesktopClient/GeneratedArtifacts 654 | **/*.DesktopClient/ModelManifest.xml 655 | **/*.Server/GeneratedArtifacts 656 | **/*.Server/ModelManifest.xml 657 | _Pvt_Extensions 658 | 659 | # Paket dependency manager 660 | .paket/paket.exe 661 | paket-files/ 662 | 663 | # FAKE - F# Make 664 | .fake/ 665 | 666 | # CodeRush personal settings 667 | .cr/personal 668 | 669 | # Python Tools for Visual Studio (PTVS) 670 | __pycache__/ 671 | *.pyc 672 | 673 | # Cake - Uncomment if you are using it 674 | # tools/** 675 | # !tools/packages.config 676 | 677 | # Tabs Studio 678 | *.tss 679 | 680 | # Telerik's JustMock configuration file 681 | *.jmconfig 682 | 683 | # BizTalk build output 684 | *.btp.cs 685 | *.btm.cs 686 | *.odx.cs 687 | *.xsd.cs 688 | 689 | # OpenCover UI analysis results 690 | OpenCover/ 691 | 692 | # Azure Stream Analytics local run output 693 | ASALocalRun/ 694 | 695 | # MSBuild Binary and Structured Log 696 | *.binlog 697 | 698 | # NVidia Nsight GPU debugger configuration file 699 | *.nvuser 700 | 701 | # MFractors (Xamarin productivity tool) working folder 702 | .mfractor/ 703 | 704 | # Local History for Visual Studio 705 | .localhistory/ 706 | 707 | # BeatPulse healthcheck temp database 708 | healthchecksdb 709 | 710 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 711 | MigrationBackup/ 712 | -------------------------------------------------------------------------------- /GodotAddinVS.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29806.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotAddinVS", "GodotAddinVS\GodotAddinVS.csproj", "{7D64803D-2F8D-4597-9762-A316C74E9816}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotCompletionProviders", "GodotCompletionProviders\GodotCompletionProviders.csproj", "{A9EA6427-C5E2-4207-BBBF-A1F44A361339}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotCompletionProviders.Test", "GodotCompletionProviders.Test\GodotCompletionProviders.Test.csproj", "{B2BAAEA3-8B1D-4584-A5D1-9D4EF487E0DF}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | net_4_5_Debug|Any CPU = net_4_5_Debug|Any CPU 16 | net_4_5_Release|Any CPU = net_4_5_Release|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {7D64803D-2F8D-4597-9762-A316C74E9816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {7D64803D-2F8D-4597-9762-A316C74E9816}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {7D64803D-2F8D-4597-9762-A316C74E9816}.net_4_5_Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {7D64803D-2F8D-4597-9762-A316C74E9816}.net_4_5_Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {7D64803D-2F8D-4597-9762-A316C74E9816}.net_4_5_Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {7D64803D-2F8D-4597-9762-A316C74E9816}.net_4_5_Release|Any CPU.Build.0 = Release|Any CPU 26 | {7D64803D-2F8D-4597-9762-A316C74E9816}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {7D64803D-2F8D-4597-9762-A316C74E9816}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {A9EA6427-C5E2-4207-BBBF-A1F44A361339}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {A9EA6427-C5E2-4207-BBBF-A1F44A361339}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {A9EA6427-C5E2-4207-BBBF-A1F44A361339}.net_4_5_Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {A9EA6427-C5E2-4207-BBBF-A1F44A361339}.net_4_5_Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {A9EA6427-C5E2-4207-BBBF-A1F44A361339}.net_4_5_Release|Any CPU.ActiveCfg = Debug|Any CPU 33 | {A9EA6427-C5E2-4207-BBBF-A1F44A361339}.net_4_5_Release|Any CPU.Build.0 = Debug|Any CPU 34 | {A9EA6427-C5E2-4207-BBBF-A1F44A361339}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {A9EA6427-C5E2-4207-BBBF-A1F44A361339}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {B2BAAEA3-8B1D-4584-A5D1-9D4EF487E0DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {B2BAAEA3-8B1D-4584-A5D1-9D4EF487E0DF}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {B2BAAEA3-8B1D-4584-A5D1-9D4EF487E0DF}.net_4_5_Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {B2BAAEA3-8B1D-4584-A5D1-9D4EF487E0DF}.net_4_5_Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {B2BAAEA3-8B1D-4584-A5D1-9D4EF487E0DF}.net_4_5_Release|Any CPU.ActiveCfg = Debug|Any CPU 41 | {B2BAAEA3-8B1D-4584-A5D1-9D4EF487E0DF}.net_4_5_Release|Any CPU.Build.0 = Debug|Any CPU 42 | {B2BAAEA3-8B1D-4584-A5D1-9D4EF487E0DF}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {B2BAAEA3-8B1D-4584-A5D1-9D4EF487E0DF}.Release|Any CPU.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(ExtensibilityGlobals) = postSolution 49 | SolutionGuid = {60B650D3-F98F-4016-99A7-C3C7B12E6BBC} 50 | EndGlobalSection 51 | EndGlobal 52 | -------------------------------------------------------------------------------- /GodotAddinVS.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True 4 | True 5 | True 6 | True 7 | True 8 | -------------------------------------------------------------------------------- /GodotAddinVS/Commands/CommandResetGodot.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using Microsoft.VisualStudio.Shell.Interop; 3 | using System; 4 | using System.ComponentModel.Design; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | using Microsoft.VisualStudio.Settings; 11 | using Microsoft.VisualStudio.Shell.Settings; 12 | using Task = System.Threading.Tasks.Task; 13 | 14 | namespace GodotAddinVS.Commands { 15 | /// 16 | /// Command handler 17 | /// 18 | internal sealed class CommandResetGodot { 19 | /// 20 | /// Command ID. 21 | /// 22 | public const int CommandId = 256; 23 | 24 | /// 25 | /// Command menu group (command set GUID). 26 | /// 27 | public static readonly Guid CommandSet = new Guid("38009f93-330e-4875-ab88-e127fd85bb88"); 28 | 29 | /// 30 | /// VS Package that provides this command, not null. 31 | /// 32 | private readonly AsyncPackage package; 33 | 34 | /// 35 | /// Initializes a new instance of the class. 36 | /// Adds our command handlers for menu (commands must exist in the command table file) 37 | /// 38 | /// Owner package, not null. 39 | /// Command service to add command to, not null. 40 | private CommandResetGodot(AsyncPackage package, OleMenuCommandService commandService) { 41 | this.package = package ?? throw new ArgumentNullException(nameof(package)); 42 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 43 | 44 | var menuCommandID = new CommandID(CommandSet, CommandId); 45 | var menuItem = new MenuCommand(this.Execute, menuCommandID); 46 | commandService.AddCommand(menuItem); 47 | } 48 | 49 | /// 50 | /// Gets the instance of the command. 51 | /// 52 | public static CommandResetGodot Instance { 53 | get; 54 | private set; 55 | } 56 | 57 | /// 58 | /// Gets the service provider from the owner package. 59 | /// 60 | private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider { 61 | get { 62 | return this.package; 63 | } 64 | } 65 | 66 | /// 67 | /// Initializes the singleton instance of the command. 68 | /// 69 | /// Owner package, not null. 70 | public static async Task InitializeAsync(AsyncPackage package) { 71 | // Switch to the main thread - the call to AddCommand in CommandResetGodot's constructor requires 72 | // the UI thread. 73 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 74 | 75 | OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 76 | Instance = new CommandResetGodot(package, commandService); 77 | } 78 | 79 | /// 80 | /// This function is the callback used to execute the command when the menu item is clicked. 81 | /// See the constructor to see how the menu item is associated with this function using 82 | /// OleMenuCommandService service and MenuCommand class. 83 | /// 84 | /// Event sender. 85 | /// Event args. 86 | private void Execute(object sender, EventArgs e) { 87 | ThreadHelper.ThrowIfNotOnUIThread(); 88 | var settingsManager = new ShellSettingsManager(package); 89 | var config = settingsManager.GetWritableSettingsStore(SettingsScope.UserSettings); 90 | 91 | var ofd = new OpenFileDialog { 92 | Filter = @"Godot executable (.exe)|*.exe" 93 | }; 94 | var result = ofd.ShowDialog(null); 95 | 96 | if (result != DialogResult.OK) return; 97 | config.SetString("External Tools", "GodotExecutable", ofd.FileName); 98 | config.SetString("External Tools", "GodotPath", Path.GetDirectoryName(ofd.FileName)); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /GodotAddinVS/Commands/CommandRunGodot.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using Microsoft.VisualStudio.Shell.Interop; 3 | using System; 4 | using System.ComponentModel.Design; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using System.IO; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using System.Windows.Forms; 11 | using Microsoft.VisualStudio.Settings; 12 | using Microsoft.VisualStudio.Shell.Settings; 13 | using Task = System.Threading.Tasks.Task; 14 | 15 | namespace GodotAddinVS.Commands { 16 | /// 17 | /// Command handler 18 | /// 19 | internal sealed class CommandRunGodot { 20 | /// 21 | /// Command ID. 22 | /// 23 | public const int CommandId = 256; 24 | 25 | /// 26 | /// Command menu group (command set GUID). 27 | /// 28 | public static readonly Guid CommandSet = new Guid("d71528ca-92b8-49bb-8655-8b478b495499"); 29 | 30 | /// 31 | /// VS Package that provides this command, not null. 32 | /// 33 | private readonly AsyncPackage package; 34 | 35 | /// 36 | /// Initializes a new instance of the class. 37 | /// Adds our command handlers for menu (commands must exist in the command table file) 38 | /// 39 | /// Owner package, not null. 40 | /// Command service to add command to, not null. 41 | private CommandRunGodot(AsyncPackage package, OleMenuCommandService commandService) { 42 | this.package = package ?? throw new ArgumentNullException(nameof(package)); 43 | commandService = commandService ?? throw new ArgumentNullException(nameof(commandService)); 44 | 45 | var menuCommandId = new CommandID(CommandSet, CommandId); 46 | var menuItem = new MenuCommand(Execute, menuCommandId); 47 | commandService.AddCommand(menuItem); 48 | } 49 | 50 | /// 51 | /// Gets the instance of the command. 52 | /// 53 | public static CommandRunGodot Instance { 54 | get; 55 | private set; 56 | } 57 | 58 | /// 59 | /// Gets the service provider from the owner package. 60 | /// 61 | private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider { 62 | get { 63 | return this.package; 64 | } 65 | } 66 | 67 | /// 68 | /// Initializes the singleton instance of the command. 69 | /// 70 | /// Owner package, not null. 71 | public static async Task InitializeAsync(AsyncPackage package) { 72 | // Switch to the main thread - the call to AddCommand in CommandRunGodot's constructor requires 73 | // the UI thread. 74 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken); 75 | 76 | var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; 77 | Instance = new CommandRunGodot(package, commandService); 78 | } 79 | 80 | /// 81 | /// This function is the callback used to execute the command when the menu item is clicked. 82 | /// See the constructor to see how the menu item is associated with this function using 83 | /// OleMenuCommandService service and MenuCommand class. 84 | /// 85 | /// Event sender. 86 | /// Event args. 87 | private void Execute(object sender, EventArgs e) { 88 | ThreadHelper.ThrowIfNotOnUIThread(); 89 | 90 | string godotPath; 91 | string godotExecutable; 92 | 93 | var settingsManager = new ShellSettingsManager(package); 94 | var config = settingsManager.GetWritableSettingsStore(SettingsScope.UserSettings); 95 | 96 | if (!config.CollectionExists("External Tools")) { 97 | config.CreateCollection("External Tools"); 98 | } 99 | 100 | if (!config.PropertyExists("External Tools", "GodotExecutable")) { 101 | var ofd = new OpenFileDialog { 102 | Filter = @"Godot executable (.exe)|*.exe" 103 | }; 104 | var result = ofd.ShowDialog(null); 105 | 106 | if (result == DialogResult.OK) { 107 | godotExecutable = ofd.FileName; 108 | godotPath = Path.GetDirectoryName(godotExecutable); 109 | 110 | config.SetString("External Tools", "GodotExecutable", godotExecutable); 111 | config.SetString("External Tools", "GodotPath", godotPath); 112 | } else return; 113 | 114 | } else { 115 | godotPath = config.GetString("External Tools", "GodotPath"); 116 | godotExecutable = config.GetString("External Tools", "GodotExecutable"); 117 | } 118 | 119 | if (string.IsNullOrEmpty(godotExecutable)) { 120 | MessageBox.Show("Godot", "Godot path not set"); 121 | return; 122 | } 123 | 124 | if (!File.Exists(godotExecutable)) { 125 | MessageBox.Show("Godot", @$"Godot does not exist at {godotExecutable}"); 126 | return; 127 | } 128 | 129 | Process.Start(godotExecutable); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /GodotAddinVS/Debugging/GodotDebugTarget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GodotAddinVS.Debugging 4 | { 5 | public class GodotDebugTarget 6 | { 7 | private const string DebugTargetsGuidStr = "4E50788E-B023-4F77-AFE9-797603876907"; 8 | public static readonly Guid DebugTargetsGuid = new Guid(DebugTargetsGuidStr); 9 | 10 | public Guid Guid { get; } 11 | 12 | public uint Id { get; } 13 | 14 | public string Name { get; } 15 | 16 | public ExecutionType ExecutionType { get; } 17 | 18 | public GodotDebugTarget(ExecutionType executionType, string name) 19 | { 20 | Guid = DebugTargetsGuid; 21 | Id = 0x8192 + (uint) executionType; 22 | ExecutionType = executionType; 23 | Name = name; 24 | } 25 | } 26 | 27 | public enum ExecutionType : uint 28 | { 29 | PlayInEditor = 0, 30 | Launch, 31 | Attach 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /GodotAddinVS/Debugging/GodotDebugTargetSelection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.Shell.Interop; 5 | 6 | namespace GodotAddinVS.Debugging 7 | { 8 | public class GodotDebugTargetSelection : IVsProjectCfgDebugTargetSelection 9 | { 10 | private static readonly List DebugTargets = new List() 11 | { 12 | new GodotDebugTarget(ExecutionType.PlayInEditor, "Play in Editor"), 13 | new GodotDebugTarget(ExecutionType.Launch, "Launch"), 14 | new GodotDebugTarget(ExecutionType.Attach, "Attach") 15 | }; 16 | 17 | public static readonly GodotDebugTargetSelection Instance = new GodotDebugTargetSelection(); 18 | 19 | private IVsDebugTargetSelectionService _debugTargetSelectionService; 20 | 21 | public GodotDebugTarget CurrentDebugTarget { get; private set; } = DebugTargets.First(); 22 | 23 | public void GetCurrentDebugTarget(out Guid pguidDebugTargetType, 24 | out uint pDebugTargetTypeId, out string pbstrCurrentDebugTarget) 25 | { 26 | pguidDebugTargetType = CurrentDebugTarget.Guid; 27 | pDebugTargetTypeId = CurrentDebugTarget.Id; 28 | pbstrCurrentDebugTarget = CurrentDebugTarget.Name; 29 | } 30 | 31 | public Array GetDebugTargetListOfType(Guid guidDebugTargetType, uint debugTargetTypeId) 32 | { 33 | return DebugTargets 34 | .Where(t => t.Guid == guidDebugTargetType && t.Id == debugTargetTypeId) 35 | .Select(t => t.Name).ToArray(); 36 | } 37 | 38 | public bool HasDebugTargets( 39 | IVsDebugTargetSelectionService pDebugTargetSelectionService, out Array pbstrSupportedTargetCommandIDs) 40 | { 41 | _debugTargetSelectionService = pDebugTargetSelectionService; 42 | pbstrSupportedTargetCommandIDs = DebugTargets 43 | .Select(t => $"{t.Guid}:{t.Id}").ToArray(); 44 | return true; 45 | } 46 | 47 | public void SetCurrentDebugTarget(Guid guidDebugTargetType, 48 | uint debugTargetTypeId, string bstrCurrentDebugTarget) 49 | { 50 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 51 | 52 | CurrentDebugTarget = DebugTargets 53 | .First(t => t.Guid == guidDebugTargetType && t.Id == debugTargetTypeId); 54 | _debugTargetSelectionService?.UpdateDebugTargets(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /GodotAddinVS/Debugging/GodotDebuggableProjectCfg.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio; 2 | using Microsoft.VisualStudio.OLE.Interop; 3 | using Microsoft.VisualStudio.Shell.Interop; 4 | using Mono.Debugging.Soft; 5 | using Mono.Debugging.VisualStudio; 6 | using System; 7 | using System.Net; 8 | using System.Runtime.InteropServices; 9 | using VSLangProj; 10 | 11 | namespace GodotAddinVS.Debugging 12 | { 13 | internal class GodotDebuggableProjectCfg : IVsDebuggableProjectCfg, IVsProjectFlavorCfg 14 | { 15 | private IVsProjectFlavorCfg _baseProjectCfg; 16 | private readonly EnvDTE.Project _baseProject; 17 | 18 | public GodotDebuggableProjectCfg(IVsProjectFlavorCfg baseProjectCfg, EnvDTE.Project project) 19 | { 20 | _baseProject = project; 21 | _baseProjectCfg = baseProjectCfg; 22 | } 23 | 24 | public int get_DisplayName(out string pbstrDisplayName) 25 | { 26 | throw new NotImplementedException(); 27 | } 28 | 29 | public int get_IsDebugOnly(out int pfIsDebugOnly) 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | 34 | public int get_IsReleaseOnly(out int pfIsReleaseOnly) 35 | { 36 | throw new NotImplementedException(); 37 | } 38 | 39 | public int EnumOutputs(out IVsEnumOutputs ppIVsEnumOutputs) 40 | { 41 | throw new NotImplementedException(); 42 | } 43 | 44 | public int OpenOutput(string szOutputCanonicalName, out IVsOutput ppIVsOutput) 45 | { 46 | throw new NotImplementedException(); 47 | } 48 | 49 | public int get_ProjectCfgProvider(out IVsProjectCfgProvider ppIVsProjectCfgProvider) 50 | { 51 | throw new NotImplementedException(); 52 | } 53 | 54 | public int get_BuildableProjectCfg(out IVsBuildableProjectCfg ppIVsBuildableProjectCfg) 55 | { 56 | throw new NotImplementedException(); 57 | } 58 | 59 | public int get_CanonicalName(out string pbstrCanonicalName) 60 | { 61 | throw new NotImplementedException(); 62 | } 63 | 64 | public int get_Platform(out Guid pguidPlatform) 65 | { 66 | throw new NotImplementedException(); 67 | } 68 | 69 | public int get_IsPackaged(out int pfIsPackaged) 70 | { 71 | throw new NotImplementedException(); 72 | } 73 | 74 | public int get_IsSpecifyingOutputSupported(out int pfIsSpecifyingOutputSupported) 75 | { 76 | throw new NotImplementedException(); 77 | } 78 | 79 | public int get_TargetCodePage(out uint puiTargetCodePage) 80 | { 81 | throw new NotImplementedException(); 82 | } 83 | 84 | public int get_UpdateSequenceNumber(ULARGE_INTEGER[] puliUSN) 85 | { 86 | throw new NotImplementedException(); 87 | } 88 | 89 | public int get_RootURL(out string pbstrRootURL) 90 | { 91 | throw new NotImplementedException(); 92 | } 93 | 94 | public int DebugLaunch(uint grfLaunch) 95 | { 96 | bool noDebug = ((__VSDBGLAUNCHFLAGS)grfLaunch & __VSDBGLAUNCHFLAGS.DBGLAUNCH_NoDebug) != 0; 97 | _ = noDebug; // TODO: Run without Debugging 98 | 99 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 100 | 101 | var random = new Random(DateTime.Now.Millisecond); 102 | var port = 8800 + random.Next(0, 100); 103 | 104 | var startArgs = new SoftDebuggerListenArgs(_baseProject.Name, IPAddress.Loopback, port) {MaxConnectionAttempts = 3}; 105 | 106 | var startInfo = new GodotStartInfo(startArgs, null, _baseProject) {WorkingDirectory = GodotPackage.Instance.GodotSolutionEventsListener?.SolutionDir}; 107 | 108 | try 109 | { 110 | if (_baseProject.ConfigurationManager.ActiveConfiguration.Object is ProjectConfigurationProperties props) 111 | startInfo.StartArguments = props.StartArguments; 112 | } catch(Exception){} 113 | 114 | var session = new GodotDebuggerSession(); 115 | 116 | var launcher = new MonoDebuggerLauncher(new Progress()); 117 | 118 | launcher.StartSession(startInfo, session); 119 | 120 | return VSConstants.S_OK; 121 | } 122 | 123 | public int QueryDebugLaunch(uint grfLaunch, out int pfCanLaunch) 124 | { 125 | pfCanLaunch = 1; 126 | return VSConstants.S_OK; 127 | } 128 | 129 | public int get_CfgType(ref Guid iidCfg, out IntPtr ppCfg) 130 | { 131 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 132 | 133 | ppCfg = IntPtr.Zero; 134 | 135 | try 136 | { 137 | if (iidCfg == typeof(IVsDebuggableProjectCfg).GUID) 138 | { 139 | ppCfg = Marshal.GetComInterfaceForObject(this, typeof(IVsDebuggableProjectCfg)); 140 | return VSConstants.S_OK; 141 | } 142 | 143 | if (iidCfg == typeof(IVsProjectCfgDebugTargetSelection).GUID) 144 | { 145 | ppCfg = Marshal.GetComInterfaceForObject(GodotDebugTargetSelection.Instance, 146 | typeof(IVsProjectCfgDebugTargetSelection)); 147 | return VSConstants.S_OK; 148 | } 149 | 150 | if ((ppCfg == IntPtr.Zero) && (_baseProjectCfg != null)) 151 | { 152 | return _baseProjectCfg.get_CfgType(ref iidCfg, out ppCfg); 153 | } 154 | } 155 | catch (InvalidCastException) 156 | { 157 | } 158 | 159 | return VSConstants.E_NOINTERFACE; 160 | } 161 | 162 | public int Close() 163 | { 164 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 165 | 166 | if (_baseProjectCfg != null) 167 | { 168 | _baseProjectCfg.Close(); 169 | _baseProjectCfg = null; 170 | } 171 | 172 | return VSConstants.S_OK; 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /GodotAddinVS/Debugging/GodotDebuggerSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Threading.Tasks; 9 | using GodotTools.IdeMessaging; 10 | using GodotTools.IdeMessaging.Requests; 11 | using Microsoft.VisualStudio; 12 | using Microsoft.VisualStudio.Shell.Interop; 13 | using Mono.Debugging.Client; 14 | using Mono.Debugging.Soft; 15 | 16 | namespace GodotAddinVS.Debugging 17 | { 18 | internal class GodotDebuggerSession : SoftDebuggerSession 19 | { 20 | private bool _attached; 21 | private NetworkStream _godotRemoteDebuggerStream; 22 | private Process _process; 23 | 24 | // TODO: Unused. Find a way to trigger this. 25 | public void SendReloadScripts() 26 | { 27 | var executionType = GodotDebugTargetSelection.Instance.CurrentDebugTarget.ExecutionType; 28 | 29 | switch (executionType) 30 | { 31 | case ExecutionType.Launch: 32 | GodotVariantEncoder.Encode( 33 | new List {"reload_scripts"}, 34 | _godotRemoteDebuggerStream 35 | ); 36 | _godotRemoteDebuggerStream.Flush(); 37 | break; 38 | case ExecutionType.PlayInEditor: 39 | case ExecutionType.Attach: 40 | var godotMessagingClient = 41 | GodotPackage.Instance.GodotSolutionEventsListener?.GodotMessagingClient; 42 | godotMessagingClient?.SendRequest(new ReloadScriptsRequest()); 43 | break; 44 | default: 45 | throw new ArgumentOutOfRangeException(executionType.ToString()); 46 | } 47 | } 48 | 49 | private string GetGodotExecutablePath() 50 | { 51 | var options = (GeneralOptionsPage)GodotPackage.Instance.GetDialogPage(typeof(GeneralOptionsPage)); 52 | 53 | if (options.AlwaysUseConfiguredExecutable) 54 | return options.GodotExecutablePath; 55 | 56 | var godotMessagingClient = GodotPackage.Instance.GodotSolutionEventsListener?.GodotMessagingClient; 57 | 58 | string godotPath = godotMessagingClient?.GodotEditorExecutablePath; 59 | 60 | if (!string.IsNullOrEmpty(godotPath) && File.Exists(godotPath)) 61 | { 62 | // If the setting is not yet assigned any value, set it to the currently connected Godot editor path 63 | if (string.IsNullOrEmpty(options.GodotExecutablePath)) 64 | options.GodotExecutablePath = godotPath; 65 | return godotPath; 66 | } 67 | 68 | return options.GodotExecutablePath; 69 | } 70 | 71 | private void EndSessionWithError(string title, string errorMessage) 72 | { 73 | _ = GodotPackage.Instance.ShowErrorMessageBoxAsync(title, errorMessage); 74 | EndSession(); 75 | } 76 | 77 | protected override void OnRun(DebuggerStartInfo startInfo) 78 | { 79 | var godotStartInfo = (GodotStartInfo)startInfo; 80 | 81 | var executionType = GodotDebugTargetSelection.Instance.CurrentDebugTarget.ExecutionType; 82 | 83 | switch (executionType) 84 | { 85 | case ExecutionType.PlayInEditor: 86 | { 87 | _attached = false; 88 | StartListening(godotStartInfo, out var assignedDebugPort); 89 | 90 | var godotMessagingClient = 91 | GodotPackage.Instance.GodotSolutionEventsListener?.GodotMessagingClient; 92 | 93 | if (godotMessagingClient == null || !godotMessagingClient.IsConnected) 94 | { 95 | EndSessionWithError("Play Error", "No Godot editor instance connected"); 96 | return; 97 | } 98 | 99 | const string host = "127.0.0.1"; 100 | 101 | var playRequest = new DebugPlayRequest 102 | { 103 | DebuggerHost = host, 104 | DebuggerPort = assignedDebugPort, 105 | BuildBeforePlaying = false 106 | }; 107 | 108 | _ = godotMessagingClient.SendRequest(playRequest) 109 | .ContinueWith(t => 110 | { 111 | if (t.Result.Status != MessageStatus.Ok) 112 | EndSessionWithError("Play Error", $"Received Play response with status: {MessageStatus.Ok}"); 113 | }, TaskScheduler.Default); 114 | 115 | // TODO: Read the editor player stdout and stderr somehow 116 | 117 | break; 118 | } 119 | case ExecutionType.Launch: 120 | { 121 | _attached = false; 122 | StartListening(godotStartInfo, out var assignedDebugPort); 123 | 124 | // Listener to replace the Godot editor remote debugger. 125 | // We use it to notify the game when assemblies should be reloaded. 126 | var remoteDebugListener = new TcpListener(IPAddress.Any, 0); 127 | remoteDebugListener.Start(); 128 | _ = remoteDebugListener.AcceptTcpClientAsync() 129 | .ContinueWith(OnGodotRemoteDebuggerConnectedAsync, TaskScheduler.Default); 130 | 131 | string workingDir = startInfo.WorkingDirectory; 132 | const string host = "127.0.0.1"; 133 | int remoteDebugPort = ((IPEndPoint)remoteDebugListener.LocalEndpoint).Port; 134 | 135 | // Launch Godot to run the game and connect to our remote debugger 136 | 137 | var processStartInfo = new ProcessStartInfo(GetGodotExecutablePath()) 138 | { 139 | Arguments = $"--path {workingDir} --remote-debug {host}:{remoteDebugPort} {godotStartInfo.StartArguments}", // TODO: Doesn't work with 4.0dev. Should be tcp://host:port which doesn't work in 3.2... 140 | WorkingDirectory = workingDir, 141 | RedirectStandardOutput = true, 142 | RedirectStandardError = true, 143 | UseShellExecute = false, 144 | CreateNoWindow = true 145 | }; 146 | 147 | // Tells Godot to connect to the mono debugger we just started 148 | processStartInfo.EnvironmentVariables["GODOT_MONO_DEBUGGER_AGENT"] = 149 | "--debugger-agent=transport=dt_socket" + 150 | $",address={host}:{assignedDebugPort}" + 151 | ",server=n"; 152 | 153 | _process = new Process {StartInfo = processStartInfo}; 154 | 155 | _process.OutputDataReceived += (sendingProcess, outLine) => OutputData(outLine.Data, false); 156 | _process.ErrorDataReceived += (sendingProcess, outLine) => OutputData(outLine.Data, true); 157 | 158 | if (!_process.Start()) 159 | { 160 | EndSessionWithError("Launch Error", "Failed to start Godot process"); 161 | return; 162 | } 163 | 164 | _process.BeginOutputReadLine(); 165 | 166 | if (_process.HasExited) 167 | { 168 | EndSessionWithError("Launch Error", $"Godot process exited with code: {_process.ExitCode}"); 169 | return; 170 | } 171 | 172 | _process.Exited += (sender, args) => EndSession(); 173 | 174 | OnDebuggerOutput(false, $"Godot PID:{_process.Id}{Environment.NewLine}"); 175 | 176 | break; 177 | } 178 | case ExecutionType.Attach: 179 | { 180 | _attached = true; 181 | StartConnecting(godotStartInfo); 182 | break; 183 | } 184 | default: 185 | throw new ArgumentOutOfRangeException(executionType.ToString()); 186 | } 187 | 188 | if (!_attached) 189 | { 190 | var options = (GeneralOptionsPage)GodotPackage.Instance.GetDialogPage(typeof(GeneralOptionsPage)); 191 | 192 | // If a connection is never established and we try to stop debugging, Visual Studio will freeze 193 | // for a long time for some reason. I have no idea why this happens. There may be something 194 | // we're doing wrong. For now we'll limit the time we wait for incoming connections. 195 | _ = Task.Delay(options.DebuggerListenTimeout).ContinueWith(r => 196 | { 197 | if (!HasExited && !IsConnected) 198 | { 199 | EndSession(); 200 | 201 | if (_process != null && !_process.HasExited) 202 | _process.Kill(); 203 | } 204 | }, TaskScheduler.Default); 205 | } 206 | } 207 | 208 | protected override void OnExit() 209 | { 210 | if (_attached) 211 | { 212 | base.OnDetach(); 213 | } 214 | else 215 | { 216 | base.OnExit(); 217 | 218 | if (_process != null && !_process.HasExited) 219 | _process.Kill(); 220 | } 221 | } 222 | 223 | [SuppressMessage("ReSharper", "VSTHRD103")] 224 | private async Task OnGodotRemoteDebuggerConnectedAsync(Task task) 225 | { 226 | var tcpClient = task.Result; 227 | _godotRemoteDebuggerStream = tcpClient.GetStream(); 228 | var buffer = new byte[1000]; 229 | while (tcpClient.Connected) 230 | { 231 | // There is no library to decode this messages, so 232 | // we just pump buffer so it doesn't go out of memory 233 | var readBytes = await _godotRemoteDebuggerStream.ReadAsync(buffer, 0, buffer.Length); 234 | _ = readBytes; 235 | } 236 | } 237 | 238 | private void OutputData(string data, bool isStdErr) 239 | { 240 | try 241 | { 242 | OnTargetOutput(isStdErr, data + Environment.NewLine); 243 | _ = Task.Run(async () => 244 | { 245 | await GodotPackage.Instance.JoinableTaskFactory.SwitchToMainThreadAsync(); 246 | IVsOutputWindowPane outputPane = GodotPackage.Instance.GetOutputPane(VSConstants.OutputWindowPaneGuid.DebugPane_guid, "Output"); 247 | outputPane.OutputStringThreadSafe(data + Environment.NewLine); 248 | }); 249 | } 250 | catch (Exception e) 251 | { 252 | Console.Error.WriteLine(e); 253 | } 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /GodotAddinVS/Debugging/GodotStartInfo.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using Mono.Debugging.Soft; 3 | using Mono.Debugging.VisualStudio; 4 | 5 | namespace GodotAddinVS.Debugging 6 | { 7 | internal class GodotStartInfo : StartInfo 8 | { 9 | public string StartArguments; 10 | public GodotStartInfo(SoftDebuggerStartArgs args, DebuggingOptions options, Project startupProject) : 11 | base(args, options, startupProject) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /GodotAddinVS/GeneralOptionsPage.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using Microsoft.VisualStudio.Shell; 3 | 4 | namespace GodotAddinVS 5 | { 6 | public class GeneralOptionsPage : DialogPage 7 | { 8 | [Category("Debugging")] 9 | [DisplayName("Always Use Configured Executable")] 10 | [Description("When disabled, Visual Studio will attempt to get the Godot executable path from a running Godot editor instance")] 11 | public bool AlwaysUseConfiguredExecutable { get; set; } = false; 12 | 13 | [Category("Debugging")] 14 | [DisplayName("Godot Executable Path")] 15 | [Description("Path to the Godot executable to use when launching the application for debugging")] 16 | public string GodotExecutablePath { get; set; } = ""; 17 | 18 | [Category("Debugging")] 19 | [DisplayName("Debugger Listen Timeout")] 20 | [Description("Time in milliseconds after which the debugging session will end if no debugger is connected")] 21 | public int DebuggerListenTimeout { get; set; } = 10000; 22 | 23 | [Category("Code Completion")] 24 | [DisplayName("Provide Node Path Completions")] 25 | [Description("Whether to provide code completion for node paths when a Godot editor is connected")] 26 | public bool ProvideNodePathCompletions { get; set; } = true; 27 | 28 | [Category("Code Completion")] 29 | [DisplayName("Provide Input Action Completions")] 30 | [Description("Whether to provide code completion for input actions when a Godot editor is connected")] 31 | public bool ProvideInputActionCompletions { get; set; } = true; 32 | 33 | [Category("Code Completion")] 34 | [DisplayName("Provide Resource Path Completions")] 35 | [Description("Whether to provide code completion for resource paths when a Godot editor is connected")] 36 | public bool ProvideResourcePathCompletions { get; set; } = true; 37 | 38 | [Category("Code Completion")] 39 | [DisplayName("Provide Scene Path Completions")] 40 | [Description("Whether to provide code completion for scene paths when a Godot editor is connected")] 41 | public bool ProvideScenePathCompletions { get; set; } = true; 42 | 43 | [Category("Code Completion")] 44 | [DisplayName("Provide Signal Name Completions")] 45 | [Description("Whether to provide code completion for signal names when a Godot editor is connected")] 46 | public bool ProvideSignalNameCompletions { get; set; } = true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /GodotAddinVS/GodotAddinVS.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 8 7 | 8 | 9 | 10 | Debug 11 | AnyCPU 12 | 2.0 13 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | {7D64803D-2F8D-4597-9762-A316C74E9816} 15 | Library 16 | Properties 17 | GodotAddinVS 18 | GodotAddinVS 19 | v4.7.2 20 | true 21 | true 22 | true 23 | false 24 | false 25 | true 26 | true 27 | Program 28 | $(DevEnvDir)devenv.exe 29 | /rootsuffix Exp 30 | 31 | 32 | true 33 | full 34 | false 35 | bin\Debug\ 36 | DEBUG;TRACE 37 | prompt 38 | 4 39 | 40 | 41 | pdbonly 42 | true 43 | bin\Release\ 44 | TRACE 45 | prompt 46 | 4 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Component 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Designer 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | compile; build; native; contentfiles; analyzers; buildtransitive 90 | 91 | 92 | runtime; build; native; contentfiles; analyzers; buildtransitive 93 | all 94 | 95 | 96 | 97 | 98 | 99 | Always 100 | true 101 | 102 | 103 | Always 104 | true 105 | 106 | 107 | Menus.ctmenu 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | {a9ea6427-c5e2-4207-bbbf-a1f44a361339} 116 | GodotCompletionProviders 117 | 118 | 119 | 120 | 121 | $(GetVsixSourceItemsDependsOn);IncludeNuGetResolvedAssets 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /GodotAddinVS/GodotFlavoredProject.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio; 2 | using Microsoft.VisualStudio.Shell.Flavor; 3 | using Microsoft.VisualStudio.Shell.Interop; 4 | using System; 5 | using System.Runtime.InteropServices; 6 | using GodotAddinVS.Debugging; 7 | 8 | namespace GodotAddinVS 9 | { 10 | [ComVisible(true)] 11 | [ClassInterface(ClassInterfaceType.None)] 12 | [Guid(GodotPackage.GodotProjectGuid)] 13 | internal class GodotFlavoredProject : FlavoredProjectBase, IVsProjectFlavorCfgProvider 14 | { 15 | private IVsProjectFlavorCfgProvider _innerFlavorConfig; 16 | private GodotPackage _package; 17 | 18 | public GodotFlavoredProject(GodotPackage package) 19 | { 20 | _package = package; 21 | } 22 | 23 | public int CreateProjectFlavorCfg(IVsCfg pBaseProjectCfg, out IVsProjectFlavorCfg ppFlavorCfg) 24 | { 25 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 26 | 27 | ppFlavorCfg = null; 28 | 29 | if (_innerFlavorConfig != null) 30 | { 31 | GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out var project); 32 | 33 | _innerFlavorConfig.CreateProjectFlavorCfg(pBaseProjectCfg, out IVsProjectFlavorCfg cfg); 34 | ppFlavorCfg = new GodotDebuggableProjectCfg(cfg, project as EnvDTE.Project); 35 | } 36 | 37 | return ppFlavorCfg != null ? VSConstants.S_OK : VSConstants.E_FAIL; 38 | } 39 | 40 | protected override void SetInnerProject(IntPtr innerIUnknown) 41 | { 42 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 43 | 44 | object inner = Marshal.GetObjectForIUnknown(innerIUnknown); 45 | _innerFlavorConfig = inner as IVsProjectFlavorCfgProvider; 46 | 47 | if (serviceProvider == null) 48 | serviceProvider = _package; 49 | 50 | base.SetInnerProject(innerIUnknown); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /GodotAddinVS/GodotFlavoredProjectFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell.Flavor; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace GodotAddinVS 6 | { 7 | [Guid(GodotPackage.GodotProjectGuid)] 8 | public class GodotFlavoredProjectFactory : FlavoredProjectFactoryBase 9 | { 10 | private readonly GodotPackage _package; 11 | 12 | public GodotFlavoredProjectFactory(GodotPackage package) 13 | { 14 | _package = package; 15 | } 16 | 17 | protected override object PreCreateForOuter(IntPtr outerProjectIUnknown) 18 | { 19 | return new GodotFlavoredProject(_package); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /GodotAddinVS/GodotMessaging/MessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Threading.Tasks; 4 | using EnvDTE; 5 | using GodotTools.IdeMessaging; 6 | using GodotTools.IdeMessaging.Requests; 7 | using Microsoft.VisualStudio.Shell; 8 | 9 | namespace GodotAddinVS.GodotMessaging 10 | { 11 | public class MessageHandler : ClientMessageHandler 12 | { 13 | private static ILogger Logger => GodotPackage.Instance.Logger; 14 | 15 | protected override async Task HandleOpenFile(OpenFileRequest request) 16 | { 17 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 18 | 19 | var dte = GodotPackage.Instance.GetService(); 20 | 21 | try 22 | { 23 | dte.ItemOperations.OpenFile(request.File); 24 | } 25 | catch (ArgumentException e) 26 | { 27 | Logger?.LogError("ItemOperations.OpenFile: Invalid path or file not found", e); 28 | return new OpenFileResponse {Status = MessageStatus.InvalidRequestBody}; 29 | } 30 | 31 | if (request.Line != null) 32 | { 33 | var textSelection = (TextSelection)dte.ActiveDocument.Selection; 34 | 35 | if (request.Column != null) 36 | { 37 | textSelection.MoveToLineAndOffset(request.Line.Value, request.Column.Value); 38 | } 39 | else 40 | { 41 | textSelection.GotoLine(request.Line.Value, Select: true); 42 | } 43 | } 44 | 45 | var mainWindow = dte.MainWindow; 46 | mainWindow.Activate(); 47 | SetForegroundWindow(mainWindow.HWnd); 48 | 49 | return new OpenFileResponse {Status = MessageStatus.Ok}; 50 | } 51 | 52 | [DllImport("user32.dll")] 53 | private static extern bool SetForegroundWindow(IntPtr hWnd); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /GodotAddinVS/GodotPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using GodotCompletionProviders; 6 | using GodotTools.IdeMessaging; 7 | using GodotTools.IdeMessaging.Requests; 8 | using Microsoft.VisualStudio.Shell; 9 | using Microsoft.VisualStudio.Shell.Interop; 10 | using ILogger = GodotCompletionProviders.ILogger; 11 | using Task = System.Threading.Tasks.Task; 12 | 13 | namespace GodotAddinVS 14 | { 15 | /// 16 | /// This is the class that implements the package exposed by this assembly. 17 | /// 18 | /// 19 | /// 20 | /// The minimum requirement for a class to be considered a valid package for Visual Studio 21 | /// is to implement the IVsPackage interface and register itself with the shell. 22 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF) 23 | /// to do it: it derives from the Package class that provides the implementation of the 24 | /// IVsPackage interface and uses the registration attributes defined in the framework to 25 | /// register itself and its components with the shell. These attributes tell the pkgdef creation 26 | /// utility what data to put into .pkgdef file. 27 | /// 28 | /// 29 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. 30 | /// 31 | /// 32 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 33 | [Guid(PackageGuidString)] 34 | [ProvideProjectFactory(typeof(GodotFlavoredProjectFactory), "Godot.Project", null, "csproj", "csproj", null, 35 | LanguageVsTemplate = "CSharp", TemplateGroupIDsVsTemplate = "Godot")] 36 | [ProvideOptionPage(typeof(GeneralOptionsPage), 37 | "Godot", "General", 0, 0, true)] 38 | [ProvideMenuResource("Menus.ctmenu", 1)] 39 | public sealed class GodotPackage : AsyncPackage 40 | { 41 | /// 42 | /// GodotPackage GUID string. 43 | /// 44 | public const string PackageGuidString = "fbf828da-088b-482a-a550-befaed4b5d25"; 45 | 46 | public const string GodotProjectGuid = "8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED"; 47 | 48 | #region Package Members 49 | 50 | public static GodotPackage Instance { get; private set; } 51 | 52 | public GodotPackage() 53 | { 54 | Instance = this; 55 | } 56 | 57 | /// 58 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 59 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 60 | /// 61 | /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down. 62 | /// A provider for progress updates. 63 | /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method. 64 | protected override async Task InitializeAsync(CancellationToken cancellationToken, 65 | IProgress progress) 66 | { 67 | // When initialized asynchronously, the current thread may be a background thread at this point. 68 | // Do any initialization that requires the UI thread after switching to the UI thread. 69 | await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 70 | 71 | // Commands 72 | await Commands.CommandRunGodot.InitializeAsync(this); 73 | await Commands.CommandResetGodot.InitializeAsync(this); 74 | 75 | RegisterProjectFactory(new GodotFlavoredProjectFactory(this)); 76 | 77 | GodotSolutionEventsListener = new GodotSolutionEventsListener(this); 78 | 79 | var completionProviderContext = new GodotVsProviderContext(this); 80 | BaseCompletionProvider.Context = completionProviderContext; 81 | } 82 | 83 | internal GodotSolutionEventsListener GodotSolutionEventsListener { get; private set; } 84 | 85 | public GodotVSLogger Logger { get; } = new GodotVSLogger(); 86 | 87 | public async Task ShowErrorMessageBoxAsync(string title, string message) 88 | { 89 | await JoinableTaskFactory.SwitchToMainThreadAsync(); 90 | 91 | // ReSharper disable once SuspiciousTypeConversion.Global 92 | var uiShell = (IVsUIShell)await GetServiceAsync(typeof(SVsUIShell)); 93 | 94 | if (uiShell == null) 95 | throw new ServiceUnavailableException(typeof(SVsUIShell)); 96 | 97 | var clsid = Guid.Empty; 98 | Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(uiShell.ShowMessageBox( 99 | 0, 100 | ref clsid, 101 | title, 102 | message, 103 | string.Empty, 104 | 0, 105 | OLEMSGBUTTON.OLEMSGBUTTON_OK, 106 | OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, 107 | OLEMSGICON.OLEMSGICON_CRITICAL, 108 | 0, 109 | pnResult: out _)); 110 | } 111 | 112 | #endregion 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /GodotAddinVS/GodotPackage.vsct: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 25 | 26 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | Godot 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 120 | 121 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /GodotAddinVS/GodotSolutionEventsListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.Design; 4 | using System.IO; 5 | using System.Linq; 6 | using EnvDTE; 7 | using GodotAddinVS.Debugging; 8 | using GodotAddinVS.GodotMessaging; 9 | using GodotTools.IdeMessaging; 10 | using GodotTools.IdeMessaging.Requests; 11 | using Microsoft.VisualStudio.Shell; 12 | using Microsoft.VisualStudio.Shell.Interop; 13 | 14 | namespace GodotAddinVS 15 | { 16 | internal class GodotSolutionEventsListener : SolutionEventsListener 17 | { 18 | private static readonly object RegisterLock = new object(); 19 | private bool _registered; 20 | 21 | private string _godotProjectDir; 22 | 23 | private DebuggerEvents DebuggerEvents { get; set; } 24 | 25 | private IServiceContainer ServiceContainer => (IServiceContainer)ServiceProvider; 26 | 27 | public string SolutionDir 28 | { 29 | get 30 | { 31 | ThreadHelper.ThrowIfNotOnUIThread(); 32 | Solution.GetSolutionInfo(out string solutionDir, out string solutionFile, out string userOptsFile); 33 | _ = solutionFile; 34 | _ = userOptsFile; 35 | return solutionDir; 36 | } 37 | } 38 | 39 | public Client GodotMessagingClient { get; private set; } 40 | 41 | public GodotSolutionEventsListener(IServiceProvider serviceProvider) 42 | : base(serviceProvider) 43 | { 44 | ThreadHelper.ThrowIfNotOnUIThread(); 45 | Init(); 46 | } 47 | 48 | public override int OnBeforeCloseProject(IVsHierarchy hierarchy, int removed) 49 | { 50 | return 0; 51 | } 52 | 53 | private static IEnumerable ParseProjectTypeGuids(string projectTypeGuids) 54 | { 55 | string[] strArray = projectTypeGuids.Split(';'); 56 | var guidList = new List(strArray.Length); 57 | 58 | foreach (string input in strArray) 59 | { 60 | if (Guid.TryParse(input, out var result)) 61 | guidList.Add(result); 62 | } 63 | 64 | return guidList.ToArray(); 65 | } 66 | 67 | private static bool IsGodotProject(IVsHierarchy hierarchy) 68 | { 69 | ThreadHelper.ThrowIfNotOnUIThread(); 70 | return hierarchy is IVsAggregatableProject aggregatableProject && 71 | aggregatableProject.GetAggregateProjectTypeGuids(out string projectTypeGuids) == 0 && 72 | ParseProjectTypeGuids(projectTypeGuids) 73 | .Any(g => g == typeof(GodotFlavoredProjectFactory).GUID); 74 | } 75 | 76 | public override int OnAfterOpenProject(IVsHierarchy hierarchy, int added) 77 | { 78 | ThreadHelper.ThrowIfNotOnUIThread(); 79 | 80 | if (!IsGodotProject(hierarchy)) 81 | return 0; 82 | 83 | lock (RegisterLock) 84 | { 85 | if (_registered) 86 | return 0; 87 | 88 | _godotProjectDir = SolutionDir; 89 | 90 | DebuggerEvents = ServiceProvider.GetService().Events.DebuggerEvents; 91 | DebuggerEvents.OnEnterDesignMode += DebuggerEvents_OnEnterDesignMode; 92 | 93 | GodotMessagingClient?.Dispose(); 94 | GodotMessagingClient = new Client(identity: "VisualStudio", 95 | _godotProjectDir, new MessageHandler(), GodotPackage.Instance.Logger); 96 | GodotMessagingClient.Connected += OnClientConnected; 97 | GodotMessagingClient.Start(); 98 | 99 | ServiceContainer.AddService(typeof(Client), GodotMessagingClient); 100 | 101 | _registered = true; 102 | } 103 | 104 | return 0; 105 | } 106 | 107 | public override int OnBeforeCloseSolution(object pUnkReserved) 108 | { 109 | lock (RegisterLock) 110 | _registered = false; 111 | Close(); 112 | return 0; 113 | } 114 | 115 | protected override void Dispose(bool disposing) 116 | { 117 | if (!disposing) 118 | return; 119 | Close(); 120 | } 121 | 122 | private void OnClientConnected() 123 | { 124 | var options = (GeneralOptionsPage)GodotPackage.Instance.GetDialogPage(typeof(GeneralOptionsPage)); 125 | 126 | // If the setting is not yet assigned any value, set it to the currently connected Godot editor path 127 | if (string.IsNullOrEmpty(options.GodotExecutablePath)) 128 | { 129 | string godotPath = GodotMessagingClient?.GodotEditorExecutablePath; 130 | if (!string.IsNullOrEmpty(godotPath) && File.Exists(godotPath)) 131 | options.GodotExecutablePath = godotPath; 132 | } 133 | } 134 | 135 | private void DebuggerEvents_OnEnterDesignMode(dbgEventReason reason) 136 | { 137 | if (reason != dbgEventReason.dbgEventReasonStopDebugging) 138 | return; 139 | 140 | if (GodotMessagingClient == null || !GodotMessagingClient.IsConnected) 141 | return; 142 | 143 | var currentDebugTarget = GodotDebugTargetSelection.Instance.CurrentDebugTarget; 144 | 145 | if (currentDebugTarget != null && currentDebugTarget.ExecutionType == ExecutionType.PlayInEditor) 146 | _ = GodotMessagingClient.SendRequest(new StopPlayRequest()); 147 | } 148 | 149 | private void Close() 150 | { 151 | ThreadHelper.ThrowIfNotOnUIThread(); 152 | if (GodotMessagingClient != null) 153 | { 154 | ServiceContainer.RemoveService(typeof(Client)); 155 | GodotMessagingClient.Dispose(); 156 | GodotMessagingClient = null; 157 | } 158 | 159 | if (DebuggerEvents != null) 160 | { 161 | DebuggerEvents.OnEnterDesignMode -= DebuggerEvents_OnEnterDesignMode; 162 | DebuggerEvents = null; 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /GodotAddinVS/GodotVSLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.Shell.Interop; 3 | using System.Threading.Tasks; 4 | using ThreadHelper = Microsoft.VisualStudio.Shell.ThreadHelper; 5 | 6 | namespace GodotAddinVS 7 | { 8 | // ReSharper disable once InconsistentNaming 9 | public class GodotVSLogger : GodotTools.IdeMessaging.ILogger, GodotCompletionProviders.ILogger 10 | { 11 | private async Task LogMessageAsync(__ACTIVITYLOG_ENTRYTYPE actType, string message) 12 | { 13 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 14 | 15 | // ReSharper disable once SuspiciousTypeConversion.Global 16 | var log = (IVsActivityLog)GodotPackage.Instance.GetService(); 17 | 18 | if (log == null) 19 | return; 20 | 21 | _ = log.LogEntry((uint)actType, this.ToString(), message); 22 | } 23 | 24 | public void LogDebug(string message) 25 | { 26 | _ = LogMessageAsync(__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, message); 27 | } 28 | 29 | public void LogInfo(string message) 30 | { 31 | _ = LogMessageAsync(__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION, message); 32 | } 33 | 34 | public void LogWarning(string message) 35 | { 36 | _ = LogMessageAsync(__ACTIVITYLOG_ENTRYTYPE.ALE_WARNING, message); 37 | } 38 | 39 | public void LogError(string message) 40 | { 41 | _ = LogMessageAsync(__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR, message); 42 | } 43 | 44 | public void LogError(string message, Exception e) 45 | { 46 | _ = LogMessageAsync(__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR, message + "\n" + e); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /GodotAddinVS/GodotVariant.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace GodotAddinVS 7 | { 8 | // Incomplete implementation of the Godot's Variant encoder. Add missing parts as needed. 9 | 10 | public class GodotVariantEncoder 11 | { 12 | private readonly List _buffer = new List(); 13 | 14 | public int Length => _buffer.Count; 15 | 16 | public byte[] ToArray() => _buffer.ToArray(); 17 | 18 | public void AddBytes(params byte[] bytes) => 19 | _buffer.AddRange(bytes); 20 | 21 | public void AddInt(int value) => 22 | AddBytes(BitConverter.GetBytes(value)); 23 | 24 | public void AddType(GodotVariant.Type type) => 25 | AddInt((int) type); 26 | 27 | public void AddString(string value) 28 | { 29 | byte[] utf8Bytes = Encoding.UTF8.GetBytes(value); 30 | 31 | AddType(GodotVariant.Type.String); 32 | AddInt(utf8Bytes.Length); 33 | AddBytes(utf8Bytes); 34 | AddBytes(0); // Godot's UTF8 converter adds a trailing whitespace 35 | 36 | while (_buffer.Count % 4 != 0) 37 | _buffer.Add(0); 38 | } 39 | 40 | public void AddArray(List array) 41 | { 42 | AddType(GodotVariant.Type.Array); 43 | AddInt(array.Count); 44 | 45 | foreach (var element in array) 46 | { 47 | if (element.VariantType == GodotVariant.Type.String) 48 | AddString(element.Get()); 49 | else 50 | throw new NotImplementedException(); 51 | } 52 | } 53 | 54 | public static void Encode(GodotVariant variant, Stream stream) 55 | { 56 | using (var writer = new BinaryWriter(stream, new UTF8Encoding(false, true), leaveOpen: true)) 57 | { 58 | var encoder = new GodotVariantEncoder(); 59 | switch (variant.VariantType) 60 | { 61 | case GodotVariant.Type.String: 62 | encoder.AddString((string) variant.Value); 63 | break; 64 | case GodotVariant.Type.Array: 65 | encoder.AddArray((List) variant.Value); 66 | break; 67 | default: 68 | throw new NotImplementedException(); 69 | } 70 | 71 | // ReSharper disable once RedundantCast 72 | writer.Write((int) encoder.Length); 73 | writer.Write(encoder.ToArray()); 74 | } 75 | } 76 | } 77 | 78 | public class GodotVariant 79 | { 80 | public enum Type 81 | { 82 | Nil = 0, 83 | Bool = 1, 84 | Int = 2, 85 | Real = 3, 86 | String = 4, 87 | Vector2 = 5, 88 | Rect2 = 6, 89 | Vector3 = 7, 90 | Transform2d = 8, 91 | Quat = 10, 92 | Aabb = 11, 93 | Basis = 12, 94 | Transform = 13, 95 | Color = 14, 96 | NodePath = 15, 97 | Rid = 16, 98 | Object = 17, 99 | Dictionary = 18, 100 | Array = 19, 101 | RawArray = 20, 102 | IntArray = 21, 103 | RealArray = 22, 104 | StringArray = 23, 105 | Vector2Array = 24, 106 | Vector3Array = 25, 107 | ColorArray = 26, 108 | Max = 27 109 | } 110 | 111 | public object Value { get; } 112 | public Type VariantType { get; } 113 | 114 | public T Get() 115 | { 116 | return (T) Value; 117 | } 118 | 119 | public override string ToString() 120 | { 121 | return Value.ToString(); 122 | } 123 | 124 | public GodotVariant(string value) 125 | { 126 | Value = value; 127 | VariantType = Type.String; 128 | } 129 | 130 | public GodotVariant(List value) 131 | { 132 | Value = value; 133 | VariantType = Type.Array; 134 | } 135 | 136 | public static implicit operator GodotVariant(string value) => new GodotVariant(value); 137 | public static implicit operator GodotVariant(List value) => new GodotVariant(value); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /GodotAddinVS/GodotVsProviderContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using GodotCompletionProviders; 4 | using GodotTools.IdeMessaging; 5 | using GodotTools.IdeMessaging.Requests; 6 | using ILogger = GodotCompletionProviders.ILogger; 7 | 8 | namespace GodotAddinVS 9 | { 10 | internal class GodotVsProviderContext : IProviderContext 11 | { 12 | private readonly GodotPackage _package; 13 | 14 | public GodotVsProviderContext(GodotPackage package) 15 | { 16 | _package = package; 17 | } 18 | 19 | public ILogger GetLogger() => _package.Logger; 20 | 21 | public bool AreCompletionsEnabledFor(CompletionKind completionKind) 22 | { 23 | var options = (GeneralOptionsPage)GodotPackage.Instance.GetDialogPage(typeof(GeneralOptionsPage)); 24 | 25 | if (options == null) 26 | return false; 27 | 28 | return completionKind switch 29 | { 30 | CompletionKind.NodePaths => options.ProvideNodePathCompletions, 31 | CompletionKind.InputActions => options.ProvideInputActionCompletions, 32 | CompletionKind.ResourcePaths => options.ProvideResourcePathCompletions, 33 | CompletionKind.ScenePaths => options.ProvideScenePathCompletions, 34 | CompletionKind.Signals => options.ProvideSignalNameCompletions, 35 | _ => false 36 | }; 37 | } 38 | 39 | public bool CanRequestCompletionsFromServer() 40 | { 41 | var godotMessagingClient = _package.GodotSolutionEventsListener?.GodotMessagingClient; 42 | return godotMessagingClient != null && godotMessagingClient.IsConnected; 43 | } 44 | 45 | public async Task RequestCompletion(CompletionKind completionKind, string absoluteFilePath) 46 | { 47 | var godotMessagingClient = _package.GodotSolutionEventsListener?.GodotMessagingClient; 48 | 49 | if (godotMessagingClient == null) 50 | throw new InvalidOperationException(); 51 | 52 | var request = new CodeCompletionRequest {Kind = (CodeCompletionRequest.CompletionKind)completionKind, ScriptFile = absoluteFilePath}; 53 | var response = await godotMessagingClient.SendRequest(request); 54 | 55 | if (response.Status == MessageStatus.Ok) 56 | return response.Suggestions; 57 | 58 | GetLogger().LogError($"Received code completion response with status '{response.Status}'."); 59 | return new string[] { }; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /GodotAddinVS/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Godot Engine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /GodotAddinVS/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /GodotAddinVS/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("GodotAddinVS")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("GodotAddinVS")] 12 | [assembly: AssemblyCopyright("")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // Version information for an assembly consists of the following four values: 22 | // 23 | // Major Version 24 | // Minor Version 25 | // Build Number 26 | // Revision 27 | // 28 | // You can specify all the values or you can default the Build and Revision Numbers 29 | // by using the '*' as shown below: 30 | // [assembly: AssemblyVersion("1.0.*")] 31 | [assembly: AssemblyVersion("1.1.1.0")] 32 | [assembly: AssemblyFileVersion("1.1.1.0")] 33 | -------------------------------------------------------------------------------- /GodotAddinVS/SolutionEventsListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft; 3 | using Microsoft.VisualStudio; 4 | using Microsoft.VisualStudio.Shell.Interop; 5 | 6 | namespace GodotAddinVS 7 | { 8 | internal abstract class SolutionEventsListener : IVsSolutionEvents, IDisposable 9 | { 10 | private static volatile object _disposalLock = new object(); 11 | private uint _eventsCookie; 12 | private bool _disposed; 13 | 14 | protected SolutionEventsListener(IServiceProvider serviceProvider) 15 | { 16 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 17 | 18 | ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); 19 | 20 | Solution = ServiceProvider.GetService(typeof(SVsSolution)) as IVsSolution; 21 | Assumes.Present(Solution); 22 | } 23 | 24 | protected IVsSolution Solution { get; } 25 | 26 | protected IServiceProvider ServiceProvider { get; } 27 | 28 | public virtual int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) => VSConstants.E_NOTIMPL; 29 | 30 | public virtual int OnBeforeCloseSolution(object pUnkReserved) => VSConstants.E_NOTIMPL; 31 | 32 | public virtual int OnAfterCloseSolution(object reserved) => VSConstants.E_NOTIMPL; 33 | 34 | public virtual int OnQueryCloseSolution(object pUnkReserved, ref int cancel) => VSConstants.E_NOTIMPL; 35 | 36 | public virtual int OnAfterOpenProject(IVsHierarchy hierarchy, int added) => VSConstants.E_NOTIMPL; 37 | 38 | public virtual int OnAfterLoadProject(IVsHierarchy stubHierarchy, IVsHierarchy realHierarchy) => VSConstants.E_NOTIMPL; 39 | 40 | public virtual int OnBeforeUnloadProject(IVsHierarchy realHierarchy, IVsHierarchy rtubHierarchy) => VSConstants.E_NOTIMPL; 41 | 42 | public virtual int OnBeforeCloseProject(IVsHierarchy hierarchy, int removed) => VSConstants.E_NOTIMPL; 43 | 44 | public virtual int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int cancel) => VSConstants.E_NOTIMPL; 45 | 46 | public virtual int OnQueryCloseProject(IVsHierarchy hierarchy, int removing, ref int cancel) => VSConstants.E_NOTIMPL; 47 | 48 | public void Dispose() 49 | { 50 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 51 | Dispose(true); 52 | GC.SuppressFinalize(this); 53 | } 54 | 55 | public void Init() 56 | { 57 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 58 | ErrorHandler.ThrowOnFailure(Solution.AdviseSolutionEvents(this, out _eventsCookie)); 59 | } 60 | 61 | protected virtual void Dispose(bool disposing) 62 | { 63 | Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); 64 | 65 | if (_disposed) 66 | return; 67 | 68 | lock (_disposalLock) 69 | { 70 | if (disposing && _eventsCookie != 0U && Solution != null) 71 | { 72 | Solution.UnadviseSolutionEvents(_eventsCookie); 73 | _eventsCookie = 0U; 74 | } 75 | 76 | _disposed = true; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /GodotAddinVS/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotengine/godot-csharp-visualstudio/94d71d8addf5da40185ca02da7ff577c957ee2d4/GodotAddinVS/icon.png -------------------------------------------------------------------------------- /GodotAddinVS/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Godot Support 6 | Support for Godot Engine C# projects, including debugging and extended code completion. 7 | LICENSE.txt 8 | icon.png 9 | Godot 10 | 11 | 12 | 13 | x86 14 | 15 | 16 | x86 17 | 18 | 19 | x86 20 | 21 | 22 | amd64 23 | 24 | 25 | amd64 26 | 27 | 28 | amd64 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /GodotCompletionProviders.Test/GodotCompletionProviders.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {B2BAAEA3-8B1D-4584-A5D1-9D4EF487E0DF} 9 | {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10 | Library 11 | Properties 12 | GodotCompletionProviders.Test 13 | GodotCompletionProviders.Test 14 | v4.7.2 15 | 512 16 | 8 17 | enable 18 | 19 | 20 | AnyCPU 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | AnyCPU 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | ..\packages\Microsoft.CodeAnalysis.Common.3.3.1\lib\netstandard2.0\Microsoft.CodeAnalysis.dll 41 | True 42 | 43 | 44 | ..\packages\Microsoft.CodeAnalysis.CSharp.3.3.1\lib\netstandard2.0\Microsoft.CodeAnalysis.CSharp.dll 45 | True 46 | 47 | 48 | ..\packages\Microsoft.CodeAnalysis.CSharp.Features.3.3.1\lib\netstandard2.0\Microsoft.CodeAnalysis.CSharp.Features.dll 49 | True 50 | 51 | 52 | ..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.3.3.1\lib\netstandard2.0\Microsoft.CodeAnalysis.CSharp.Workspaces.dll 53 | True 54 | 55 | 56 | ..\packages\Microsoft.CodeAnalysis.Features.3.3.1\lib\netstandard2.0\Microsoft.CodeAnalysis.Features.dll 57 | True 58 | 59 | 60 | ..\packages\Microsoft.CodeAnalysis.FlowAnalysis.Utilities.2.9.5\lib\netstandard1.3\Microsoft.CodeAnalysis.FlowAnalysis.Utilities.dll 61 | True 62 | 63 | 64 | ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.3.3.1\lib\netstandard2.0\Microsoft.CodeAnalysis.Workspaces.dll 65 | True 66 | 67 | 68 | ..\packages\Microsoft.DiaSymReader.1.3.0\lib\net20\Microsoft.DiaSymReader.dll 69 | True 70 | 71 | 72 | 73 | 74 | ..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll 75 | True 76 | 77 | 78 | ..\packages\System.Collections.Immutable.1.5.0\lib\netstandard2.0\System.Collections.Immutable.dll 79 | True 80 | 81 | 82 | ..\packages\System.Composition.AttributedModel.1.0.31\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll 83 | True 84 | 85 | 86 | ..\packages\System.Composition.Convention.1.0.31\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll 87 | True 88 | 89 | 90 | ..\packages\System.Composition.Hosting.1.0.31\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll 91 | True 92 | 93 | 94 | ..\packages\System.Composition.Runtime.1.0.31\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll 95 | True 96 | 97 | 98 | ..\packages\System.Composition.TypedParts.1.0.31\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll 99 | True 100 | 101 | 102 | 103 | 104 | ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll 105 | True 106 | 107 | 108 | 109 | ..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll 110 | True 111 | 112 | 113 | ..\packages\System.Reflection.Metadata.1.6.0\lib\netstandard2.0\System.Reflection.Metadata.dll 114 | True 115 | 116 | 117 | ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll 118 | True 119 | 120 | 121 | ..\packages\System.Text.Encoding.CodePages.4.5.1\lib\net461\System.Text.Encoding.CodePages.dll 122 | True 123 | 124 | 125 | ..\packages\System.Threading.Tasks.Extensions.4.5.3\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll 126 | True 127 | 128 | 129 | 130 | ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll 131 | 132 | 133 | ..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll 134 | 135 | 136 | ..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll 137 | 138 | 139 | ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | {a9ea6427-c5e2-4207-bbbf-a1f44a361339} 162 | GodotCompletionProviders 163 | 164 | 165 | 166 | 167 | 168 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. 169 | 170 | 171 | 172 | 179 | 180 | -------------------------------------------------------------------------------- /GodotCompletionProviders.Test/InputActionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace GodotCompletionProviders.Test 5 | { 6 | [Collection("Sequential")] 7 | public class InputActionTests : TestsBase 8 | { 9 | private const string StubCode = @" 10 | namespace Godot 11 | { 12 | public class StringName 13 | { 14 | public StringName() { } 15 | public StringName(string from) { } 16 | public static implicit operator StringName(string from) => throw new NotImplementedException(); 17 | public static implicit operator string(StringName from) => throw new NotImplementedException(); 18 | } 19 | 20 | public static class Input 21 | { 22 | public static bool IsActionPressed(StringName action) => throw new NotImplementedException(); 23 | public static bool IsActionJustPressed(StringName action) => throw new NotImplementedException(); 24 | public static bool IsActionJustReleased(StringName action) => throw new NotImplementedException(); 25 | public static float GetActionStrength(StringName action) => throw new NotImplementedException(); 26 | public static void ActionPress(StringName action, float strength = 1f) => throw new NotImplementedException(); 27 | public static void ActionRelease(StringName action) => throw new NotImplementedException(); 28 | } 29 | } 30 | "; 31 | 32 | public InputActionTests() : base(new InputActionCompletionProvider()) 33 | { 34 | } 35 | 36 | private Task ProvidesForFull(string statements) 37 | { 38 | string testCode = $@" 39 | using Godot; 40 | {statements}"; 41 | string code = Utils.ReadSingleCaretTestCode(testCode, out int caretPosition); 42 | return ShouldProvideCompletion(StubCode, code, caretPosition); 43 | } 44 | 45 | private async Task ProvidesFor(string statements) => 46 | (await ProvidesForFull(statements)).ShouldProvideCompletion; 47 | 48 | [Fact] 49 | public void TestTestNotSomethingElse() 50 | { 51 | Assert.False(ProvidesFor("Input.Foo(⛶)").Result); 52 | Assert.False(ProvidesFor("Input.Foo(, ⛶)").Result); 53 | Assert.False(ProvidesFor("Input.Foo(, , ⛶)").Result); 54 | } 55 | 56 | [Fact] 57 | public void TestIsActionPressed() 58 | { 59 | Assert.True(ProvidesFor("Input.IsActionPressed(⛶").Result); 60 | Assert.True(ProvidesFor("Input.IsActionPressed(⛶)").Result); 61 | Assert.False(ProvidesFor("Input.IsActionPressed(, ⛶").Result); 62 | Assert.False(ProvidesFor("Input.IsActionPressed(, ⛶)").Result); 63 | } 64 | 65 | [Fact] 66 | public void TestIsActionJustPressed() 67 | { 68 | Assert.True(ProvidesFor("Input.IsActionJustPressed(⛶").Result); 69 | Assert.True(ProvidesFor("Input.IsActionJustPressed(⛶)").Result); 70 | Assert.False(ProvidesFor("Input.IsActionJustPressed(, ⛶").Result); 71 | Assert.False(ProvidesFor("Input.IsActionJustPressed(, ⛶)").Result); 72 | } 73 | 74 | [Fact] 75 | public void TestIsActionJustReleased() 76 | { 77 | Assert.True(ProvidesFor("Input.IsActionJustReleased(⛶").Result); 78 | Assert.True(ProvidesFor("Input.IsActionJustReleased(⛶)").Result); 79 | Assert.False(ProvidesFor("Input.IsActionJustReleased(, ⛶").Result); 80 | Assert.False(ProvidesFor("Input.IsActionJustReleased(, ⛶)").Result); 81 | } 82 | 83 | [Fact] 84 | public void TestGetActionStrength() 85 | { 86 | Assert.True(ProvidesFor("Input.GetActionStrength(⛶").Result); 87 | Assert.True(ProvidesFor("Input.GetActionStrength(⛶)").Result); 88 | Assert.False(ProvidesFor("Input.GetActionStrength(, ⛶").Result); 89 | Assert.False(ProvidesFor("Input.GetActionStrength(, ⛶)").Result); 90 | } 91 | 92 | [Fact] 93 | public void TestActionPress() 94 | { 95 | Assert.True(ProvidesFor("Input.ActionPress(⛶").Result); 96 | Assert.True(ProvidesFor("Input.ActionPress(⛶)").Result); 97 | Assert.False(ProvidesFor("Input.ActionPress(, ⛶").Result); 98 | Assert.False(ProvidesFor("Input.ActionPress(, ⛶)").Result); 99 | } 100 | 101 | [Fact] 102 | public void TestActionRelease() 103 | { 104 | Assert.True(ProvidesFor("Input.ActionRelease(⛶").Result); 105 | Assert.True(ProvidesFor("Input.ActionRelease(⛶)").Result); 106 | Assert.False(ProvidesFor("Input.ActionRelease(, ⛶").Result); 107 | Assert.False(ProvidesFor("Input.ActionRelease(, ⛶)").Result); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /GodotCompletionProviders.Test/NodePathTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace GodotCompletionProviders.Test 5 | { 6 | [Collection("Sequential")] 7 | public class NodePathTests : TestsBase 8 | { 9 | private const string StubCode = @" 10 | namespace Godot 11 | { 12 | public class NodePath 13 | { 14 | public NodePath() { } 15 | public NodePath(string from) { } 16 | public static implicit operator NodePath(string from) => throw new NotImplementedException(); 17 | public static implicit operator string(NodePath from) => throw new NotImplementedException(); 18 | } 19 | 20 | public class Object { } 21 | public class Node : Godot.Object { } 22 | } 23 | "; 24 | 25 | public NodePathTests() : base(new NodePathCompletionProvider()) 26 | { 27 | } 28 | 29 | private Task ProvidesForFull(string classMemberDeclaration) 30 | { 31 | string testCode = $@" 32 | using Godot; 33 | {classMemberDeclaration}"; 34 | string code = Utils.ReadSingleCaretTestCode(testCode, out int caretPosition); 35 | return ShouldProvideCompletion(StubCode, code, caretPosition); 36 | } 37 | 38 | private async Task ProvidesFor(string classMemberDeclaration) => 39 | (await ProvidesForFull(classMemberDeclaration)).ShouldProvideCompletion; 40 | 41 | [Fact] 42 | public void TestFieldDeclarations() 43 | { 44 | Assert.True(ProvidesFor("NodePath npField = ⛶;").Result); 45 | } 46 | 47 | [Fact] 48 | public void TestPropertyDeclarations() 49 | { 50 | Assert.True(ProvidesFor("NodePath npProp1 => ⛶;").Result); 51 | Assert.True(ProvidesFor("NodePath npProp2 { get => ⛶; }").Result); 52 | Assert.True(ProvidesFor("NodePath npProp3 { get { return ⛶; } }").Result); 53 | Assert.True(ProvidesFor("NodePath npProp4 { get; set; } = ⛶;").Result); 54 | } 55 | 56 | [Fact] 57 | public void TestInvocationArgument() 58 | { 59 | // First argument 60 | Assert.True(ProvidesFor(@" 61 | void FirstParam(NodePath path) { } 62 | FirstParam(⛶); 63 | ").Result); 64 | 65 | // Second argument 66 | Assert.True(ProvidesFor(@" 67 | void SecondParam(object nothing, NodePath path) { } 68 | SecondParam(null, ⛶); 69 | ").Result); 70 | 71 | // First argument of generic method invocation 72 | Assert.True(ProvidesFor(@" 73 | void FirstParamGeneric(NodePath path) { } 74 | FirstParamGeneric(⛶); 75 | ").Result); 76 | } 77 | 78 | [Fact] 79 | public void TestNodePathCreationExpression() 80 | { 81 | Assert.True(ProvidesFor(@"_ = new NodePath(⛶);").Result); 82 | } 83 | 84 | [Fact] 85 | public void TestExplicitCast() 86 | { 87 | Assert.True(ProvidesFor(@"_ = (NodePath)⛶").Result); 88 | Assert.True(ProvidesFor(@"_ = (NodePath)(⛶)").Result); 89 | Assert.True(ProvidesFor(@"_ = (NodePath)⛶;").Result); 90 | Assert.True(ProvidesFor(@"_ = (NodePath)(⛶);").Result); 91 | 92 | Assert.False(ProvidesFor(@"_ = ((NodePath))⛶").Result); 93 | Assert.False(ProvidesFor(@"_ = ((NodePath))⛶;").Result); 94 | } 95 | 96 | [Fact] 97 | public void TestBinaryOperation() 98 | { 99 | Assert.True(ProvidesFor(@"_ = new NodePath() == ⛶;").Result); 100 | Assert.True(ProvidesFor(@"_ = new NodePath() != ⛶;").Result); 101 | 102 | // TODO: Not supported by the type inference service. 103 | //Assert.True(ProvidesFor(@"_ = ⛶ == new NodePath();").Result); 104 | //Assert.True(ProvidesFor(@"_ = ⛶ != new NodePath();").Result); 105 | } 106 | 107 | [Fact] 108 | public void TestAssignment() 109 | { 110 | // Assignment in local declaration 111 | Assert.True(ProvidesFor(@"NodePath npLocal = ⛶;").Result); 112 | 113 | // Assignment to a previously declared local 114 | Assert.True(ProvidesFor(@" 115 | NodePath npLocal; 116 | npLocal = ⛶;").Result); 117 | 118 | // Assignment to a previously declared field 119 | Assert.True(ProvidesFor(@" 120 | NodePath npField; 121 | void Foo() { npField = ⛶; } 122 | ").Result); 123 | 124 | // Assignment to a previously declared property 125 | Assert.True(ProvidesFor(@" 126 | NodePath npProp { get; } 127 | void Foo() { npProp = ⛶; } 128 | ").Result); 129 | 130 | // Assignment to ref parameter 131 | Assert.True(ProvidesFor(@"void Foo(ref NodePath npRefParam) { npRefParam = ⛶; }").Result); 132 | 133 | // Assignment to out parameter 134 | Assert.True(ProvidesFor(@"void Foo(out NodePath npOutParam) { npOutParam = ⛶; }").Result); 135 | } 136 | 137 | [Fact] 138 | public void TestElementAccessArgument() 139 | { 140 | Assert.True(ProvidesFor(@" 141 | System.Collections.Generic.Dictionary npDictLocal = default; 142 | _ = npDictLocal[⛶]; 143 | ").Result); 144 | } 145 | 146 | [Fact] 147 | public void TestParenthesizedExpression() 148 | { 149 | Assert.True(ProvidesFor(@"NodePath _ = (⛶);").Result); 150 | Assert.True(ProvidesFor(@"NodePath _ = ((⛶));").Result); 151 | Assert.True(ProvidesFor(@"NodePath _ = (((⛶)));").Result); 152 | 153 | Assert.True(ProvidesFor(@" 154 | void FirstParam(NodePath path) { } 155 | FirstParam((⛶)); 156 | ").Result); 157 | } 158 | 159 | [Fact] 160 | public void TestNullCoalescing() 161 | { 162 | // TODO: Not supported by the type inference service. 163 | // Null-coalescing operator 164 | //Assert.True(ProvidesFor(@"NodePath _ = null ?? ⛶;").Result); 165 | // Null-coalescing assignment 166 | //Assert.True(ProvidesFor(@"NodePath _ ??= ⛶;").Result); 167 | 168 | // Null-coalescing assignment in expression 169 | Assert.True(ProvidesFor(@" 170 | NodePath _; 171 | _ = _ ??= ⛶; 172 | ").Result); 173 | } 174 | 175 | [Fact] 176 | public void TestConditionalOperator() 177 | { 178 | Assert.True(ProvidesFor(@"NodePath _ = false ? ⛶ : default;").Result); 179 | Assert.True(ProvidesFor(@"NodePath _ = false ? default : ⛶;").Result); 180 | } 181 | 182 | [Fact] 183 | public void TestByRefParametersAssignment() 184 | { 185 | // Return statement expression 186 | Assert.True(ProvidesFor(@"NodePath Foo() { return ⛶; }").Result); 187 | 188 | // Return expression of expression-bodied method 189 | Assert.True(ProvidesFor(@"NodePath Foo() => ⛶;").Result); 190 | 191 | // Yield return expression 192 | Assert.True(ProvidesFor(@"IEnumerable Foo() { yield return ⛶; }").Result); 193 | } 194 | 195 | [Fact] 196 | public void TestStringLiteral() 197 | { 198 | // Empty string literal 199 | Assert.True(ProvidesForFull(@"NodePath _ = ""⛶").Result.CheckLiteralResult("")); 200 | Assert.True(ProvidesForFull(@"NodePath _ = ""⛶;").Result.CheckLiteralResult(";")); 201 | 202 | // Inside string literal (not closed, at end) 203 | Assert.True(ProvidesForFull(@"NodePath _ = ""Foo⛶").Result.CheckLiteralResult("Foo")); 204 | // Inside string literal (not closed, in between) 205 | Assert.True(ProvidesForFull(@"NodePath _ = ""Foo⛶Bar").Result.CheckLiteralResult("FooBar")); 206 | // At end of string literal (closed) 207 | Assert.True(ProvidesForFull(@"NodePath _ = ""Foo⛶"";").Result.CheckLiteralResult("Foo")); 208 | // Inside string literal (closed, in between) 209 | Assert.True(ProvidesForFull(@"NodePath _ = ""Foo⛶Bar"";").Result.CheckLiteralResult("FooBar")); 210 | } 211 | 212 | [Fact] 213 | public void TestVerbatimStringLiteral() 214 | { 215 | // Empty verbatim string literal 216 | Assert.True(ProvidesForFull(@"NodePath _ = @""⛶").Result.CheckLiteralResult("")); 217 | Assert.True(ProvidesForFull(@"NodePath _ = @""⛶;").Result.CheckLiteralResult(";")); 218 | 219 | // Inside verbatim string literal (not closed, at end) 220 | Assert.True(ProvidesForFull(@"NodePath _ = @""Foo⛶").Result.CheckLiteralResult("Foo")); 221 | // Inside verbatim string literal (not closed, in between) 222 | Assert.True(ProvidesForFull(@"NodePath _ = @""Foo⛶Bar").Result.CheckLiteralResult("FooBar")); 223 | // Inside verbatim string literal (closed, at end) 224 | Assert.True(ProvidesForFull(@"NodePath _ = @""Foo⛶"";").Result.CheckLiteralResult("Foo")); 225 | // Inside verbatim string literal (closed, in between) 226 | Assert.True(ProvidesForFull(@"NodePath _ = @""Foo⛶Bar"";").Result.CheckLiteralResult("FooBar")); 227 | } 228 | 229 | [Fact] 230 | public void TestInterpolatedStringLiteral() 231 | { 232 | // Empty interpolated string 233 | Assert.True(ProvidesForFull(@"NodePath _ = $""⛶").Result.CheckLiteralResult("")); 234 | Assert.True(ProvidesForFull(@"NodePath _ = $""⛶;").Result.CheckLiteralResult(";")); 235 | 236 | // Interpolated string literal without interpolations are supported 237 | // Inside interpolated string literal (not closed, at end) 238 | Assert.True(ProvidesForFull(@"NodePath _ = $""Foo⛶").Result.CheckLiteralResult("Foo")); 239 | // Inside interpolated string literal (not closed, in between) 240 | Assert.True(ProvidesForFull(@"NodePath _ = $""Foo⛶Bar").Result.CheckLiteralResult("FooBar")); 241 | // Inside interpolated string literal (closed, at end) 242 | Assert.True(ProvidesForFull(@"NodePath _ = $""Foo⛶"";").Result.CheckLiteralResult("Foo")); 243 | // Inside interpolated string literal (closed, in between) 244 | Assert.True(ProvidesForFull(@"NodePath _ = $""Foo⛶Bar"";").Result.CheckLiteralResult("FooBar")); 245 | 246 | // Interpolated string literal with interpolations are not supported and must not provide completion 247 | // Inside interpolated string literal (not closed, at end) 248 | Assert.False(ProvidesFor(@"string aux = ""; NodePath _ = $""Foo{aux}⛶").Result); 249 | // Inside interpolated string literal (not closed, in between) 250 | Assert.False(ProvidesFor(@"string aux = ""; NodePath _ = $""Foo{aux}⛶Bar").Result); 251 | // Inside interpolated string literal (closed, at end) 252 | Assert.False(ProvidesFor(@"string aux = ""; NodePath _ = $""Foo{aux}⛶"";").Result); 253 | // Inside interpolated string literal (closed, in between) 254 | Assert.False(ProvidesFor(@"string aux = ""; NodePath _ = $""Foo{aux}⛶Bar"";").Result); 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /GodotCompletionProviders.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("GodotCompletionProviders.Test")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("GodotCompletionProviders.Test")] 12 | [assembly: AssemblyCopyright("Copyright © 2020")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("B2BAAEA3-8B1D-4584-A5D1-9D4EF487E0DF")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /GodotCompletionProviders.Test/ResourcePathTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace GodotCompletionProviders.Test 5 | { 6 | [Collection("Sequential")] 7 | public class ResourcePathTests : TestsBase 8 | { 9 | private const string StubCode = @" 10 | namespace Godot 11 | { 12 | public static class GD 13 | { 14 | public static Resource Load(string path) => throw new NotImplementedException(); 15 | public static T Load(string path) where T : class => throw new NotImplementedException(); 16 | } 17 | 18 | public static class ResourceLoader 19 | { 20 | public static Resource Load(string path, string typeHint = "", bool noCache = false) => throw new NotImplementedException(); 21 | public static T Load(string path, string typeHint = null, bool noCache = false) where T : class => throw new NotImplementedException(); 22 | } 23 | } 24 | "; 25 | 26 | public ResourcePathTests() : base(new ResourcePathCompletionProvider()) 27 | { 28 | } 29 | 30 | private Task ProvidesForFull(string statements) 31 | { 32 | string testCode = $@" 33 | using Godot; 34 | {statements}"; 35 | string code = Utils.ReadSingleCaretTestCode(testCode, out int caretPosition); 36 | return ShouldProvideCompletion(StubCode, code, caretPosition); 37 | } 38 | 39 | private async Task ProvidesFor(string statements) => 40 | (await ProvidesForFull(statements)).ShouldProvideCompletion; 41 | 42 | [Fact] 43 | public void TestNotSomethingElse() 44 | { 45 | Assert.False(ProvidesFor("ResourceLoader.Foo(⛶)").Result); 46 | Assert.False(ProvidesFor("ResourceLoader.Foo(, ⛶)").Result); 47 | Assert.False(ProvidesFor("ResourceLoader.Foo(, , ⛶)").Result); 48 | } 49 | 50 | [Fact] 51 | public void TestResourceLoaderLoad() 52 | { 53 | Assert.True(ProvidesFor("ResourceLoader.Load(⛶").Result); 54 | Assert.True(ProvidesFor("ResourceLoader.Load(⛶)").Result); 55 | Assert.False(ProvidesFor("ResourceLoader.Load(, ⛶").Result); 56 | Assert.False(ProvidesFor("ResourceLoader.Load(, ⛶)").Result); 57 | 58 | // Generic 59 | Assert.True(ProvidesFor("ResourceLoader.Load(⛶)").Result); 60 | } 61 | 62 | [Fact] 63 | public void TestGdLoad() 64 | { 65 | Assert.True(ProvidesFor("GD.Load(⛶").Result); 66 | Assert.True(ProvidesFor("GD.Load(⛶)").Result); 67 | Assert.False(ProvidesFor("GD.Load(, ⛶").Result); 68 | Assert.False(ProvidesFor("GD.Load(, ⛶)").Result); 69 | 70 | // Generic 71 | Assert.True(ProvidesFor("GD.Load(⛶)").Result); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /GodotCompletionProviders.Test/ScenePathTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace GodotCompletionProviders.Test 5 | { 6 | [Collection("Sequential")] 7 | public class ScenePathTests : TestsBase 8 | { 9 | private const string StubCode = @" 10 | namespace Godot 11 | { 12 | public class SceneTree 13 | { 14 | public Error ChangeScene(string path) => throw new NotImplementedException(); 15 | } 16 | } 17 | "; 18 | 19 | public ScenePathTests() : base(new ScenePathCompletionProvider()) 20 | { 21 | } 22 | 23 | private Task ProvidesForFull(string statements) 24 | { 25 | string testCode = $@" 26 | using Godot; 27 | {statements}"; 28 | string code = Utils.ReadSingleCaretTestCode(testCode, out int caretPosition); 29 | return ShouldProvideCompletion(StubCode, code, caretPosition); 30 | } 31 | 32 | private async Task ProvidesFor(string statements) => 33 | (await ProvidesForFull(statements)).ShouldProvideCompletion; 34 | 35 | [Fact] 36 | public void TestNotSomethingElse() 37 | { 38 | Assert.False(ProvidesFor("((SceneTree)null).Foo(⛶)").Result); 39 | Assert.False(ProvidesFor("((SceneTree)null).Foo(, ⛶)").Result); 40 | Assert.False(ProvidesFor("((SceneTree)null).Foo(, , ⛶)").Result); 41 | } 42 | 43 | [Fact] 44 | public void TestChangeScene() 45 | { 46 | Assert.True(ProvidesFor("((SceneTree)null).ChangeScene(⛶").Result); 47 | Assert.True(ProvidesFor("((SceneTree)null).ChangeScene(⛶)").Result); 48 | Assert.False(ProvidesFor("((SceneTree)null).ChangeScene(, ⛶").Result); 49 | Assert.False(ProvidesFor("((SceneTree)null).ChangeScene(, ⛶)").Result); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /GodotCompletionProviders.Test/SignalNameTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace GodotCompletionProviders.Test 5 | { 6 | [Collection("Sequential")] 7 | public class SignalNameTests : TestsBase 8 | { 9 | private const string StubCode = @" 10 | namespace Godot 11 | { 12 | public class StringName 13 | { 14 | public StringName() { } 15 | public StringName(string from) { } 16 | public static implicit operator StringName(string from) => throw new NotImplementedException(); 17 | public static implicit operator string(StringName from) => throw new NotImplementedException(); 18 | } 19 | 20 | public class Object 21 | { 22 | public Error Connect(StringName signal, Callable callable, Godot.Collections.Array binds = null, uint flags = 0) => 23 | throw new NotImplementedException(); 24 | public void Disconnect(StringName signal, Callable callable) => throw new NotImplementedException(); 25 | public bool IsConnected(StringName signal, Callable callable) => throw new NotImplementedException(); 26 | public void EmitSignal(StringName signal, params object[] @args) => throw new NotImplementedException(); 27 | public SignalAwaiter ToSignal(Object source, StringName signal) => throw new NotImplementedException(); 28 | } 29 | } 30 | "; 31 | 32 | public SignalNameTests() : base(new SignalNameCompletionProvider()) 33 | { 34 | } 35 | 36 | private Task ProvidesForFull(string statements) 37 | { 38 | string testCode = $@" 39 | using Godot; 40 | {statements}"; 41 | string code = Utils.ReadSingleCaretTestCode(testCode, out int caretPosition); 42 | return ShouldProvideCompletion(StubCode, code, caretPosition); 43 | } 44 | 45 | private async Task ProvidesFor(string statements) => 46 | (await ProvidesForFull(statements)).ShouldProvideCompletion; 47 | 48 | [Fact] 49 | public void TestNotSomethingElse() 50 | { 51 | Assert.False(ProvidesFor("((Object)null).Foo(⛶)").Result); 52 | Assert.False(ProvidesFor("((Object)null).Foo(, ⛶)").Result); 53 | Assert.False(ProvidesFor("((Object)null).Foo(, , ⛶)").Result); 54 | } 55 | 56 | [Fact] 57 | public void TestConnect() 58 | { 59 | Assert.True(ProvidesFor("((Object)null).Connect(⛶").Result); 60 | Assert.True(ProvidesFor("((Object)null).Connect(⛶)").Result); 61 | Assert.False(ProvidesFor("((Object)null).Connect(, ⛶").Result); 62 | Assert.False(ProvidesFor("((Object)null).Connect(, ⛶)").Result); 63 | } 64 | 65 | [Fact] 66 | public void TestDisconnect() 67 | { 68 | Assert.True(ProvidesFor("((Object)null).Disconnect(⛶").Result); 69 | Assert.True(ProvidesFor("((Object)null).Disconnect(⛶)").Result); 70 | Assert.False(ProvidesFor("((Object)null).Disconnect(, ⛶").Result); 71 | Assert.False(ProvidesFor("((Object)null).Disconnect(, ⛶)").Result); 72 | } 73 | 74 | [Fact] 75 | public void TestIsConnected() 76 | { 77 | Assert.True(ProvidesFor("((Object)null).IsConnected(⛶").Result); 78 | Assert.True(ProvidesFor("((Object)null).IsConnected(⛶)").Result); 79 | Assert.False(ProvidesFor("((Object)null).IsConnected(, ⛶").Result); 80 | Assert.False(ProvidesFor("((Object)null).IsConnected(, ⛶)").Result); 81 | } 82 | 83 | [Fact] 84 | public void TestEmitSignal() 85 | { 86 | Assert.True(ProvidesFor("((Object)null).EmitSignal(⛶").Result); 87 | Assert.True(ProvidesFor("((Object)null).EmitSignal(⛶)").Result); 88 | Assert.False(ProvidesFor("((Object)null).EmitSignal(, ⛶").Result); 89 | Assert.False(ProvidesFor("((Object)null).EmitSignal(, ⛶)").Result); 90 | } 91 | 92 | [Fact] 93 | public void TestToSignal() 94 | { 95 | Assert.True(ProvidesFor("((Object)null).ToSignal(, ⛶").Result); 96 | Assert.True(ProvidesFor("((Object)null).ToSignal(, ⛶)").Result); 97 | Assert.False(ProvidesFor("((Object)null).ToSignal(⛶").Result); 98 | Assert.False(ProvidesFor("((Object)null).ToSignal(⛶)").Result); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /GodotCompletionProviders.Test/TestsBase.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Host.Mef; 4 | using Microsoft.CodeAnalysis.Text; 5 | using Xunit; 6 | 7 | namespace GodotCompletionProviders.Test 8 | { 9 | public abstract class TestsBase 10 | { 11 | private readonly BaseCompletionProvider _completionProvider; 12 | 13 | protected TestsBase(BaseCompletionProvider completionProvider) 14 | { 15 | _completionProvider = completionProvider; 16 | } 17 | 18 | // ReSharper disable once MemberCanBeMadeStatic.Global 19 | protected Task ShouldProvideCompletion(string stubCode, string testCode, int caretPosition) 20 | { 21 | var host = MefHostServices.Create(MefHostServices.DefaultAssemblies); 22 | Assert.NotNull(host); 23 | var workspace = new AdhocWorkspace(host); 24 | 25 | var projectId = ProjectId.CreateNewId(); 26 | 27 | var stubDocumentInfo = DocumentInfo.Create( 28 | DocumentId.CreateNewId(projectId), "Stub.cs", sourceCodeKind: SourceCodeKind.Regular, 29 | loader: TextLoader.From(TextAndVersion.Create(SourceText.From(stubCode), VersionStamp.Create()))); 30 | 31 | var testDocumentInfo = DocumentInfo.Create( 32 | DocumentId.CreateNewId(projectId), "TestFile.cs", sourceCodeKind: SourceCodeKind.Script, 33 | loader: TextLoader.From(TextAndVersion.Create(SourceText.From(testCode), VersionStamp.Create()))); 34 | 35 | var projectInfo = ProjectInfo 36 | .Create(projectId, VersionStamp.Create(), "TestProject", "TestProject", LanguageNames.CSharp) 37 | .WithMetadataReferences(new[] {MetadataReference.CreateFromFile(typeof(object).Assembly.Location)}) 38 | .WithDocuments(new[] {stubDocumentInfo, testDocumentInfo}); 39 | var project = workspace.AddProject(projectInfo); 40 | 41 | var testDocument = project.GetDocument(testDocumentInfo.Id); 42 | 43 | return _completionProvider.ShouldProvideCompletion(testDocument, caretPosition); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /GodotCompletionProviders.Test/Utils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace GodotCompletionProviders.Test 4 | { 5 | public static class Utils 6 | { 7 | private static int IndexOfAny(this string str, char[] anyOf, out char which) 8 | { 9 | for (int i = 0; i < str.Length; i++) 10 | { 11 | char c = str[i]; 12 | 13 | foreach (char charInAnyOf in anyOf) 14 | { 15 | if (c == charInAnyOf) 16 | { 17 | which = c; 18 | return i; 19 | } 20 | } 21 | } 22 | 23 | which = default; 24 | return -1; 25 | } 26 | 27 | public static string ReadMultiCaretTestCode(string testCode, ICollection mustPassCaretPositions, ICollection mustNotPassCaretPositions) 28 | { 29 | string code = testCode; 30 | 31 | const char mustPassChar = '✔'; 32 | const char mustNotPassChar = '✘'; 33 | 34 | int indexOfCaret; 35 | while ((indexOfCaret = code.IndexOfAny(new[] {mustPassChar, mustNotPassChar}, out char which)) >= 0) 36 | { 37 | (which == mustPassChar ? mustPassCaretPositions : mustNotPassCaretPositions).Add(indexOfCaret); 38 | code = code.Remove(indexOfCaret, 1); 39 | } 40 | 41 | return code; 42 | } 43 | 44 | public static string ReadSingleCaretTestCode(string testCode, out int caretPosition) 45 | { 46 | const char caretChar = '⛶'; 47 | 48 | string code = testCode; 49 | 50 | caretPosition = code.IndexOf(caretChar); 51 | 52 | if (caretPosition >= 0) 53 | code = code.Remove(caretPosition, 1); 54 | 55 | return code; 56 | } 57 | 58 | public static bool CheckLiteralResult(this BaseCompletionProvider.CheckResult result, string expected) 59 | { 60 | if (!result.ShouldProvideCompletion) 61 | return false; 62 | 63 | if (result.StringSyntax == null) 64 | return false; 65 | 66 | return result.StringSyntaxValue is string strValue && strValue == expected; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /GodotCompletionProviders.Test/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /GodotCompletionProviders/BaseCompletionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.CodeAnalysis; 6 | using Microsoft.CodeAnalysis.Completion; 7 | using Microsoft.CodeAnalysis.Options; 8 | using Microsoft.CodeAnalysis.Text; 9 | 10 | namespace GodotCompletionProviders 11 | { 12 | public abstract class BaseCompletionProvider : CompletionProvider 13 | { 14 | private readonly CompletionKind _kind; 15 | private readonly string _inlineDescription; 16 | 17 | // No idea how else to pass this as we don't create the provider instance 18 | // ReSharper disable once UnassignedField.Global 19 | // ReSharper disable once MemberCanBePrivate.Global 20 | public static IProviderContext Context; 21 | 22 | protected BaseCompletionProvider(CompletionKind kind, string inlineDescription) 23 | { 24 | _kind = kind; 25 | _inlineDescription = inlineDescription; 26 | } 27 | 28 | public struct CheckResult 29 | { 30 | public bool ShouldProvideCompletion; 31 | public SyntaxNode StringSyntax; 32 | 33 | public object StringSyntaxValue => RoslynUtils.GetStringSyntaxValue(StringSyntax); 34 | 35 | public static CheckResult False() => new CheckResult {ShouldProvideCompletion = false}; 36 | 37 | public static CheckResult True(SyntaxNode stringSyntax) => 38 | new CheckResult {ShouldProvideCompletion = true, StringSyntax = stringSyntax}; 39 | } 40 | 41 | public abstract Task ShouldProvideCompletion(Document document, int position); 42 | 43 | // ReSharper disable once VirtualMemberNeverOverridden.Global 44 | protected virtual async Task ShouldProvideCompletion(CompletionContext context) 45 | { 46 | var document = context.Document; 47 | 48 | if (document == null) 49 | return CheckResult.False(); 50 | 51 | return await ShouldProvideCompletion(document, context.Position); 52 | } 53 | 54 | // ReSharper disable once VirtualMemberNeverOverridden.Global 55 | protected virtual bool ShouldTriggerCompletion() 56 | { 57 | if (Context == null) 58 | return false; 59 | 60 | return Context.AreCompletionsEnabledFor(_kind) && Context.CanRequestCompletionsFromServer(); 61 | } 62 | 63 | public override bool ShouldTriggerCompletion(SourceText text, int caretPosition, CompletionTrigger trigger, OptionSet options) => 64 | ShouldTriggerCompletion(); 65 | 66 | public override async Task ProvideCompletionsAsync(CompletionContext context) 67 | { 68 | if (!ShouldTriggerCompletion()) 69 | return; 70 | 71 | var checkResult = await ShouldProvideCompletion(context); 72 | 73 | if (!checkResult.ShouldProvideCompletion) 74 | return; 75 | 76 | string scriptFile = Path.GetFullPath(context.Document.FilePath); 77 | 78 | var suggestions = await Context.RequestCompletion(_kind, scriptFile); 79 | 80 | if (suggestions.Length == 0) 81 | return; 82 | 83 | ImmutableDictionary properties = null; 84 | 85 | if (checkResult.StringSyntax != null) 86 | { 87 | var propertiesBuilder = ImmutableDictionary.CreateBuilder(); 88 | propertiesBuilder.Add("GodotSpan.Start", checkResult.StringSyntax.Span.Start.ToString()); 89 | 90 | // TODO: 91 | // This is commented out because of cases like the following: `Foo("Bar$$, 10, "Baz");` 92 | // Instead of replacing only `"Bar` it would replace `"Bar, 10, "`. It gets even worse 93 | // with verbatim string literals which can be multiline. Unless we can find a way to 94 | // avoid this, it's better to only replace up to the caret position, even if that means 95 | // something like `Foo("Bar$$Baz")` will result in `Foo("BarINSERTED"Baz"). 96 | // 97 | // propertiesBuilder.Add("GodotSpan.Length", checkResult.StringSyntax.Span.Length.ToString()); 98 | 99 | propertiesBuilder.Add("GodotSpan.Length", (context.Position - checkResult.StringSyntax.Span.Start).ToString()); 100 | 101 | properties = propertiesBuilder.ToImmutable(); 102 | } 103 | 104 | foreach (string suggestion in suggestions) 105 | { 106 | var completionItem = CompletionItem.Create( 107 | displayText: suggestion, 108 | filterText: null, 109 | sortText: null, 110 | properties: properties, 111 | tags: ImmutableArray.Empty, 112 | rules: null, 113 | displayTextPrefix: null, 114 | displayTextSuffix: null, 115 | inlineDescription: _inlineDescription 116 | ); 117 | context.AddItem(completionItem); 118 | } 119 | } 120 | 121 | public override Task GetChangeAsync( 122 | Document document, CompletionItem item, char? commitKey, CancellationToken cancellationToken) 123 | { 124 | int? spanStart = null; 125 | int? spanLength = null; 126 | 127 | if (item.Properties.TryGetValue("GodotSpan.Start", out string spanStartStr)) 128 | { 129 | if (int.TryParse(spanStartStr, out int startResult)) 130 | { 131 | spanStart = startResult; 132 | } 133 | 134 | if (item.Properties.TryGetValue("GodotSpan.Length", out string spanLengthStr)) 135 | { 136 | if (int.TryParse(spanLengthStr, out int lengthResult)) 137 | { 138 | spanLength = lengthResult; 139 | } 140 | } 141 | } 142 | 143 | var span = spanStart.HasValue && spanLength.HasValue ? 144 | new TextSpan(spanStart.Value, spanLength.Value) : 145 | item.Span; 146 | 147 | return Task.FromResult(CompletionChange.Create(new TextChange(span, item.DisplayText))); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /GodotCompletionProviders/CompletionKind.cs: -------------------------------------------------------------------------------- 1 | namespace GodotCompletionProviders 2 | { 3 | public enum CompletionKind 4 | { 5 | InputActions = 0, 6 | NodePaths, 7 | ResourcePaths, 8 | ScenePaths, 9 | ShaderParams, 10 | Signals, 11 | ThemeColors, 12 | ThemeConstants, 13 | ThemeFonts, 14 | ThemeStyles 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /GodotCompletionProviders/GodotCompletionProviders.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 8 5 | GodotCompletionProviders 6 | 1.0.0 7 | $(Version) 8 | Godot Engine contributors 9 | 10 | godot 11 | https://github.com/godotengine/godot-csharp-visualstudio/tree/master/GodotCompletionProviders 12 | MIT 13 | 14 | Set of Roslyn C# code completion providers for Godot. 15 | 16 | These providers alone don't actually provide the suggestions. IProviderContext must be implemented and set in BaseCompletionProvider.Context for that. 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /GodotCompletionProviders/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GodotCompletionProviders 4 | { 5 | public interface ILogger 6 | { 7 | void LogDebug(string message); 8 | void LogInfo(string message); 9 | void LogWarning(string message); 10 | void LogError(string message); 11 | void LogError(string message, Exception e); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /GodotCompletionProviders/IProviderContext.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace GodotCompletionProviders 4 | { 5 | public interface IProviderContext 6 | { 7 | ILogger GetLogger(); 8 | bool AreCompletionsEnabledFor(CompletionKind completionKind); 9 | bool CanRequestCompletionsFromServer(); 10 | Task RequestCompletion(CompletionKind completionKind, string absoluteFilePath); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GodotCompletionProviders/InputActionCompletionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Completion; 4 | 5 | namespace GodotCompletionProviders 6 | { 7 | [ExportCompletionProvider(nameof(InputActionCompletionProvider), LanguageNames.CSharp)] 8 | public class InputActionCompletionProvider : SpecificInvocationCompletionProvider 9 | { 10 | // TODO: Support offline (not connected to a Godot editor) completion of input actions (parse godot.project). 11 | 12 | private static readonly IEnumerable ExpectedInvocations = new[] 13 | { 14 | new ExpectedInvocation {MethodContainingType = InputType, MethodName = "IsActionPressed", ArgumentIndex = 0, ArgumentTypes = StringTypes}, 15 | new ExpectedInvocation {MethodContainingType = InputType, MethodName = "IsActionJustPressed", ArgumentIndex = 0, ArgumentTypes = StringTypes}, 16 | new ExpectedInvocation {MethodContainingType = InputType, MethodName = "IsActionJustReleased", ArgumentIndex = 0, ArgumentTypes = StringTypes}, 17 | new ExpectedInvocation {MethodContainingType = InputType, MethodName = "GetActionStrength", ArgumentIndex = 0, ArgumentTypes = StringTypes}, 18 | new ExpectedInvocation {MethodContainingType = InputType, MethodName = "ActionPress", ArgumentIndex = 0, ArgumentTypes = StringTypes}, 19 | new ExpectedInvocation {MethodContainingType = InputType, MethodName = "ActionRelease", ArgumentIndex = 0, ArgumentTypes = StringTypes} 20 | }; 21 | 22 | public InputActionCompletionProvider() : base(ExpectedInvocations, CompletionKind.InputActions, "InputAction") 23 | { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /GodotCompletionProviders/NodePathCompletionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.Completion; 6 | using Microsoft.CodeAnalysis.CSharp; 7 | using Microsoft.CodeAnalysis.CSharp.Syntax; 8 | 9 | namespace GodotCompletionProviders 10 | { 11 | [ExportCompletionProvider(nameof(NodePathCompletionProvider), LanguageNames.CSharp)] 12 | public class NodePathCompletionProvider : BaseCompletionProvider 13 | { 14 | // TODO: If generic GetNode, filter by type 15 | 16 | public NodePathCompletionProvider() : base(CompletionKind.NodePaths, "NodePath") 17 | { 18 | } 19 | 20 | public override async Task ShouldProvideCompletion(Document document, int position) 21 | { 22 | if (!document.SupportsSyntaxTree || !document.SupportsSemanticModel) 23 | return CheckResult.False(); 24 | 25 | var syntaxRoot = await document.GetSyntaxRootAsync(); 26 | 27 | if (syntaxRoot == null) 28 | return CheckResult.False(); 29 | 30 | var semanticModel = await document.GetSemanticModelAsync(); 31 | 32 | if (semanticModel == null) 33 | return CheckResult.False(); 34 | 35 | // Walk up and save literal expression if present (we can autocomplete literals). 36 | var currentToken = syntaxRoot.FindToken(position - 1); 37 | var currentNode = currentToken.Parent; 38 | var literalExpression = RoslynUtils.WalkUpStringSyntaxOnce(ref currentNode, ref position); 39 | 40 | // Walk up parenthesis because the inference service doesn't handle that. 41 | currentToken = syntaxRoot.FindToken(position - 1); 42 | currentNode = currentToken.Parent; 43 | if (currentToken.Kind() != SyntaxKind.CloseParenToken) 44 | RoslynUtils.WalkUpParenthesisExpressions(ref currentNode, ref position); 45 | 46 | var inferredTypes = RoslynUtils.InferTypes(semanticModel, position, null, CancellationToken.None); 47 | 48 | if (inferredTypes.Any(RoslynUtils.TypeIsNodePath)) 49 | return CheckResult.True(literalExpression); 50 | 51 | // Our own custom inference for NodePath 52 | 53 | if (IsPathConstructorArgumentOfNodePath(syntaxRoot, semanticModel, currentNode, position)) 54 | return CheckResult.True(literalExpression); 55 | 56 | if (IsParenthesizedExprActuallyCastToNodePath(semanticModel, currentNode)) 57 | return CheckResult.True(literalExpression); 58 | 59 | return CheckResult.False(); 60 | } 61 | 62 | private static bool IsPathConstructorArgumentOfNodePath(SyntaxNode syntaxRoot, SemanticModel semanticModel, SyntaxNode currentNode, int position) 63 | { 64 | // new NodePath($$) for NodePath(string) ctor 65 | 66 | if (!(currentNode is ArgumentListSyntax argumentList && currentNode.Parent is ObjectCreationExpressionSyntax objectCreation)) 67 | return false; 68 | 69 | var previousToken = syntaxRoot.FindToken(position - 1); 70 | 71 | if (previousToken != argumentList.OpenParenToken) 72 | return false; 73 | 74 | if (argumentList.Arguments.Count > 1) 75 | return false; // The NodePath constructor we are looking for has only one parameter 76 | 77 | int index = RoslynUtils.GetArgumentListIndex(argumentList, previousToken); 78 | 79 | var info = semanticModel.GetSymbolInfo(objectCreation.Type); 80 | 81 | if (!(info.Symbol is INamedTypeSymbol type)) 82 | return false; 83 | 84 | if (type.TypeKind == TypeKind.Delegate) 85 | return false; 86 | 87 | if (!RoslynUtils.TypeIsNodePath(type)) 88 | return false; 89 | 90 | var constructors = type.InstanceConstructors.Where(m => m.Parameters.Length == 1); 91 | var types = RoslynUtils.InferTypeInArgument(index, constructors.Select(m => m.Parameters), argumentOpt: null); 92 | 93 | return types.Any(RoslynUtils.TypeIsString); 94 | } 95 | 96 | private static bool IsParenthesizedExprActuallyCastToNodePath(SemanticModel semanticModel, SyntaxNode currentNode) 97 | { 98 | // (NodePath)$$ which is detected as a parenthesized expression rather than a cast 99 | 100 | if (!(currentNode is ParenthesizedExpressionSyntax parenthesizedExpression)) 101 | return false; 102 | 103 | if (!(parenthesizedExpression.Expression is IdentifierNameSyntax identifierNameSyntax)) 104 | return false; 105 | 106 | var typeInfo = semanticModel.GetTypeInfo(identifierNameSyntax).Type; 107 | 108 | return typeInfo != null && RoslynUtils.TypeIsNodePath(typeInfo); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /GodotCompletionProviders/ResourcePathCompletionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Completion; 4 | 5 | namespace GodotCompletionProviders 6 | { 7 | [ExportCompletionProvider(nameof(ResourcePathCompletionProvider), LanguageNames.CSharp)] 8 | public class ResourcePathCompletionProvider : SpecificInvocationCompletionProvider 9 | { 10 | // TODO: If generic Load, filter by type 11 | // TODO: Support offline (not connected to a Godot editor) completion of resource paths (from the file system). 12 | 13 | private static readonly IEnumerable ExpectedInvocations = new[] 14 | { 15 | new ExpectedInvocation {MethodContainingType = GdType, MethodName = "Load", ArgumentIndex = 0, ArgumentTypes = StringTypes}, 16 | new ExpectedInvocation {MethodContainingType = ResourceLoaderType, MethodName = "Load", ArgumentIndex = 0, ArgumentTypes = StringTypes} 17 | }; 18 | 19 | public ResourcePathCompletionProvider() : base(ExpectedInvocations, CompletionKind.ResourcePaths, "Resource") 20 | { 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /GodotCompletionProviders/RoslynUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Threading; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CSharp; 9 | using Microsoft.CodeAnalysis.CSharp.Syntax; 10 | 11 | namespace GodotCompletionProviders 12 | { 13 | internal static class RoslynUtils 14 | { 15 | public static bool TypeIsNodePath(ITypeSymbol type) => 16 | type.Name == "NodePath" && type.ContainingNamespace.Name == "Godot"; 17 | 18 | public static bool TypeIsString(ITypeSymbol type) => 19 | type.Name == "String" && type.ContainingNamespace.Name == "System"; 20 | 21 | public static void WalkUpParenthesisExpressions(ref SyntaxNode currentNode, ref int position) 22 | { 23 | while (currentNode.Kind() == SyntaxKind.ParenthesizedExpression) 24 | { 25 | position = currentNode.SpanStart; 26 | currentNode = currentNode.Parent; 27 | } 28 | } 29 | 30 | private static object StringSyntaxValueFromInterpolated(InterpolatedStringExpressionSyntax interpolatedStringExpression) 31 | { 32 | if (interpolatedStringExpression.Contents.Count > 1) 33 | return null; 34 | 35 | if (interpolatedStringExpression.Contents.Count == 1) 36 | { 37 | if (interpolatedStringExpression.Contents[0] is InterpolatedStringTextSyntax interpolatedStringTextSyntax) 38 | return interpolatedStringTextSyntax.TextToken.Value; 39 | } 40 | 41 | return ""; 42 | } 43 | 44 | private static object StringSyntaxValueFromInterpolated(InterpolatedStringTextSyntax interpolatedStringText) => 45 | interpolatedStringText.Parent is InterpolatedStringExpressionSyntax interpolatedStringExpression ? 46 | StringSyntaxValueFromInterpolated(interpolatedStringExpression) : 47 | null; 48 | 49 | public static object GetStringSyntaxValue(SyntaxNode stringSyntax) 50 | { 51 | return stringSyntax switch 52 | { 53 | LiteralExpressionSyntax literalExpression => literalExpression.Token.Kind() == SyntaxKind.StringLiteralToken ? literalExpression.Token.Value : null, 54 | InterpolatedStringTextSyntax interpolatedStringText => StringSyntaxValueFromInterpolated(interpolatedStringText), 55 | InterpolatedStringExpressionSyntax interpolatedStringExpression => StringSyntaxValueFromInterpolated(interpolatedStringExpression), 56 | _ => null 57 | }; 58 | } 59 | 60 | private static SyntaxNode StringSyntaxFromInterpolated(InterpolatedStringExpressionSyntax interpolatedStringExpression) 61 | { 62 | if (interpolatedStringExpression.Contents.Count > 1) 63 | return null; 64 | 65 | if (interpolatedStringExpression.Contents.Count == 1) 66 | { 67 | if (!(interpolatedStringExpression.Contents[0] is InterpolatedStringTextSyntax)) 68 | return null; 69 | } 70 | 71 | return interpolatedStringExpression; 72 | } 73 | 74 | private static SyntaxNode StringSyntaxFromInterpolated(InterpolatedStringTextSyntax interpolatedStringText) => 75 | interpolatedStringText.Parent is InterpolatedStringExpressionSyntax interpolatedStringExpression ? 76 | StringSyntaxFromInterpolated(interpolatedStringExpression) : 77 | null; 78 | 79 | public static SyntaxNode WalkUpStringSyntaxOnce(ref SyntaxNode currentNode, ref int position) 80 | { 81 | var result = currentNode switch 82 | { 83 | LiteralExpressionSyntax literalExpression => literalExpression.Token.Kind() == SyntaxKind.StringLiteralToken ? literalExpression : null, 84 | InterpolatedStringTextSyntax interpolatedStringText => StringSyntaxFromInterpolated(interpolatedStringText), 85 | InterpolatedStringExpressionSyntax interpolatedStringExpression => StringSyntaxFromInterpolated(interpolatedStringExpression), 86 | _ => null 87 | }; 88 | 89 | if (result != null) 90 | position = result.SpanStart; 91 | 92 | return result; 93 | } 94 | 95 | // Borrowed from Roslyn 96 | private static SyntaxToken GetOpenToken(BaseArgumentListSyntax node) 97 | { 98 | if (node == null) 99 | return default; 100 | 101 | return node.Kind() switch 102 | { 103 | SyntaxKind.ArgumentList => ((ArgumentListSyntax)node).OpenParenToken, 104 | SyntaxKind.BracketedArgumentList => ((BracketedArgumentListSyntax)node).OpenBracketToken, 105 | _ => default 106 | }; 107 | } 108 | 109 | // Borrowed from Roslyn 110 | public static int GetArgumentListIndex(BaseArgumentListSyntax argumentList, SyntaxToken previousToken) 111 | { 112 | if (previousToken == GetOpenToken(argumentList)) 113 | return 0; 114 | 115 | int tokenIndex = argumentList.Arguments.GetWithSeparators().IndexOf(previousToken); 116 | return (tokenIndex + 1) / 2; 117 | } 118 | 119 | // Borrowed from Roslyn 120 | private static RefKind GetRefKind(this ArgumentSyntax argument) 121 | { 122 | switch (argument?.RefKindKeyword.Kind()) 123 | { 124 | case SyntaxKind.RefKeyword: 125 | return RefKind.Ref; 126 | case SyntaxKind.OutKeyword: 127 | return RefKind.Out; 128 | case SyntaxKind.InKeyword: 129 | return RefKind.In; 130 | default: 131 | return RefKind.None; 132 | } 133 | } 134 | 135 | // Borrowed from Roslyn 136 | internal static IEnumerable InferTypeInArgument( 137 | int index, 138 | IEnumerable> parameterizedSymbols, 139 | ArgumentSyntax argumentOpt) 140 | { 141 | var name = argumentOpt != null && argumentOpt.NameColon != null ? argumentOpt.NameColon.Name.Identifier.ValueText : null; 142 | var refKind = argumentOpt.GetRefKind(); 143 | return InferTypeInArgument(index, parameterizedSymbols, name, refKind); 144 | } 145 | 146 | // Borrowed from Roslyn 147 | private static IEnumerable InferTypeInArgument( 148 | int index, 149 | IEnumerable> parameterizedSymbols, 150 | string name, 151 | RefKind refKind) 152 | { 153 | // If the callsite has a named argument, then try to find a method overload that has a 154 | // parameter with that name. If we can find one, then return the type of that one. 155 | if (name != null) 156 | { 157 | var matchingNameParameters = parameterizedSymbols.SelectMany(m => m) 158 | .Where(p => p.Name == name) 159 | .Select(p => p.Type); 160 | 161 | return matchingNameParameters; 162 | } 163 | 164 | var allParameters = new List(); 165 | var matchingRefParameters = new List(); 166 | 167 | foreach (var parameterSet in parameterizedSymbols) 168 | { 169 | if (index < parameterSet.Length) 170 | { 171 | var parameter = parameterSet[index]; 172 | allParameters.Add(parameter.Type); 173 | 174 | if (parameter.RefKind == refKind) 175 | { 176 | matchingRefParameters.Add(parameter.Type); 177 | } 178 | } 179 | } 180 | 181 | return matchingRefParameters.Count > 0 ? matchingRefParameters.ToImmutableArray() : allParameters.ToImmutableArray(); 182 | } 183 | 184 | // Borrowed from Roslyn 185 | private static ImmutableArray GetBestOrAllSymbols(this SymbolInfo info) 186 | { 187 | if (info.Symbol != null) 188 | return ImmutableArray.Create(info.Symbol); 189 | 190 | if (info.CandidateSymbols.Length > 0) 191 | return info.CandidateSymbols; 192 | 193 | return ImmutableArray.Empty; 194 | } 195 | 196 | public static bool IsExpectedInvocationArgument(SemanticModel semanticModel, SyntaxToken previousToken, 197 | InvocationExpressionSyntax invocation, ArgumentListSyntax argumentList, 198 | IEnumerable expectedInvocations) 199 | { 200 | if (previousToken != argumentList.OpenParenToken && previousToken.Kind() != SyntaxKind.CommaToken) 201 | return false; 202 | 203 | // ReSharper disable PossibleMultipleEnumeration 204 | 205 | expectedInvocations = expectedInvocations.Where(ei => argumentList.Arguments.Count <= ei.ArgumentIndex + 1); 206 | 207 | if (!expectedInvocations.Any()) 208 | return false; 209 | 210 | int index = GetArgumentListIndex(argumentList, previousToken); 211 | 212 | expectedInvocations = expectedInvocations.Where(ei => index == ei.ArgumentIndex); 213 | 214 | if (!expectedInvocations.Any()) 215 | return false; 216 | 217 | var info = semanticModel.GetSymbolInfo(invocation); 218 | var methods = info.GetBestOrAllSymbols().OfType(); 219 | 220 | if (info.Symbol == null) 221 | { 222 | var memberGroupMethods = semanticModel.GetMemberGroup(invocation.Expression).OfType(); 223 | methods = methods.Concat(memberGroupMethods).Distinct(); 224 | } 225 | 226 | foreach (var expected in expectedInvocations) 227 | { 228 | var filteredMethods = methods.Where(m => 229 | m.ContainingType.ContainingNamespace.Name == expected.MethodContainingType.Namespace && 230 | m.ContainingType.Name == expected.MethodContainingType.Name && 231 | m.Name == expected.MethodName); 232 | 233 | var types = InferTypeInArgument(index, filteredMethods.Select(m => m.Parameters), argumentOpt: null); 234 | 235 | if (types.Any(t => expected.ArgumentTypes 236 | .Any(at => t.Name == at.Name && t.ContainingNamespace.Name == at.Namespace))) 237 | { 238 | return true; 239 | } 240 | } 241 | 242 | return false; 243 | 244 | // ReSharper restore PossibleMultipleEnumeration 245 | } 246 | 247 | private static Type _inferenceServiceType; 248 | private static object _inferenceService; 249 | private static MethodInfo _inferTypesMethod; 250 | 251 | internal static ImmutableArray InferTypes( 252 | SemanticModel semanticModel, int position, 253 | string nameOpt, CancellationToken cancellationToken) 254 | { 255 | // I know, I know... Don't look at me like that >_> 256 | const string inferenceServiceTypeQualifiedName = 257 | "Microsoft.CodeAnalysis.CSharp.CSharpTypeInferenceService, Microsoft.CodeAnalysis.CSharp.Workspaces"; 258 | _inferenceServiceType ??= Type.GetType(inferenceServiceTypeQualifiedName, throwOnError: true); 259 | _inferenceService ??= Activator.CreateInstance(_inferenceServiceType); 260 | _inferTypesMethod ??= _inferenceServiceType.GetMethod("InferTypes", 261 | new[] {typeof(SemanticModel), typeof(int), typeof(string), typeof(CancellationToken)}); 262 | 263 | if (_inferTypesMethod == null) 264 | throw new MissingMethodException("Couldn't find InferTypes"); 265 | 266 | return (ImmutableArray)_inferTypesMethod.Invoke(_inferenceService, 267 | new object[] {semanticModel, position, null, CancellationToken.None}); 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /GodotCompletionProviders/ScenePathCompletionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Completion; 4 | 5 | namespace GodotCompletionProviders 6 | { 7 | [ExportCompletionProvider(nameof(ScenePathCompletionProvider), LanguageNames.CSharp)] 8 | public class ScenePathCompletionProvider : SpecificInvocationCompletionProvider 9 | { 10 | // TODO: Support offline (not connected to a Godot editor) completion of scene paths (from the file system). 11 | 12 | private static readonly IEnumerable ExpectedInvocations = new[] 13 | { 14 | new ExpectedInvocation {MethodContainingType = SceneTreeType, MethodName = "ChangeScene", ArgumentIndex = 0, ArgumentTypes = StringTypes} 15 | }; 16 | 17 | public ScenePathCompletionProvider() : base(ExpectedInvocations, CompletionKind.ScenePaths, "Scene") 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /GodotCompletionProviders/SignalNameCompletionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Completion; 4 | 5 | namespace GodotCompletionProviders 6 | { 7 | [ExportCompletionProvider(nameof(SignalNameCompletionProvider), LanguageNames.CSharp)] 8 | public class SignalNameCompletionProvider : SpecificInvocationCompletionProvider 9 | { 10 | private static readonly IEnumerable ExpectedInvocations = new[] 11 | { 12 | new ExpectedInvocation {MethodContainingType = GodotObjectType, MethodName = "Connect", ArgumentIndex = 0, ArgumentTypes = StringTypes}, 13 | new ExpectedInvocation {MethodContainingType = GodotObjectType, MethodName = "Disconnect", ArgumentIndex = 0, ArgumentTypes = StringTypes}, 14 | new ExpectedInvocation {MethodContainingType = GodotObjectType, MethodName = "IsConnected", ArgumentIndex = 0, ArgumentTypes = StringTypes}, 15 | new ExpectedInvocation {MethodContainingType = GodotObjectType, MethodName = "EmitSignal", ArgumentIndex = 0, ArgumentTypes = StringTypes}, 16 | new ExpectedInvocation {MethodContainingType = GodotObjectType, MethodName = "ToSignal", ArgumentIndex = 1, ArgumentTypes = StringTypes}, 17 | }; 18 | 19 | public SignalNameCompletionProvider() : base(ExpectedInvocations, CompletionKind.Signals, "Signal") 20 | { 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /GodotCompletionProviders/SpecificInvocationCompletionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp; 5 | using Microsoft.CodeAnalysis.CSharp.Syntax; 6 | 7 | namespace GodotCompletionProviders 8 | { 9 | public abstract class SpecificInvocationCompletionProvider : BaseCompletionProvider 10 | { 11 | internal static readonly TypeName GodotObjectType = new TypeName {Namespace = "Godot", Name = "Object"}; 12 | internal static readonly TypeName SceneTreeType = new TypeName {Namespace = "Godot", Name = "SceneTree"}; 13 | internal static readonly TypeName GdType = new TypeName {Namespace = "Godot", Name = "GD"}; 14 | internal static readonly TypeName ResourceLoaderType = new TypeName {Namespace = "Godot", Name = "ResourceLoader"}; 15 | internal static readonly TypeName InputType = new TypeName {Namespace = "Godot", Name = "Input"}; 16 | internal static readonly TypeName StringNameType = new TypeName {Namespace = "Godot", Name = "StringName"}; 17 | internal static readonly TypeName StringType = new TypeName {Namespace = "System", Name = "String"}; 18 | 19 | internal static readonly IEnumerable StringTypes = new[] {StringNameType, StringType}; 20 | 21 | public struct TypeName 22 | { 23 | public string Namespace; 24 | public string Name; 25 | } 26 | 27 | public struct ExpectedInvocation 28 | { 29 | public TypeName MethodContainingType; 30 | public string MethodName; 31 | public int ArgumentIndex; 32 | public IEnumerable ArgumentTypes; 33 | } 34 | 35 | private readonly IEnumerable _expectedInvocations; 36 | 37 | protected SpecificInvocationCompletionProvider(IEnumerable expectedInvocations, CompletionKind kind, string inlineDescription) : base(kind, inlineDescription) 38 | { 39 | _expectedInvocations = expectedInvocations; 40 | } 41 | 42 | public override async Task ShouldProvideCompletion(Document document, int position) 43 | { 44 | if (!document.SupportsSyntaxTree || !document.SupportsSemanticModel) 45 | return CheckResult.False(); 46 | 47 | var syntaxRoot = await document.GetSyntaxRootAsync(); 48 | 49 | if (syntaxRoot == null) 50 | return CheckResult.False(); 51 | 52 | var semanticModel = await document.GetSemanticModelAsync(); 53 | 54 | if (semanticModel == null) 55 | return CheckResult.False(); 56 | 57 | // Walk up and save literal expression if present (we can autocomplete literals). 58 | var currentToken = syntaxRoot.FindToken(position - 1); 59 | var currentNode = currentToken.Parent; 60 | var literalExpression = RoslynUtils.WalkUpStringSyntaxOnce(ref currentNode, ref position); 61 | 62 | // Walk up parenthesis because the inference service doesn't handle that. 63 | currentToken = syntaxRoot.FindToken(position - 1); 64 | currentNode = currentToken.Parent; 65 | if (currentToken.Kind() != SyntaxKind.CloseParenToken) 66 | RoslynUtils.WalkUpParenthesisExpressions(ref currentNode, ref position); 67 | 68 | if (!(currentNode is ArgumentListSyntax argumentList && currentNode.Parent is InvocationExpressionSyntax invocation)) 69 | return CheckResult.False(); 70 | 71 | var previousToken = syntaxRoot.FindToken(position - 1); 72 | 73 | if (previousToken != argumentList.OpenParenToken && previousToken.Kind() != SyntaxKind.CommaToken) 74 | return CheckResult.False(); 75 | 76 | if (RoslynUtils.IsExpectedInvocationArgument(semanticModel, previousToken, invocation, argumentList, _expectedInvocations)) 77 | return CheckResult.True(literalExpression); 78 | 79 | return CheckResult.False(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Godot Engine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot C# extension for Visual Studio 2 | 3 | Visual Studio extension for the Godot game engine C# projects. 4 | 5 | ## Requirements 6 | 7 | - **Godot 3.2.3** or greater. Older versions of Godot are not supported and do not work. 8 | - **Visual Studio 2022**. VS 2019 or earlier are not supported. 9 | - **Visit 1.x** branch to get the **Visual Studio 2019** supported version. 10 | 11 | ## Features 12 | 13 | - Debugging. 14 | - Launch a game directly in the Godot editor from Visual Studio. 15 | - Additional code completion for Node paths, Input actions, Resource paths, Scene paths and Signal names. 16 | 17 | **NOTES:** 18 | 19 | - A running Godot instance must be editing the project in order for code completion and the `Play in Editor` debug target to work. 20 | - Node path suggestions are provided from the currently edited scene in the Godot editor. 21 | 22 | ## Debug targets 23 | 24 | - **Play in Editor**\ 25 | Launches the game in the Godot editor for debugging in Visual Studio.\ 26 | For this option to work, a running Godot instance must be editing the project. 27 | - **Launch**\ 28 | Launches the game with a Godot executable for debugging in Visual Studio.\ 29 | Before using this option, the value of the _"executable"_ property must be changed 30 | to a path that points to the Godot executable that will be launched. 31 | - **Attach**\ 32 | Attaches to a running Godot instance that was configured to listen for a debugger connection. 33 | -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail 4 | IFS=$'\n\t' 5 | 6 | # Loops through all text files tracked by Git. 7 | git grep -zIl '' | 8 | while IFS= read -rd '' f; do 9 | # Exclude csproj and hdr files. 10 | if [[ "$f" == *"csproj" ]]; then 11 | continue 12 | elif [[ "$f" == *"hdr" ]]; then 13 | continue 14 | fi 15 | # Ensures that files are UTF-8 formatted. 16 | recode UTF-8 "$f" 2> /dev/null 17 | # Ensures that files have LF line endings. 18 | dos2unix "$f" 2> /dev/null 19 | # Ensures that files do not contain a BOM. 20 | sed -i '1s/^\xEF\xBB\xBF//' "$f" 21 | # Ensures that files end with newline characters. 22 | tail -c1 < "$f" | read -r _ || echo >> "$f"; 23 | # Remove trailing space characters. 24 | sed -z -i 's/\x20\x0A/\x0A/g' "$f" 25 | done 26 | 27 | git diff > patch.patch 28 | FILESIZE="$(stat -c%s patch.patch)" 29 | MAXSIZE=5 30 | 31 | # If no patch has been generated all is OK, clean up, and exit. 32 | if (( FILESIZE < MAXSIZE )); then 33 | printf "Files in this commit comply with the formatting rules.\n" 34 | rm -f patch.patch 35 | exit 0 36 | fi 37 | 38 | # A patch has been created, notify the user, clean up, and exit. 39 | printf "\n*** The following differences were found between the code " 40 | printf "and the formatting rules:\n\n" 41 | cat patch.patch 42 | printf "\n*** Aborting, please fix your commit(s) with 'git commit --amend' or 'git rebase -i '\n" 43 | rm -f patch.patch 44 | exit 1 45 | --------------------------------------------------------------------------------