├── .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 |
99 |
100 |
101 |
102 |
105 |
106 |
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 |
15 |
16 | x86
17 |
18 |
19 | x86
20 |
21 |
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 |
--------------------------------------------------------------------------------