summaryrefslogtreecommitdiff
path: root/modules/mono/editor/GodotTools
diff options
context:
space:
mode:
Diffstat (limited to 'modules/mono/editor/GodotTools')
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs33
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs94
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs219
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs207
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs45
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj52
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs13
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs21
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs46
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs88
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs35
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.sln6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs (renamed from modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs)30
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs16
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs14
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs (renamed from modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs)8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildManager.cs (renamed from modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs)87
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildTab.cs (renamed from modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs)20
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs (renamed from modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs)2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs11
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs121
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs166
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs208
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs (renamed from modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs)43
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs15
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs64
32 files changed, 1545 insertions, 211 deletions
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs
new file mode 100644
index 0000000000..7a2ff2ca56
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs
@@ -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);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs
new file mode 100644
index 0000000000..be89638241
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs
@@ -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();
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs
new file mode 100644
index 0000000000..4f56a8d71b
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs
@@ -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);
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs
new file mode 100644
index 0000000000..e7e81f175e
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs
@@ -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();
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs
new file mode 100644
index 0000000000..1b11a14358
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs
@@ -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;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs
new file mode 100644
index 0000000000..aa98dc7ca3
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs
@@ -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;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs
new file mode 100644
index 0000000000..d16daba0e2
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs
@@ -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);
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj
new file mode 100644
index 0000000000..84c08251ab
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj
@@ -0,0 +1,52 @@
+<?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>
+ </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> \ No newline at end of file
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs
new file mode 100644
index 0000000000..614bb30271
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs
@@ -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);
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs
new file mode 100644
index 0000000000..f24d324ae3
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs
@@ -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)}')";
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs
new file mode 100644
index 0000000000..9e4cd6ec1a
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs
@@ -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();
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs
new file mode 100644
index 0000000000..ed691e481f
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs
@@ -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;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..c7c00e66a2
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("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")]
diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln
index 6f7d44bec2..a3438ea5f3 100644
--- a/modules/mono/editor/GodotTools/GodotTools.sln
+++ b/modules/mono/editor/GodotTools/GodotTools.sln
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Core", "GodotToo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "GodotTools.BuildLogger\GodotTools.BuildLogger.csproj", "{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeConnection", "GodotTools.IdeConnection\GodotTools.IdeConnection.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -31,5 +33,9 @@ Global
{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
index 53ff0891d5..44813f962c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
@@ -9,7 +9,7 @@ using Path = System.IO.Path;
namespace GodotTools
{
- public class MonoBottomPanel : VBoxContainer
+ public class BottomPanel : VBoxContainer
{
private EditorInterface editorInterface;
@@ -34,7 +34,7 @@ namespace GodotTools
for (int i = 0; i < buildTabs.GetChildCount(); i++)
{
- var tab = (MonoBuildTab) buildTabs.GetChild(i);
+ var tab = (BuildTab) buildTabs.GetChild(i);
if (tab == null)
continue;
@@ -49,11 +49,11 @@ namespace GodotTools
itemTooltip += "\nStatus: ";
if (tab.BuildExited)
- itemTooltip += tab.BuildResult == MonoBuildTab.BuildResults.Success ? "Succeeded" : "Errored";
+ itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
else
itemTooltip += "Running";
- if (!tab.BuildExited || tab.BuildResult == MonoBuildTab.BuildResults.Error)
+ if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
itemTooltip += $"\nErrors: {tab.ErrorCount}";
itemTooltip += $"\nWarnings: {tab.WarningCount}";
@@ -68,15 +68,15 @@ namespace GodotTools
}
}
- public MonoBuildTab GetBuildTabFor(MonoBuildInfo buildInfo)
+ public BuildTab GetBuildTabFor(BuildInfo buildInfo)
{
- foreach (var buildTab in new Array<MonoBuildTab>(buildTabs.GetChildren()))
+ foreach (var buildTab in new Array<BuildTab>(buildTabs.GetChildren()))
{
if (buildTab.BuildInfo.Equals(buildInfo))
return buildTab;
}
- var newBuildTab = new MonoBuildTab(buildInfo);
+ var newBuildTab = new BuildTab(buildInfo);
AddBuildTab(newBuildTab);
return newBuildTab;
@@ -120,7 +120,7 @@ namespace GodotTools
if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
throw new InvalidOperationException("No tab selected");
- var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab);
+ var buildTab = (BuildTab) buildTabs.GetChild(currentTab);
buildTab.WarningsVisible = pressed;
buildTab.UpdateIssuesList();
}
@@ -132,7 +132,7 @@ namespace GodotTools
if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
throw new InvalidOperationException("No tab selected");
- var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab);
+ var buildTab = (BuildTab) buildTabs.GetChild(currentTab);
buildTab.ErrorsVisible = pressed;
buildTab.UpdateIssuesList();
}
@@ -145,7 +145,7 @@ namespace GodotTools
string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
- CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
+ CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
if (File.Exists(editorScriptsMetadataPath))
{
@@ -166,7 +166,7 @@ namespace GodotTools
Internal.GodotIs32Bits() ? "32" : "64"
};
- bool buildSuccess = GodotSharpBuilds.BuildProjectBlocking("Tools", godotDefines);
+ bool buildSuccess = BuildManager.BuildProjectBlocking("Tools", godotDefines);
if (!buildSuccess)
return;
@@ -193,9 +193,9 @@ namespace GodotTools
int selectedItem = selectedItems[0];
- var buildTab = (MonoBuildTab) buildTabs.GetTabControl(selectedItem);
+ var buildTab = (BuildTab) buildTabs.GetTabControl(selectedItem);
- OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildLogFileName));
+ OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, BuildManager.MsBuildLogFileName));
}
public override void _Notification(int what)
@@ -211,13 +211,13 @@ namespace GodotTools
}
}
- public void AddBuildTab(MonoBuildTab buildTab)
+ public void AddBuildTab(BuildTab buildTab)
{
buildTabs.AddChild(buildTab);
RaiseBuildTab(buildTab);
}
- public void RaiseBuildTab(MonoBuildTab buildTab)
+ public void RaiseBuildTab(BuildTab buildTab)
{
if (buildTab.GetParent() != buildTabs)
throw new InvalidOperationException("Build tab is not in the tabs list");
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
index d8cb9024c3..9a2b2e3a26 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
@@ -46,8 +46,8 @@ namespace GodotTools.Build
{
if (OS.IsWindows())
{
- return (GodotSharpBuilds.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool")
- == GodotSharpBuilds.BuildTool.MsBuildMono;
+ return (BuildManager.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool")
+ == BuildManager.BuildTool.MsBuildMono;
}
return false;
@@ -103,16 +103,16 @@ namespace GodotTools.Build
return process;
}
- public static int Build(MonoBuildInfo monoBuildInfo)
+ public static int Build(BuildInfo buildInfo)
{
- return Build(monoBuildInfo.Solution, monoBuildInfo.Configuration,
- monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties);
+ return Build(buildInfo.Solution, buildInfo.Configuration,
+ buildInfo.LogsDirPath, buildInfo.CustomProperties);
}
- public static async Task<int> BuildAsync(MonoBuildInfo monoBuildInfo)
+ public static async Task<int> BuildAsync(BuildInfo buildInfo)
{
- return await BuildAsync(monoBuildInfo.Solution, monoBuildInfo.Configuration,
- monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties);
+ return await BuildAsync(buildInfo.Solution, buildInfo.Configuration,
+ buildInfo.LogsDirPath, buildInfo.CustomProperties);
}
public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
index 926aabdf89..4c1e47ecad 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
@@ -19,13 +19,13 @@ namespace GodotTools.Build
public static string FindMsBuild()
{
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- var buildTool = (GodotSharpBuilds.BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
+ var buildTool = (BuildManager.BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
if (OS.IsWindows())
{
switch (buildTool)
{
- case GodotSharpBuilds.BuildTool.MsBuildVs:
+ case BuildManager.BuildTool.MsBuildVs:
{
if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath))
{
@@ -34,7 +34,7 @@ namespace GodotTools.Build
if (_msbuildToolsPath.Empty())
{
- throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}");
+ throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}");
}
}
@@ -43,13 +43,13 @@ namespace GodotTools.Build
return Path.Combine(_msbuildToolsPath, "MSBuild.exe");
}
- case GodotSharpBuilds.BuildTool.MsBuildMono:
+ case BuildManager.BuildTool.MsBuildMono:
{
string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat");
if (!File.Exists(msbuildPath))
{
- throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildMono}'. Tried with path: {msbuildPath}");
+ throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildMono}'. Tried with path: {msbuildPath}");
}
return msbuildPath;
@@ -61,7 +61,7 @@ namespace GodotTools.Build
if (OS.IsUnix())
{
- if (buildTool == GodotSharpBuilds.BuildTool.MsBuildMono)
+ if (buildTool == BuildManager.BuildTool.MsBuildMono)
{
if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath))
{
@@ -71,7 +71,7 @@ namespace GodotTools.Build
if (_msbuildUnixPath.Empty())
{
- throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameMsbuildMono}'");
+ throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMsbuildMono}'");
}
return _msbuildUnixPath;
diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs
index 858e852392..70bd552f2f 100644
--- a/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs
@@ -7,7 +7,7 @@ using Path = System.IO.Path;
namespace GodotTools
{
[Serializable]
- public sealed class MonoBuildInfo : Reference // TODO Remove Reference once we have proper serialization
+ public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization
{
public string Solution { get; }
public string Configuration { get; }
@@ -17,7 +17,7 @@ namespace GodotTools
public override bool Equals(object obj)
{
- if (obj is MonoBuildInfo other)
+ if (obj is BuildInfo other)
return other.Solution == Solution && other.Configuration == Configuration;
return false;
@@ -34,11 +34,11 @@ namespace GodotTools
}
}
- private MonoBuildInfo()
+ private BuildInfo()
{
}
- public MonoBuildInfo(string solution, string configuration)
+ public BuildInfo(string solution, string configuration)
{
Solution = solution;
Configuration = configuration;
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
index de3a4d9a6e..417032da54 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
@@ -6,15 +6,13 @@ using GodotTools.Build;
using GodotTools.Internals;
using GodotTools.Utils;
using static GodotTools.Internals.Globals;
-using Error = Godot.Error;
using File = GodotTools.Utils.File;
-using Directory = GodotTools.Utils.Directory;
namespace GodotTools
{
- public static class GodotSharpBuilds
+ public static class BuildManager
{
- private static readonly List<MonoBuildInfo> BuildsInProgress = new List<MonoBuildInfo>();
+ private static readonly List<BuildInfo> BuildsInProgress = new List<BuildInfo>();
public const string PropNameMsbuildMono = "MSBuild (Mono)";
public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)";
@@ -28,7 +26,7 @@ namespace GodotTools
MsBuildVs
}
- private static void RemoveOldIssuesFile(MonoBuildInfo buildInfo)
+ private static void RemoveOldIssuesFile(BuildInfo buildInfo)
{
var issuesFile = GetIssuesFilePath(buildInfo);
@@ -38,29 +36,21 @@ namespace GodotTools
File.Delete(issuesFile);
}
- private static string _ApiFolderName(ApiAssemblyType apiType)
- {
- ulong apiHash = apiType == ApiAssemblyType.Core ?
- Internal.GetCoreApiHash() :
- Internal.GetEditorApiHash();
- return $"{apiHash}_{BindingsGenerator.Version}_{BindingsGenerator.CsGlueVersion}";
- }
-
private static void ShowBuildErrorDialog(string message)
{
GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error");
- GodotSharpEditor.Instance.MonoBottomPanel.ShowBuildTab();
+ GodotSharpEditor.Instance.BottomPanel.ShowBuildTab();
}
- public static void RestartBuild(MonoBuildTab buildTab) => throw new NotImplementedException();
- public static void StopBuild(MonoBuildTab buildTab) => throw new NotImplementedException();
+ public static void RestartBuild(BuildTab buildTab) => throw new NotImplementedException();
+ public static void StopBuild(BuildTab buildTab) => throw new NotImplementedException();
- private static string GetLogFilePath(MonoBuildInfo buildInfo)
+ private static string GetLogFilePath(BuildInfo buildInfo)
{
return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName);
}
- private static string GetIssuesFilePath(MonoBuildInfo buildInfo)
+ private static string GetIssuesFilePath(BuildInfo buildInfo)
{
return Path.Combine(buildInfo.LogsDirPath, MsBuildIssuesFileName);
}
@@ -71,7 +61,7 @@ namespace GodotTools
Godot.GD.Print(text);
}
- public static bool Build(MonoBuildInfo buildInfo)
+ public static bool Build(BuildInfo buildInfo)
{
if (BuildsInProgress.Contains(buildInfo))
throw new InvalidOperationException("A build is already in progress");
@@ -80,7 +70,7 @@ namespace GodotTools
try
{
- MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo);
+ BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo);
buildTab.OnBuildStart();
// Required in order to update the build tasks list
@@ -103,7 +93,7 @@ namespace GodotTools
if (exitCode != 0)
PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
- buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error);
+ buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error);
return exitCode == 0;
}
@@ -120,7 +110,7 @@ namespace GodotTools
}
}
- public static async Task<bool> BuildAsync(MonoBuildInfo buildInfo)
+ public static async Task<bool> BuildAsync(BuildInfo buildInfo)
{
if (BuildsInProgress.Contains(buildInfo))
throw new InvalidOperationException("A build is already in progress");
@@ -129,7 +119,7 @@ namespace GodotTools
try
{
- MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo);
+ BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo);
try
{
@@ -148,7 +138,7 @@ namespace GodotTools
if (exitCode != 0)
PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
- buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error);
+ buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error);
return exitCode == 0;
}
@@ -165,32 +155,6 @@ namespace GodotTools
}
}
- public static bool BuildApiSolution(string apiSlnDir, string config)
- {
- string apiSlnFile = Path.Combine(apiSlnDir, $"{ApiAssemblyNames.SolutionName}.sln");
-
- string coreApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Core, "bin", config);
- string coreApiAssemblyFile = Path.Combine(coreApiAssemblyDir, $"{ApiAssemblyNames.Core}.dll");
-
- string editorApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Editor, "bin", config);
- string editorApiAssemblyFile = Path.Combine(editorApiAssemblyDir, $"{ApiAssemblyNames.Editor}.dll");
-
- if (File.Exists(coreApiAssemblyFile) && File.Exists(editorApiAssemblyFile))
- return true; // The assemblies are in the output folder; assume the solution is already built
-
- var apiBuildInfo = new MonoBuildInfo(apiSlnFile, config);
-
- // TODO Replace this global NoWarn with '#pragma warning' directives on generated files,
- // once we start to actively document manually maintained C# classes
- apiBuildInfo.CustomProperties.Add("NoWarn=1591"); // Ignore missing documentation warnings
-
- if (Build(apiBuildInfo))
- return true;
-
- ShowBuildErrorDialog($"Failed to build {ApiAssemblyNames.SolutionName} solution.");
- return false;
- }
-
public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines)
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
@@ -201,13 +165,13 @@ namespace GodotTools
Internal.UpdateApiAssembliesFromPrebuilt();
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
+ var buildTool = (BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
{
pr.Step("Building project solution", 0);
- var buildInfo = new MonoBuildInfo(GodotSharpDirs.ProjectSlnPath, config);
+ var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, config);
// Add Godot defines
string constants = buildTool == BuildTool.MsBuildVs ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
@@ -240,11 +204,28 @@ namespace GodotTools
string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
- CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
+ CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
if (File.Exists(editorScriptsMetadataPath))
File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
+ var currentPlayRequest = GodotSharpEditor.Instance.GodotIdeManager.GodotIdeServer.CurrentPlayRequest;
+
+ if (currentPlayRequest != null)
+ {
+ if (currentPlayRequest.Value.HasDebugger)
+ {
+ // Set the environment variable that will tell the player to connect to the IDE debugger
+ // TODO: We should probably add a better way to do this
+ Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT",
+ "--debugger-agent=transport=dt_socket" +
+ $",address={currentPlayRequest.Value.DebuggerHost}:{currentPlayRequest.Value.DebuggerPort}" +
+ ",server=n");
+ }
+
+ return true; // Requested play from an external editor/IDE which already built the project
+ }
+
var godotDefines = new[]
{
Godot.OS.GetName(),
diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs
index 3a74fa2f66..807a20d9a1 100644
--- a/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs
@@ -7,7 +7,7 @@ using Path = System.IO.Path;
namespace GodotTools
{
- public class MonoBuildTab : VBoxContainer
+ public class BuildTab : VBoxContainer
{
public enum BuildResults
{
@@ -55,7 +55,7 @@ namespace GodotTools
}
}
- public MonoBuildInfo BuildInfo { get; private set; }
+ public BuildInfo BuildInfo { get; private set; }
private void _LoadIssuesFromFile(string csvFile)
{
@@ -199,7 +199,7 @@ namespace GodotTools
ErrorCount = 0;
UpdateIssuesList();
- GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
+ GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
}
public void OnBuildExit(BuildResults result)
@@ -207,10 +207,10 @@ namespace GodotTools
BuildExited = true;
BuildResult = result;
- _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildIssuesFileName));
+ _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName));
UpdateIssuesList();
- GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
+ GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
}
public void OnBuildExecFailed(string cause)
@@ -227,7 +227,7 @@ namespace GodotTools
UpdateIssuesList();
- GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
+ GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
}
public void RestartBuild()
@@ -235,7 +235,7 @@ namespace GodotTools
if (!BuildExited)
throw new InvalidOperationException("Build already started");
- GodotSharpBuilds.RestartBuild(this);
+ BuildManager.RestartBuild(this);
}
public void StopBuild()
@@ -243,7 +243,7 @@ namespace GodotTools
if (!BuildExited)
throw new InvalidOperationException("Build is not in progress");
- GodotSharpBuilds.StopBuild(this);
+ BuildManager.StopBuild(this);
}
public override void _Ready()
@@ -255,11 +255,11 @@ namespace GodotTools
AddChild(issuesList);
}
- private MonoBuildTab()
+ private BuildTab()
{
}
- public MonoBuildTab(MonoBuildInfo buildInfo)
+ public BuildTab(BuildInfo buildInfo)
{
BuildInfo = buildInfo;
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
index 4535ed7247..c021a9051e 100644
--- a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
@@ -9,7 +9,7 @@ using Directory = GodotTools.Utils.Directory;
namespace GodotTools
{
- public static class CSharpProject
+ public static class CsProjOperations
{
public static string GenerateGameProject(string dir, string name)
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs
new file mode 100644
index 0000000000..4312ca0230
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs
@@ -0,0 +1,11 @@
+namespace GodotTools
+{
+ public enum ExternalEditorId
+ {
+ None,
+ VisualStudio, // TODO (Windows-only)
+ VisualStudioForMac, // Mac-only
+ MonoDevelop,
+ VsCode
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 9b5afb94a3..099c7fcb56 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -2,16 +2,18 @@ using Godot;
using GodotTools.Utils;
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
+using GodotTools.Ides;
using GodotTools.Internals;
using GodotTools.ProjectEditor;
using static GodotTools.Internals.Globals;
using File = GodotTools.Utils.File;
-using Path = System.IO.Path;
using OS = GodotTools.Utils.OS;
namespace GodotTools
{
+ [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
public class GodotSharpEditor : EditorPlugin, ISerializationListener
{
private EditorSettings editorSettings;
@@ -24,12 +26,11 @@ namespace GodotTools
private ToolButton bottomPanelBtn;
- private MonoDevelopInstance monoDevelopInstance;
- private MonoDevelopInstance visualStudioForMacInstance;
+ public GodotIdeManager GodotIdeManager { get; private set; }
private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization
- public MonoBottomPanel MonoBottomPanel { get; private set; }
+ public BottomPanel BottomPanel { get; private set; }
private bool CreateProjectSolution()
{
@@ -44,7 +45,7 @@ namespace GodotTools
if (name.Empty())
name = "UnnamedProject";
- string guid = CSharpProject.GenerateGameProject(path, name);
+ string guid = CsProjOperations.GenerateGameProject(path, name);
if (guid.Length > 0)
{
@@ -133,7 +134,7 @@ namespace GodotTools
return; // Failed to create solution
}
- Instance.MonoBottomPanel.BuildProjectPressed();
+ Instance.BottomPanel.BuildProjectPressed();
}
public override void _Notification(int what)
@@ -153,21 +154,12 @@ namespace GodotTools
}
}
- public enum MenuOptions
+ private enum MenuOptions
{
CreateSln,
AboutCSharp,
}
- public enum ExternalEditor
- {
- None,
- VisualStudio, // TODO (Windows-only)
- VisualStudioForMac, // Mac-only
- MonoDevelop,
- VsCode
- }
-
public void ShowErrorDialog(string message, string title = "Error")
{
errorDialog.WindowTitle = title;
@@ -184,11 +176,30 @@ namespace GodotTools
public Error OpenInExternalEditor(Script script, int line, int col)
{
- var editor = (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor");
+ var editor = (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor");
switch (editor)
{
- case ExternalEditor.VsCode:
+ case ExternalEditorId.None:
+ // Tells the caller to fallback to the global external editor settings or the built-in editor
+ return Error.Unavailable;
+ case ExternalEditorId.VisualStudio:
+ throw new NotSupportedException();
+ case ExternalEditorId.VisualStudioForMac:
+ goto case ExternalEditorId.MonoDevelop;
+ case ExternalEditorId.MonoDevelop:
+ {
+ string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
+
+ if (line >= 0)
+ GodotIdeManager.SendOpenFile(scriptPath, line + 1, col);
+ else
+ GodotIdeManager.SendOpenFile(scriptPath);
+
+ break;
+ }
+
+ case ExternalEditorId.VsCode:
{
if (_vsCodePath.Empty() || !File.Exists(_vsCodePath))
{
@@ -273,47 +284,6 @@ namespace GodotTools
break;
}
- case ExternalEditor.VisualStudioForMac:
- goto case ExternalEditor.MonoDevelop;
- case ExternalEditor.MonoDevelop:
- {
- MonoDevelopInstance GetMonoDevelopInstance(string solutionPath)
- {
- if (OS.IsOSX() && editor == ExternalEditor.VisualStudioForMac)
- {
- if (visualStudioForMacInstance == null)
- visualStudioForMacInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.VisualStudioForMac);
-
- return visualStudioForMacInstance;
- }
-
- if (monoDevelopInstance == null)
- monoDevelopInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.MonoDevelop);
-
- return monoDevelopInstance;
- }
-
- string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
-
- if (line >= 0)
- scriptPath += $";{line + 1};{col}";
-
- try
- {
- GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath);
- }
- catch (FileNotFoundException)
- {
- string editorName = editor == ExternalEditor.VisualStudioForMac ? "Visual Studio" : "MonoDevelop";
- GD.PushError($"Cannot find code editor: {editorName}");
- return Error.FileNotFound;
- }
-
- break;
- }
-
- case ExternalEditor.None:
- return Error.Unavailable;
default:
throw new ArgumentOutOfRangeException();
}
@@ -323,12 +293,12 @@ namespace GodotTools
public bool OverridesExternalEditor()
{
- return (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditor.None;
+ return (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None;
}
public override bool Build()
{
- return GodotSharpBuilds.EditorBuildCallback();
+ return BuildManager.EditorBuildCallback();
}
public override void EnablePlugin()
@@ -347,9 +317,9 @@ namespace GodotTools
errorDialog = new AcceptDialog();
editorBaseControl.AddChild(errorDialog);
- MonoBottomPanel = new MonoBottomPanel();
+ BottomPanel = new BottomPanel();
- bottomPanelBtn = AddControlToBottomPanel(MonoBottomPanel, "Mono".TTR());
+ bottomPanelBtn = AddControlToBottomPanel(BottomPanel, "Mono".TTR());
AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"});
@@ -407,7 +377,7 @@ namespace GodotTools
if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
{
// Make sure the existing project has Api assembly references configured correctly
- CSharpProject.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath);
+ CsProjOperations.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath);
}
else
{
@@ -427,25 +397,25 @@ namespace GodotTools
AddControlToContainer(CustomControlContainer.Toolbar, buildButton);
// External editor settings
- EditorDef("mono/editor/external_editor", ExternalEditor.None);
+ EditorDef("mono/editor/external_editor", ExternalEditorId.None);
string settingsHintStr = "Disabled";
if (OS.IsWindows())
{
- settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
- $",Visual Studio Code:{(int) ExternalEditor.VsCode}";
+ settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
+ $",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
}
else if (OS.IsOSX())
{
- settingsHintStr += $",Visual Studio:{(int) ExternalEditor.VisualStudioForMac}" +
- $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
- $",Visual Studio Code:{(int) ExternalEditor.VsCode}";
+ settingsHintStr += $",Visual Studio:{(int) ExternalEditorId.VisualStudioForMac}" +
+ $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
+ $",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
}
else if (OS.IsUnix())
{
- settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
- $",Visual Studio Code:{(int) ExternalEditor.VsCode}";
+ settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
+ $",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
}
editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
@@ -461,7 +431,10 @@ namespace GodotTools
AddExportPlugin(exportPlugin);
exportPluginWeak = WeakRef(exportPlugin);
- GodotSharpBuilds.Initialize();
+ BuildManager.Initialize();
+
+ GodotIdeManager = new GodotIdeManager();
+ AddChild(GodotIdeManager);
}
protected override void Dispose(bool disposing)
@@ -478,6 +451,8 @@ namespace GodotTools
exportPluginWeak.Dispose();
}
+
+ GodotIdeManager?.Dispose();
}
public void OnBeforeSerialize()
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs
index b80fe1fab7..aefc51545e 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs
@@ -65,14 +65,14 @@ namespace GodotTools
string buildConfig = isDebug ? "Debug" : "Release";
string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}");
- CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);
+ CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);
AddFile(scriptsMetadataPath, scriptsMetadataPath);
// Turn export features into defines
var godotDefines = features;
- if (!GodotSharpBuilds.BuildProjectBlocking(buildConfig, godotDefines))
+ if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines))
{
GD.PushError("Failed to build project");
return;
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
index 01e8c87d14..e2d576caef 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
@@ -39,26 +39,31 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Build\MsBuildFinder.cs" />
+ <Compile Include="ExternalEditorId.cs" />
+ <Compile Include="Ides\GodotIdeManager.cs" />
+ <Compile Include="Ides\GodotIdeServer.cs" />
+ <Compile Include="Ides\MonoDevelop\EditorId.cs" />
+ <Compile Include="Ides\MonoDevelop\Instance.cs" />
<Compile Include="Internals\BindingsGenerator.cs" />
<Compile Include="Internals\EditorProgress.cs" />
<Compile Include="Internals\GodotSharpDirs.cs" />
<Compile Include="Internals\Internal.cs" />
<Compile Include="Internals\ScriptClassParser.cs" />
<Compile Include="Internals\Globals.cs" />
- <Compile Include="MonoDevelopInstance.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Build\BuildSystem.cs" />
<Compile Include="Utils\Directory.cs" />
<Compile Include="Utils\File.cs" />
+ <Compile Include="Utils\NotifyAwaiter.cs" />
<Compile Include="Utils\OS.cs" />
<Compile Include="GodotSharpEditor.cs" />
- <Compile Include="GodotSharpBuilds.cs" />
+ <Compile Include="BuildManager.cs" />
<Compile Include="HotReloadAssemblyWatcher.cs" />
- <Compile Include="MonoBuildInfo.cs" />
- <Compile Include="MonoBuildTab.cs" />
- <Compile Include="MonoBottomPanel.cs" />
+ <Compile Include="BuildInfo.cs" />
+ <Compile Include="BuildTab.cs" />
+ <Compile Include="BottomPanel.cs" />
<Compile Include="GodotSharpExport.cs" />
- <Compile Include="CSharpProject.cs" />
+ <Compile Include="CsProjOperations.cs" />
<Compile Include="Utils\CollectionExtensions.cs" />
</ItemGroup>
<ItemGroup>
@@ -66,6 +71,10 @@
<Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project>
<Name>GodotTools.BuildLogger</Name>
</ProjectReference>
+ <ProjectReference Include="..\GodotTools.IdeConnection\GodotTools.IdeConnection.csproj">
+ <Project>{92600954-25f0-4291-8e11-1fee9fc4be20}</Project>
+ <Name>GodotTools.IdeConnection</Name>
+ </ProjectReference>
<ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj">
<Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project>
<Name>GodotTools.ProjectEditor</Name>
@@ -75,8 +84,5 @@
<Name>GodotTools.Core</Name>
</ProjectReference>
</ItemGroup>
- <ItemGroup>
- <Folder Include="Editor" />
- </ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project> \ No newline at end of file
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
new file mode 100644
index 0000000000..9e24138143
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
@@ -0,0 +1,166 @@
+using System;
+using System.IO;
+using Godot;
+using GodotTools.IdeConnection;
+using GodotTools.Internals;
+
+namespace GodotTools.Ides
+{
+ public class GodotIdeManager : Node, ISerializationListener
+ {
+ public GodotIdeServer GodotIdeServer { get; private set; }
+
+ private MonoDevelop.Instance monoDevelInstance;
+ private MonoDevelop.Instance vsForMacInstance;
+
+ private GodotIdeServer GetRunningServer()
+ {
+ if (GodotIdeServer != null && !GodotIdeServer.IsDisposed)
+ return GodotIdeServer;
+ StartServer();
+ return GodotIdeServer;
+ }
+
+ public override void _Ready()
+ {
+ StartServer();
+ }
+
+ public void OnBeforeSerialize()
+ {
+ GodotIdeServer?.Dispose();
+ }
+
+ public void OnAfterDeserialize()
+ {
+ StartServer();
+ }
+
+ private ILogger logger;
+
+ protected ILogger Logger
+ {
+ get => logger ?? (logger = new ConsoleLogger());
+ set => logger = value;
+ }
+
+ private void StartServer()
+ {
+ GodotIdeServer?.Dispose();
+ GodotIdeServer = new GodotIdeServer(LaunchIde,
+ OS.GetExecutablePath(),
+ ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir));
+
+ GodotIdeServer.Logger = Logger;
+
+ GodotIdeServer.StartServer();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ GodotIdeServer?.Dispose();
+ }
+
+ private void LaunchIde()
+ {
+ var editor = (ExternalEditorId) GodotSharpEditor.Instance.GetEditorInterface()
+ .GetEditorSettings().GetSetting("mono/editor/external_editor");
+
+ switch (editor)
+ {
+ case ExternalEditorId.None:
+ case ExternalEditorId.VisualStudio:
+ case ExternalEditorId.VsCode:
+ throw new NotSupportedException();
+ case ExternalEditorId.VisualStudioForMac:
+ goto case ExternalEditorId.MonoDevelop;
+ case ExternalEditorId.MonoDevelop:
+ {
+ MonoDevelop.Instance GetMonoDevelopInstance(string solutionPath)
+ {
+ if (Utils.OS.IsOSX() && editor == ExternalEditorId.VisualStudioForMac)
+ {
+ vsForMacInstance = vsForMacInstance ??
+ new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac);
+ return vsForMacInstance;
+ }
+
+ monoDevelInstance = monoDevelInstance ??
+ new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop);
+ return monoDevelInstance;
+ }
+
+ try
+ {
+ var instance = GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath);
+
+ if (!instance.IsRunning)
+ instance.Execute();
+ }
+ catch (FileNotFoundException)
+ {
+ string editorName = editor == ExternalEditorId.VisualStudioForMac ? "Visual Studio" : "MonoDevelop";
+ GD.PushError($"Cannot find code editor: {editorName}");
+ }
+
+ break;
+ }
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void WriteMessage(string id, params string[] arguments)
+ {
+ GetRunningServer().WriteMessage(new Message(id, arguments));
+ }
+
+ public void SendOpenFile(string file)
+ {
+ WriteMessage("OpenFile", file);
+ }
+
+ public void SendOpenFile(string file, int line)
+ {
+ WriteMessage("OpenFile", file, line.ToString());
+ }
+
+ public void SendOpenFile(string file, int line, int column)
+ {
+ WriteMessage("OpenFile", file, line.ToString(), column.ToString());
+ }
+
+ private class GodotLogger : ILogger
+ {
+ public void LogDebug(string message)
+ {
+ if (OS.IsStdoutVerbose())
+ Console.WriteLine(message);
+ }
+
+ public void LogInfo(string message)
+ {
+ if (OS.IsStdoutVerbose())
+ Console.WriteLine(message);
+ }
+
+ public void LogWarning(string message)
+ {
+ GD.PushWarning(message);
+ }
+
+ public void LogError(string message)
+ {
+ GD.PushError(message);
+ }
+
+ public void LogError(string message, Exception e)
+ {
+ GD.PushError(message + "\n" + e);
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs
new file mode 100644
index 0000000000..d515254e65
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs
@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using GodotTools.IdeConnection;
+using GodotTools.Internals;
+using GodotTools.Utils;
+using File = System.IO.File;
+using Thread = System.Threading.Thread;
+
+namespace GodotTools.Ides
+{
+ public class GodotIdeServer : GodotIdeBase
+ {
+ private readonly TcpListener listener;
+ private readonly FileStream metaFile;
+ private readonly Action launchIdeAction;
+ private readonly NotifyAwaiter<bool> clientConnectedAwaiter = new NotifyAwaiter<bool>();
+
+ private async Task<bool> AwaitClientConnected()
+ {
+ return await clientConnectedAwaiter.Reset();
+ }
+
+ public GodotIdeServer(Action launchIdeAction, string editorExecutablePath, string projectMetadataDir)
+ : base(projectMetadataDir)
+ {
+ messageHandlers = InitializeMessageHandlers();
+
+ this.launchIdeAction = launchIdeAction;
+
+ // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing...
+ const FileShare metaFileShare = FileShare.ReadWrite;
+
+ metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare);
+
+ listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0));
+ listener.Start();
+
+ int port = ((IPEndPoint) listener.Server.LocalEndPoint).Port;
+ using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8))
+ {
+ metaFileWriter.WriteLine(port);
+ metaFileWriter.WriteLine(editorExecutablePath);
+ }
+
+ StartServer();
+ }
+
+ public void StartServer()
+ {
+ var serverThread = new Thread(RunServerThread) {Name = "Godot Ide Connection Server"};
+ serverThread.Start();
+ }
+
+ private void RunServerThread()
+ {
+ SynchronizationContext.SetSynchronizationContext(Godot.Dispatcher.SynchronizationContext);
+
+ try
+ {
+ while (!IsDisposed)
+ {
+ TcpClient tcpClient = listener.AcceptTcpClient();
+
+ Logger.LogInfo("Connection open with Ide Client");
+
+ lock (ConnectionLock)
+ {
+ Connection = new GodotIdeConnectionServer(tcpClient, HandleMessage);
+ Connection.Logger = Logger;
+ }
+
+ Connected += () => clientConnectedAwaiter.SetResult(true);
+
+ Connection.Start();
+ }
+ }
+ catch (Exception e)
+ {
+ if (!IsDisposed && !(e is SocketException se && se.SocketErrorCode == SocketError.Interrupted))
+ throw;
+ }
+ }
+
+ public async void WriteMessage(Message message)
+ {
+ async Task LaunchIde()
+ {
+ if (IsConnected)
+ return;
+
+ launchIdeAction();
+ await Task.WhenAny(Task.Delay(10000), AwaitClientConnected());
+ }
+
+ await LaunchIde();
+
+ if (!IsConnected)
+ {
+ Logger.LogError("Cannot write message: Godot Ide Server not connected");
+ return;
+ }
+
+ Connection.WriteMessage(message);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ listener?.Stop();
+
+ metaFile?.Dispose();
+
+ File.Delete(MetaFilePath);
+ }
+ }
+
+ 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[]>>
+ {
+ ["Play"] = args =>
+ {
+ switch (args.Length)
+ {
+ case 0:
+ Play();
+ return;
+ case 2:
+ Play(debuggerHost: args[0], debuggerPort: int.Parse(args[1]));
+ return;
+ default:
+ throw new ArgumentException();
+ }
+ },
+ ["ReloadScripts"] = args => ReloadScripts()
+ };
+ }
+
+ private void DispatchToMainThread(Action action)
+ {
+ var d = new SendOrPostCallback(state => action());
+ Godot.Dispatcher.SynchronizationContext.Post(d, null);
+ }
+
+ private void Play()
+ {
+ DispatchToMainThread(() =>
+ {
+ CurrentPlayRequest = new PlayRequest();
+ Internal.EditorRunPlay();
+ CurrentPlayRequest = null;
+ });
+ }
+
+ private void Play(string debuggerHost, int debuggerPort)
+ {
+ DispatchToMainThread(() =>
+ {
+ CurrentPlayRequest = new PlayRequest(debuggerHost, debuggerPort);
+ Internal.EditorRunPlay();
+ CurrentPlayRequest = null;
+ });
+ }
+
+ private void ReloadScripts()
+ {
+ DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts);
+ }
+
+ public PlayRequest? CurrentPlayRequest { get; private set; }
+
+ public struct PlayRequest
+ {
+ public bool HasDebugger { get; }
+ public string DebuggerHost { get; }
+ public int DebuggerPort { get; }
+
+ public PlayRequest(string debuggerHost, int debuggerPort)
+ {
+ HasDebugger = true;
+ DebuggerHost = debuggerHost;
+ DebuggerPort = debuggerPort;
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs
new file mode 100644
index 0000000000..1dfc91d6d1
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/EditorId.cs
@@ -0,0 +1,8 @@
+namespace GodotTools.Ides.MonoDevelop
+{
+ public enum EditorId
+ {
+ MonoDevelop = 0,
+ VisualStudioForMac = 1
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
index 61a0a992ce..1fdccf5bbd 100644
--- a/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
@@ -1,4 +1,3 @@
-using GodotTools.Core;
using System;
using System.IO;
using System.Collections.Generic;
@@ -6,22 +5,18 @@ using System.Diagnostics;
using GodotTools.Internals;
using GodotTools.Utils;
-namespace GodotTools
+namespace GodotTools.Ides.MonoDevelop
{
- public class MonoDevelopInstance
+ public class Instance
{
- public enum EditorId
- {
- MonoDevelop = 0,
- VisualStudioForMac = 1
- }
-
private readonly string solutionFile;
private readonly EditorId editorId;
private Process process;
- public void Execute(params string[] files)
+ public bool IsRunning => process != null && !process.HasExited;
+
+ public void Execute()
{
bool newWindow = process == null || process.HasExited;
@@ -29,7 +24,7 @@ namespace GodotTools
string command;
- if (Utils.OS.IsOSX())
+ if (OS.IsOSX())
{
string bundleId = BundleIds[editorId];
@@ -61,16 +56,6 @@ namespace GodotTools
if (newWindow)
args.Add("\"" + Path.GetFullPath(solutionFile) + "\"");
- foreach (var file in files)
- {
- int semicolonIndex = file.IndexOf(';');
-
- string filePath = semicolonIndex < 0 ? file : file.Substring(0, semicolonIndex);
- string cursor = semicolonIndex < 0 ? string.Empty : file.Substring(semicolonIndex);
-
- args.Add("\"" + Path.GetFullPath(filePath.NormalizePath()) + cursor + "\"");
- }
-
if (command == null)
throw new FileNotFoundException();
@@ -80,7 +65,7 @@ namespace GodotTools
{
FileName = command,
Arguments = string.Join(" ", args),
- UseShellExecute = false
+ UseShellExecute = true
});
}
else
@@ -89,14 +74,14 @@ namespace GodotTools
{
FileName = command,
Arguments = string.Join(" ", args),
- UseShellExecute = false
+ UseShellExecute = true
})?.Dispose();
}
}
- public MonoDevelopInstance(string solutionFile, EditorId editorId)
+ public Instance(string solutionFile, EditorId editorId)
{
- if (editorId == EditorId.VisualStudioForMac && !Utils.OS.IsOSX())
+ if (editorId == EditorId.VisualStudioForMac && !OS.IsOSX())
throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform");
this.solutionFile = solutionFile;
@@ -106,9 +91,9 @@ namespace GodotTools
private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames;
private static readonly IReadOnlyDictionary<EditorId, string> BundleIds;
- static MonoDevelopInstance()
+ static Instance()
{
- if (Utils.OS.IsOSX())
+ if (OS.IsOSX())
{
ExecutableNames = new Dictionary<EditorId, string>
{
@@ -122,7 +107,7 @@ namespace GodotTools
{EditorId.VisualStudioForMac, "com.microsoft.visual-studio"}
};
}
- else if (Utils.OS.IsWindows())
+ else if (OS.IsWindows())
{
ExecutableNames = new Dictionary<EditorId, string>
{
@@ -133,7 +118,7 @@ namespace GodotTools
{EditorId.MonoDevelop, "MonoDevelop.exe"}
};
}
- else if (Utils.OS.IsUnix())
+ else if (OS.IsUnix())
{
ExecutableNames = new Dictionary<EditorId, string>
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
index 9526dd3c6f..7783576910 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
@@ -46,6 +46,12 @@ namespace GodotTools.Internals
public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot();
+ public static void EditorRunPlay() => internal_EditorRunPlay();
+
+ public static void EditorRunStop() => internal_EditorRunStop();
+
+ public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts();
+
// Internal Calls
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -95,5 +101,14 @@ namespace GodotTools.Internals
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoWindowsInstallRoot();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_EditorRunPlay();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_EditorRunStop();
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void internal_ScriptEditorDebugger_ReloadScripts();
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs
index 288c65de74..e3c2c822a5 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
namespace GodotTools.Utils
{
@@ -17,5 +18,12 @@ namespace GodotTools.Utils
return orElse;
}
+
+ public static IEnumerable<string> EnumerateLines(this TextReader textReader)
+ {
+ string line;
+ while ((line = textReader.ReadLine()) != null)
+ yield return line;
+ }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs
new file mode 100644
index 0000000000..a3490fa89f
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace GodotTools.Utils
+{
+ public sealed class NotifyAwaiter<T> : INotifyCompletion
+ {
+ private Action continuation;
+ private Exception exception;
+ private T result;
+
+ public bool IsCompleted { get; private set; }
+
+ public T GetResult()
+ {
+ if (exception != null)
+ throw exception;
+ return result;
+ }
+
+ public void OnCompleted(Action continuation)
+ {
+ if (this.continuation != null)
+ throw new InvalidOperationException("This awaiter has already been listened");
+ this.continuation = continuation;
+ }
+
+ public void SetResult(T result)
+ {
+ if (IsCompleted)
+ throw new InvalidOperationException("This awaiter is already completed");
+
+ IsCompleted = true;
+ this.result = result;
+
+ continuation?.Invoke();
+ }
+
+ public void SetException(Exception exception)
+ {
+ if (IsCompleted)
+ throw new InvalidOperationException("This awaiter is already completed");
+
+ IsCompleted = true;
+ this.exception = exception;
+
+ continuation?.Invoke();
+ }
+
+ public NotifyAwaiter<T> Reset()
+ {
+ continuation = null;
+ exception = null;
+ result = default;
+ IsCompleted = false;
+ return this;
+ }
+
+ public NotifyAwaiter<T> GetAwaiter()
+ {
+ return this;
+ }
+ }
+}