Skip to content

Commit

Permalink
pick the language server changes
Browse files Browse the repository at this point in the history
  • Loading branch information
yatli committed May 24, 2019
1 parent f09819d commit 85b0409
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 99 deletions.
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ obj/
out/
packages/
node_modules/
.idea/
build.vsix
Scratch.fsx
Scratch.ts
Scratch.ts
publish/
coc-fsharp-*.tgz
.vs
10 changes: 7 additions & 3 deletions src/FSharpLanguageServer/FSharpLanguageServer.fsproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RuntimeIdentifiers>win10-x64;osx.10.11-x64;linux-x64</RuntimeIdentifiers>
</PropertyGroup>

Expand All @@ -19,12 +19,16 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="FSharp.Compiler.Service" Version="27.0.1" />
<PackageReference Include="FSharp.Compiler.Service" Version="28.0.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.8.4" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\LSP\LSP.fsproj" />
<ProjectReference Include="..\ProjectCracker\ProjectCracker.fsproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Update="FSharp.Core" Version="4.6.2" />
</ItemGroup>
</Project>
13 changes: 9 additions & 4 deletions src/FSharpLanguageServer/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -842,13 +842,18 @@ type Server(client: ILanguageClient) =
}
member this.ExecuteCommand(p: ExecuteCommandParams): Async<unit> = TODO()
member this.DidChangeWorkspaceFolders(p: DidChangeWorkspaceFoldersParams): Async<unit> =
let __doworkspace (xs: WorkspaceFolder list) fn =
async {
for root in xs do
let file = FileInfo(root.uri.LocalPath)
do! fn file.Directory
}
async {
for root in p.event.added do
let file = FileInfo(root.uri.LocalPath)
do! projects.AddWorkspaceRoot(file.Directory)
// TODO removed
do! __doworkspace p.event.added projects.AddWorkspaceRoot
do! __doworkspace p.event.removed projects.RemoveWorkspaceRoot
}


[<EntryPoint>]
let main(argv: array<string>): int =
let read = new BinaryReader(Console.OpenStandardInput())
Expand Down
53 changes: 31 additions & 22 deletions src/FSharpLanguageServer/ProjectManager.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ type private LazyProject = {
type private ProjectCache() =
let knownProjects = new Dictionary<String, LazyProject>()

member this.Invalidate(fsprojOrFsx: FileInfo) =
member __.Invalidate(fsprojOrFsx: FileInfo) =
knownProjects.Remove(fsprojOrFsx.FullName) |> ignore
member this.Get(fsprojOrFsx: FileInfo, analyzeLater: FileInfo -> LazyProject): LazyProject =
ProjectCracker.invalidateProjectAssets fsprojOrFsx
member __.Get(fsprojOrFsx: FileInfo, analyzeLater: FileInfo -> LazyProject): LazyProject =
if not(knownProjects.ContainsKey(fsprojOrFsx.FullName)) then
knownProjects.Add(fsprojOrFsx.FullName, analyzeLater(fsprojOrFsx))
knownProjects.[fsprojOrFsx.FullName]
Expand Down Expand Up @@ -65,17 +66,12 @@ type ProjectManager(checker: FSharpChecker) as this =
/// When was this .fsx, .fsproj or corresponding project.assets.json file modified?
// TODO use checksum instead of time
let lastModified(fsprojOrFsx: FileInfo) =
let assets = FileInfo(Path.Combine [| fsprojOrFsx.Directory.FullName; "obj"; "project.assets.json" |])
let assets = ProjectCracker.getAssets fsprojOrFsx
if assets.Exists then
max fsprojOrFsx.LastWriteTime assets.LastWriteTime
else
fsprojOrFsx.LastWriteTime

/// Find any .fsproj files associated with a project.assets.json
let projectFileForAssets(assetsJson: FileInfo) =
let dir = assetsJson.Directory.Parent
dir.GetFiles("*.fsproj")

/// Find base dlls
/// Workaround of https://github.com/fsharp/FSharp.Compiler.Service/issues/847
let dotNetFramework =
Expand Down Expand Up @@ -314,7 +310,7 @@ type ProjectManager(checker: FSharpChecker) as this =
errors=match cracked.error with None -> [] | Some(e) -> [Conversions.errorAtTop(e)]
}
// Direct to analyzeFsx or analyzeFsproj, depending on type
if fsprojOrFsx.Name.EndsWith(".fsx") then
if fsprojOrFsx.Extension =".fsx" then
{file=fsprojOrFsx; resolved=lazy(analyzeFsx(fsprojOrFsx))}
elif fsprojOrFsx.Name.EndsWith(".fsproj") then
{file=fsprojOrFsx; resolved=lazy(analyzeFsproj(fsprojOrFsx))}
Expand Down Expand Up @@ -368,30 +364,43 @@ type ProjectManager(checker: FSharpChecker) as this =
let path = Path.Combine(sln.Directory.FullName, relativePath)
let normalize = Path.GetFullPath(path)
yield FileInfo(normalize) ]
member this.AddWorkspaceRoot(root: DirectoryInfo): Async<unit> =
member __.AddWorkspaceRoot(root: DirectoryInfo): Async<unit> =
async {
for f in root.EnumerateFiles("*.*", SearchOption.AllDirectories) do
if f.Name.EndsWith(".fsx") || f.Name.EndsWith(".fsproj") then
match f.Extension with
| ".fsx" | ".fsproj" ->
knownProjects.Add(f.FullName) |> ignore
else if f.Name.EndsWith(".sln") then
| ".sln" ->
knownSolutions.[f.FullName] <- slnProjectReferences(f)
| _ -> ()
}
member __.RemoveWorkspaceRoot(root: DirectoryInfo): Async<unit> =
async {
for f in root.EnumerateFiles("*.*", SearchOption.AllDirectories) do
knownProjects.Remove(f.FullName) |> ignore
knownSolutions.Remove(f.FullName) |> ignore
}
member __.ClearWorkspaceRoot(): Async<unit> =
async {
knownProjects.Clear()
knownSolutions.Clear()
}
member this.DeleteProjectFile(fsprojOrFsx: FileInfo) =
member __.DeleteProjectFile(fsprojOrFsx: FileInfo) =
knownProjects.Remove(fsprojOrFsx.FullName) |> ignore
cache.Invalidate(fsprojOrFsx) |> ignore
invalidateDescendents(fsprojOrFsx)
member this.NewProjectFile(fsprojOrFsx: FileInfo) =
member __.NewProjectFile(fsprojOrFsx: FileInfo) =
knownProjects.Add(fsprojOrFsx.FullName) |> ignore
invalidateDescendents(fsprojOrFsx)
member this.UpdateProjectFile(fsprojOrFsx: FileInfo) =
member __.UpdateProjectFile(fsprojOrFsx: FileInfo) =
invalidateDescendents(fsprojOrFsx)
member this.DeleteSlnFile(sln: FileInfo) =
member __.DeleteSlnFile(sln: FileInfo) =
knownSolutions.Remove(sln.FullName) |> ignore
member this.UpdateSlnFile(sln: FileInfo) =
member __.UpdateSlnFile(sln: FileInfo) =
knownSolutions.[sln.FullName] <- slnProjectReferences(sln)
member this.UpdateAssetsJson(assets: FileInfo) =
for fsproj in projectFileForAssets(assets) do invalidateDescendents(fsproj)
member this.FindProjectOptions(sourceFile: FileInfo): Result<FSharpProjectOptions, Diagnostic list> =
member __.UpdateAssetsJson(assets: FileInfo) =
for fsproj in ProjectCracker.getProject(assets) do invalidateDescendents(fsproj)
member __.FindProjectOptions(sourceFile: FileInfo): Result<FSharpProjectOptions, Diagnostic list> =
let isSourceFile(f: FileInfo) = f.FullName = sourceFile.FullName
// Does `p` contain a reference to `sourceFile`?
let isMatch(p: ResolvedProject) = List.exists isSourceFile p.sources
Expand Down Expand Up @@ -440,7 +449,7 @@ type ProjectManager(checker: FSharpChecker) as this =
Error(cracked.errors)
/// All open projects, in dependency order
/// Ancestor projects come before projects that depend on them
member this.OpenProjects: FSharpProjectOptions list =
member __.OpenProjects: FSharpProjectOptions list =
let touched = new HashSet<String>()
let result = new List<FSharpProjectOptions>()
let rec walk(options: FSharpProjectOptions) =
Expand All @@ -453,7 +462,7 @@ type ProjectManager(checker: FSharpChecker) as this =
walk(project.resolved.Value.options)
List.ofSeq(result)
/// All transitive dependencies of `projectFile`, in dependency order
member this.TransitiveDeps(projectFile: FileInfo): FSharpProjectOptions list =
member __.TransitiveDeps(projectFile: FileInfo): FSharpProjectOptions list =
transitiveDeps(projectFile)
/// Is `targetSourceFile` visible from `fromSourceFile`?
member this.IsVisible(targetSourceFile: FileInfo, fromSourceFile: FileInfo) =
Expand Down
12 changes: 8 additions & 4 deletions src/LSP/LSP.fsproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
Expand All @@ -14,6 +14,10 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="FSharp.Data" Version="3.0.0-beta3" />
<PackageReference Include="FSharp.Data" Version="3.1.1" />
</ItemGroup>
</Project>

<ItemGroup>
<PackageReference Update="FSharp.Core" Version="4.6.2" />
</ItemGroup>
</Project>
150 changes: 90 additions & 60 deletions src/ProjectCracker/ProjectCracker.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ open Microsoft.Build.Utilities
open Microsoft.Build.Framework
open Microsoft.Build.Logging
open Buildalyzer
open System.Linq

// Other points of reference:
// Omnisharp-roslyn cracks .csproj files: https://github.com/OmniSharp/omnisharp-roslyn/blob/master/tests/OmniSharp.MSBuild.Tests/ProjectFileInfoTests.cs
Expand Down Expand Up @@ -106,6 +107,75 @@ let private frameworkPreference = [
"net20", ".NETFramework,Version=v2.0";
"net11", ".NETFramework,Version=v1.1" ]

let private project(fsproj: FileInfo): ProjectAnalyzer =
let options = new AnalyzerManagerOptions()
options.LogWriter <- !diagnosticsLog // TODO this doesn't follow ref changes
let manager = AnalyzerManager(options)
manager.GetProject(fsproj.FullName)

let private inferTargetFramework(fsproj: FileInfo): AnalyzerResult =
let proj = project(fsproj)
let builds = proj.Build()

// TODO get target framework from project.assets.json
let build_tfms = builds |> Seq.map (fun build -> build, build.TargetFramework)
let pref_tfms = frameworkPreference

Enumerable
.Join(build_tfms, pref_tfms, (fun (_, b) -> b), (fun (a, _) -> a), (fun a _ -> fst a))
.Concat(builds)
.First()

let private absoluteIncludePath(fsproj: FileInfo, i: ProjectItem) =
let relativePath = i.ItemSpec.Replace('\\', Path.DirectorySeparatorChar)
let absolutePath = Path.Combine(fsproj.DirectoryName, relativePath)
let normalizePath = Path.GetFullPath(absolutePath)
FileInfo(normalizePath)

let private projectAssets = new Dictionary<String, String>()

let invalidateProjectAssets (fsprojOrFsx: FileInfo) =
projectAssets.Remove(fsprojOrFsx.FullName) |> ignore

let getAssets(fsprojOrFsx: FileInfo) =
if fsprojOrFsx.Extension = ".fsx" then
FileInfo(Path.Combine [| fsprojOrFsx.Directory.FullName; "obj"; "project.assets.json" |])
else

let projfile = fsprojOrFsx.FullName
let mutable assets = ""
if projectAssets.TryGetValue(projfile, &assets) then
FileInfo(assets)
else

let msbuildprops =
try
Path.Combine [|fsprojOrFsx.DirectoryName; project(fsprojOrFsx).Build().First().GetProperty("BaseIntermediateOutputPath") |]
with | ex ->
dprintfn "ProjectManager: msbuildprops: %s" <| ex.ToString()
Path.Combine [| fsprojOrFsx.Directory.FullName; "obj" |]

let assets = msbuildprops
|> (fun p -> Path.Combine [| p; "project.assets.json" |])
|> FileInfo

projectAssets.[projfile] <- assets.FullName
assets

let getProject(assets: FileInfo) =
seq {
for KeyValue(k,v) in projectAssets do
if v = assets.FullName then
yield FileInfo k
}

let private projectTarget(csproj: FileInfo) =
let project = inferTargetFramework csproj
let dllName = project.GetProperty("AssemblyName") + ".dll"
let dllPath = Path.Combine [|csproj.DirectoryName; project.GetProperty("OutputPath"); dllName |]
FileInfo(dllPath)


let private parseProjectAssets(projectAssetsJson: FileInfo): ProjectAssets =
dprintfn "Parsing %s" projectAssetsJson.FullName
let root = JsonValue.Parse(File.ReadAllText(projectAssetsJson.FullName))
Expand Down Expand Up @@ -319,55 +389,11 @@ let private parseProjectAssets(projectAssetsJson: FileInfo): ProjectAssets =
projects=projects
}

let private project(fsproj: FileInfo): ProjectAnalyzer =
let options = new AnalyzerManagerOptions()
options.LogWriter <- !diagnosticsLog // TODO this doesn't follow ref changes
let manager = AnalyzerManager(options)
manager.GetProject(fsproj.FullName)

let private inferTargetFramework(fsproj: FileInfo): AnalyzerResult =
let builds = project(fsproj).Build()
// TODO get target framework from project.assets.json
let mutable chosen: AnalyzerResult option = None
for shortFramework, _ in frameworkPreference do
if chosen.IsNone then
for build in builds do
if build.TargetFramework = shortFramework then
chosen <- Some(build)
if chosen.IsNone then
for build in builds do
if chosen.IsNone then
chosen <- Some(build)
chosen.Value

let private projectTarget(csproj: FileInfo) =
let baseName = Path.GetFileNameWithoutExtension(csproj.Name)
let dllName = baseName + ".dll"
let placeholderTarget = FileInfo(Path.Combine [|csproj.DirectoryName; "bin"; "Debug"; "placeholder"; dllName|])
let projectAssetsJson = FileInfo(Path.Combine [|csproj.DirectoryName; "obj"; "project.assets.json"|])
if projectAssetsJson.Exists then
let assets = parseProjectAssets(projectAssetsJson)
let dllName = assets.projectName + ".dll"
// TODO this seems fragile
FileInfo(Path.Combine [|csproj.DirectoryName; "bin"; "Debug"; assets.framework; dllName|])
else
placeholderTarget

let private absoluteIncludePath(fsproj: FileInfo, i: ProjectItem) =
let relativePath = i.ItemSpec.Replace('\\', Path.DirectorySeparatorChar)
let absolutePath = Path.Combine(fsproj.DirectoryName, relativePath)
let normalizePath = Path.GetFullPath(absolutePath)
FileInfo(normalizePath)

/// Crack an .fsproj file by:
/// - Running the "Restore" target and reading
/// - Reading .fsproj using the MSBuild API
/// - Reading libraries from project.assets.json
let crack(fsproj: FileInfo): CrackedProject =
// Figure out name of output .dll
let baseName = Path.GetFileNameWithoutExtension(fsproj.Name)
let dllName = baseName + ".dll"
let placeholderTarget = FileInfo(Path.Combine [|fsproj.DirectoryName; "bin"; "Debug"; "placeholder"; dllName|])
try
// Get source info from .fsproj
let timeProject = Stopwatch.StartNew()
Expand All @@ -384,27 +410,28 @@ let crack(fsproj: FileInfo): CrackedProject =
yield absoluteIncludePath(fsproj, i) ]
dprintfn "Cracked %s in %dms" fsproj.Name timeProject.ElapsedMilliseconds
// Get package info from project.assets.json
let projectAssetsJson = FileInfo(Path.Combine [|fsproj.DirectoryName; "obj"; "project.assets.json"|])
let projectAssetsJson = getAssets fsproj
let outputPath = project.GetProperty("OutputPath")
let dllName = project.GetProperty("AssemblyName") + ".dll"
let dllTarget = FileInfo(Path.Combine [|outputPath; dllName|])
if not(projectAssetsJson.Exists) then
{
fsproj=fsproj
target=placeholderTarget
sources=sources
projectReferences=[]
otherProjectReferences=[]
packageReferences=[]
directReferences=directReferences
error=Some(sprintf "%s does not exist; maybe you need to build your project?" projectAssetsJson.FullName)
fsproj = fsproj
target = dllTarget
sources = sources
projectReferences = []
otherProjectReferences = []
packageReferences = []
directReferences = directReferences
error = Some(sprintf "%s does not exist; maybe you need to build your project?" projectAssetsJson.FullName)
}
else
let timeAssets = Stopwatch.StartNew()
let assets = parseProjectAssets(projectAssetsJson)
// msbuild produces paths like src/LSP/bin/Debug/netcoreapp2.0/LSP.dll
// TODO this seems fragile
let target = FileInfo(Path.Combine [|fsproj.DirectoryName; "bin"; "Debug"; assets.framework; dllName|])
let isFsproj(f: FileInfo) = f.Name.EndsWith(".fsproj")
let timeAssets = Stopwatch.StartNew()
let assets = parseProjectAssets(projectAssetsJson)
let target = FileInfo(Path.Combine [|outputPath; dllName|])
let isFsproj(f: FileInfo) = f.Name.EndsWith(".fsproj")
let fsProjects, csProjects = List.partition isFsproj assets.projects
let otherProjects = [for csproj in csProjects do yield projectTarget(csproj)]
let otherProjects = [for csproj in csProjects do yield projectTarget(csproj)]
dprintfn "Cracked project.assets.json in %dms" timeAssets.ElapsedMilliseconds
{
fsproj=fsproj
Expand All @@ -417,6 +444,9 @@ let crack(fsproj: FileInfo): CrackedProject =
error=None
}
with e ->
let baseName = Path.GetFileNameWithoutExtension(fsproj.Name)
let dllName = baseName + ".dll"
let placeholderTarget = FileInfo(Path.Combine [|fsproj.DirectoryName; "bin"; "Debug"; "placeholder"; dllName|])
dprintfn "Failed to build %s: %s" fsproj.Name e.Message
{
fsproj=fsproj
Expand Down
Loading

0 comments on commit 85b0409

Please sign in to comment.