Skip to content

Commit

Permalink
Fantomas: move out-of-process (#252)
Browse files Browse the repository at this point in the history
  • Loading branch information
DedSec256 authored May 13, 2021
1 parent b34e6d2 commit 95d1c13
Show file tree
Hide file tree
Showing 31 changed files with 664 additions and 75 deletions.
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>
</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

0 comments on commit 95d1c13

Please sign in to comment.