From 94e3f57d7aba1ed3d67e2541ccf25805e6a8b0a1 Mon Sep 17 00:00:00 2001 From: Ignacio Etcheverry Date: Sat, 9 May 2020 16:15:18 +0200 Subject: [PATCH] Initial commit --- .gitignore | 5 + .gitmodules | 9 + .vscode/launch.json | 18 + .vscode/settings.json | 17 + .vscodeignore | 17 + CHANGELOG.md | 5 + GodotDebugSession/.gitignore | 355 ++++++++++++ GodotDebugSession/GodotDebugSession.cs | 130 +++++ GodotDebugSession/GodotDebugSession.csproj | 85 +++ GodotDebugSession/GodotDebugSession.sln | 46 ++ GodotDebugSession/GodotDebuggerSession.cs | 168 ++++++ GodotDebugSession/GodotDebuggerStartInfo.cs | 33 ++ GodotDebugSession/GodotMessageHandler.cs | 14 + GodotDebugSession/Logger.cs | 55 ++ GodotDebugSession/MonoDebug.cs | 19 + GodotDebugSession/Program.cs | 25 + GodotDebugSession/Properties/AssemblyInfo.cs | 35 ++ LICENSE | 21 + Makefile | 31 ++ README.md | 25 + images/codeCompletionInputActions.png | Bin 0 -> 16673 bytes images/codeCompletionNodePaths.png | Bin 0 -> 12869 bytes images/codeCompletionSignals.png | Bin 0 -> 14057 bytes images/debugging.png | Bin 0 -> 51440 bytes package.json | 155 ++++++ src/completion-provider.ts | 134 +++++ src/extension.ts | 178 ++++++ src/godot-tools-messaging/client.ts | 556 +++++++++++++++++++ thirdparty/debugger-libs | 1 + thirdparty/nrefactory | 1 + thirdparty/vscode-mono-debug | 1 + tsconfig.json | 22 + tslint.json | 15 + 33 files changed, 2176 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscodeignore create mode 100644 CHANGELOG.md create mode 100644 GodotDebugSession/.gitignore create mode 100644 GodotDebugSession/GodotDebugSession.cs create mode 100644 GodotDebugSession/GodotDebugSession.csproj create mode 100644 GodotDebugSession/GodotDebugSession.sln create mode 100644 GodotDebugSession/GodotDebuggerSession.cs create mode 100644 GodotDebugSession/GodotDebuggerStartInfo.cs create mode 100644 GodotDebugSession/GodotMessageHandler.cs create mode 100644 GodotDebugSession/Logger.cs create mode 100644 GodotDebugSession/MonoDebug.cs create mode 100644 GodotDebugSession/Program.cs create mode 100644 GodotDebugSession/Properties/AssemblyInfo.cs create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 images/codeCompletionInputActions.png create mode 100644 images/codeCompletionNodePaths.png create mode 100644 images/codeCompletionSignals.png create mode 100644 images/debugging.png create mode 100644 package.json create mode 100644 src/completion-provider.ts create mode 100644 src/extension.ts create mode 100644 src/godot-tools-messaging/client.ts create mode 160000 thirdparty/debugger-libs create mode 160000 thirdparty/nrefactory create mode 160000 thirdparty/vscode-mono-debug create mode 100644 tsconfig.json create mode 100644 tslint.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27c0b6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +out +node_modules +.vscode-test/ +*.vsix +package-lock.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..be3ed4e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "thirdparty/debugger-libs"] + path = thirdparty/debugger-libs + url = https://github.com/mono/debugger-libs.git +[submodule "thirdparty/nrefactory"] + path = thirdparty/nrefactory + url = https://github.com/icsharpcode/NRefactory.git +[submodule "thirdparty/vscode-mono-debug"] + path = thirdparty/vscode-mono-debug + url = https://github.com/neikeq/vscode-mono-debug.git diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f8c04c0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "npm: compile-tsc" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..026b0bf --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off", + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/node_modules/**": true, + "**/.hg/store/**": true, + "thirdparty/**": true, + } +} \ No newline at end of file diff --git a/.vscodeignore b/.vscodeignore new file mode 100644 index 0000000..3f853af --- /dev/null +++ b/.vscodeignore @@ -0,0 +1,17 @@ +.vscode/** +.vscode-test/** +out/test/** +src/** +.gitignore +vsc-extension-quickstart.md +**/tsconfig.json +**/tslint.json +**/*.map +**/*.ts +node_modules/** +thirdparty/** +GodotDebugSession/** +!GodotDebugSession/bin/Release/* +!node_modules/vscode-debugprotocol/**/* +!node_modules/promise-socket/**/* +!node_modules/chokidar/**/* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d5b1d21 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Change Log + +## 0.1.0 + +- Initial release diff --git a/GodotDebugSession/.gitignore b/GodotDebugSession/.gitignore new file mode 100644 index 0000000..a41d1c8 --- /dev/null +++ b/GodotDebugSession/.gitignore @@ -0,0 +1,355 @@ +# Rider +.idea/ + +# Visual Studio Code +.vscode/ + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ diff --git a/GodotDebugSession/GodotDebugSession.cs b/GodotDebugSession/GodotDebugSession.cs new file mode 100644 index 0000000..3fd144f --- /dev/null +++ b/GodotDebugSession/GodotDebugSession.cs @@ -0,0 +1,130 @@ +using System; +using System.Net; +using Mono.Debugging.Client; +using Mono.Debugging.Soft; +using VSCodeDebug; + +namespace GodotDebugSession +{ + public class GodotDebugSession : MonoDebugSession, IProcessOutputListener + { + public GodotDebugSession() : base(new GodotDebuggerSession(), new CustomLogger()) + { + } + + public override void Launch(Response response, dynamic args) + { + ExecutionType executionType; + + string godotExecutablePath = getString(args, "executable"); + + string mode = getString(args, "mode"); + if (string.IsNullOrEmpty(mode) || (mode != "playInEditor" && mode != "executable")) + { + executionType = !string.IsNullOrEmpty(godotExecutablePath) ? + ExecutionType.Launch : + ExecutionType.PlayInEditor; + } + else + { + executionType = mode == "playInEditor" ? ExecutionType.PlayInEditor : ExecutionType.Launch; + } + + RunImpl(response, args, executionType); + } + + public override void Attach(Response response, dynamic args) + { + RunImpl(response, args, ExecutionType.Attach); + } + + private void RunImpl(Response response, dynamic args, ExecutionType executionType) + { + lock (_lock) + { + _attachMode = executionType == ExecutionType.Attach; + + SetExceptionBreakpoints(args.__exceptionOptions); + + SoftDebuggerRemoteArgs listenArgs; + + if (executionType == ExecutionType.Attach) + { + // validate argument 'address' + string host = getString(args, "address"); + if (host == null) + { + SendErrorResponse(response, 3007, "Property 'address' is missing or empty."); + return; + } + + // validate argument 'port' + int port = getInt(args, "port", -1); + if (port == -1) + { + SendErrorResponse(response, 3008, "Property 'port' is missing."); + return; + } + + IPAddress address = Utilities.ResolveIPAddress(host); + if (address == null) + { + SendErrorResponse(response, 3013, "Invalid address '{host}'.", new {host}); + return; + } + + listenArgs = new SoftDebuggerConnectArgs("Godot", IPAddress.Loopback, port); + } + else + { + listenArgs = new SoftDebuggerListenArgs("Godot", IPAddress.Loopback, 0); + } + + // ------ + + _debuggeeKilled = false; + + string godotExecutablePath = (string)args.executable; + + string godotProjectDir = (string)args.godotProjectDir; + + var startInfo = new GodotDebuggerStartInfo(executionType, godotExecutablePath, + processOutputListener: this, listenArgs) {WorkingDirectory = godotProjectDir}; + + _session.Run(startInfo, _debuggerSessionOptions); + + _debuggeeExecuting = true; + } + } + + private class CustomLogger : ICustomLogger + { + public void LogError(string message, Exception ex) => Logger.LogError(message, ex); + + public void LogAndShowException(string message, Exception ex) => LogError(message, ex); + + public void LogMessage(string messageFormat, params object[] args) + { + Logger.Log(string.Format(messageFormat, args)); + } + + public string GetNewDebuggerLogFilename() => Logger.NewLogPath; + } + + public void ReceiveStdOut(string data) + { + if (data == null) + _stdoutEOF = true; + + SendOutput("stdout", data); + } + + public void ReceiveStdErr(string data) + { + if (data == null) + _stdoutEOF = true; + + SendOutput("stderr", data); + } + } +} diff --git a/GodotDebugSession/GodotDebugSession.csproj b/GodotDebugSession/GodotDebugSession.csproj new file mode 100644 index 0000000..4fd0e96 --- /dev/null +++ b/GodotDebugSession/GodotDebugSession.csproj @@ -0,0 +1,85 @@ + + + + + Debug + AnyCPU + {52059825-2921-4EFB-B6C3-350303F587EE} + Exe + Properties + GodotDebugSession + GodotDebugSession + v4.7.2 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + DebugSession.cs + + + Handles.cs + + + MonoDebugSession.cs + + + Protocol.cs + + + Utilities.cs + + + + + + + + + + + + + {372e8e3e-29d5-4b4d-88a2-4711cd628c4e} + Mono.Debugger.Soft + + + {de40756e-57f6-4af2-b155-55e3a88cced8} + Mono.Debugging.Soft + + + {90c99adb-7d4b-4eb4-98c2-40bd1b14c7d2} + Mono.Debugging + + + + + + + + diff --git a/GodotDebugSession/GodotDebugSession.sln b/GodotDebugSession/GodotDebugSession.sln new file mode 100644 index 0000000..cc7086f --- /dev/null +++ b/GodotDebugSession/GodotDebugSession.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotDebugSession", "GodotDebugSession.csproj", "{52059825-2921-4EFB-B6C3-350303F587EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Debugging.Soft", "..\thirdparty\debugger-libs\Mono.Debugging.Soft\Mono.Debugging.Soft.csproj", "{DE40756E-57F6-4AF2-B155-55E3A88CCED8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Debugging", "..\thirdparty\debugger-libs\Mono.Debugging\Mono.Debugging.csproj", "{90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Debugger.Soft", "..\thirdparty\debugger-libs\Mono.Debugger.Soft\Mono.Debugger.Soft.csproj", "{372E8E3E-29D5-4B4D-88A2-4711CD628C4E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.NRefactory", "..\thirdparty\nrefactory\ICSharpCode.NRefactory\ICSharpCode.NRefactory.csproj", "{3B2A5653-EC97-4001-BB9B-D90F1AF2C371}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.NRefactory.CSharp", "..\thirdparty\nrefactory\ICSharpCode.NRefactory.CSharp\ICSharpCode.NRefactory.CSharp.csproj", "{53DCA265-3C3C-42F9-B647-F72BA678122B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {52059825-2921-4EFB-B6C3-350303F587EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52059825-2921-4EFB-B6C3-350303F587EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52059825-2921-4EFB-B6C3-350303F587EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52059825-2921-4EFB-B6C3-350303F587EE}.Release|Any CPU.Build.0 = Release|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE40756E-57F6-4AF2-B155-55E3A88CCED8}.Release|Any CPU.Build.0 = Release|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90C99ADB-7D4B-4EB4-98C2-40BD1B14C7D2}.Release|Any CPU.Build.0 = Release|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {372E8E3E-29D5-4B4D-88A2-4711CD628C4E}.Release|Any CPU.Build.0 = Release|Any CPU + {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B2A5653-EC97-4001-BB9B-D90F1AF2C371}.Release|Any CPU.Build.0 = Release|Any CPU + {53DCA265-3C3C-42F9-B647-F72BA678122B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53DCA265-3C3C-42F9-B647-F72BA678122B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53DCA265-3C3C-42F9-B647-F72BA678122B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53DCA265-3C3C-42F9-B647-F72BA678122B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/GodotDebugSession/GodotDebuggerSession.cs b/GodotDebugSession/GodotDebuggerSession.cs new file mode 100644 index 0000000..dd4596f --- /dev/null +++ b/GodotDebugSession/GodotDebuggerSession.cs @@ -0,0 +1,168 @@ +using System; +using System.Diagnostics; +using System.Net.Sockets; +using System.Net; +using System.Threading.Tasks; +using GodotTools.IdeMessaging.Requests; +using Mono.Debugging.Client; +using Mono.Debugging.Soft; + +namespace GodotDebugSession +{ + public class GodotDebuggerSession : SoftDebuggerSession + { + private bool _attached; + private NetworkStream _godotRemoteDebuggerStream; + + private class GodotMessagingLogger : GodotTools.IdeMessaging.ILogger + { + public void LogDebug(string message) => Logger.Log(message); + + public void LogInfo(string message) => Logger.Log(message); + + public void LogWarning(string message) => Logger.Log(message); + + public void LogError(string message) => Logger.LogError(message); + + public void LogError(string message, Exception e) => Logger.LogError(message, e); + } + + protected override async void OnRun(DebuggerStartInfo startInfo) + { + var godotStartInfo = (GodotDebuggerStartInfo)startInfo; + + switch (godotStartInfo.ExecutionType) + { + case ExecutionType.PlayInEditor: + { + try + { + _attached = false; + StartListening(godotStartInfo, out var assignedDebugPort); + + string host = "127.0.0.1"; + string godotProjectDir = startInfo.WorkingDirectory; + + using (var messagingClient = new GodotTools.IdeMessaging.Client( + identity: "VSCodeGodotDebugSession", godotProjectDir, + new GodotMessageHandler(), new GodotMessagingLogger())) + { + messagingClient.Start(); + await messagingClient.AwaitConnected(); + var response = await messagingClient.SendRequest( + new DebugPlayRequest {DebuggerHost = host, DebuggerPort = assignedDebugPort}); + + if (response.Status != GodotTools.IdeMessaging.MessageStatus.Ok) + { + Logger.Log("Debug play request failed."); + Exit(); + // ReSharper disable once RedundantJumpStatement + return; + } + } + + // TODO: Read the editor player stdout and stderr somehow + } + catch (Exception e) + { + Logger.LogError(e); + } + + break; + } + case ExecutionType.Launch: + { + try + { + _attached = false; + StartListening(godotStartInfo, out var assignedDebugPort); + + // Listener to replace the Godot editor remote debugger. + // We use it to notify the game when assemblies should be reloaded. + var remoteDebugListener = new TcpListener(IPAddress.Any, 0); + remoteDebugListener.Start(); + _ = remoteDebugListener.AcceptTcpClientAsync().ContinueWith(OnGodotRemoteDebuggerConnected); + + string workingDir = startInfo.WorkingDirectory; + string host = "127.0.0.1"; + int remoteDebugPort = ((IPEndPoint)remoteDebugListener.LocalEndpoint).Port; + + // Launch Godot to run the game and connect to our remote debugger + + var processStartInfo = new ProcessStartInfo(godotStartInfo.GodotExecutablePath) + { + Arguments = $"--path {workingDir} --remote-debug {host}:{remoteDebugPort}", + WorkingDirectory = workingDir, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + // Tells Godot to connect to the mono debugger we just started + processStartInfo.EnvironmentVariables["GODOT_MONO_DEBUGGER_AGENT"] = + "--debugger-agent=transport=dt_socket" + + $",address={host}:{assignedDebugPort}" + + ",server=n"; + + var process = new Process {StartInfo = processStartInfo, EnableRaisingEvents = true}; + process.OutputDataReceived += (sender, e) => + godotStartInfo.ProcessOutputListener.ReceiveStdOut(e.Data); + process.ErrorDataReceived += (sender, e) => + godotStartInfo.ProcessOutputListener.ReceiveStdErr(e.Data); + + try + { + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } + catch (Exception e) + { + Logger.Log($"Godot launch request failed: {e.Message}."); + Exit(); + return; + } + + OnDebuggerOutput(false, $"Godot PID:{process.Id}{Environment.NewLine}"); + } + catch (Exception e) + { + Logger.LogError(e); + } + + break; + } + case ExecutionType.Attach: + { + _attached = true; + StartConnecting(godotStartInfo); + break; + } + default: + throw new NotImplementedException(godotStartInfo.ExecutionType.ToString()); + } + } + + private async Task OnGodotRemoteDebuggerConnected(Task task) + { + var tcpClient = task.Result; + _godotRemoteDebuggerStream = tcpClient.GetStream(); + byte[] buffer = new byte[1000]; + while (tcpClient.Connected) + { + // There is no library to decode this messages, so + // we just pump buffer so it doesn't go out of memory + var readBytes = await _godotRemoteDebuggerStream.ReadAsync(buffer, 0, buffer.Length); + } + } + + protected override void OnExit() + { + if (_attached) + base.OnDetach(); + else + base.OnExit(); + } + } +} diff --git a/GodotDebugSession/GodotDebuggerStartInfo.cs b/GodotDebugSession/GodotDebuggerStartInfo.cs new file mode 100644 index 0000000..d563c10 --- /dev/null +++ b/GodotDebugSession/GodotDebuggerStartInfo.cs @@ -0,0 +1,33 @@ +using Mono.Debugging.Soft; + +namespace GodotDebugSession +{ + public class GodotDebuggerStartInfo : SoftDebuggerStartInfo + { + public string GodotExecutablePath { get; } + public ExecutionType ExecutionType { get; } + public IProcessOutputListener ProcessOutputListener { get; } + + public GodotDebuggerStartInfo(ExecutionType executionType, string godotExecutablePath, + IProcessOutputListener processOutputListener, SoftDebuggerRemoteArgs softDebuggerConnectArgs) : + base(softDebuggerConnectArgs) + { + ExecutionType = executionType; + GodotExecutablePath = godotExecutablePath; + ProcessOutputListener = processOutputListener; + } + } + + public enum ExecutionType + { + PlayInEditor, + Launch, + Attach + } + + public interface IProcessOutputListener + { + void ReceiveStdOut(string data); + void ReceiveStdErr(string data); + } +} diff --git a/GodotDebugSession/GodotMessageHandler.cs b/GodotDebugSession/GodotMessageHandler.cs new file mode 100644 index 0000000..211d469 --- /dev/null +++ b/GodotDebugSession/GodotMessageHandler.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using GodotTools.IdeMessaging; +using GodotTools.IdeMessaging.Requests; + +namespace GodotDebugSession +{ + public class GodotMessageHandler : ClientMessageHandler + { + protected override Task HandleOpenFile(OpenFileRequest request) + { + return Task.FromResult(new OpenFileResponse {Status = MessageStatus.RequestNotSupported}); + } + } +} diff --git a/GodotDebugSession/Logger.cs b/GodotDebugSession/Logger.cs new file mode 100644 index 0000000..09b6c74 --- /dev/null +++ b/GodotDebugSession/Logger.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; +using System.Reflection; +using System.Text; + +namespace GodotDebugSession +{ + static class Logger + { + private static string ThisAppPath => Assembly.GetExecutingAssembly().Location; + private static string ThisAppPathWithoutExtension => Path.ChangeExtension(ThisAppPath, null); + + private static readonly string LogPath = $"{ThisAppPathWithoutExtension}.log"; + internal static readonly string NewLogPath = $"{ThisAppPathWithoutExtension}.new.log"; + + private static StreamWriter NewWriter() => new StreamWriter(LogPath, append: true, Encoding.UTF8); + + private static void Log(StreamWriter writer, string message) + { + writer.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}: {message}"); + } + + public static void Log(string message) + { + using (var writer = NewWriter()) + { + Log(writer, message); + } + } + + public static void LogError(string message) + { + using (var writer = NewWriter()) + { + Log(writer, message); + } + } + + public static void LogError(string message, Exception ex) + { + using (var writer = NewWriter()) + { + Log(writer, $"{message}\n{ex}"); + } + } + + public static void LogError(Exception ex) + { + using (var writer = NewWriter()) + { + Log(writer, ex.ToString()); + } + } + } +} diff --git a/GodotDebugSession/MonoDebug.cs b/GodotDebugSession/MonoDebug.cs new file mode 100644 index 0000000..d5d16c8 --- /dev/null +++ b/GodotDebugSession/MonoDebug.cs @@ -0,0 +1,19 @@ +using System; + +namespace VSCodeDebug +{ + // Placeholder for VSCodeDebug.Program in vscode-mono-debug + static class Program + { + public static void Log(bool predicate, string format, params object[] data) + { + if (predicate) + Log(format, data); + } + + public static void Log(string format, params object[] data) + { + Console.Error.WriteLine(format, data); + } + } +} diff --git a/GodotDebugSession/Program.cs b/GodotDebugSession/Program.cs new file mode 100644 index 0000000..9c763ae --- /dev/null +++ b/GodotDebugSession/Program.cs @@ -0,0 +1,25 @@ +using System; + +namespace GodotDebugSession +{ + static class Program + { + static void Main() + { + try + { + Logger.Log("GodotDebugSession: Starting debug session..."); + + new GodotDebugSession() + .Start(Console.OpenStandardInput(), Console.OpenStandardOutput()) + .Wait(); + + Logger.Log("GodotDebugSession: Debug session terminated."); + } + catch (Exception ex) + { + Logger.LogError(ex); + } + } + } +} diff --git a/GodotDebugSession/Properties/AssemblyInfo.cs b/GodotDebugSession/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2666f6f --- /dev/null +++ b/GodotDebugSession/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GodotDebugSession")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GodotDebugSession")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("52059825-2921-4EFB-B6C3-350303F587EE")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..feeb8a8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Godot Engine + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c534b40 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +SOLUTION_DIR = "./GodotDebugSession/" + +GODOT_DEBUG_SESSION_RELEASE = "$(SOLUTION_DIR)/bin/Release/GodotDebugSession.exe" +GODOT_DEBUG_SESSION_DEBUG = "$(SOLUTION_DIR)/bin/Debug/GodotDebugSession.exe" + +all: vsix + @echo "vsix created" + +vsix: build + ./node_modules/.bin/vsce package + +publish: build + ./node_modules/.bin/vsce publish + +build: $(GODOT_DEBUG_SESSION_RELEASE) tsc + @echo "build finished" + +build-debug: $(GODOT_DEBUG_SESSION_DEBUG) tsc + @echo "build finished" + +tsc: + node_modules/.bin/tsc -p ./ + +$(GODOT_DEBUG_SESSION_RELEASE): + msbuild /p:Configuration=Release $(SOLUTION_DIR)/GodotDebugSession.sln + +$(GODOT_DEBUG_SESSION_DEBUG): + msbuild /p:Configuration=Debug $(SOLUTION_DIR)/GodotDebugSession.sln + +clean: + msbuild /t:Clean $(SOLUTION_DIR)/GodotDebugSession.sln diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa56575 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# C# Tools for Godot + +Debugger and utilities for working with Godot C# projects in VSCode. + +## Features + +- Debugging. +- Launch a game directly from the Godot editor. +- Code completion for Node paths, Input actions, Resource paths, Scene paths and Signal names. + +## Requirements + +Godot 3.2.3 or 4.0 or greater is required (both versions are currently unreleaded). + +A Godot editor instance must be running for the project in order for code completion to work. + +## Screenshots + +![Debugging](images/debugging.png) + +![Nodes code completion](images/codeCompletionNodePaths.png) + +![Input actions code completion](images/codeCompletionInputActions.png) + +![Signals code completion](images/codeCompletionSignals.png) diff --git a/images/codeCompletionInputActions.png b/images/codeCompletionInputActions.png new file mode 100644 index 0000000000000000000000000000000000000000..fe7e580b618e6d63922657cd0d03d3f0bd34871b GIT binary patch literal 16673 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 literal 0 HcmV?d00001 diff --git a/images/codeCompletionNodePaths.png b/images/codeCompletionNodePaths.png new file mode 100644 index 0000000000000000000000000000000000000000..d62639121dbb3950a9f33f9dc3875f45f278ad26 GIT binary patch literal 12869 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 G*Z&7XrK&Xm literal 0 HcmV?d00001 diff --git a/images/debugging.png b/images/debugging.png new file mode 100644 index 0000000000000000000000000000000000000000..8d270c6458ef27f279d7f2df0399f2d17b80d236 GIT binary patch literal 51440 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 F2>@VOj?Mr8 literal 0 HcmV?d00001 diff --git a/package.json b/package.json new file mode 100644 index 0000000..e0101b5 --- /dev/null +++ b/package.json @@ -0,0 +1,155 @@ +{ + "name": "godot-csharp-vscode", + "displayName": "C# Tools for Godot", + "description": "Debugger and utilities for working with Godot C# projects", + "version": "0.1.0", + "publisher": "neikeq", + "license": "MIT", + "repository": { + "url": "https://github.com/godotengine/godot-csharp-vscode" + }, + "engines": { + "vscode": "^1.28.0" + }, + "categories": [ + "Debuggers", + "Other" + ], + "activationEvents": [ + "workspaceContains:project.godot", + "onDebugResolve:godot" + ], + "main": "./out/extension.js", + "scripts": { + "vscode:prepublish": "make build", + "compile": "make build", + "compile-tsc": "make tsc", + "watch": "tsc -watch -p ./" + }, + "dependencies": { + "chokidar": "^3.4.0", + "promise-socket": "^6.0.3", + "vscode-debugprotocol": "^1.40.0" + }, + "extensionDependencies": [ + "ms-dotnettools.csharp", + "ms-vscode.mono-debug" + ], + "devDependencies": { + "@types/glob": "^7.1.1", + "@types/mocha": "^5.2.6", + "@types/node": "^10.12.21", + "@types/vscode": "^1.28.0", + "glob": "^7.1.4", + "mocha": "^6.1.4", + "typescript": "^3.3.1", + "tslint": "^5.12.1", + "vsce": "^1.20.0", + "make": "^0.8.1" + }, + "breakpoints": [ + { + "language": "csharp" + }, + { + "language": "fsharp" + } + ], + "contributes": { + "debuggers": [ + { + "type": "godot-mono", + "label": "C# Godot", + "enableBreakpointsFor": { + "languageIds": [ + "csharp", + "fsharp" + ] + }, + "program": "./GodotDebugSession/bin/Release/GodotDebugSession.exe", + "osx": { + "runtime": "mono" + }, + "linux": { + "runtime": "mono" + }, + "initialConfigurations": [ + { + "name": "Play in Editor", + "type": "godot-mono", + "mode": "playInEditor", + "request": "launch" + }, + { + "name": "Launch", + "type": "godot-mono", + "request": "launch", + "mode": "executable", + "executable": "${workspaceRoot}/Godot.exe", + "executableArguments": [ + "--path", + "${workspaceRoot}" + ] + }, + { + "name": "Attach", + "type": "godot-mono", + "request": "attach", + "address": "localhost", + "port": 23685 + } + ], + "configurationAttributes": { + "playInEditor": {}, + "launch": { + "properties": { + "executable": { + "type": "string", + "description": "Path to the Godot executable" + }, + "mode": { + "type": "string", + "enum": [ + "playInEditor", + "executable" + ], + "enumDescriptions": [ + "Launches the project in the Godot editor for debugging", + "Launches the project with the specified executable for debugging" + ], + "description": "Launch mode" + }, + "executableArguments": { + "type": "array", + "description": "Arguments to append after the executable" + }, + "env": { + "type": "object", + "description": "Environment variables", + "default": null + } + } + }, + "attach": { + "required": [ + "address", + "port" + ], + "properties": { + "address": { + "type": "string", + "description": "Debugger address to attach to.", + "default": "localhost" + }, + "port": { + "type": "number", + "description": "Debugger port to attach to.", + "default": 23685 + } + } + } + } + } + ] + } +} diff --git a/src/completion-provider.ts b/src/completion-provider.ts new file mode 100644 index 0000000..75dd283 --- /dev/null +++ b/src/completion-provider.ts @@ -0,0 +1,134 @@ +import * as vscode from 'vscode'; +import { Client, MessageStatus } from './godot-tools-messaging/client'; + +enum CompletionKind { + InputActions = 0, + NodePaths, + ResourcePaths, + ScenePaths, + ShaderParams, + Signals, + ThemeColors, + ThemeConstants, + ThemeFonts, + ThemeStyles +} + +const qualifiedNameRegex = /(?:((?!\d)\w+(?:[\n\r\s]*\.[\n\r\s]*(?!\d)\w+)*)[\n\r\s]*\.[\n\r\s]*)?((?!\d)\w+)/; +const genericPartRegex = new RegExp('(?:[\\n\\r\\s]*<[\\n\\r\\s]*' + qualifiedNameRegex.source + '[\\n\\r\\s]*>)?'); + +const getNodeRegex = new RegExp(/\bGetNode/.source + genericPartRegex.source + /[\n\r\s]*\([\n\r\s]*(?"(?\w*))?$/.source); +const inputActionRegex = /\b(IsActionPressed|IsActionJustPressed|IsActionJustReleased|GetActionStrength|ActionPress|ActionRelease)[\n\r\s]*\([\n\r\s]*(?"(?\w*))?$/; +const resourcePathRegex = new RegExp(/\b(GD[\n\r\s]*\.[\n\r\s]*Load|ResourceLoader[\n\r\s]*\.[\n\r\s]*Load)/.source + genericPartRegex.source + /[\n\r\s]*\([\n\r\s]*(?"(?\w*))?/.source); +const scenePathRegex = /\bChangeScene[\n\r\s]*\([\n\r\s]*(?"(?\w*))?/; +const signalsRegex = /\b(Connect|Disconnect|IsConnected|EmitSignal)[\n\r\s]*\([\n\r\s]*(?"(?\w*))?$/; +const toSignalRegex = new RegExp(/\bToSignal[\n\r\s]*\([\n\r\s]*/.source + qualifiedNameRegex.source + /[\n\r\s]*,[\n\r\s]*(?"(?\w*))?$/.source); + +export class GodotCompletionProvider implements vscode.CompletionItemProvider { + client: Client; + + constructor(client: Client) { + this.client = client; + } + + getPrefixLines(document: vscode.TextDocument, position: vscode.Position): [string, number] { + let result: string = ''; + if (position.line > 1) { + result += document.lineAt(position.line - 2).text + '\n'; + } + if (position.line > 0) { + result += document.lineAt(position.line - 1).text + '\n'; + } + let extraLength = result.length; + result += document.lineAt(position.line).text; + return [result, extraLength + position.character]; + } + + provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext): vscode.ProviderResult { + if (!this.client.isConnected() || (!this.client.peer?.isConnected ?? false)) { + return undefined; + } + + let filePath = document.uri.fsPath; + + let [lines, character] = this.getPrefixLines(document, position); + let linePrefix = lines.substr(0, character); + let lineSuffix = lines.substr(character); + + let genericStringItemImpl = (regexes: RegExp[] | RegExp, completionKind: CompletionKind) => + this.genericStringItemImpl(regexes, completionKind, filePath, token, context, linePrefix, lineSuffix); + + return genericStringItemImpl(getNodeRegex, CompletionKind.NodePaths) || + genericStringItemImpl(inputActionRegex, CompletionKind.InputActions) || + genericStringItemImpl(resourcePathRegex, CompletionKind.ResourcePaths) || + genericStringItemImpl(scenePathRegex, CompletionKind.ScenePaths) || + genericStringItemImpl([signalsRegex, toSignalRegex], CompletionKind.Signals) || + undefined; + } + + genericStringItemImpl(regexes: RegExp[] | RegExp, completionKind: CompletionKind, filePath: string, + token: vscode.CancellationToken, context: vscode.CompletionContext, + linePrefix: string, lineSuffix: string): vscode.ProviderResult { + let isMatch = false; + let startsWithQuote = false; + let endsWithQuote = false; + let partialString = ''; + + let match; + + if (regexes instanceof RegExp) { + regexes = [regexes]; + } + + for (let regex in regexes) { + if ((match = linePrefix.match(regexes[regex]))) { + isMatch = true; + if (match.groups?.withQuote !== undefined) { + startsWithQuote = true; + endsWithQuote = lineSuffix.startsWith('"'); + partialString = match.groups.partialString || ''; + } + break; + } + } + + if (isMatch) { + return new Promise(async (resolve, reject) => { + let request = { + Kind: completionKind as number, + ScriptFile: filePath + }; + + const response = await this.client.peer?.sendRequest('CodeCompletion', JSON.stringify(request)); + + if (response === undefined || response.status !== MessageStatus.Ok) { + if (response) { + console.error(`Code completion request failed with status: ${response?.status}`); + } else { + console.error('Code completion request failed'); + } + reject(); + return; + } + + let responseObj = JSON.parse(response.body); + let suggestions: string[] = responseObj.Suggestions; + + let tweak = (str: string) => { + if (startsWithQuote && str.startsWith('"')) { + return str.substr(1, str.length - (endsWithQuote ? 2 : 1)); + } + return str; + }; + + resolve(suggestions.filter(suggestion => suggestion.startsWith('"' + partialString)).map(suggestion => { + let item = new vscode.CompletionItem(suggestion, vscode.CompletionItemKind.Value); + item.insertText = tweak(suggestion); + return item; + })); + }); + } + + return undefined; + } +} diff --git a/src/extension.ts b/src/extension.ts new file mode 100644 index 0000000..9b41fff --- /dev/null +++ b/src/extension.ts @@ -0,0 +1,178 @@ +import * as vscode from 'vscode'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { Client, Peer, MessageContent, MessageStatus, ILogger, IMessageHandler } from './godot-tools-messaging/client'; +import * as completion_provider from './completion-provider'; +import * as path from 'path'; +import * as fs from 'fs'; + +let client: Client; + +class Logger implements ILogger { + logDebug(message: string): void { + console.debug(message); + } + logInfo(message: string): void { + console.info(message); + } + logWarning(message: string): void { + console.warn(message); + } + logError(message: string): void; + logError(message: string, e: Error): void; + logError(message: any, e?: any) { + console.error(message, e); + } +} + +type RequestHandler = (peer: Peer, content: MessageContent) => Promise; + +class MessageHandler implements IMessageHandler { + requestHandlers = new Map(); + + constructor() { + this.requestHandlers.set('OpenFile', this.handleOpenFile); + } + + async handleRequest(peer: Peer, id: string, content: MessageContent, logger: ILogger): Promise { + let handler = this.requestHandlers.get(id); + if (handler === undefined) { + logger.logError(`Received unknown request: ${id}`); + return new MessageContent(MessageStatus.RequestNotSupported, 'null'); + } + + let response = await handler(peer, content); + return new MessageContent(response.status, JSON.stringify(response)); + } + + async handleOpenFile(peer: Peer, content: MessageContent): Promise { + // Not used yet by Godot as it doesn't brind the VSCode window to foreground + + let request = JSON.parse(content.body); + + let file: string | undefined = request.File; + let line: number | undefined = request.Line; + let column: number | undefined = request.Column; + + if (file === undefined) { + return new MessageContent(MessageStatus.InvalidRequestBody, 'null'); + } + + let openPath = vscode.Uri.parse(file); + vscode.workspace.openTextDocument(openPath).then(doc => { + vscode.window.showTextDocument(doc); + + if (line !== undefined) { + line -= 1; + + if (column !== undefined) { + column -= 1; + } + + let editor = vscode.window.activeTextEditor; + + if (editor !== undefined) { + const position = editor.selection.active; + + let newPosition = position.with(line, column); + let range = new vscode.Range(newPosition, newPosition); + editor.selection = new vscode.Selection(range.start, range.end); + editor.revealRange(range); + } + } + }); + + return new MessageContent(MessageStatus.Ok, '{}'); + } +} + +export function activate(context: vscode.ExtensionContext) { + let godotProjectDir = vscode.workspace.workspaceFolders?.find((workspaceFolder) => { + let rootPath = workspaceFolder.uri.fsPath; + let godotProjectFile = path.join(rootPath, 'project.godot'); + + return fs.existsSync(godotProjectFile); + })!.uri.path; + + client = new Client('VisualStudioCode', godotProjectDir, new MessageHandler(), new Logger()); + client.start(); + + const debugConfigProvider = vscode.debug.registerDebugConfigurationProvider( + 'godot-mono', + new GodotMonoDebugConfigProvider() + ); + + // There's no way to extend OmniSharp without having to provide our own language server. + // That will be a big task so for now we will provide this basic completion provider. + const codeCompletionProvider = vscode.languages.registerCompletionItemProvider( + 'csharp', new completion_provider.GodotCompletionProvider(client), + // Trigger characters + '(', '"', ',', ' ' + ); + + context.subscriptions.push( + debugConfigProvider, + codeCompletionProvider + ); +} + +export function deactivate() { + client.dispose(); +} + +class GodotMonoDebugConfigProvider implements vscode.DebugConfigurationProvider { + public async resolveDebugConfiguration( + folder: vscode.WorkspaceFolder | undefined, + debugConfiguration: vscode.DebugConfiguration, + token?: vscode.CancellationToken + ): Promise { + if (!debugConfiguration.__exceptionOptions) { + debugConfiguration.__exceptionOptions = convertToExceptionOptions(getModel()); + } + + debugConfiguration['godotProjectDir'] = folder?.uri.fsPath; + + return debugConfiguration; + } +} + +// Too lazy so we're re-using mono-debug extension settings for now... +const configuration = vscode.workspace.getConfiguration('mono-debug'); + +type ExceptionConfigurations = { [exception: string]: DebugProtocol.ExceptionBreakMode; }; + +const DEFAULT_EXCEPTIONS: ExceptionConfigurations = { + 'System.Exception': 'never', + 'System.SystemException': 'never', + 'System.ArithmeticException': 'never', + 'System.ArrayTypeMismatchException': 'never', + 'System.DivideByZeroException': 'never', + 'System.IndexOutOfRangeException': 'never', + 'System.InvalidCastException': 'never', + 'System.NullReferenceException': 'never', + 'System.OutOfMemoryException': 'never', + 'System.OverflowException': 'never', + 'System.StackOverflowException': 'never', + 'System.TypeInitializationException': 'never' +}; + +function getModel(): ExceptionConfigurations { + let model = DEFAULT_EXCEPTIONS; + if (configuration) { + const exceptionOptions = configuration.get('exceptionOptions'); + if (exceptionOptions) { + model = exceptionOptions; + } + } + return model; +} + +function convertToExceptionOptions(model: ExceptionConfigurations): DebugProtocol.ExceptionOptions[] { + const exceptionItems: DebugProtocol.ExceptionOptions[] = []; + for (let exception in model) { + exceptionItems.push({ + path: [{ names: [exception] }], + breakMode: model[exception] + }); + } + return exceptionItems; +} diff --git a/src/godot-tools-messaging/client.ts b/src/godot-tools-messaging/client.ts new file mode 100644 index 0000000..1d963d8 --- /dev/null +++ b/src/godot-tools-messaging/client.ts @@ -0,0 +1,556 @@ +import * as net from 'net'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as chokidar from 'chokidar'; +import PromiseSocket from 'promise-socket'; +import { Disposable } from 'vscode'; +import { throws } from 'assert'; + +async function timeout(ms: number) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + +class CustomSocket extends PromiseSocket { + buffer: string = ''; + + constructor() { + super(); + this.setEncoding('utf-8'); + } + + async readLine(): Promise { + let chunk: Buffer | string | undefined; + + do { + if (chunk !== undefined) { + if (chunk instanceof Buffer) { + this.buffer += chunk.toString('utf-8'); + } else { + this.buffer += chunk; + } + } + + let indexOfLineBreak = this.buffer.indexOf('\n'); + + if (indexOfLineBreak >= 0) { + let line = this.buffer.substring(0, indexOfLineBreak); + this.buffer = this.buffer.substring(indexOfLineBreak + 1); + return line; + } + } + while ((chunk = await this.read()) !== undefined); + + return undefined; + } + + async writeLine(line: string): Promise { + return await this.write(line + '\n', 'utf-8'); + } +} + +type ResponseListener = (response: MessageContent) => void; + +export class Peer implements Disposable { + static readonly protocolVersionMajor: number = 1; + static readonly protocolVersionMinor: number = 1; + static readonly protocolVersionRevision: number = 0; + + static readonly clientHandshakeName: string = 'GodotIdeClient'; + static readonly serverHandshakeName: string = 'GodotIdeServer'; + + socket: CustomSocket; + handshake: IHandshake; + messageHandler: IMessageHandler; + logger: ILogger; + + remoteIdentity?: string; + isConnected: boolean = false; + + requestAwaiterQueues = new Map(); + + constructor(socket: CustomSocket, handshake: IHandshake, messageHandler: IMessageHandler, logger: ILogger) { + this.socket = socket; + this.handshake = handshake; + this.messageHandler = messageHandler; + this.logger = logger; + } + + dispose() { + this.socket?.destroy(); + } + + async process(): Promise { + let decoder = new MessageDecoder(); + + let messageLine: string | undefined; + while ((messageLine = await this.socket.readLine()) !== undefined) { + let [state, msg] = decoder.decode(messageLine); + + if (state === MessageDecoderState.Decoding) { + continue; // Not finished decoding yet + } + + if (state === MessageDecoderState.Errored || msg === undefined) { + this.logger.logError(`Received message line with invalid format: ${messageLine}`); + continue; + } + + this.logger.logDebug(`Received message: ${msg.toString()}`); + + if (msg.kind === MessageKind.Request) { + let responseContent = await this.messageHandler + .handleRequest(this, msg.id, msg.content, this.logger); + await this.writeMessage(new Message(MessageKind.Response, msg.id, responseContent)); + } else if (msg.kind === MessageKind.Response) { + let responseAwaiter: ResponseListener | undefined; + let queue = this.requestAwaiterQueues.get(msg.id); + if (queue === undefined || (responseAwaiter = queue.shift()) === undefined) { + this.logger.logError(`Received unexpected response: ${msg.id}`); + return; + } + responseAwaiter(msg.content); + } else { + this.logger.logError(`Invalid message kind ${MessageKind[msg.kind]}`); + return; + } + } + + this.isConnected = false; + } + + async doHandshake(identity: string): Promise { + if (await this.socket.writeLine(this.handshake.getHandshakeLine(identity)) === 0) { + this.logger.logError('Could not write handshake'); + return false; + } + + let handshakeReceived = false; + + let readHandshakeImpl = async (): Promise => { + let result = await this.socket.readLine(); + handshakeReceived = true; + return result; + }; + let readHandshakePromise = readHandshakeImpl(); + + let maybePeerHandshake = await Promise.race([readHandshakePromise, timeout(8000)]); + + if (!handshakeReceived || maybePeerHandshake === undefined || typeof maybePeerHandshake !== 'string') { + this.logger.logError('Timeout waiting for the client handshake'); + return false; + } + + let peerHandshake = maybePeerHandshake as string; + + let [valid, remoteIdentity] = this.handshake.isValidPeerHandshake(peerHandshake, this.logger); + + if (!valid || remoteIdentity === undefined) { + this.logger.logError('Received invalid handshake: ' + peerHandshake); + return false; + } + + this.remoteIdentity = remoteIdentity; + + this.isConnected = true; + + this.logger.logInfo('Peer connection started'); + + return true; + } + + private async writeMessage(message: Message): Promise { + this.logger.logDebug(`Sending message: ${message.toString()}`); + + let bodyLineCount = message.content.body.match(/[^\n]*\n[^\n]*/gi)?.length ?? 0; + bodyLineCount += 1; // Extra line break at the end + + let messageLines: string = ''; + + messageLines += MessageKind[message.kind] + '\n'; + messageLines += message.id + '\n'; + messageLines += MessageStatus[message.content.status] + '\n'; + messageLines += bodyLineCount + '\n'; + messageLines += message.content.body + '\n'; + + return await this.socket.writeLine(messageLines); + } + + async sendRequest(id: string, body: string): Promise { + let responseListener: ResponseListener; + + let msg = new Message(MessageKind.Request, id, new MessageContent(MessageStatus.Ok, body)); + let written = await this.writeMessage(msg) > 0; + + if (!written) { + return undefined; + } + + return new Promise((resolve) => { + let queue = this.requestAwaiterQueues.get(id); + + if (queue === undefined) { + queue = []; + this.requestAwaiterQueues.set(id, queue); + } + + responseListener = (response) => { + resolve(response); + }; + queue.push(responseListener); + }); + } +} + +export class Client implements Disposable { + identity: string; + projectMetadataDir: string; + metaFilePath: string; + messageHandler: IMessageHandler; + logger: ILogger; + + fsWatcher?: chokidar.FSWatcher; + metaFileModifiedTime?: Date; + + metadata?: GodotIdeMetadata; + isDisposed: boolean = false; + + peer?: Peer; + + constructor(identity: string, godotProjectDir: string, messageHandler: IMessageHandler, logger: ILogger) { + this.identity = identity; + this.messageHandler = messageHandler; + this.logger = logger; + + this.projectMetadataDir = path.join(godotProjectDir, '.mono', 'metadata'); + + this.metaFilePath = path.join(this.projectMetadataDir, GodotIdeMetadata.defaultFileName); + } + + isConnected(): boolean { + return !this.isDisposed && this.peer !== undefined && this.peer.isConnected; + } + + dispose() { + this.isDisposed = true; + this.fsWatcher?.close(); + this.peer?.dispose(); + } + + start(): void { + this.startWatching(); + + if (this.isDisposed || this.isConnected()) { + return; + } + + if (!fs.existsSync(this.metaFilePath)) { + this.logger.logInfo('There is no Godot Ide Server running'); + return; + } + + // Check to store the modified time. Needed for the onMetaFileChanged check to work. + if (!this.metaFileModifiedTimeChanged()) { + return; + } + + const metadata = this.readMetadataFile(); + + if (metadata !== undefined && metadata !== this.metadata) { + this.metadata = metadata; + this.connectToServer(); + } + } + + async connectToServer(): Promise { + if (this.peer !== undefined && this.peer.isConnected) { + this.logger.logError('Attempted to connect to Godot Ide Server again when already connected'); + return; + } + + const socket = new CustomSocket(); + + this.logger.logInfo('Connecting to Godot Ide Server'); + + await socket.connect(this.metadata!.port, 'localhost'); + + this.logger.logInfo('Connection open with Godot Ide Server'); + + this.peer = new Peer(socket, new ClientHandshake(), this.messageHandler, this.logger); + + if (!await this.peer.doHandshake(this.identity)) { + this.logger.logError('Handshake failed'); + return; + } + + await this.peer.process(); + + this.logger.logInfo('Connection closed with Ide Client'); + } + + startWatching(): void { + this.fsWatcher = chokidar.watch(this.metaFilePath); + this.fsWatcher.on('add', path => this.onMetaFileChanged()); + this.fsWatcher.on('change', path => this.onMetaFileChanged()); + this.fsWatcher.on('unlink', path => this.onMetaFileDeleted()); + } + + metaFileModifiedTimeChanged(): boolean { + const stats = fs.statSync(this.metaFilePath); + if (this.metaFileModifiedTime !== undefined && + stats.mtime.valueOf() === this.metaFileModifiedTime.valueOf()) { + return false; + } + this.metaFileModifiedTime = stats.mtime; + return true; + } + + onMetaFileChanged(): void { + if (this.isDisposed) { + return; + } + + if (!fs.existsSync(this.metaFilePath)) { + return; + } + + // Check the modified time to discard some irrelevant changes + if (!this.metaFileModifiedTimeChanged()) { + return; + } + + const metadata = this.readMetadataFile(); + + if (metadata !== undefined && metadata !== this.metadata) { + this.metadata = metadata; + this.connectToServer(); + } + } + + onMetaFileDeleted(): void { + if (this.isConnected() || !fs.existsSync(this.metaFilePath)) { + return; + } + + const metadata = this.readMetadataFile(); + + if (metadata !== undefined) { + this.metadata = metadata; + this.connectToServer(); + } + } + + readMetadataFile(): GodotIdeMetadata | undefined { + const buffer = fs.readFileSync(this.metaFilePath); + const metaFileContent = buffer.toString('utf-8'); + const lines = metaFileContent.split('\n'); + + if (lines.length < 2) { + return undefined; + } + + const port: number = parseInt(lines[0]); + const editorExecutablePath = lines[1]; + + if (isNaN(port)) { + return undefined; + } + + return new GodotIdeMetadata(port, editorExecutablePath); + } +} + +class GodotIdeMetadata { + port: number; + editorExecutablePath: string; + + static readonly defaultFileName = 'ide_messaging_meta.txt'; + + constructor(port: number, editorExecutablePath: string) { + this.port = port; + this.editorExecutablePath = editorExecutablePath; + } + + equals(other: GodotIdeMetadata): boolean { + return this.port === other.port && this.editorExecutablePath === other.editorExecutablePath; + } +} + +function escapeRegex(s: string): string { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); +} + +class ClientHandshake implements IHandshake { + readonly clientHandshakeBase: string = `${Peer.clientHandshakeName},Version=${Peer.protocolVersionMajor}.${Peer.protocolVersionMinor}.${Peer.protocolVersionRevision}`; + readonly serverHandshakePattern: RegExp = new RegExp(escapeRegex(Peer.serverHandshakeName) + /,Version=([0-9]+)\.([0-9]+)\.([0-9]+),([_a-zA-Z][_a-zA-Z0-9]{0,63})/.source); + + getHandshakeLine(identity: string): string { + return `${this.clientHandshakeBase},${identity}`; + } + + isValidPeerHandshake(handshake: string, logger: ILogger): [boolean, string | undefined] { + let match = this.serverHandshakePattern.exec(handshake); + + if (match === null) { + return [false, undefined]; + } + + let serverMajor = parseInt(match[1], 10); + if (isNaN(serverMajor) || Peer.protocolVersionMajor !== serverMajor) { + logger.logDebug('Incompatible major version: ' + match[1]); + return [false, undefined]; + } + + let serverMinor = parseInt(match[2], 10); + if (isNaN(serverMinor) || Peer.protocolVersionMinor < serverMinor) { + logger.logDebug('Incompatible minor version: ' + match[2]); + return [false, undefined]; + } + + let serverRevision = parseInt(match[3], 10); + if (isNaN(serverRevision)) { + logger.logDebug('Incompatible revision build: ' + match[3]); + return [false, undefined]; + } + + let identity = match[4]; + + return [true, identity]; + } +} + +interface IHandshake { + getHandshakeLine(identity: string): string; + isValidPeerHandshake(handshake: string, logger: ILogger): [boolean, string | undefined]; +} + +export interface IMessageHandler { + handleRequest(peer: Peer, id: string, content: MessageContent, logger: ILogger): Promise; +} + +export interface ILogger { + logDebug(message: string): void; + logInfo(message: string): void; + logWarning(message: string): void; + logError(message: string): void; + logError(message: string, e: Error): void; +} + +class Message { + kind: MessageKind; + id: string; + content: MessageContent; + + constructor(kind: MessageKind, id: string, content: MessageContent) { + this.kind = kind; + this.id = id; + this.content = content; + } + + toString(): string { + return `${this.kind} | ${this.id}`; + } +} + +enum MessageKind { + Request, + Response +} + +export enum MessageStatus { + Ok, + RequestNotSupported, + InvalidRequestBody +} + +export class MessageContent { + status: MessageStatus; + body: string; + + constructor(status: MessageStatus, body: string) { + this.status = status; + this.body = body; + } +} + +class DecodedMessage { + kind?: MessageKind; + id?: string; + status?: MessageStatus; + body: string = ''; + pendingBodyLines?: number; + + clear(): void { + this.kind = undefined; + this.id = undefined; + this.status = undefined; + this.body = ''; + this.pendingBodyLines = undefined; + } + + toMessage(): Message | undefined { + if (this.kind === undefined || this.id === undefined || this.status === undefined || + this.pendingBodyLines === undefined || this.pendingBodyLines > 0) { + return undefined; + } + + return new Message(this.kind, this.id, new MessageContent(this.status, this.body)); + } +} + +enum MessageDecoderState { + Decoding, + Decoded, + Errored +} + +function tryParseEnumCaseInsensitive(enumObj: T, value: string): T[keyof T] | undefined { + let key = Object.keys(enumObj).find(key => key.toLowerCase() === value.toLowerCase()); + if (key === undefined) { + return undefined; + } + return enumObj[key]; +} + +class MessageDecoder { + readonly decodingMessage: DecodedMessage = new DecodedMessage(); + + decode(messageLine: string): [MessageDecoderState, Message | undefined] { + if (this.decodingMessage.kind === undefined) { + let kind = tryParseEnumCaseInsensitive(MessageKind, messageLine); + if (kind === undefined) { + this.decodingMessage.clear(); + return [MessageDecoderState.Errored, undefined]; + } + this.decodingMessage.kind = kind; + } else if (this.decodingMessage.id === undefined) { + this.decodingMessage.id = messageLine; + } else if (this.decodingMessage.status === undefined) { + let status = tryParseEnumCaseInsensitive(MessageStatus, messageLine); + if (status === undefined) { + this.decodingMessage.clear(); + return [MessageDecoderState.Errored, undefined]; + } + this.decodingMessage.status = status; + } else if (this.decodingMessage.pendingBodyLines === undefined) { + let pendingBodyLines = parseInt(messageLine); + if (isNaN(pendingBodyLines)) { + this.decodingMessage.clear(); + return [MessageDecoderState.Errored, undefined]; + } + this.decodingMessage.pendingBodyLines = pendingBodyLines; + } else { + if (this.decodingMessage.pendingBodyLines > 0) { + this.decodingMessage.body += messageLine + '\n'; + this.decodingMessage.pendingBodyLines -= 1; + } else { + let decodedMessage = this.decodingMessage.toMessage(); + this.decodingMessage.clear(); + return [MessageDecoderState.Decoded, decodedMessage]; + } + } + + return [MessageDecoderState.Decoding, undefined]; + } +} diff --git a/thirdparty/debugger-libs b/thirdparty/debugger-libs new file mode 160000 index 0000000..3266c3e --- /dev/null +++ b/thirdparty/debugger-libs @@ -0,0 +1 @@ +Subproject commit 3266c3e1e740fb4b9d21c8fa7e7a4542f3d65a02 diff --git a/thirdparty/nrefactory b/thirdparty/nrefactory new file mode 160000 index 0000000..0607a4a --- /dev/null +++ b/thirdparty/nrefactory @@ -0,0 +1 @@ +Subproject commit 0607a4ad96ebdd16817e47dcae85b1cfcb5b5bf5 diff --git a/thirdparty/vscode-mono-debug b/thirdparty/vscode-mono-debug new file mode 160000 index 0000000..84de36b --- /dev/null +++ b/thirdparty/vscode-mono-debug @@ -0,0 +1 @@ +Subproject commit 84de36ba8ba4fa927844bfcc2595bd9e05da076c diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e2110c5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": [ + "es6" + ], + "sourceMap": true, + "rootDir": "src", + "strict": true, /* enable all strict type-checking options */ + /* Additional Checks */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + }, + "exclude": [ + "node_modules", + ".vscode-test", + "thirdparty/vscode-mono-debug", + ] +} diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..c81ff28 --- /dev/null +++ b/tslint.json @@ -0,0 +1,15 @@ +{ + "rules": { + "no-string-throw": true, + "no-unused-expression": true, + "no-duplicate-variable": true, + "curly": true, + "class-name": true, + "semicolon": [ + true, + "always" + ], + "triple-equals": true + }, + "defaultSeverity": "warning" +}