Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Fantomas out-of-process #252

Merged
merged 17 commits into from
May 13, 2021
14 changes: 14 additions & 0 deletions ReSharper.FSharp/ReSharper.FSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSharp.TypeProviders.Host",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSharp.TypeProviders.Protocol", "src\FSharp.TypeProviders.Protocol\FSharp.TypeProviders.Protocol.csproj", "{912A7F5F-73F2-4DFE-B81A-C13A4F58F31C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSharp.Fantomas.Host", "src\FSharp.Fantomas.Host\FSharp.Fantomas.Host.csproj", "{CF12A71A-1296-418B-8C0A-CC4B398AAB16}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSharp.Fantomas.Protocol", "src\FSharp.Fantomas.Protocol\FSharp.Fantomas.Protocol.csproj", "{D8DE097E-04DC-4A9B-88BF-10923FF44F8B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -67,6 +71,14 @@ Global
{912A7F5F-73F2-4DFE-B81A-C13A4F58F31C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{912A7F5F-73F2-4DFE-B81A-C13A4F58F31C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{912A7F5F-73F2-4DFE-B81A-C13A4F58F31C}.Release|Any CPU.Build.0 = Release|Any CPU
{CF12A71A-1296-418B-8C0A-CC4B398AAB16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CF12A71A-1296-418B-8C0A-CC4B398AAB16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CF12A71A-1296-418B-8C0A-CC4B398AAB16}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CF12A71A-1296-418B-8C0A-CC4B398AAB16}.Release|Any CPU.Build.0 = Release|Any CPU
{D8DE097E-04DC-4A9B-88BF-10923FF44F8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8DE097E-04DC-4A9B-88BF-10923FF44F8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8DE097E-04DC-4A9B-88BF-10923FF44F8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8DE097E-04DC-4A9B-88BF-10923FF44F8B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -81,5 +93,7 @@ Global
{83C5104C-057E-4AF8-B3FE-3437968CA621} = {7B41BA27-ACA5-43D2-9EBA-2E604D63A4E2}
{4EB01188-F511-4991-9A0F-604EE28E8F9B} = {2E19336B-3133-4C1A-8E65-E2F7C465663B}
{912A7F5F-73F2-4DFE-B81A-C13A4F58F31C} = {2E19336B-3133-4C1A-8E65-E2F7C465663B}
{CF12A71A-1296-418B-8C0A-CC4B398AAB16} = {2E19336B-3133-4C1A-8E65-E2F7C465663B}
{D8DE097E-04DC-4A9B-88BF-10923FF44F8B} = {2E19336B-3133-4C1A-8E65-E2F7C465663B}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<AssemblyName>JetBrains.ReSharper.Plugins.FSharp.Fantomas.Host</AssemblyName>
<OutputType>Exe</OutputType>
<LangVersion>$(CSharpLanguageVersion)</LangVersion>
<RootNamespace>JetBrains.ReSharper.Plugins.FSharp.Fantomas.Host</RootNamespace>
</PropertyGroup>

<!-- TODO: needed for 461 (because the default is x86), remove for netstandard/net5 -->
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<PlatformTarget>AnyCPU</PlatformTarget>
auduchinok marked this conversation as resolved.
Show resolved Hide resolved
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\FSharp.Fantomas.Protocol\FSharp.Fantomas.Protocol.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="JetBrains.Fantomas" Version="$(FantomasVersion)" />
</ItemGroup>

<ItemGroup>
<None Update="JetBrains.ReSharper.Plugins.FSharp.Fantomas.Host.runtimeconfig.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

<Import Project="$(DotNetSdkPath)\Build\SubplatformReference.Psi.Features_src.Props" Condition="Exists('$(DotNetSdkPath)\Build\SubplatformReference.Psi.Features_src.Props')" />
<Import Project="$(DotNetSdkPath)\Build\SubplatformReference.ReSharperAutomationTools_src_ReSharperHost.Props" Condition="Exists('$(DotNetSdkPath)\Build\SubplatformReference.ReSharperAutomationTools_src_ReSharperHost.Props')" />
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"runtimeOptions": {
"tfm": "netcoreapp5.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "5.0.0"
},
"rollForward": "Major"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace JetBrains.ReSharper.Plugins.FSharp.Fantomas.Host
{
public static class FantomasAssemblyResolver
{
private const string AdditionalProbingPathsEnvVar = "RIDER_PLUGIN_ADDITIONAL_PROBING_PATHS";
private static readonly List<string> OurAdditionalProbingPaths = new List<string>();

static FantomasAssemblyResolver()
{
var paths = Environment.GetEnvironmentVariable(AdditionalProbingPathsEnvVar);
if (string.IsNullOrWhiteSpace(paths)) return;

foreach (var path in paths.Split(';'))
{
if (!string.IsNullOrEmpty(path)) OurAdditionalProbingPaths.Add(path);
}
}

public static Assembly Resolve(object sender, ResolveEventArgs eventArgs)
{
var assemblyName = $"{new AssemblyName(eventArgs.Name).Name}.dll";

foreach (var path in OurAdditionalProbingPaths)
{
var assemblyPath = Path.Combine(path, assemblyName);
if (!File.Exists(assemblyPath)) continue;

var assembly = Assembly.LoadFrom(assemblyPath);
return assembly;
}

Console.Error.Write($"\nFailed to resolve assembly by name '{eventArgs.Name}'" +
$"\n Requesting assembly: {eventArgs.RequestingAssembly?.FullName}" +
$"\n Probing paths: {string.Join("\n", OurAdditionalProbingPaths)}");
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Fantomas;
using FSharp.Compiler.CodeAnalysis;
using FSharp.Compiler.Diagnostics;
using FSharp.Compiler.Text;
using JetBrains.ReSharper.Plugins.FSharp.Fantomas.Server;
using Microsoft.FSharp.Collections;
using Microsoft.FSharp.Control;

namespace JetBrains.ReSharper.Plugins.FSharp.Fantomas.Host
{
internal class FantomasCodeFormatter
{
private readonly FSharpChecker myChecker =
FSharpChecker.Create(null, null, null, null, null, null, null, null, null, null);

private readonly FormatConfig.FormatConfig myDefaultFormatConfig = FormatConfig.FormatConfig.Default;

public string FormatSelection(RdFormatSelectionArgs args) =>
FSharpAsync.StartAsTask(
CodeFormatter.FormatSelectionAsync(args.FileName, Convert(args.Range),
SourceOrigin.SourceOrigin.NewSourceString(args.Source), Convert(args.FormatConfig),
Convert(args.ParsingOptions), myChecker), null, null)
.Result.Replace("\r\n", args.NewLineText);

public string FormatDocument(RdFormatDocumentArgs args) =>
FSharpAsync.StartAsTask(
CodeFormatter.FormatDocumentAsync(args.FileName, SourceOrigin.SourceOrigin.NewSourceString(args.Source),
Convert(args.FormatConfig), Convert(args.ParsingOptions), myChecker), null, null)
.Result.Replace("\r\n", args.NewLineText);

private static Range Convert(RdFcsRange range) =>
CodeFormatter.MakeRange(range.FileName, range.StartLine, range.StartCol, range.EndLine, range.EndCol);

private static FSharpParsingOptions Convert(RdFcsParsingOptions options) =>
new FSharpParsingOptions(new[] {options.LastSourceFile},
ListModule.OfArray(options.ConditionalCompilationDefines), FSharpDiagnosticOptions.Default, false,
options.LightSyntax, false, options.IsExe);

private FormatConfig.FormatConfig Convert(RdFantomasFormatConfig config) =>
new FormatConfig.FormatConfig(config.IndentSize, config.MaxLineLength, config.SemicolonAtEndOfLine,
config.SpaceBeforeParameter, config.SpaceBeforeLowercaseInvocation, config.SpaceBeforeUppercaseInvocation,
config.SpaceBeforeClassConstructor, config.SpaceBeforeMember, config.SpaceBeforeColon, config.SpaceAfterComma,
config.SpaceBeforeSemicolon, config.SpaceAfterSemicolon, config.IndentOnTryWith, config.SpaceAroundDelimiter,
config.MaxIfThenElseShortWidth, config.MaxInfixOperatorExpression, config.MaxRecordWidth,
myDefaultFormatConfig.MaxRecordNumberOfItems, myDefaultFormatConfig.RecordMultilineFormatter,
config.MaxArrayOrListWidth, myDefaultFormatConfig.MaxArrayOrListNumberOfItems,
myDefaultFormatConfig.ArrayOrListMultilineFormatter, config.MaxValueBindingWidth,
config.MaxFunctionBindingWidth, myDefaultFormatConfig.MaxDotGetExpressionWidth,
config.MultilineBlockBracketsOnSameColumn, config.NewlineBetweenTypeDefinitionAndMembers,
config.KeepIfThenInSameLine, config.MaxElmishWidth, config.SingleArgumentWebMode,
config.AlignFunctionSignatureToIndentation, config.AlternativeLongMemberDefinitions,
myDefaultFormatConfig.MultiLineLambdaClosingNewline, myDefaultFormatConfig.DisableElmishSyntax,
myDefaultFormatConfig.EndOfLine, myDefaultFormatConfig.StrictMode);
}
}
46 changes: 46 additions & 0 deletions ReSharper.FSharp/src/FSharp.Fantomas.Host/src/FantomasEndPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using JetBrains.Collections.Viewable;
using JetBrains.Diagnostics;
using JetBrains.Lifetimes;
using JetBrains.Platform.RdFramework.ExternalProcess;
using JetBrains.Platform.RdFramework.ExternalProcess.Util;
using JetBrains.Rd.Impl;
using JetBrains.Util;
using JetBrains.Rd.Tasks;
using JetBrains.ReSharper.Plugins.FSharp.Fantomas.Protocol;
using JetBrains.ReSharper.Plugins.FSharp.Fantomas.Server;

namespace JetBrains.ReSharper.Plugins.FSharp.Fantomas.Host
{
internal class ExternalFormatterEndPoint : ProtocolEndPoint<RdFantomasModel, RdSimpleDispatcher>
{
private readonly FantomasCodeFormatter myCodeFormatter;
protected override string ProtocolName => "External Formatter Host";

public ExternalFormatterEndPoint() : base(FantomasProtocolConstants.PARENT_PROCESS_PID_ENV_VARIABLE)
{
myCodeFormatter = new FantomasCodeFormatter();
}

protected override RdSimpleDispatcher InitDispatcher(Lifetime lifetime, ILogger logger) =>
new RdSimpleDispatcher(lifetime, logger);

protected override void InitLogger(Lifetime lifetime, string path) =>
ProtocolEndPointUtil.InitLogger(path, lifetime, LoggingLevel.TRACE);

protected override RdFantomasModel InitModel(Lifetime lifetime, JetBrains.Rd.Impl.Protocol protocol)
{
var model = new RdFantomasModel(lifetime, protocol);

model.FormatSelection.Set(FormatSelection);
model.FormatDocument.Set(FormatDocument);
model.Exit.Advise(lifetime, Terminate);

return model;
}

private string FormatSelection(RdFormatSelectionArgs args) => myCodeFormatter.FormatSelection(args);
private string FormatDocument(RdFormatDocumentArgs args) => myCodeFormatter.FormatDocument(args);

protected override void Run(Lifetime lifetime, RdSimpleDispatcher dispatcher) => dispatcher.Run();
}
}
27 changes: 27 additions & 0 deletions ReSharper.FSharp/src/FSharp.Fantomas.Host/src/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;

namespace JetBrains.ReSharper.Plugins.FSharp.Fantomas.Host
{
public static class Program
{
public static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += FantomasAssemblyResolver.Resolve;
MainInternal(args);
}

private static void MainInternal(string[] args)
{
var endPoint = new ExternalFormatterEndPoint();

var portValue = args[0];
var logPath = string.Empty;
if (args.Length > 1)
{
logPath = args[1];
}

endPoint.Start(portValue, logPath);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<LangVersion>$(CSharpLanguageVersion)</LangVersion>
<AssemblyName>JetBrains.ReSharper.Plugins.FSharp.Fantomas.Protocol</AssemblyName>
<RootNamespace>JetBrains.ReSharper.Plugins.FSharp.Fantomas.Protocol</RootNamespace>
</PropertyGroup>

<Import Project="$(DotNetSdkPath)\Build\SubplatformReference.Psi.Features_src.Props" Condition="Exists('$(DotNetSdkPath)\Build\SubplatformReference.Psi.Features_src.Props')" />
<Import Project="$(DotNetSdkPath)\Build\SubplatformReference.ReSharperAutomationTools_src_ReSharperHost.Props" Condition="Exists('$(DotNetSdkPath)\Build\SubplatformReference.ReSharperAutomationTools_src_ReSharperHost.Props')" />
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using JetBrains.DataFlow;
using JetBrains.Lifetimes;
using JetBrains.Platform.RdFramework.ExternalProcess;
using JetBrains.Rd;
using JetBrains.ReSharper.Plugins.FSharp.Fantomas.Client;

namespace JetBrains.ReSharper.Plugins.FSharp.Fantomas.Protocol
{
public class FantomasConnection : ProtocolConnection<RdFantomasModel>
{
public FantomasConnection(Lifetime lifetime, RdFantomasModel protocolModel,
IProtocol protocol, StartupOutputWriter startupOutputWriter, int processId, ISignal<int> processUnexpectedExited)
: base(lifetime, protocolModel, protocol, startupOutputWriter, processId, processUnexpectedExited)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using JetBrains.Application.Processes;
using JetBrains.Application.Threading;
using JetBrains.Core;
using JetBrains.DataFlow;
using JetBrains.Diagnostics;
using JetBrains.Lifetimes;
using JetBrains.Platform.RdFramework.ExternalProcess;
using JetBrains.Rd;
using JetBrains.ReSharper.Plugins.FSharp.Fantomas.Client;
using JetBrains.Util;

namespace JetBrains.ReSharper.Plugins.FSharp.Fantomas.Protocol
{
public class FantomasProcess : ProtocolExternalProcess<RdFantomasModel, FantomasConnection>
{
protected override string Name => "External Formatter";

protected override RdFantomasModel CreateModel(Lifetime lifetime, IProtocol protocol) =>
new RdFantomasModel(lifetime, protocol);

protected override FantomasConnection CreateConnection(Lifetime lifetime,
RdFantomasModel model, IProtocol protocol, StartupOutputWriter outputWriter, int processId,
Signal<int> processUnexpectedExited) =>
new FantomasConnection(lifetime, model, protocol, outputWriter, processId,
processUnexpectedExited);

protected override ProcessStartInfo GetProcessStartInfo(int port)
{
var launchPath = GetType().Assembly.GetPath().Directory.Combine(FantomasProtocolConstants.PROCESS_FILENAME);
Assertion.Assert(launchPath.ExistsFile, $"can't find '{FantomasProtocolConstants.PROCESS_FILENAME}'");

return new ProcessStartInfo
{
Arguments =
$"{port} \"{FantomasProtocolConstants.LogFolder.Combine($"{DateTime.UtcNow:yyyy_MM_dd_HH_mm_ss_ffff}.log")}\"",
FileName = launchPath.FullPath
};
}

protected override IDictionary<string, string> GetAdditionalProcessEnvVars()
{
return new Dictionary<string, string>()
{
{
"RIDER_PLUGIN_ADDITIONAL_PROBING_PATHS",
Environment.GetEnvironmentVariable("RIDER_PLUGIN_ADDITIONAL_PROBING_PATHS")
},
{
FantomasProtocolConstants.PARENT_PROCESS_PID_ENV_VARIABLE,
Process.GetCurrentProcess().Id.ToString()
},
};
}

protected override bool Shutdown(RdFantomasModel model)
{
model.Proto.Scheduler.Queue(() => model.Exit.Fire(Unit.Instance));
return true;
}

public FantomasProcess(Lifetime lifetime, ILogger logger, IShellLocks locks,
IProcessStartInfoPatcher processInfoPatcher, JetProcessRuntimeRequest request)
: base(lifetime, logger, locks, processInfoPatcher, request)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using JetBrains.Annotations;
using JetBrains.Application.Processes;
using JetBrains.Application.Threading;
using JetBrains.Lifetimes;
using JetBrains.ProjectModel;
using JetBrains.Util;

namespace JetBrains.ReSharper.Plugins.FSharp.Fantomas.Protocol
{
[SolutionComponent]
public class FantomasProcessFactory
{
[NotNull] private readonly ISolutionProcessStartInfoPatcher mySolutionProcessStartInfoPatcher;
[NotNull] private readonly ILogger myLogger;
[NotNull] private readonly IShellLocks myShellLocks;

public FantomasProcessFactory(
[NotNull] ISolutionProcessStartInfoPatcher solutionProcessStartInfoPatcher,
[NotNull] ILogger logger,
[NotNull] IShellLocks shellLocks)
{
mySolutionProcessStartInfoPatcher = solutionProcessStartInfoPatcher;
myLogger = logger;
myShellLocks = shellLocks;
}

public FantomasProcess Create(Lifetime lifetime)
{
return new FantomasProcess(lifetime,
myLogger,
myShellLocks,
mySolutionProcessStartInfoPatcher,
JetProcessRuntimeRequest.CreateInternalRuntime());
}
}
}
Loading