mirror of
https://github.com/godotengine/godot-monodevelop-addin.git
synced 2025-12-31 17:48:12 +03:00
Initial commit
This commit is contained in:
356
.gitignore
vendored
Normal file
356
.gitignore
vendored
Normal file
@@ -0,0 +1,356 @@
|
||||
# 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/
|
||||
|
||||
23
GodotAddin.sln
Normal file
23
GodotAddin.sln
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotAddin", "GodotAddin\GodotAddin.csproj", "{EE86BFF1-F6B6-42AA-9121-EE2FAC123A12}"
|
||||
EndProject
|
||||
Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "GodotTools.IdeConnection", "GodotTools.IdeConnection\GodotTools.IdeConnection.csproj", "{12E66B0C-89B7-4E95-AE75-7E92A4B90EB9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{EE86BFF1-F6B6-42AA-9121-EE2FAC123A12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EE86BFF1-F6B6-42AA-9121-EE2FAC123A12}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EE86BFF1-F6B6-42AA-9121-EE2FAC123A12}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EE86BFF1-F6B6-42AA-9121-EE2FAC123A12}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{12E66B0C-89B7-4E95-AE75-7E92A4B90EB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{12E66B0C-89B7-4E95-AE75-7E92A4B90EB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{12E66B0C-89B7-4E95-AE75-7E92A4B90EB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{12E66B0C-89B7-4E95-AE75-7E92A4B90EB9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
16
GodotAddin/GodotAddin.csproj
Normal file
16
GodotAddin/GodotAddin.csproj
Normal file
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net471</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AddinReference Include="MonoDevelop.Debugger" />
|
||||
<AddinReference Include="MonoDevelop.Debugger.Soft" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MonoDevelop.Addins" Version="0.4.7" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\GodotTools.IdeConnection\GodotTools.IdeConnection.csproj">
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
80
GodotAddin/GodotDebuggerEngine.cs
Normal file
80
GodotAddin/GodotDebuggerEngine.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using Mono.Debugging.Client;
|
||||
using Mono.Debugging.Soft;
|
||||
using MonoDevelop.Core.Execution;
|
||||
using MonoDevelop.Debugger;
|
||||
using MonoDevelop.Ide;
|
||||
|
||||
namespace GodotAddin
|
||||
{
|
||||
public class GodotDebuggerEngine : DebuggerEngineBackend
|
||||
{
|
||||
public GodotDebuggerEngine()
|
||||
{
|
||||
IdeApp.ProjectOperations.EndBuild += OnProjectOperationsEndBuild;
|
||||
}
|
||||
|
||||
private void OnProjectOperationsEndBuild(object sender, MonoDevelop.Projects.BuildEventArgs args)
|
||||
{
|
||||
foreach (var session in DebuggingService.GetSessions())
|
||||
{
|
||||
if (session is GodotDebuggerSession godotSession)
|
||||
godotSession.SendReloadScipts();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanDebugCommand(ExecutionCommand cmd)
|
||||
{
|
||||
return cmd is GodotExecutionCommand;
|
||||
}
|
||||
|
||||
public override DebuggerStartInfo CreateDebuggerStartInfo(ExecutionCommand cmd)
|
||||
{
|
||||
var godotCmd = (GodotExecutionCommand)cmd;
|
||||
var godotProjectPath = godotCmd.GodotProjectPath;
|
||||
|
||||
int attachPort = 23685; // Default if not modified
|
||||
|
||||
// Try read the debugger agent port from the 'project.godot' file
|
||||
if (File.Exists(godotProjectPath))
|
||||
{
|
||||
// [mono] "debugger_agent/port"
|
||||
var regex = new Regex(@"debugger_agent/port=([0-9]+)");
|
||||
foreach (string line in File.ReadAllLines(godotProjectPath))
|
||||
{
|
||||
var match = regex.Match(line);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
attachPort = int.Parse(match.Groups[1].Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SoftDebuggerRemoteArgs args;
|
||||
|
||||
if (godotCmd.ExecutionType == ExecutionType.Launch)
|
||||
args = new SoftDebuggerListenArgs("Godot", IPAddress.Loopback, 0);
|
||||
else
|
||||
args = new SoftDebuggerConnectArgs("Godot", IPAddress.Loopback, attachPort);
|
||||
|
||||
return new GodotDebuggerStartInfo(godotCmd, args)
|
||||
{
|
||||
WorkingDirectory = godotCmd.WorkingDirectory
|
||||
};
|
||||
}
|
||||
|
||||
public override DebuggerSession CreateSession()
|
||||
{
|
||||
return new GodotDebuggerSession();
|
||||
}
|
||||
|
||||
public override bool IsDefaultDebugger(ExecutionCommand cmd)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
199
GodotAddin/GodotDebuggerSession.cs
Normal file
199
GodotAddin/GodotDebuggerSession.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Mono.Debugging.Client;
|
||||
using Mono.Debugging.Soft;
|
||||
using GodotAddin.Utils;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GodotAddin
|
||||
{
|
||||
public class GodotDebuggerSession : SoftDebuggerSession
|
||||
{
|
||||
bool attached;
|
||||
private NetworkStream godotRemoteDebuggerStream;
|
||||
private GodotExecutionCommand GodotCmd;
|
||||
|
||||
public void SendReloadScipts()
|
||||
{
|
||||
switch (GodotCmd.ExecutionType)
|
||||
{
|
||||
case ExecutionType.Launch:
|
||||
GodotVariantEncoder.Encode(
|
||||
new List<GodotVariant> { "reload_scripts" },
|
||||
godotRemoteDebuggerStream
|
||||
);
|
||||
break;
|
||||
case ExecutionType.PlayInEditor:
|
||||
case ExecutionType.Attach:
|
||||
GodotCmd.GodotIdeClient.SendReloadScripts();
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException(GodotCmd.ExecutionType.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private struct ThreadStartArgs
|
||||
{
|
||||
public bool IsStdErr;
|
||||
public StreamReader Stream;
|
||||
}
|
||||
|
||||
private string GetGodotExecutablePath()
|
||||
{
|
||||
if (Settings.AlwaysUseConfiguredExecutable)
|
||||
return Settings.GodotExecutablePath;
|
||||
|
||||
string godotPath = GodotCmd.GodotIdeClient.GodotEditorExecutablePath;
|
||||
|
||||
if (string.IsNullOrEmpty(godotPath) || !File.Exists(godotPath))
|
||||
return Settings.GodotExecutablePath;
|
||||
|
||||
return godotPath;
|
||||
}
|
||||
|
||||
protected override void OnRun(DebuggerStartInfo startInfo)
|
||||
{
|
||||
var godotStartInfo = (GodotDebuggerStartInfo)startInfo;
|
||||
|
||||
GodotCmd = godotStartInfo.GodotCmd;
|
||||
|
||||
switch (GodotCmd.ExecutionType)
|
||||
{
|
||||
case ExecutionType.PlayInEditor:
|
||||
{
|
||||
attached = false;
|
||||
StartListening(godotStartInfo, out var assignedDebugPort);
|
||||
|
||||
string host = "127.0.0.1";
|
||||
|
||||
GodotCmd.GodotIdeClient.SendPlay(host, assignedDebugPort);
|
||||
|
||||
// TODO: Read the editor player stdout and stderr somehow
|
||||
|
||||
break;
|
||||
}
|
||||
case ExecutionType.Launch:
|
||||
{
|
||||
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(GetGodotExecutablePath())
|
||||
{
|
||||
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 = Process.Start(processStartInfo);
|
||||
|
||||
// Listen for StdOut and StdErr
|
||||
|
||||
var stdOutThread = new Thread(OutputReader)
|
||||
{
|
||||
Name = "Godot StandardOutput Reader",
|
||||
IsBackground = true
|
||||
};
|
||||
stdOutThread.Start(new ThreadStartArgs {
|
||||
IsStdErr = false, Stream = process.StandardOutput
|
||||
});
|
||||
|
||||
var stdErrThread = new Thread(OutputReader)
|
||||
{
|
||||
Name = "Godot StandardError Reader",
|
||||
IsBackground = true
|
||||
};
|
||||
stdErrThread.Start(new ThreadStartArgs {
|
||||
IsStdErr = true, Stream = process.StandardError
|
||||
});
|
||||
|
||||
OnDebuggerOutput(false, $"Godot PID:{process.Id}{Environment.NewLine}");
|
||||
|
||||
break;
|
||||
}
|
||||
case ExecutionType.Attach:
|
||||
{
|
||||
attached = true;
|
||||
StartConnecting(godotStartInfo);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new NotImplementedException(GodotCmd.ExecutionType.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnGodotRemoteDebuggerConnected(Task<TcpClient> 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 bool HandleException(Exception ex)
|
||||
{
|
||||
// When we attach to running Mono process it sends us AssemblyLoad, ThreadStart and other
|
||||
// delayed events, problem is, that we send VM_START command back since we have to do that on
|
||||
// every event, but in this case when Mono is sending delayed events when we attach
|
||||
// runtime is not really suspended, hence it's throwing this exceptions, just ignore...
|
||||
if (attached && ex is Mono.Debugger.Soft.VMNotSuspendedException)
|
||||
return true;
|
||||
return base.HandleException(ex);
|
||||
}
|
||||
|
||||
protected override void OnExit()
|
||||
{
|
||||
if (attached)
|
||||
base.OnDetach();
|
||||
else
|
||||
base.OnExit();
|
||||
}
|
||||
|
||||
private void OutputReader(object args)
|
||||
{
|
||||
var startArgs = (ThreadStartArgs)args;
|
||||
|
||||
foreach (string line in startArgs.Stream.EnumerateLines())
|
||||
{
|
||||
try
|
||||
{
|
||||
OnTargetOutput(startArgs.IsStdErr, line + Environment.NewLine);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
GodotAddin/GodotDebuggerStartInfo.cs
Normal file
15
GodotAddin/GodotDebuggerStartInfo.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Mono.Debugging.Soft;
|
||||
|
||||
namespace GodotAddin
|
||||
{
|
||||
public class GodotDebuggerStartInfo : SoftDebuggerStartInfo
|
||||
{
|
||||
public GodotExecutionCommand GodotCmd { get; }
|
||||
|
||||
public GodotDebuggerStartInfo(GodotExecutionCommand godotCmd, SoftDebuggerRemoteArgs softDebuggerConnectArgs) :
|
||||
base(softDebuggerConnectArgs)
|
||||
{
|
||||
GodotCmd = godotCmd;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
GodotAddin/GodotExecutionCommand.cs
Normal file
28
GodotAddin/GodotExecutionCommand.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using MonoDevelop.Core.Execution;
|
||||
|
||||
namespace GodotAddin
|
||||
{
|
||||
public class GodotExecutionCommand : ExecutionCommand
|
||||
{
|
||||
public GodotExecutionCommand(string godotProjectPath, ExecutionType executionType,
|
||||
string workingDirectory, GodotMonoDevelopClient godotIdeClient)
|
||||
{
|
||||
GodotProjectPath = godotProjectPath;
|
||||
ExecutionType = executionType;
|
||||
WorkingDirectory = workingDirectory;
|
||||
GodotIdeClient = godotIdeClient;
|
||||
}
|
||||
|
||||
public string GodotProjectPath { get; }
|
||||
public ExecutionType ExecutionType { get; }
|
||||
public string WorkingDirectory { get; }
|
||||
public GodotMonoDevelopClient GodotIdeClient { get; }
|
||||
}
|
||||
|
||||
public enum ExecutionType
|
||||
{
|
||||
PlayInEditor,
|
||||
Launch,
|
||||
Attach
|
||||
}
|
||||
}
|
||||
97
GodotAddin/GodotMonoDevelopClient.cs
Normal file
97
GodotAddin/GodotMonoDevelopClient.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using GodotTools.IdeConnection;
|
||||
using MonoDevelop.Core;
|
||||
using MonoDevelop.Ide;
|
||||
using MonoDevelop.Ide.Gui;
|
||||
|
||||
namespace GodotAddin
|
||||
{
|
||||
public class GodotMonoDevelopClient : GodotIdeClient
|
||||
{
|
||||
private class MonoDevelopLogger : ILogger
|
||||
{
|
||||
public void LogDebug(string message)
|
||||
{
|
||||
LoggingService.LogDebug(message);
|
||||
}
|
||||
|
||||
public void LogInfo(string message)
|
||||
{
|
||||
LoggingService.LogInfo(message);
|
||||
}
|
||||
|
||||
public void LogWarning(string message)
|
||||
{
|
||||
LoggingService.LogWarning(message);
|
||||
}
|
||||
|
||||
public void LogError(string message)
|
||||
{
|
||||
LoggingService.LogError(message);
|
||||
}
|
||||
|
||||
public void LogError(string message, Exception e)
|
||||
{
|
||||
LoggingService.LogError(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
public GodotMonoDevelopClient(string projectMetadataDir) : base(projectMetadataDir)
|
||||
{
|
||||
Logger = new MonoDevelopLogger();
|
||||
}
|
||||
|
||||
public string GodotEditorExecutablePath => GodotIdeMetadata.EditorExecutablePath;
|
||||
|
||||
private static void DispatchToGuiThread(Action action)
|
||||
{
|
||||
var d = new SendOrPostCallback((target) => action());
|
||||
DispatchService.SynchronizationContext.Send(d, null);
|
||||
}
|
||||
|
||||
protected override void OpenFile(string file)
|
||||
{
|
||||
OpenFile(file, line: 0, column: 0);
|
||||
}
|
||||
|
||||
protected override void OpenFile(string file, int line)
|
||||
{
|
||||
OpenFile(file, line, column: 0);
|
||||
}
|
||||
|
||||
protected override void OpenFile(string file, int line, int column)
|
||||
{
|
||||
|
||||
DispatchToGuiThread(() =>
|
||||
{
|
||||
var fileOpenInfo = new FileOpenInformation(new FilePath(file),
|
||||
project: null /* autodetect */,
|
||||
line: line,
|
||||
column: column,
|
||||
options: OpenDocumentOptions.Default
|
||||
);
|
||||
|
||||
IdeApp.OpenFiles(new[] { fileOpenInfo });
|
||||
|
||||
// Make the Ide window grab focus
|
||||
IdeApp.Workbench.Present();
|
||||
});
|
||||
}
|
||||
|
||||
public bool SendPlay()
|
||||
{
|
||||
return WriteMessage(new Message("Play"));
|
||||
}
|
||||
|
||||
public bool SendPlay(string debuggerHost, int debuggerPort)
|
||||
{
|
||||
return WriteMessage(new Message("Play", debuggerHost, debuggerPort.ToString()));
|
||||
}
|
||||
|
||||
public bool SendReloadScripts()
|
||||
{
|
||||
return WriteMessage(new Message("ReloadScripts"));
|
||||
}
|
||||
}
|
||||
}
|
||||
64
GodotAddin/GodotOptionsPanel.cs
Normal file
64
GodotAddin/GodotOptionsPanel.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using MonoDevelop.Ide.Gui.Dialogs;
|
||||
using MonoDevelop.Components;
|
||||
using MonoDevelop.Core;
|
||||
|
||||
namespace GodotAddin
|
||||
{
|
||||
public class GodotOptionsPanel : OptionsPanel
|
||||
{
|
||||
FileEntry godotExeFileEntry = new FileEntry();
|
||||
Gtk.CheckButton alwaysUseExeCheckButton = new Gtk.CheckButton();
|
||||
|
||||
public override Control CreatePanelWidget()
|
||||
{
|
||||
var vbox = new Gtk.VBox { Spacing = 6 };
|
||||
|
||||
var generalSectionLabel = new Gtk.Label
|
||||
{
|
||||
Text = $"<b>{GettextCatalog.GetString("General")}</b>",
|
||||
UseMarkup = true,
|
||||
Xalign = 0
|
||||
};
|
||||
|
||||
vbox.PackStart(generalSectionLabel, false, false, 0);
|
||||
|
||||
var godotExeHBox = new Gtk.HBox { BorderWidth = 10, Spacing = 6 };
|
||||
|
||||
var godotExeLabel = new Gtk.Label
|
||||
{
|
||||
Text = GettextCatalog.GetString("Godot executable:"),
|
||||
Xalign = 0
|
||||
};
|
||||
|
||||
godotExeHBox.PackStart(godotExeLabel, false, false, 0);
|
||||
godotExeFileEntry.Path = Settings.GodotExecutablePath.Value;
|
||||
godotExeHBox.PackStart(godotExeFileEntry, true, true, 0);
|
||||
|
||||
vbox.PackStart(godotExeHBox, false, false, 0);
|
||||
|
||||
var alwaysUseExeHBox = new Gtk.HBox { BorderWidth = 10, Spacing = 6 };
|
||||
|
||||
var alwaysUseExeLabel = new Gtk.Label
|
||||
{
|
||||
Text = GettextCatalog.GetString("Always use this executable:"),
|
||||
Xalign = 0
|
||||
};
|
||||
|
||||
alwaysUseExeHBox.PackStart(alwaysUseExeLabel, false, false, 0);
|
||||
alwaysUseExeCheckButton.Active = Settings.AlwaysUseConfiguredExecutable.Value;
|
||||
alwaysUseExeHBox.PackStart(alwaysUseExeCheckButton, true, true, 0);
|
||||
|
||||
vbox.PackStart(alwaysUseExeHBox, false, false, 0);
|
||||
|
||||
vbox.ShowAll();
|
||||
|
||||
return vbox;
|
||||
}
|
||||
|
||||
public override void ApplyChanges()
|
||||
{
|
||||
Settings.GodotExecutablePath.Value = godotExeFileEntry.Path;
|
||||
Settings.GodotExecutablePath.Value = godotExeFileEntry.Path;
|
||||
}
|
||||
}
|
||||
}
|
||||
169
GodotAddin/GodotProjectExtension.cs
Normal file
169
GodotAddin/GodotProjectExtension.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using MonoDevelop.Core;
|
||||
using MonoDevelop.Core.Execution;
|
||||
using MonoDevelop.Ide;
|
||||
using MonoDevelop.Projects;
|
||||
|
||||
namespace GodotAddin
|
||||
{
|
||||
[ExportProjectModelExtension]
|
||||
public class GodotProjectExtension : DotNetProjectExtension, IDisposable
|
||||
{
|
||||
private static readonly SolutionItemRunConfiguration[] runConfigurations =
|
||||
{
|
||||
new ProjectRunConfiguration("Play in Editor"),
|
||||
new ProjectRunConfiguration("Launch"),
|
||||
new ProjectRunConfiguration("Attach")
|
||||
};
|
||||
|
||||
private static readonly ExecutionType[] executionTypes =
|
||||
{
|
||||
ExecutionType.PlayInEditor,
|
||||
ExecutionType.Launch,
|
||||
ExecutionType.Attach
|
||||
};
|
||||
|
||||
private static SolutionItemRunConfiguration GetRunConfiguration(ExecutionType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ExecutionType.PlayInEditor:
|
||||
return runConfigurations[0];
|
||||
case ExecutionType.Launch:
|
||||
return runConfigurations[1];
|
||||
case ExecutionType.Attach:
|
||||
return runConfigurations[2];
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
private GodotMonoDevelopClient godotIdeClient;
|
||||
|
||||
protected override void OnItemReady()
|
||||
{
|
||||
base.OnItemReady();
|
||||
|
||||
if (!IsGodotProject())
|
||||
return;
|
||||
|
||||
string godotProjectDir = Path.GetDirectoryName(GetGodotProjectPath());
|
||||
string godotProjectMetadataDir = Path.Combine(godotProjectDir, ".mono", "metadata");
|
||||
LoggingService.LogInfo($"Godot project directory is: {godotProjectDir}");
|
||||
|
||||
try
|
||||
{
|
||||
godotIdeClient?.Dispose();
|
||||
godotIdeClient = new GodotMonoDevelopClient(godotProjectMetadataDir);
|
||||
godotIdeClient.Start();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LoggingService.LogError("Exception when initializing Godot Ide Client", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<SolutionItemRunConfiguration> OnGetRunConfigurations(OperationContext ctx)
|
||||
{
|
||||
if (IsGodotProject())
|
||||
return runConfigurations;
|
||||
|
||||
return base.OnGetRunConfigurations(ctx);
|
||||
}
|
||||
|
||||
protected override ExecutionCommand OnCreateExecutionCommand(ConfigurationSelector configSel, DotNetProjectConfiguration configuration, ProjectRunConfiguration runConfiguration)
|
||||
{
|
||||
if (IsGodotProject())
|
||||
{
|
||||
var runConfigurationIndex = runConfigurations.IndexOf(runConfiguration);
|
||||
|
||||
if (runConfigurationIndex == -1)
|
||||
LoggingService.LogError($"Unexpected RunConfiguration {runConfiguration.Id} {runConfiguration.GetType().FullName}");
|
||||
|
||||
var executionType = executionTypes[runConfigurationIndex];
|
||||
|
||||
if (executionType == ExecutionType.PlayInEditor && !godotIdeClient.IsConnected)
|
||||
LoggingService.LogError($"Cannot launch editor player because the Godot Ide Client is not connected");
|
||||
|
||||
string godotProjectPath = GetGodotProjectPath();
|
||||
|
||||
return new GodotExecutionCommand(
|
||||
godotProjectPath,
|
||||
executionType,
|
||||
Path.GetDirectoryName(godotProjectPath),
|
||||
godotIdeClient
|
||||
);
|
||||
}
|
||||
|
||||
return base.OnCreateExecutionCommand(configSel, configuration, runConfiguration);
|
||||
}
|
||||
|
||||
private string GetGodotProjectPath()
|
||||
{
|
||||
return Path.Combine(Path.GetDirectoryName(Project.ParentSolution.FileName), "project.godot");
|
||||
}
|
||||
|
||||
private bool? cachedIsGodotProject;
|
||||
|
||||
private bool IsGodotProject()
|
||||
{
|
||||
if (!cachedIsGodotProject.HasValue)
|
||||
cachedIsGodotProject = File.Exists(GetGodotProjectPath());
|
||||
return cachedIsGodotProject.Value;
|
||||
}
|
||||
|
||||
protected override ProjectFeatures OnGetSupportedFeatures()
|
||||
{
|
||||
var features = base.OnGetSupportedFeatures();
|
||||
if (IsGodotProject())
|
||||
features |= ProjectFeatures.Execute;
|
||||
return features;
|
||||
}
|
||||
|
||||
protected override bool OnGetCanExecute(ExecutionContext context, ConfigurationSelector configuration, SolutionItemRunConfiguration runConfiguration)
|
||||
{
|
||||
if (IsGodotProject())
|
||||
{
|
||||
if (runConfiguration == GetRunConfiguration(ExecutionType.PlayInEditor))
|
||||
{
|
||||
// 'Play in Editor' requires the Godot Ide Client to be connected
|
||||
// to a server and the selected run configuration to be 'Tools'.
|
||||
if (!godotIdeClient.IsConnected || IdeApp.Workspace.ActiveConfigurationId != "Tools")
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnGetCanExecute(context, configuration, runConfiguration);
|
||||
}
|
||||
|
||||
protected override async Task OnExecuteCommand(ProgressMonitor monitor, ExecutionContext context, ConfigurationSelector configuration, ExecutionCommand executionCommand)
|
||||
{
|
||||
if (executionCommand is GodotExecutionCommand godotCmd)
|
||||
{
|
||||
if (godotCmd.ExecutionType == ExecutionType.Launch)
|
||||
{
|
||||
if (!File.Exists(Settings.GodotExecutablePath))
|
||||
{
|
||||
// Delay for 1 sec so it's not overriden by build message
|
||||
await Task.Delay(1000);
|
||||
monitor.ReportError(GettextCatalog.GetString($"Godot executable \"{Settings.GodotExecutablePath.Value}\" not found. Update Godot executable setting in perferences."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
await base.OnExecuteCommand(monitor, context, configuration, executionCommand);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
|
||||
godotIdeClient?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
14
GodotAddin/GodotSoftDebuggerArgs.cs
Normal file
14
GodotAddin/GodotSoftDebuggerArgs.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Net;
|
||||
using Mono.Debugging.Soft;
|
||||
|
||||
namespace GodotAddin
|
||||
{
|
||||
public class GodotSoftDebuggerArgs : SoftDebuggerRemoteArgs
|
||||
{
|
||||
public GodotSoftDebuggerArgs(string appName, IPAddress address, int debugPort, int outputPort) : base(appName, address, debugPort, outputPort)
|
||||
{
|
||||
}
|
||||
|
||||
public override ISoftDebuggerConnectionProvider ConnectionProvider => null;
|
||||
}
|
||||
}
|
||||
128
GodotAddin/GodotVariant.cs
Normal file
128
GodotAddin/GodotVariant.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace GodotAddin
|
||||
{
|
||||
// Incomplete implementation of the Godot's Variant encoder. Add missing parts as needed.
|
||||
|
||||
public class GodotVariantEncoder
|
||||
{
|
||||
private List<byte> buffer = new List<byte>();
|
||||
|
||||
public int Length => buffer.Count;
|
||||
|
||||
public byte[] ToArray() => buffer.ToArray();
|
||||
|
||||
public void AddBytes(params byte[] bytes) =>
|
||||
buffer.AddRange(bytes);
|
||||
|
||||
public void AddInt(int value) =>
|
||||
AddBytes(BitConverter.GetBytes(value));
|
||||
|
||||
public void AddType(GodotVariant.Type type) =>
|
||||
AddInt((int)type);
|
||||
|
||||
public void AddString(string value)
|
||||
{
|
||||
byte[] utf8Bytes = Encoding.UTF8.GetBytes(value);
|
||||
|
||||
AddType(GodotVariant.Type.String);
|
||||
AddInt(utf8Bytes.Length);
|
||||
AddBytes(utf8Bytes);
|
||||
AddBytes(0); // Godot's UTF8 converter adds a trailing whitespace
|
||||
|
||||
while (buffer.Count % 4 != 0)
|
||||
buffer.Add(0);
|
||||
}
|
||||
|
||||
public void AddArray(List<GodotVariant> array)
|
||||
{
|
||||
AddType(GodotVariant.Type.Array);
|
||||
AddInt((int)array.Count);
|
||||
|
||||
foreach (object element in array)
|
||||
{
|
||||
if (element is string str)
|
||||
AddString(str);
|
||||
else
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Encode(GodotVariant variant, Stream stream)
|
||||
{
|
||||
using (var writer = new BinaryWriter(stream, new UTF8Encoding(false, true), leaveOpen: true))
|
||||
{
|
||||
var encoder = new GodotVariantEncoder();
|
||||
switch (variant.VariantType)
|
||||
{
|
||||
case GodotVariant.Type.String:
|
||||
encoder.AddString((string)variant.Value);
|
||||
break;
|
||||
case GodotVariant.Type.Array:
|
||||
encoder.AddArray((List<GodotVariant>)variant.Value);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
writer.Write((int)encoder.Length);
|
||||
writer.Write(encoder.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GodotVariant
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
Nil = 0,
|
||||
Bool = 1,
|
||||
Int = 2,
|
||||
Real = 3,
|
||||
String = 4,
|
||||
Vector2 = 5,
|
||||
Rect2 = 6,
|
||||
Vector3 = 7,
|
||||
Transform2d = 8,
|
||||
Quat = 10,
|
||||
Aabb = 11,
|
||||
Basis = 12,
|
||||
Transform = 13,
|
||||
Color = 14,
|
||||
NodePath = 15,
|
||||
Rid = 16,
|
||||
Object = 17,
|
||||
Dictionary = 18,
|
||||
Array = 19,
|
||||
RawArray = 20,
|
||||
IntArray = 21,
|
||||
RealArray = 22,
|
||||
StringArray = 23,
|
||||
Vector2Array = 24,
|
||||
Vector3Array = 25,
|
||||
ColorArray = 26,
|
||||
Max = 27
|
||||
}
|
||||
|
||||
public object Value { get; }
|
||||
public Type VariantType { get; }
|
||||
|
||||
public GodotVariant(string value)
|
||||
{
|
||||
Value = value;
|
||||
VariantType = Type.String;
|
||||
}
|
||||
|
||||
public GodotVariant(List<GodotVariant> value)
|
||||
{
|
||||
Value = value;
|
||||
VariantType = Type.Array;
|
||||
}
|
||||
|
||||
public static implicit operator GodotVariant(string value) => new GodotVariant(value);
|
||||
public static implicit operator GodotVariant(List<GodotVariant> value) => new GodotVariant(value);
|
||||
}
|
||||
}
|
||||
13
GodotAddin/Properties/AddinInfo.cs
Normal file
13
GodotAddin/Properties/AddinInfo.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Mono.Addins;
|
||||
using Mono.Addins.Description;
|
||||
|
||||
[assembly: Addin(
|
||||
"GodotAddin",
|
||||
Namespace = "GodotAddin",
|
||||
Version = "1.0"
|
||||
)]
|
||||
|
||||
[assembly: AddinName("Godot Addin")]
|
||||
[assembly: AddinCategory("IDE extensions")]
|
||||
[assembly: AddinDescription("Extension for the Godot game engine")]
|
||||
[assembly: AddinAuthor("Godot Engine contributors")]
|
||||
15
GodotAddin/Properties/Manifest.addin.xml
Normal file
15
GodotAddin/Properties/Manifest.addin.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ExtensionModel>
|
||||
<Extension path="/MonoDevelop/Debugging/DebuggerEngines">
|
||||
<DebuggerEngine
|
||||
id="MonoDevelop.MicroFramework.Debugger"
|
||||
name="Debugger for Godot"
|
||||
features="Breakpoints, Pause, Stepping, DebugFile, ConditionalBreakpoints, Tracepoints, Catchpoints, Disassembly"
|
||||
type="GodotAddin.GodotDebuggerEngine" />
|
||||
</Extension>
|
||||
<Extension path="/MonoDevelop/Ide/GlobalOptionsDialog">
|
||||
<Section id="Godot" _label="Godot Engine" insertafter="VersionControl">
|
||||
<Section id="GodotGeneral" _label="General" fill="true" class="GodotAddin.GodotOptionsPanel" />
|
||||
</Section>
|
||||
</Extension>
|
||||
</ExtensionModel>
|
||||
30
GodotAddin/Settings.cs
Normal file
30
GodotAddin/Settings.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using MonoDevelop.Core;
|
||||
|
||||
namespace GodotAddin
|
||||
{
|
||||
public static class Settings
|
||||
{
|
||||
private static string DetermineDefaultGodotPath()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
return "/Applications/Godot_mono.app/Contents/MacOS/Godot";
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
return Path.Combine(Environment.GetEnvironmentVariable("HOME") ?? string.Empty, "Godot_x11");
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE") ?? string.Empty, "Godot_win32.exe");
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static readonly ConfigurationProperty<string> GodotExecutablePath =
|
||||
ConfigurationProperty.Create("Godot.GodotExecutable", DetermineDefaultGodotPath());
|
||||
|
||||
public static readonly ConfigurationProperty<bool> AlwaysUseConfiguredExecutable =
|
||||
ConfigurationProperty.Create("Godot.AlwaysUseConfiguredExecutable", false);
|
||||
}
|
||||
}
|
||||
15
GodotAddin/Utils/ExtensionMethods.cs
Normal file
15
GodotAddin/Utils/ExtensionMethods.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace GodotAddin.Utils
|
||||
{
|
||||
public static class ExtensionMethods
|
||||
{
|
||||
public static IEnumerable<string> EnumerateLines(this TextReader textReader)
|
||||
{
|
||||
string line;
|
||||
while ((line = textReader.ReadLine()) != null)
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
GodotTools.IdeConnection/ConsoleLogger.cs
Normal file
33
GodotTools.IdeConnection/ConsoleLogger.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public class ConsoleLogger : ILogger
|
||||
{
|
||||
public void LogDebug(string message)
|
||||
{
|
||||
Console.WriteLine("DEBUG: " + message);
|
||||
}
|
||||
|
||||
public void LogInfo(string message)
|
||||
{
|
||||
Console.WriteLine("INFO: " + message);
|
||||
}
|
||||
|
||||
public void LogWarning(string message)
|
||||
{
|
||||
Console.WriteLine("WARN: " + message);
|
||||
}
|
||||
|
||||
public void LogError(string message)
|
||||
{
|
||||
Console.WriteLine("ERROR: " + message);
|
||||
}
|
||||
|
||||
public void LogError(string message, Exception e)
|
||||
{
|
||||
Console.WriteLine("EXCEPTION: " + message);
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
GodotTools.IdeConnection/GodotIdeBase.cs
Normal file
94
GodotTools.IdeConnection/GodotIdeBase.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public class GodotIdeBase : IDisposable
|
||||
{
|
||||
private ILogger logger;
|
||||
|
||||
public ILogger Logger
|
||||
{
|
||||
get => logger ?? (logger = new ConsoleLogger());
|
||||
set => logger = value;
|
||||
}
|
||||
|
||||
private readonly string projectMetadataDir;
|
||||
|
||||
protected const string MetaFileName = "ide_server_meta.txt";
|
||||
protected string MetaFilePath => Path.Combine(projectMetadataDir, MetaFileName);
|
||||
|
||||
private GodotIdeConnection connection;
|
||||
protected readonly object ConnectionLock = new object();
|
||||
|
||||
public bool IsDisposed { get; private set; } = false;
|
||||
|
||||
public bool IsConnected => connection != null && !connection.IsDisposed && connection.IsConnected;
|
||||
|
||||
public event Action Connected
|
||||
{
|
||||
add
|
||||
{
|
||||
if (connection != null && !connection.IsDisposed)
|
||||
connection.Connected += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
if (connection != null && !connection.IsDisposed)
|
||||
connection.Connected -= value;
|
||||
}
|
||||
}
|
||||
|
||||
protected GodotIdeConnection Connection
|
||||
{
|
||||
get => connection;
|
||||
set
|
||||
{
|
||||
connection?.Dispose();
|
||||
connection = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected GodotIdeBase(string projectMetadataDir)
|
||||
{
|
||||
this.projectMetadataDir = projectMetadataDir;
|
||||
}
|
||||
|
||||
protected void DisposeConnection()
|
||||
{
|
||||
lock (ConnectionLock)
|
||||
{
|
||||
connection?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
~GodotIdeBase()
|
||||
{
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
lock (ConnectionLock)
|
||||
{
|
||||
if (IsDisposed) // lock may not be fair
|
||||
return;
|
||||
IsDisposed = true;
|
||||
}
|
||||
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
connection?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
219
GodotTools.IdeConnection/GodotIdeClient.cs
Normal file
219
GodotTools.IdeConnection/GodotIdeClient.cs
Normal file
@@ -0,0 +1,219 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public abstract class GodotIdeClient : GodotIdeBase
|
||||
{
|
||||
protected GodotIdeMetadata GodotIdeMetadata;
|
||||
|
||||
private readonly FileSystemWatcher fsWatcher;
|
||||
|
||||
protected GodotIdeClient(string projectMetadataDir) : base(projectMetadataDir)
|
||||
{
|
||||
messageHandlers = InitializeMessageHandlers();
|
||||
|
||||
// FileSystemWatcher requires an existing directory
|
||||
if (!File.Exists(projectMetadataDir))
|
||||
Directory.CreateDirectory(projectMetadataDir);
|
||||
|
||||
fsWatcher = new FileSystemWatcher(projectMetadataDir, MetaFileName);
|
||||
}
|
||||
|
||||
private void OnMetaFileChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
lock (ConnectionLock)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
if (!File.Exists(MetaFilePath))
|
||||
return;
|
||||
|
||||
var metadata = ReadMetadataFile();
|
||||
|
||||
if (metadata != null && metadata != GodotIdeMetadata)
|
||||
{
|
||||
GodotIdeMetadata = metadata.Value;
|
||||
ConnectToServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMetaFileDeleted(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
if (IsConnected)
|
||||
DisposeConnection();
|
||||
|
||||
// The file may have been re-created
|
||||
|
||||
lock (ConnectionLock)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
if (IsConnected || !File.Exists(MetaFilePath))
|
||||
return;
|
||||
|
||||
var metadata = ReadMetadataFile();
|
||||
|
||||
if (metadata != null)
|
||||
{
|
||||
GodotIdeMetadata = metadata.Value;
|
||||
ConnectToServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private GodotIdeMetadata? ReadMetadataFile()
|
||||
{
|
||||
using (var reader = File.OpenText(MetaFilePath))
|
||||
{
|
||||
string portStr = reader.ReadLine();
|
||||
|
||||
if (portStr == null)
|
||||
return null;
|
||||
|
||||
string editorExecutablePath = reader.ReadLine();
|
||||
|
||||
if (editorExecutablePath == null)
|
||||
return null;
|
||||
|
||||
if (!int.TryParse(portStr, out int port))
|
||||
return null;
|
||||
|
||||
return new GodotIdeMetadata(port, editorExecutablePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectToServer()
|
||||
{
|
||||
var tcpClient = new TcpClient();
|
||||
|
||||
Connection = new GodotIdeConnectionClient(tcpClient, HandleMessage);
|
||||
Connection.Logger = Logger;
|
||||
|
||||
try
|
||||
{
|
||||
Logger.LogInfo("Connecting to Godot Ide Server");
|
||||
|
||||
tcpClient.Connect(IPAddress.Loopback, GodotIdeMetadata.Port);
|
||||
|
||||
Logger.LogInfo("Connection open with Godot Ide Server");
|
||||
|
||||
var clientThread = new Thread(Connection.Start)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "Godot Ide Connection Client"
|
||||
};
|
||||
clientThread.Start();
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
if (e.SocketErrorCode == SocketError.ConnectionRefused)
|
||||
Logger.LogError("The connection to the Godot Ide Server was refused");
|
||||
else
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Logger.LogInfo("Starting Godot Ide Client");
|
||||
|
||||
fsWatcher.Changed += OnMetaFileChanged;
|
||||
fsWatcher.Deleted += OnMetaFileDeleted;
|
||||
fsWatcher.EnableRaisingEvents = true;
|
||||
|
||||
lock (ConnectionLock)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
if (!File.Exists(MetaFilePath))
|
||||
{
|
||||
Logger.LogInfo("There is no Godot Ide Server running");
|
||||
return;
|
||||
}
|
||||
|
||||
var metadata = ReadMetadataFile();
|
||||
|
||||
if (metadata != null)
|
||||
{
|
||||
GodotIdeMetadata = metadata.Value;
|
||||
ConnectToServer();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogError("Failed to read Godot Ide metadata file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool WriteMessage(Message message)
|
||||
{
|
||||
return Connection.WriteMessage(message);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
fsWatcher?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool HandleMessage(Message message)
|
||||
{
|
||||
if (messageHandlers.TryGetValue(message.Id, out var action))
|
||||
{
|
||||
action(message.Arguments);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, Action<string[]>> messageHandlers;
|
||||
|
||||
private Dictionary<string, Action<string[]>> InitializeMessageHandlers()
|
||||
{
|
||||
return new Dictionary<string, Action<string[]>>
|
||||
{
|
||||
["OpenFile"] = args =>
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1:
|
||||
OpenFile(file: args[0]);
|
||||
return;
|
||||
case 2:
|
||||
OpenFile(file: args[0], line: int.Parse(args[1]));
|
||||
return;
|
||||
case 3:
|
||||
OpenFile(file: args[0], line: int.Parse(args[1]), column: int.Parse(args[2]));
|
||||
return;
|
||||
default:
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract void OpenFile(string file);
|
||||
protected abstract void OpenFile(string file, int line);
|
||||
protected abstract void OpenFile(string file, int line, int column);
|
||||
}
|
||||
}
|
||||
207
GodotTools.IdeConnection/GodotIdeConnection.cs
Normal file
207
GodotTools.IdeConnection/GodotIdeConnection.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public abstract class GodotIdeConnection : IDisposable
|
||||
{
|
||||
protected const string Version = "1.0";
|
||||
|
||||
protected static readonly string ClientHandshake = $"Godot Ide Client Version {Version}";
|
||||
protected static readonly string ServerHandshake = $"Godot Ide Server Version {Version}";
|
||||
|
||||
private const int ClientWriteTimeout = 8000;
|
||||
private readonly TcpClient tcpClient;
|
||||
|
||||
private TextReader clientReader;
|
||||
private TextWriter clientWriter;
|
||||
|
||||
private readonly object writeLock = new object();
|
||||
|
||||
private readonly Func<Message, bool> messageHandler;
|
||||
|
||||
public event Action Connected;
|
||||
|
||||
private ILogger logger;
|
||||
|
||||
public ILogger Logger
|
||||
{
|
||||
get => logger ?? (logger = new ConsoleLogger());
|
||||
set => logger = value;
|
||||
}
|
||||
|
||||
public bool IsDisposed { get; private set; } = false;
|
||||
|
||||
public bool IsConnected => tcpClient.Client != null && tcpClient.Client.Connected;
|
||||
|
||||
protected GodotIdeConnection(TcpClient tcpClient, Func<Message, bool> messageHandler)
|
||||
{
|
||||
this.tcpClient = tcpClient;
|
||||
this.messageHandler = messageHandler;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!StartConnection())
|
||||
return;
|
||||
|
||||
string messageLine;
|
||||
while ((messageLine = ReadLine()) != null)
|
||||
{
|
||||
if (!MessageParser.TryParse(messageLine, out Message msg))
|
||||
{
|
||||
Logger.LogError($"Received message with invalid format: {messageLine}");
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.LogDebug($"Received message: {msg}");
|
||||
|
||||
if (msg.Id == "close")
|
||||
{
|
||||
Logger.LogInfo("Closing connection");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.Assert(messageHandler != null);
|
||||
|
||||
if (!messageHandler(msg))
|
||||
Logger.LogError($"Received unknown message: {msg}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Message handler for '{msg}' failed with exception", e);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Exception thrown from message handler. Message: {msg}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Unhandled exception in the Godot Ide Connection thread", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private bool StartConnection()
|
||||
{
|
||||
NetworkStream clientStream = tcpClient.GetStream();
|
||||
|
||||
clientReader = new StreamReader(clientStream, Encoding.UTF8);
|
||||
|
||||
lock (writeLock)
|
||||
clientWriter = new StreamWriter(clientStream, Encoding.UTF8);
|
||||
|
||||
clientStream.WriteTimeout = ClientWriteTimeout;
|
||||
|
||||
if (!WriteHandshake())
|
||||
{
|
||||
Logger.LogError("Could not write handshake");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsValidResponseHandshake(ReadLine()))
|
||||
{
|
||||
Logger.LogError("Received invalid handshake");
|
||||
return false;
|
||||
}
|
||||
|
||||
Connected?.Invoke();
|
||||
|
||||
Logger.LogInfo("Godot Ide connection started");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string ReadLine()
|
||||
{
|
||||
try
|
||||
{
|
||||
return clientReader?.ReadLine();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (IsDisposed)
|
||||
{
|
||||
var se = e as SocketException ?? e.InnerException as SocketException;
|
||||
if (se != null && se.SocketErrorCode == SocketError.Interrupted)
|
||||
return null;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public bool WriteMessage(Message message)
|
||||
{
|
||||
Logger.LogDebug($"Sending message {message}");
|
||||
|
||||
var messageComposer = new MessageComposer();
|
||||
|
||||
messageComposer.AddArgument(message.Id);
|
||||
foreach (string argument in message.Arguments)
|
||||
messageComposer.AddArgument(argument);
|
||||
|
||||
return WriteLine(messageComposer.ToString());
|
||||
}
|
||||
|
||||
protected bool WriteLine(string text)
|
||||
{
|
||||
if (clientWriter == null || IsDisposed || !IsConnected)
|
||||
return false;
|
||||
|
||||
lock (writeLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
clientWriter.WriteLine(text);
|
||||
clientWriter.Flush();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
var se = e as SocketException ?? e.InnerException as SocketException;
|
||||
if (se != null && se.SocketErrorCode == SocketError.Shutdown)
|
||||
Logger.LogInfo("Client disconnected ungracefully");
|
||||
else
|
||||
Logger.LogError("Exception thrown when trying to write to client", e);
|
||||
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract bool WriteHandshake();
|
||||
protected abstract bool IsValidResponseHandshake(string handshakeLine);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
IsDisposed = true;
|
||||
|
||||
clientReader?.Dispose();
|
||||
clientWriter?.Dispose();
|
||||
((IDisposable) tcpClient)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
24
GodotTools.IdeConnection/GodotIdeConnectionClient.cs
Normal file
24
GodotTools.IdeConnection/GodotIdeConnectionClient.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public class GodotIdeConnectionClient : GodotIdeConnection
|
||||
{
|
||||
public GodotIdeConnectionClient(TcpClient tcpClient, Func<Message, bool> messageHandler)
|
||||
: base(tcpClient, messageHandler)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool WriteHandshake()
|
||||
{
|
||||
return WriteLine(ClientHandshake);
|
||||
}
|
||||
|
||||
protected override bool IsValidResponseHandshake(string handshakeLine)
|
||||
{
|
||||
return handshakeLine == ServerHandshake;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
GodotTools.IdeConnection/GodotIdeConnectionServer.cs
Normal file
24
GodotTools.IdeConnection/GodotIdeConnectionServer.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public class GodotIdeConnectionServer : GodotIdeConnection
|
||||
{
|
||||
public GodotIdeConnectionServer(TcpClient tcpClient, Func<Message, bool> messageHandler)
|
||||
: base(tcpClient, messageHandler)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool WriteHandshake()
|
||||
{
|
||||
return WriteLine(ServerHandshake);
|
||||
}
|
||||
|
||||
protected override bool IsValidResponseHandshake(string handshakeLine)
|
||||
{
|
||||
return handshakeLine == ClientHandshake;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
GodotTools.IdeConnection/GodotIdeMetadata.cs
Normal file
45
GodotTools.IdeConnection/GodotIdeMetadata.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public struct GodotIdeMetadata
|
||||
{
|
||||
public int Port { get; }
|
||||
public string EditorExecutablePath { get; }
|
||||
|
||||
public GodotIdeMetadata(int port, string editorExecutablePath)
|
||||
{
|
||||
Port = port;
|
||||
EditorExecutablePath = editorExecutablePath;
|
||||
}
|
||||
|
||||
public static bool operator ==(GodotIdeMetadata a, GodotIdeMetadata b)
|
||||
{
|
||||
return a.Port == b.Port && a.EditorExecutablePath == b.EditorExecutablePath;
|
||||
}
|
||||
|
||||
public static bool operator !=(GodotIdeMetadata a, GodotIdeMetadata b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is GodotIdeMetadata metadata)
|
||||
return metadata == this;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Equals(GodotIdeMetadata other)
|
||||
{
|
||||
return Port == other.Port && EditorExecutablePath == other.EditorExecutablePath;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (Port * 397) ^ (EditorExecutablePath != null ? EditorExecutablePath.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
GodotTools.IdeConnection/GodotTools.IdeConnection.csproj
Normal file
53
GodotTools.IdeConnection/GodotTools.IdeConnection.csproj
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>GodotTools.IdeConnection</RootNamespace>
|
||||
<AssemblyName>GodotTools.IdeConnection</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<LangVersion>7</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ConsoleLogger.cs" />
|
||||
<Compile Include="GodotIdeMetadata.cs" />
|
||||
<Compile Include="GodotIdeBase.cs" />
|
||||
<Compile Include="GodotIdeClient.cs" />
|
||||
<Compile Include="GodotIdeConnection.cs" />
|
||||
<Compile Include="GodotIdeConnectionClient.cs" />
|
||||
<Compile Include="GodotIdeConnectionServer.cs" />
|
||||
<Compile Include="ILogger.cs" />
|
||||
<Compile Include="Message.cs" />
|
||||
<Compile Include="MessageComposer.cs" />
|
||||
<Compile Include="MessageParser.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
13
GodotTools.IdeConnection/ILogger.cs
Normal file
13
GodotTools.IdeConnection/ILogger.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public interface ILogger
|
||||
{
|
||||
void LogDebug(string message);
|
||||
void LogInfo(string message);
|
||||
void LogWarning(string message);
|
||||
void LogError(string message);
|
||||
void LogError(string message, Exception e);
|
||||
}
|
||||
}
|
||||
21
GodotTools.IdeConnection/Message.cs
Normal file
21
GodotTools.IdeConnection/Message.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public struct Message
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string[] Arguments { get; set; }
|
||||
|
||||
public Message(string id, params string[] arguments)
|
||||
{
|
||||
Id = id;
|
||||
Arguments = arguments;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"(Id: '{Id}', Arguments: '{string.Join(",", Arguments)}')";
|
||||
}
|
||||
}
|
||||
}
|
||||
46
GodotTools.IdeConnection/MessageComposer.cs
Normal file
46
GodotTools.IdeConnection/MessageComposer.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public class MessageComposer
|
||||
{
|
||||
private readonly StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
private static readonly char[] CharsToEscape = { '\\', '"' };
|
||||
|
||||
public void AddArgument(string argument)
|
||||
{
|
||||
AddArgument(argument, quoted: argument.Contains(","));
|
||||
}
|
||||
|
||||
public void AddArgument(string argument, bool quoted)
|
||||
{
|
||||
if (stringBuilder.Length > 0)
|
||||
stringBuilder.Append(',');
|
||||
|
||||
if (quoted)
|
||||
{
|
||||
stringBuilder.Append('"');
|
||||
|
||||
foreach (char @char in argument)
|
||||
{
|
||||
if (CharsToEscape.Contains(@char))
|
||||
stringBuilder.Append('\\');
|
||||
stringBuilder.Append(@char);
|
||||
}
|
||||
|
||||
stringBuilder.Append('"');
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(argument);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
88
GodotTools.IdeConnection/MessageParser.cs
Normal file
88
GodotTools.IdeConnection/MessageParser.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace GodotTools.IdeConnection
|
||||
{
|
||||
public static class MessageParser
|
||||
{
|
||||
public static bool TryParse(string messageLine, out Message message)
|
||||
{
|
||||
var arguments = new List<string>();
|
||||
var stringBuilder = new StringBuilder();
|
||||
|
||||
bool expectingArgument = true;
|
||||
|
||||
for (int i = 0; i < messageLine.Length; i++)
|
||||
{
|
||||
char @char = messageLine[i];
|
||||
|
||||
if (@char == ',')
|
||||
{
|
||||
if (expectingArgument)
|
||||
arguments.Add(string.Empty);
|
||||
|
||||
expectingArgument = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool quoted = false;
|
||||
|
||||
if (messageLine[i] == '"')
|
||||
{
|
||||
quoted = true;
|
||||
i++;
|
||||
}
|
||||
|
||||
while (i < messageLine.Length)
|
||||
{
|
||||
@char = messageLine[i];
|
||||
|
||||
if (quoted && @char == '"')
|
||||
{
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (@char == '\\')
|
||||
{
|
||||
i++;
|
||||
if (i < messageLine.Length)
|
||||
break;
|
||||
|
||||
stringBuilder.Append(messageLine[i]);
|
||||
}
|
||||
else if (!quoted && @char == ',')
|
||||
{
|
||||
break; // We don't increment the counter to allow the colon to be parsed after this
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(@char);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
arguments.Add(stringBuilder.ToString());
|
||||
stringBuilder.Clear();
|
||||
|
||||
expectingArgument = false;
|
||||
}
|
||||
|
||||
if (arguments.Count == 0)
|
||||
{
|
||||
message = new Message();
|
||||
return false;
|
||||
}
|
||||
|
||||
message = new Message
|
||||
{
|
||||
Id = arguments[0],
|
||||
Arguments = arguments.Skip(1).ToArray()
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
GodotTools.IdeConnection/Properties/AssemblyInfo.cs
Normal file
35
GodotTools.IdeConnection/Properties/AssemblyInfo.cs
Normal file
@@ -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("GodotTools.IdeConnection")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("Godot Engine contributors")]
|
||||
[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("92600954-25F0-4291-8E11-1FEE9FC4BE20")]
|
||||
|
||||
// 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")]
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 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.
|
||||
11
README.md
Executable file
11
README.md
Executable file
@@ -0,0 +1,11 @@
|
||||
# Godot Add-in for MonoDevelop and Visual Studio for Mac
|
||||
MonoDevelop and Visual Studio for Mac add-in for seamless integration with the Godot game engine.
|
||||
|
||||
This add-in is based on [GodotExtension](https://github.com/DavidKarlas/GodotExtension).
|
||||
|
||||
## Features
|
||||
|
||||
- Handling file open requests from the Godot editor.
|
||||
- Launching and debugging the Godot editor player.
|
||||
- Launching and debugging a Godot executable.
|
||||
- Attach the debugger to a running Godot instance.
|
||||
Reference in New Issue
Block a user