summaryrefslogtreecommitdiff
path: root/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI
diff options
context:
space:
mode:
Diffstat (limited to 'modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI')
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs57
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj17
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs218
3 files changed, 292 insertions, 0 deletions
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs
new file mode 100644
index 0000000000..3cb6a6687e
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs
@@ -0,0 +1,57 @@
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging.Utils;
+
+namespace GodotTools.IdeMessaging.CLI
+{
+ public class ForwarderMessageHandler : IMessageHandler
+ {
+ private readonly StreamWriter outputWriter;
+ private readonly SemaphoreSlim outputWriteSem = new SemaphoreSlim(1);
+
+ public ForwarderMessageHandler(StreamWriter outputWriter)
+ {
+ this.outputWriter = outputWriter;
+ }
+
+ public async Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger)
+ {
+ await WriteRequestToOutput(id, content);
+ return new MessageContent(MessageStatus.RequestNotSupported, "null");
+ }
+
+ private async Task WriteRequestToOutput(string id, MessageContent content)
+ {
+ using (await outputWriteSem.UseAsync())
+ {
+ await outputWriter.WriteLineAsync("======= Request =======");
+ await outputWriter.WriteLineAsync(id);
+ await outputWriter.WriteLineAsync(content.Body.Count(c => c == '\n').ToString());
+ await outputWriter.WriteLineAsync(content.Body);
+ await outputWriter.WriteLineAsync("=======================");
+ await outputWriter.FlushAsync();
+ }
+ }
+
+ public async Task WriteResponseToOutput(string id, MessageContent content)
+ {
+ using (await outputWriteSem.UseAsync())
+ {
+ await outputWriter.WriteLineAsync("======= Response =======");
+ await outputWriter.WriteLineAsync(id);
+ await outputWriter.WriteLineAsync(content.Body.Count(c => c == '\n').ToString());
+ await outputWriter.WriteLineAsync(content.Body);
+ await outputWriter.WriteLineAsync("========================");
+ await outputWriter.FlushAsync();
+ }
+ }
+
+ public async Task WriteLineToOutput(string eventName)
+ {
+ using (await outputWriteSem.UseAsync())
+ await outputWriter.WriteLineAsync($"======= {eventName} =======");
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj
new file mode 100644
index 0000000000..ae78da27bc
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <ProjectGuid>{B06C2951-C8E3-4F28-80B2-717CF327EB19}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net472</TargetFramework>
+ <LangVersion>7.2</LangVersion>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" />
+ </ItemGroup>
+ <ItemGroup>
+ <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+ </ItemGroup>
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs
new file mode 100644
index 0000000000..99a55c471b
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs
@@ -0,0 +1,218 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging.Requests;
+using Newtonsoft.Json;
+
+namespace GodotTools.IdeMessaging.CLI
+{
+ internal static class Program
+ {
+ private static readonly ILogger Logger = new CustomLogger();
+
+ public static int Main(string[] args)
+ {
+ try
+ {
+ var mainTask = StartAsync(args, Console.OpenStandardInput(), Console.OpenStandardOutput());
+ mainTask.Wait();
+ return mainTask.Result;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Unhandled exception: ", ex);
+ return 1;
+ }
+ }
+
+ private static async Task<int> StartAsync(string[] args, Stream inputStream, Stream outputStream)
+ {
+ var inputReader = new StreamReader(inputStream, Encoding.UTF8);
+ var outputWriter = new StreamWriter(outputStream, Encoding.UTF8);
+
+ try
+ {
+ if (args.Length == 0)
+ {
+ Logger.LogError("Expected at least 1 argument");
+ return 1;
+ }
+
+ string godotProjectDir = args[0];
+
+ if (!Directory.Exists(godotProjectDir))
+ {
+ Logger.LogError($"The specified Godot project directory does not exist: {godotProjectDir}");
+ return 1;
+ }
+
+ var forwarder = new ForwarderMessageHandler(outputWriter);
+
+ using (var fwdClient = new Client("VisualStudioCode", godotProjectDir, forwarder, Logger))
+ {
+ fwdClient.Start();
+
+ // ReSharper disable AccessToDisposedClosure
+ fwdClient.Connected += async () => await forwarder.WriteLineToOutput("Event=Connected");
+ fwdClient.Disconnected += async () => await forwarder.WriteLineToOutput("Event=Disconnected");
+ // ReSharper restore AccessToDisposedClosure
+
+ // TODO: Await connected with timeout
+
+ while (!fwdClient.IsDisposed)
+ {
+ string firstLine = await inputReader.ReadLineAsync();
+
+ if (firstLine == null || firstLine == "QUIT")
+ goto ExitMainLoop;
+
+ string messageId = firstLine;
+
+ string messageArgcLine = await inputReader.ReadLineAsync();
+
+ if (messageArgcLine == null)
+ {
+ Logger.LogInfo("EOF when expecting argument count");
+ goto ExitMainLoop;
+ }
+
+ if (!int.TryParse(messageArgcLine, out int messageArgc))
+ {
+ Logger.LogError("Received invalid line for argument count: " + firstLine);
+ continue;
+ }
+
+ var body = new StringBuilder();
+
+ for (int i = 0; i < messageArgc; i++)
+ {
+ string bodyLine = await inputReader.ReadLineAsync();
+
+ if (bodyLine == null)
+ {
+ Logger.LogInfo($"EOF when expecting body line #{i + 1}");
+ goto ExitMainLoop;
+ }
+
+ body.AppendLine(bodyLine);
+ }
+
+ var response = await SendRequest(fwdClient, messageId, new MessageContent(MessageStatus.Ok, body.ToString()));
+
+ if (response == null)
+ {
+ Logger.LogError($"Failed to write message to the server: {messageId}");
+ }
+ else
+ {
+ var content = new MessageContent(response.Status, JsonConvert.SerializeObject(response));
+ await forwarder.WriteResponseToOutput(messageId, content);
+ }
+ }
+
+ ExitMainLoop:
+
+ await forwarder.WriteLineToOutput("Event=Quit");
+ }
+
+ return 0;
+ }
+ catch (Exception e)
+ {
+ Logger.LogError("Unhandled exception", e);
+ return 1;
+ }
+ }
+
+ private static async Task<Response> SendRequest(Client client, string id, MessageContent content)
+ {
+ var handlers = new Dictionary<string, Func<Task<Response>>>
+ {
+ [PlayRequest.Id] = async () =>
+ {
+ var request = JsonConvert.DeserializeObject<PlayRequest>(content.Body);
+ return await client.SendRequest<PlayResponse>(request);
+ },
+ [DebugPlayRequest.Id] = async () =>
+ {
+ var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body);
+ return await client.SendRequest<DebugPlayResponse>(request);
+ },
+ [ReloadScriptsRequest.Id] = async () =>
+ {
+ var request = JsonConvert.DeserializeObject<ReloadScriptsRequest>(content.Body);
+ return await client.SendRequest<ReloadScriptsResponse>(request);
+ },
+ [CodeCompletionRequest.Id] = async () =>
+ {
+ var request = JsonConvert.DeserializeObject<CodeCompletionRequest>(content.Body);
+ return await client.SendRequest<CodeCompletionResponse>(request);
+ }
+ };
+
+ if (handlers.TryGetValue(id, out var handler))
+ return await handler();
+
+ Console.WriteLine("INVALID REQUEST");
+ return null;
+ }
+
+ private class CustomLogger : ILogger
+ {
+ private static string ThisAppPath => Assembly.GetExecutingAssembly().Location;
+ private static string ThisAppPathWithoutExtension => Path.ChangeExtension(ThisAppPath, null);
+
+ private static readonly string LogPath = $"{ThisAppPathWithoutExtension}.log";
+
+ private static StreamWriter NewWriter() => new StreamWriter(LogPath, append: true, encoding: Encoding.UTF8);
+
+ private static void Log(StreamWriter writer, string message)
+ {
+ writer.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}: {message}");
+ }
+
+ public void LogDebug(string message)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "DEBUG: " + message);
+ }
+ }
+
+ public void LogInfo(string message)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "INFO: " + message);
+ }
+ }
+
+ public void LogWarning(string message)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "WARN: " + message);
+ }
+ }
+
+ public void LogError(string message)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "ERROR: " + message);
+ }
+ }
+
+ public void LogError(string message, Exception e)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "EXCEPTION: " + message + '\n' + e);
+ }
+ }
+ }
+ }
+}