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

Add support for .Net Core scripting #466

Merged
merged 11 commits into from
Oct 6, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="..\FsAutoComplete.Core\Debug.fs" />
<Compile Include="..\FsAutoComplete.Core\FSIRefs.fs" />
<Compile Include="..\FsAutoComplete.Core\Utils.fs" />
<Compile Include="..\FsAutoComplete.Core\ProcessWatcher.fs" />
<Compile Include="..\FsAutoComplete.Core\UntypedAstUtils.fs" />
Expand Down
124 changes: 79 additions & 45 deletions src/FsAutoComplete.BackgroundServices/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,18 @@ open FSharp.Compiler.SourceCodeServices
open System.Collections.Concurrent
open FsAutoComplete

type BackgroundFileCheckType =
| SourceFile of filePath: string
| ScriptFile of filePath: string * tfm: FSIRefs.TFM
with
member x.FilePath =
match x with
| SourceFile(path)
| ScriptFile(path, _) -> path


type UpdateFileParms = {
File: string
File: BackgroundFileCheckType
Content: string
Version: int
}
Expand All @@ -24,15 +34,16 @@ type ProjectParms = {
}

type FileParms = {
File: string
File: BackgroundFileCheckType
}

type Msg = {Value: string}

type State ={
type State = {
Files : ConcurrentDictionary<SourceFilePath, VolatileFile>
FileCheckOptions : ConcurrentDictionary<SourceFilePath, FSharpProjectOptions>
}

with
static member Initial =
{ Files = ConcurrentDictionary(); FileCheckOptions = ConcurrentDictionary() }
Expand Down Expand Up @@ -87,7 +98,7 @@ type FsacClient(sendServerRequest: ClientNotificationSender) =
member __.SendDiagnostics(p: PublishDiagnosticsParams) =
sendServerRequest "background/diagnostics" (box p) |> Async.Ignore

member __.Notifiy(o: Msg) =
member __.Notify(o: Msg) =
sendServerRequest "background/notify" o |> Async.Ignore

type BackgroundServiceServer(state: State, client: FsacClient) =
Expand All @@ -104,45 +115,67 @@ type BackgroundServiceServer(state: State, client: FsacClient) =
else
opts.SourceFiles

let getListOfFilesForProjectChecking file =
match state.FileCheckOptions.TryFind file with
| None ->
if file.EndsWith ".fsx" then
state.Files.TryFind file |> Option.map (fun st ->
async {
let targetFramework = NETFrameworkInfoProvider.latestInstalledNETVersion ()
let! opts = fsxBinder.GetProjectOptionsFromScriptBy(targetFramework, file, st.Lines |> String.concat "\n")
let sf = getFilesFromOpts opts

return
sf
|> Array.skipWhile (fun n -> (Utils.normalizePath n) <> (Utils.normalizePath file))
|> Array.map (fun n -> (Utils.normalizePath n))
|> Array.toList
}
)
else
None
| Some opts ->
let sf = getFilesFromOpts opts
let getListOfFilesForProjectChecking (file: BackgroundFileCheckType) =
let replaceRefs (projOptions: FSharpProjectOptions) =
let okOtherOpts = projOptions.OtherOptions |> Array.filter (fun r -> not <| r.StartsWith("-r"))
let assemblyPaths =
match Environment.latest3xSdkVersion.Value, Environment.latest3xRuntimeVersion.Value with
| None, _
| _, None ->
[]
| Some sdkVersion, Some runtimeVersion ->
FSIRefs.netCoreRefs Environment.dotnetSDKRoot.Value (string sdkVersion) (string runtimeVersion) Environment.fsiTFMMoniker true
let refs = assemblyPaths |> List.map (fun r -> "-r:" + r)
let finalOpts = Array.append okOtherOpts (Array.ofList refs)
{ projOptions with OtherOptions = finalOpts }

let getScriptOptions file lines tfm =
match tfm with
| FSIRefs.NetFx ->
checker.GetProjectOptionsFromScript(file, SourceText.ofString lines, assumeDotNetFramework = true, useSdkRefs = false, useFsiAuxLib = true)
| FSIRefs.NetCore ->
async {
let! (opts, errors) = checker.GetProjectOptionsFromScript(file, SourceText.ofString lines, assumeDotNetFramework = false, useSdkRefs = true, useFsiAuxLib = true)
return replaceRefs opts, errors
}

sf
|> Array.skipWhile (fun n -> (Utils.normalizePath n) <> (Utils.normalizePath file))
|> Array.map (fun n -> (Utils.normalizePath n))
|> Array.toList
|> async.Return
|> Some
match file with
| ScriptFile(file, tfm) ->
state.Files.TryFind file |> Option.map (fun st ->
async {
let! (opts, _errors) = getScriptOptions file (st.Lines |> String.concat "\n") tfm
let sf = getFilesFromOpts opts

return
sf
|> Array.skipWhile (fun n -> (Utils.normalizePath n) <> (Utils.normalizePath file))
|> Array.map (fun n -> (Utils.normalizePath n))
|> Array.toList
}
)
| SourceFile file ->
match state.FileCheckOptions.TryFind file with
| None -> None
| Some opts ->
let sf = getFilesFromOpts opts

sf
|> Array.skipWhile (fun n -> (Utils.normalizePath n) <> (Utils.normalizePath file))
|> Array.map (fun n -> (Utils.normalizePath n))
|> Array.toList
|> async.Return
|> Some

let typecheckFile ignoredFile file =
async {
do! client.Notifiy {Value = sprintf "Typechecking %s" file }
do! client.Notify {Value = sprintf "Typechecking %s" file }
match state.Files.TryFind file, state.FileCheckOptions.TryFind file with
| Some vf, Some opts ->
let txt = vf.Lines |> String.concat "\n"
let! pr, cr = checker.ParseAndCheckFileInProject(file, defaultArg vf.Version 0, SourceText.ofString txt, opts)
match cr with
| FSharpCheckFileAnswer.Aborted ->
do! client.Notifiy {Value = sprintf "Typechecking aborted %s" file }
do! client.Notify {Value = sprintf "Typechecking aborted %s" file }
return ()
| FSharpCheckFileAnswer.Succeeded res ->
async {
Expand All @@ -159,12 +192,11 @@ type BackgroundServiceServer(state: State, client: FsacClient) =
return ()
| Some vf, None when file.EndsWith ".fsx" ->
let txt = vf.Lines |> String.concat "\n"
let targetFramework = NETFrameworkInfoProvider.latestInstalledNETVersion ()
let! opts = fsxBinder.GetProjectOptionsFromScriptBy(targetFramework, file, txt)
let! (opts, _errors) = checker.GetProjectOptionsFromScript(file, SourceText.ofString txt, assumeDotNetFramework = true, useSdkRefs = false)
let! pr, cr = checker.ParseAndCheckFileInProject(file, defaultArg vf.Version 0, SourceText.ofString txt, opts)
match cr with
| FSharpCheckFileAnswer.Aborted ->
do! client.Notifiy {Value = sprintf "Typechecking aborted %s" file }
do! client.Notify {Value = sprintf "Typechecking aborted %s" file }
return ()
| FSharpCheckFileAnswer.Succeeded res ->
async {
Expand All @@ -180,7 +212,7 @@ type BackgroundServiceServer(state: State, client: FsacClient) =
do! client.SendDiagnostics msg
return ()
| _ ->
do! client.Notifiy {Value = sprintf "Couldn't find state %s" file }
do! client.Notify {Value = sprintf "Couldn't find state %s" file }
return ()
}

Expand Down Expand Up @@ -262,12 +294,13 @@ type BackgroundServiceServer(state: State, client: FsacClient) =

member __.UpdateTextFile(p: UpdateFileParms) =
async {
do! client.Notifiy {Value = sprintf "File update %s" p.File }
let file = Utils.normalizePath p.File
do! client.Notify {Value = sprintf "File update %s" p.File.FilePath }
let file = Utils.normalizePath p.File.FilePath

let vf = {Lines = p.Content.Split( [|'\n' |] ); Touched = DateTime.Now; Version = Some p.Version }
state.Files.AddOrUpdate(file, (fun _ -> vf),( fun _ _ -> vf) ) |> ignore
let! filesToCheck = defaultArg (getListOfFilesForProjectChecking file) (async.Return [])
do! client.Notifiy {Value = sprintf "Files to check %A" filesToCheck }
let! filesToCheck = defaultArg (getListOfFilesForProjectChecking p.File) (async.Return [])
do! client.Notify { Value = sprintf "Files to check %A" filesToCheck }
bouncer.Bounce (false, file,filesToCheck)
return LspResult.success ()
}
Expand All @@ -284,18 +317,19 @@ type BackgroundServiceServer(state: State, client: FsacClient) =
state.FileCheckOptions.AddOrUpdate(file, (fun _ -> p.Options), (fun _ _ -> p.Options))
|> ignore
)
do! client.Notifiy {Value = sprintf "Project Updated %s" p.Options.ProjectFileName}
do! client.Notify {Value = sprintf "Project Updated %s" p.Options.ProjectFileName}
return LspResult.success ()
}

member __.FileSaved(p: FileParms) =
async {
let file = Utils.normalizePath p.File

do! client.Notifiy {Value = sprintf "File Saved %s " file }
let file = Utils.normalizePath p.File.FilePath

do! client.Notify {Value = sprintf "File Saved %s " file }

let projects = getDependingProjects file
let! filesToCheck = defaultArg (getListOfFilesForProjectChecking file) (async.Return [])
let! filesToCheck = defaultArg (getListOfFilesForProjectChecking p.File) (async.Return [])
let filesToCheck =
[
yield! filesToCheck
Expand Down
20 changes: 15 additions & 5 deletions src/FsAutoComplete.Core/BackgroundServices.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,18 @@ open FSharp.Compiler.SourceCodeServices

type Msg = {Value: string}

type BackgroundFileCheckType =
| SourceFile of filePath: string
| ScriptFile of filePath: string * tfm: FSIRefs.TFM
with
member x.FilePath =
match x with
| SourceFile(path)
| ScriptFile(path, _) -> path


type UpdateFileParms = {
File: string
File: BackgroundFileCheckType
Content: string
Version: int
}
Expand All @@ -19,7 +29,7 @@ type ProjectParms = {
}

type FileParms = {
File: string
File: BackgroundFileCheckType
}

let p =
Expand Down Expand Up @@ -60,13 +70,13 @@ let start () =
client.Start ()

let updateFile(file, content, version) =
let msg = {File = file; Content = content; Version = version}
let msg: UpdateFileParms = { File = file; Content = content; Version = version }
client.SendRequest "background/update" msg

let updateProject(file, opts) =
let msg = {File = file; Options = opts}
let msg = { File = file; Options = opts }
client.SendRequest "background/project" msg

let saveFile(file) =
let msg = {File = file}
let msg: FileParms = { File = file }
client.SendRequest "background/save" msg
42 changes: 27 additions & 15 deletions src/FsAutoComplete.Core/Commands.fs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ type Commands (serialize : Serializer, backgroundServiceEnabled) =
OtherOptions = opts.OtherOptions |> Array.map (fun n -> if FscArguments.isCompileFile(n) then Path.GetFullPath n else n)
}

let parseFilesInTheBackground files =
let parseFilesInTheBackground fsiScriptTFM files =
async {
files
|> List.toArray
Expand All @@ -178,8 +178,12 @@ type Commands (serialize : Serializer, backgroundServiceEnabled) =
| Some f -> Some (f.Lines)
| None when File.Exists(file) ->
let ctn = File.ReadAllLines file
state.Files.[file] <- {Touched = DateTime.Now; Lines = ctn; Version = None }
if backgroundServiceEnabled then BackgroundServices.updateFile(file, ctn |> String.concat "\n", 0)
state.Files.[file] <- { Touched = DateTime.Now; Lines = ctn; Version = None }
let payload =
if Utils.isAScript file
then BackgroundServices.ScriptFile(file, fsiScriptTFM)
else BackgroundServices.SourceFile file
if backgroundServiceEnabled then BackgroundServices.updateFile(payload, ctn |> String.concat "\n", 0)
Some (ctn)
| None -> None
match sourceOpt with
Expand Down Expand Up @@ -225,13 +229,13 @@ type Commands (serialize : Serializer, backgroundServiceEnabled) =
if insert.IsSome then state.CompletionNamespaceInsert.[n] <- insert.Value
} |> Async.Start

let onProjectLoaded projectFileName (response: ProjectCrackerCache) =
let onProjectLoaded projectFileName (response: ProjectCrackerCache) tfmForScripts =
for file in response.Items |> List.choose (function Dotnet.ProjInfo.Workspace.ProjectViewerItem.Compile(p, _) -> Some p) do
state.FileCheckOptions.[file] <- normalizeOptions response.Options

response.Items
|> List.choose (function Dotnet.ProjInfo.Workspace.ProjectViewerItem.Compile(p, _) -> Some p)
|> parseFilesInTheBackground
|> parseFilesInTheBackground tfmForScripts
|> Async.Start

let workspaceBinder () =
Expand All @@ -256,9 +260,14 @@ type Commands (serialize : Serializer, backgroundServiceEnabled) =
member __.LastCheckResult
with get() = lastCheckResult

member __.SetFileContent(file: SourceFilePath, lines: LineStr[], version) =
member __.SetFileContent(file: SourceFilePath, lines: LineStr[], version, tfmIfScript) =
state.AddFileText(file, lines, version)
if backgroundServiceEnabled then BackgroundServices.updateFile(file, lines |> String.concat "\n", defaultArg version 0)
let payload =
if Utils.isAScript file
then BackgroundServices.ScriptFile(file, tfmIfScript)
else BackgroundServices.SourceFile file

if backgroundServiceEnabled then BackgroundServices.updateFile(payload, lines |> String.concat "\n", defaultArg version 0)

member private x.MapResultAsync (successToString: 'a -> Async<CoreResponse>, ?failureToString: string -> CoreResponse) =
Async.bind <| function
Expand Down Expand Up @@ -359,8 +368,10 @@ type Commands (serialize : Serializer, backgroundServiceEnabled) =

member x.TryGetFileVersion = state.TryGetFileVersion

member x.Parse file lines version =
member x.Parse file lines version (isSdkScript: bool option) =
let file = Path.GetFullPath file
let tmf = isSdkScript |> Option.map (fun n -> if n then FSIRefs.NetCore else FSIRefs.NetFx) |> Option.defaultValue FSIRefs.NetFx

do x.CancelQueue file
async {
let colorizations = state.ColorizationOutput
Expand All @@ -369,7 +380,8 @@ type Commands (serialize : Serializer, backgroundServiceEnabled) =
let! result = checker.ParseAndCheckFileInProject(fileName, version, text, options)
return
match result with
| ResultOrString.Error e -> [CoreResponse.ErrorRes e]
| ResultOrString.Error e ->
[CoreResponse.ErrorRes e]
| ResultOrString.Ok (parseAndCheck) ->
let parseResult = parseAndCheck.GetParseResults
let results = parseAndCheck.GetCheckResults
Expand All @@ -387,7 +399,7 @@ type Commands (serialize : Serializer, backgroundServiceEnabled) =
let text = String.concat "\n" lines

if Utils.isAScript file then
let! checkOptions = checker.GetProjectOptionsFromScript(file, text)
let! checkOptions = checker.GetProjectOptionsFromScript(file, text, tmf)
state.AddFileTextAndCheckerOptions(file, lines, normalizeOptions checkOptions, Some version)
fileStateSet.Trigger ()
return! parse' file text checkOptions
Expand All @@ -398,7 +410,7 @@ type Commands (serialize : Serializer, backgroundServiceEnabled) =
state.SetFileVersion file version
async.Return c
| None -> async {
let! checkOptions = checker.GetProjectOptionsFromScript(file, text)
let! checkOptions = checker.GetProjectOptionsFromScript(file, text, tmf)
state.AddFileTextAndCheckerOptions(file, lines, normalizeOptions checkOptions, Some version)
return checkOptions
}
Expand Down Expand Up @@ -428,7 +440,7 @@ type Commands (serialize : Serializer, backgroundServiceEnabled) =

(opts.ProjectFileName, cached)

member x.Project projectFileName _verbose onChange = async {
member x.Project projectFileName _verbose onChange tfmForScripts = async {
let projectFileName = Path.GetFullPath projectFileName
let project =
match state.Projects.TryFind projectFileName with
Expand Down Expand Up @@ -460,7 +472,7 @@ type Commands (serialize : Serializer, backgroundServiceEnabled) =
return
match projResponse with
| Result.Ok (projectFileName, response) ->
onProjectLoaded projectFileName response
onProjectLoaded projectFileName response tfmForScripts
let responseFiles =
response.Items
|> List.choose (function Dotnet.ProjInfo.Workspace.ProjectViewerItem.Compile(p, _) -> Some p)
Expand Down Expand Up @@ -895,7 +907,7 @@ type Commands (serialize : Serializer, backgroundServiceEnabled) =
return [CoreResponse.WorkspacePeek d]
}

member x.WorkspaceLoad onChange (files: string list) (disableInMemoryProjectReferences: bool) = async {
member x.WorkspaceLoad onChange (files: string list) (disableInMemoryProjectReferences: bool) tfmForScripts = async {
checker.DisableInMemoryProjectReferences <- disableInMemoryProjectReferences
//TODO check full path
let projectFileNames = files |> List.map Path.GetFullPath
Expand Down Expand Up @@ -929,7 +941,7 @@ type Commands (serialize : Serializer, backgroundServiceEnabled) =
| WorkspaceProjectState.Loaded (opts, extraInfo, projectFiles, logMap) ->
let projectFileName, response = x.ToProjectCache(opts, extraInfo, projectFiles, logMap)
if backgroundServiceEnabled then BackgroundServices.updateProject(projectFileName, opts)
projectLoadedSuccessfully projectFileName response
projectLoadedSuccessfully projectFileName response tfmForScripts

let responseFiles =
response.Items
Expand Down
Loading