mirror of
https://github.com/godotengine/godot-monodevelop-addin.git
synced 2026-01-05 14:10:11 +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