Skip to content

Commit

Permalink
initial support for F#
Browse files Browse the repository at this point in the history
  • Loading branch information
brettfo committed May 25, 2019
1 parent b1b6dc7 commit 810ce36
Show file tree
Hide file tree
Showing 27 changed files with 604 additions and 27 deletions.
7 changes: 7 additions & 0 deletions DotNetTry.sln
Expand Up @@ -53,6 +53,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Try.Projec
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.ProjectTemplate", "Microsoft.DotNet.Try.ProjectTemplate\Tutorial\Microsoft.DotNet.ProjectTemplate.csproj", "{E047D81A-7A18-4A1A-98E8-EDBB51EBB7DB}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharpWorkspaceShim", "FSharpWorkspaceShim\FSharpWorkspaceShim.fsproj", "{9128FCED-2A19-4502-BCEE-BE1BAB6882EB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -151,6 +153,10 @@ Global
{E047D81A-7A18-4A1A-98E8-EDBB51EBB7DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E047D81A-7A18-4A1A-98E8-EDBB51EBB7DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E047D81A-7A18-4A1A-98E8-EDBB51EBB7DB}.Release|Any CPU.Build.0 = Release|Any CPU
{9128FCED-2A19-4502-BCEE-BE1BAB6882EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9128FCED-2A19-4502-BCEE-BE1BAB6882EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9128FCED-2A19-4502-BCEE-BE1BAB6882EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9128FCED-2A19-4502-BCEE-BE1BAB6882EB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -179,6 +185,7 @@ Global
{11FDD0E8-D07B-41C9-AF7E-E7F735D91ECF} = {8192FEAD-BCE6-4E62-97E5-2E9EA884BD71}
{1F1A7554-1E88-4514-8602-EC00899E0C49} = {8192FEAD-BCE6-4E62-97E5-2E9EA884BD71}
{E047D81A-7A18-4A1A-98E8-EDBB51EBB7DB} = {6EE8F484-DFA2-4F0F-939F-400CE78DFAC2}
{9128FCED-2A19-4502-BCEE-BE1BAB6882EB} = {6EE8F484-DFA2-4F0F-939F-400CE78DFAC2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D6CD99BA-B16B-4570-8910-225CBDFFA3AD}
Expand Down
18 changes: 18 additions & 0 deletions FSharpWorkspaceShim/FSharpWorkspaceShim.fsproj
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<NoWarn>$(NoWarn);2003</NoWarn><!-- AssemblyInformationalVersionAttribute contains a non-standard value -->
</PropertyGroup>

<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>

<ItemGroup>
<PackageReference Update="FSharp.Core" Version="4.6.2" />
<PackageReference Include="FSharp.Compiler.Service" Version="28.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.1.0-beta3-final" />
</ItemGroup>

</Project>
130 changes: 130 additions & 0 deletions FSharpWorkspaceShim/Library.fs
@@ -0,0 +1,130 @@
namespace FSharpWorkspaceShim

open System
open System.IO
open FSharp.Compiler.SourceCodeServices
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text

module Shim =

let private checker = FSharpChecker.Create()

let private getIndex (text: string) (line: int) (column: int) =
let mutable index = -1
let mutable currentLine = 0
let mutable currentColumn = 0
text.ToCharArray()
|> Array.iteri (fun i c ->
if line = currentLine && column = currentColumn then index <- i
match c with
| '\n' ->
currentLine <- currentLine + 1
currentColumn <- 0
| _ -> currentColumn <- currentColumn + 1)
index

let private newlineProxy = System.String [|char 29|]

// adapted from https://github.com/dotnet/fsharp/blob/master/src/fsharp/ErrorLogger.fs
let private normalizeErrorString (text : string) =
if isNull text then nullArg "text"
let text = text.Trim()

let buf = System.Text.StringBuilder()
let mutable i = 0
while i < text.Length do
let delta =
match text.[i] with
| '\r' when i + 1 < text.Length && text.[i + 1] = '\n' ->
// handle \r\n sequence - replace it with one single space
buf.Append newlineProxy |> ignore
2
| '\n' | '\r' ->
buf.Append newlineProxy |> ignore
1
| c ->
// handle remaining chars: control - replace with space, others - keep unchanged
let c = if Char.IsControl c then ' ' else c
buf.Append c |> ignore
1
i <- i + delta
buf.ToString()

let private newlineifyErrorString (message:string) = message.Replace(newlineProxy, Environment.NewLine)

// adapted from https://github.com/dotnet/fsharp/blob/master/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs
let private convertError (error: FSharpErrorInfo) (location: Location) =
// Normalize the error message into the same format that we will receive it from the compiler.
// This ensures that IntelliSense and Compiler errors in the 'Error List' are de-duplicated.
// (i.e the same error does not appear twice, where the only difference is the line endings.)
let normalizedMessage = error.Message |> normalizeErrorString |> newlineifyErrorString

let id = "FS" + error.ErrorNumber.ToString("0000")
let emptyString = LocalizableString.op_Implicit("")
let description = LocalizableString.op_Implicit(normalizedMessage)
let severity = if error.Severity = FSharpErrorSeverity.Error then DiagnosticSeverity.Error else DiagnosticSeverity.Warning
let customTags =
match error.ErrorNumber with
| 1182 -> WellKnownDiagnosticTags.Unnecessary
| _ -> null
let descriptor = new DiagnosticDescriptor(id, emptyString, description, error.Subcategory, severity, true, emptyString, String.Empty, customTags)
Diagnostic.Create(descriptor, location)

let GetDiagnostics (projectPath: string) (files: string[]) (pathMapSource: string) (pathMapDest: string) =
async {
let projectOptions = {
ProjectFileName = projectPath
ProjectId = None
SourceFiles = files
OtherOptions = [||]
ReferencedProjects = [||]
IsIncompleteTypeCheckEnvironment = false
UseScriptResolutionRules = false
LoadTime = DateTime.Now
UnresolvedReferences = None
OriginalLoadReferences = []
ExtraProjectInfo = None
Stamp = None
}
let ensureDirectorySeparator (path: string) =
if path.EndsWith(Path.DirectorySeparatorChar |> string) |> not then path + (string Path.DirectorySeparatorChar)
else path
let pathMapSource = ensureDirectorySeparator pathMapSource
let pathMapDest = ensureDirectorySeparator pathMapDest
let! results = checker.ParseAndCheckProject projectOptions
// adapted from from https://github.com/dotnet/fsharp/blob/master/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs
let diagnostics =
results.Errors
|> Seq.choose (fun error ->
if error.StartLineAlternate = 0 || error.EndLineAlternate = 0 then
// F# error line numbers are one-based. Compiler returns 0 for global errors (reported by ProjectDiagnosticAnalyzer)
None
else
// Roslyn line numbers are zero-based
let linePositionSpan = LinePositionSpan(LinePosition(error.StartLineAlternate - 1, error.StartColumn), LinePosition(error.EndLineAlternate - 1, error.EndColumn))
let text = File.ReadAllText(error.FileName)
let textSpan =
TextSpan.FromBounds(
getIndex text (error.StartLineAlternate - 1) error.StartColumn,
getIndex text (error.EndLineAlternate - 1) error.EndColumn)

// F# compiler report errors at end of file if parsing fails. It should be corrected to match Roslyn boundaries
let correctedTextSpan =
if textSpan.End <= text.Length then
textSpan
else
let start =
min textSpan.Start (text.Length - 1)
|> max 0

TextSpan.FromBounds(start, text.Length)

let filePath =
if error.FileName.StartsWith(pathMapSource) then String.Concat(pathMapDest, error.FileName.Substring(pathMapSource.Length))
else error.FileName
let location = Location.Create(filePath, correctedTextSpan, linePositionSpan)
Some(convertError error location))
|> Seq.toArray
return diagnostics
} |> Async.StartAsTask
4 changes: 2 additions & 2 deletions MLS.Agent/CommandLine/VerifyCommand.cs
Expand Up @@ -11,7 +11,7 @@
using Microsoft.DotNet.Try.Protocol;
using MLS.Agent.Markdown;
using WorkspaceServer;
using WorkspaceServer.Servers.Roslyn;
using WorkspaceServer.Servers;
using Buffer = Microsoft.DotNet.Try.Protocol.Buffer;

namespace MLS.Agent.CommandLine
Expand All @@ -31,7 +31,7 @@ public static class VerifyCommand
packageRegistry,
startupOptions);
var errorCount = 0;
var workspaceServer = new Lazy<RoslynWorkspaceServer>(() => new RoslynWorkspaceServer(packageRegistry));
var workspaceServer = new Lazy<IWorkspaceServer>(() => new WorkspaceServerMultiplexer(packageRegistry));

var markdownFiles = markdownProject.GetAllMarkdownFiles().ToArray();

Expand Down
6 changes: 3 additions & 3 deletions MLS.Agent/Controllers/CompileController.cs
Expand Up @@ -8,7 +8,7 @@
using Microsoft.DotNet.Try.Protocol;
using MLS.Agent.Middleware;
using Pocket;
using WorkspaceServer.Servers.Roslyn;
using WorkspaceServer.Servers;
using static Pocket.Logger<MLS.Agent.Controllers.CompileController>;

namespace MLS.Agent.Controllers
Expand All @@ -19,11 +19,11 @@ public class CompileController : Controller
public static RequestDescriptor CompileApi => new RequestDescriptor(CompileRoute, timeoutMs: 600000);


private readonly RoslynWorkspaceServer _workspaceServer;
private readonly IWorkspaceServer _workspaceServer;
private readonly CompositeDisposable _disposables = new CompositeDisposable();

public CompileController(
RoslynWorkspaceServer workspaceServer)
IWorkspaceServer workspaceServer)
{
_workspaceServer = workspaceServer;
}
Expand Down
6 changes: 3 additions & 3 deletions MLS.Agent/Controllers/LanguageServicesController.cs
Expand Up @@ -10,7 +10,7 @@
using MLS.Agent.Middleware;
using Pocket;
using WorkspaceServer;
using WorkspaceServer.Servers.Roslyn;
using WorkspaceServer.Servers;
using static Pocket.Logger<MLS.Agent.Controllers.LanguageServicesController>;

namespace MLS.Agent.Controllers
Expand All @@ -33,9 +33,9 @@ public class LanguageServicesController : Controller
public static RequestDescriptor SignatureHelpApi => new RequestDescriptor(SignatureHelpRoute, timeoutMs: 60000);

private readonly CompositeDisposable _disposables = new CompositeDisposable();
private readonly RoslynWorkspaceServer _workspaceServer;
private readonly IWorkspaceServer _workspaceServer;

public LanguageServicesController(RoslynWorkspaceServer workspaceServer)
public LanguageServicesController(IWorkspaceServer workspaceServer)
{
_workspaceServer = workspaceServer ?? throw new ArgumentNullException(nameof(workspaceServer));
}
Expand Down
7 changes: 3 additions & 4 deletions MLS.Agent/Controllers/RunController.cs
Expand Up @@ -8,13 +8,12 @@
using Microsoft.DotNet.Try.Protocol;
using MLS.Agent.Middleware;
using Pocket;
using WorkspaceServer;
using WorkspaceServer.Models.Execution;
using WorkspaceServer.Servers.Roslyn;
using WorkspaceServer.Servers.Scripting;
using WorkspaceServer.Features;
using static Pocket.Logger<MLS.Agent.Controllers.RunController>;
using MLS.Agent.CommandLine;
using WorkspaceServer.Servers;

namespace MLS.Agent.Controllers
{
Expand All @@ -24,12 +23,12 @@ public class RunController : Controller
public static RequestDescriptor RunApi => new RequestDescriptor(RunRoute, timeoutMs:600000);

private readonly StartupOptions _options;
private readonly RoslynWorkspaceServer _workspaceServer;
private readonly IWorkspaceServer _workspaceServer;
private readonly CompositeDisposable _disposables = new CompositeDisposable();

public RunController(
StartupOptions options,
RoslynWorkspaceServer workspaceServer)
IWorkspaceServer workspaceServer)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_workspaceServer = workspaceServer;
Expand Down
6 changes: 2 additions & 4 deletions MLS.Agent/Program.cs
Expand Up @@ -12,18 +12,16 @@
using Recipes;
using Serilog.Sinks.RollingFileAlternate;
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.IO;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.DotNet.Try.Jupyter;
using WorkspaceServer.Servers.Roslyn;
using static Pocket.Logger<MLS.Agent.Program>;
using SerilogLoggerConfiguration = Serilog.LoggerConfiguration;
using WorkspaceServer;
using MLS.Agent.CommandLine;
using WorkspaceServer.Servers;

namespace MLS.Agent
{
Expand All @@ -45,7 +43,7 @@ public static X509Certificate2 ParseKey(string base64EncodedKey)
private static readonly Assembly[] assembliesEmittingPocketLoggerLogs = {
typeof(Startup).Assembly,
typeof(AsyncLazy<>).Assembly,
typeof(RoslynWorkspaceServer).Assembly,
typeof(IWorkspaceServer).Assembly,
typeof(Shell).Assembly
};

Expand Down
4 changes: 2 additions & 2 deletions MLS.Agent/Startup.cs
Expand Up @@ -26,7 +26,7 @@
using Pocket;
using Recipes;
using WorkspaceServer;
using WorkspaceServer.Servers.Roslyn;
using WorkspaceServer.Servers;
using static Pocket.Logger<MLS.Agent.Startup>;
using IApplicationLifetime = Microsoft.Extensions.Hosting.IApplicationLifetime;
using IHostingEnvironment = Microsoft.Extensions.Hosting.IHostingEnvironment;
Expand Down Expand Up @@ -80,7 +80,7 @@ public void ConfigureServices(IServiceCollection services)

services.AddSingleton(Configuration);

services.AddSingleton(c => new RoslynWorkspaceServer(c.GetRequiredService<PackageRegistry>()));
services.AddSingleton<IWorkspaceServer>(c => new WorkspaceServerMultiplexer(c.GetRequiredService<PackageRegistry>()));

services.TryAddSingleton<IBrowserLauncher>(c => new BrowserLauncher());

Expand Down
4 changes: 2 additions & 2 deletions Microsoft.DotNet.Try.Jupyter/JupyterRequestContextHandler.cs
Expand Up @@ -9,7 +9,7 @@
using Microsoft.DotNet.Try.Protocol;
using Newtonsoft.Json.Linq;
using WorkspaceServer;
using WorkspaceServer.Servers.Roslyn;
using WorkspaceServer.Servers;
using Buffer = Microsoft.DotNet.Try.Protocol.Buffer;

namespace Microsoft.DotNet.Try.Jupyter
Expand Down Expand Up @@ -50,7 +50,7 @@ public JupyterRequestContextHandler(PackageRegistry packageRegistry)

var workspaceRequest = new WorkspaceRequest(workspace);

var server = new RoslynWorkspaceServer(new PackageRegistry());
var server = new WorkspaceServerMultiplexer(new PackageRegistry());

var result = await server.Run(workspaceRequest);

Expand Down
7 changes: 7 additions & 0 deletions Microsoft.DotNet.Try.Project/BufferInliningTransformer.cs
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -112,6 +113,12 @@ private static TextSpan CreateTextSpanBefore(TextSpan viewPortRegion)

private static async Task InjectBufferAtSpan(Viewport viewPort, Buffer sourceBuffer, ICollection<Buffer> buffers, IDictionary<string, SourceFile> files, TextSpan span)
{
if (Path.GetExtension(sourceBuffer.Id.FileName) == ".fs")
{
await FSharpMethods.InjectBufferAtSpan(viewPort, sourceBuffer, buffers, files, span);
return;
}

var tree = CSharpSyntaxTree.ParseText(viewPort.Destination.Text.ToString());
var textChange = new TextChange(
span,
Expand Down
6 changes: 5 additions & 1 deletion Microsoft.DotNet.Try.Project/DiagnosticExtensions.cs
Expand Up @@ -25,9 +25,13 @@ public static TdnDiagnosticSeverity ConvertSeverity(this Diagnostic diagnostic)

var startPosition = diagnostic.Location.GetLineSpan().Span.Start;

var diagnosticFilePath = diagnostic?.Location.SourceTree?.FilePath
?? bufferId?.FileName // F# doesn't have a source tree
?? diagnostic?.Location.GetLineSpan().Path;

var location =
diagnostic.Location != null
? $"{diagnostic.Location.SourceTree?.FilePath}({startPosition.Line + 1},{startPosition.Character + 1}): {GetMessagePrefix()}"
? $"{diagnosticFilePath}({startPosition.Line + 1},{startPosition.Character + 1}): {GetMessagePrefix()}"
: null;

return new SerializableDiagnostic(diagnostic.Location?.SourceSpan.Start ?? throw new ArgumentException(nameof(diagnostic.Location)),
Expand Down

0 comments on commit 810ce36

Please sign in to comment.