diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 1d064ef2..1c1ac00b 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,8 +7,10 @@ "tasks": [ { "command": "${workspaceRoot}/build.sh", + "type": "shell", "windows": { - "command": "${workspaceRoot}/build.cmd" + "command": "${workspaceRoot}/build.cmd", + "type": "process" // some people have bash as default in windows }, "label": "Build", "args": [ @@ -21,8 +23,10 @@ }, { "command": "${workspaceRoot}/build.sh", + "type": "shell", "windows": { - "command": "${workspaceRoot}/build.cmd" + "command": "${workspaceRoot}/build.cmd", + "type": "process" // some people have bash as default in windows }, "label": "Full Build", "args": [ @@ -32,8 +36,10 @@ }, { "command": "${workspaceRoot}/build.sh", + "type": "shell", "windows": { - "command": "${workspaceRoot}/build.cmd" + "command": "${workspaceRoot}/build.cmd", + "type": "process" // some people have bash as default in windows }, "label": "Watch", "args": [ diff --git a/build.sh b/build.sh index cae78dc4..d372dd9f 100755 --- a/build.sh +++ b/build.sh @@ -3,7 +3,7 @@ if test "$OS" = "Windows_NT" then # use .Net - ./paket.exe restore + .paket/paket.exe restore exit_code=$? if [ $exit_code -ne 0 ]; then exit $exit_code diff --git a/paket.lock b/paket.lock index 6be52e9f..9fef18f2 100644 --- a/paket.lock +++ b/paket.lock @@ -632,10 +632,10 @@ GIT (4b0866224a344c09f06254c0f70a78582530792c) GITHUB remote: ionide/ionide-vscode-helpers - src/Fable.Import.Showdown.fs (2c171bb4ccab0db3b1704389cdc736f9a23682d9) - src/Fable.Import.VSCode.fs (2c171bb4ccab0db3b1704389cdc736f9a23682d9) - src/Fable.Import.VSCode.LanguageServer.fs (2c171bb4ccab0db3b1704389cdc736f9a23682d9) - src/Helpers.fs (2c171bb4ccab0db3b1704389cdc736f9a23682d9) + src/Fable.Import.Showdown.fs (be3ff3c7c454525fee4bb9c36a3811e403a19f0b) + src/Fable.Import.VSCode.fs (be3ff3c7c454525fee4bb9c36a3811e403a19f0b) + src/Fable.Import.VSCode.LanguageServer.fs (be3ff3c7c454525fee4bb9c36a3811e403a19f0b) + src/Helpers.fs (be3ff3c7c454525fee4bb9c36a3811e403a19f0b) GROUP build NUGET remote: https://www.nuget.org/api/v2 diff --git a/release/images/error-inverse.svg b/release/images/error-inverse.svg new file mode 100644 index 00000000..51e9dc81 --- /dev/null +++ b/release/images/error-inverse.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/release/images/error.svg b/release/images/error.svg new file mode 100644 index 00000000..04b66689 --- /dev/null +++ b/release/images/error.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + diff --git a/release/images/warning-inverse.svg b/release/images/warning-inverse.svg new file mode 100644 index 00000000..a7f4afbc --- /dev/null +++ b/release/images/warning-inverse.svg @@ -0,0 +1,15 @@ + + + + +StatusWarning_16x + + + + + diff --git a/release/images/warning.svg b/release/images/warning.svg new file mode 100644 index 00000000..6d8cffe9 --- /dev/null +++ b/release/images/warning.svg @@ -0,0 +1,15 @@ + + + + +StatusWarning_16x + + + + + diff --git a/release/package.json b/release/package.json index 5160b6f7..217dff65 100644 --- a/release/package.json +++ b/release/package.json @@ -466,6 +466,46 @@ "light": "./images/lock-open-solid-light.svg", "dark": "./images/lock-open-solid.svg" } + }, + { + "command": "fake.targetsOutline.reloadTargets", + "title": "Reload targets from script", + "icon": { + "light": "./images/refresh-light.svg", + "dark": "./images/refresh-dark.svg" + } + }, + { + "command": "fake.targetsOutline.runTarget", + "title": "Run", + "icon": { + "light": "./images/run-mono-light.svg", + "dark": "./images/run-mono-dark.svg" + } + }, + { + "command": "fake.targetsOutline.debugTarget", + "title": "Debug", + "icon": { + "light": "./images/debug-mono-light.svg", + "dark": "./images/debug-mono-dark.svg" + } + }, + { + "command": "fake.targetsOutline.runSingleTarget", + "title": "Run Single", + "icon": { + "light": "./images/run-mono-light.svg", + "dark": "./images/run-mono-dark.svg" + } + }, + { + "command": "fake.targetsOutline.debugSingleTarget", + "title": "Debug Single", + "icon": { + "light": "./images/debug-mono-light.svg", + "dark": "./images/debug-mono-dark.svg" + } } ], "viewsContainers": { @@ -484,6 +524,11 @@ "id": "ionide.projectExplorer", "name": "F# Solution Explorer", "when": "fsharp.project.any && fsharp.showProjectExplorerInExplorerActivity" + }, + { + "id": "fake.targetsOutline", + "name": "FAKE Targets Outline", + "when": "fake.targetsOutline.show && fake.targetsOutline.showInExplorerActivity" } ], "ionide-fsharp": [ @@ -491,6 +536,11 @@ "id": "ionide.projectExplorerInActivity", "name": "Solution Explorer", "when": "fsharp.project.any && fsharp.showProjectExplorerInFsharpActivity" + }, + { + "id": "fake.targetsOutlineInActivity", + "name": "FAKE Targets Outline", + "when": "fake.targetsOutline.show && fake.targetsOutline.showInFsharpActivity" } ] }, @@ -675,6 +725,26 @@ { "command": "fsharp.showDocumentation", "when": "false" + }, + { + "command": "fake.targetsOutline.reloadTargets", + "when": "false" + }, + { + "command": "fake.targetsOutline.runTarget", + "when": "false" + }, + { + "command": "fake.targetsOutline.debugTarget", + "when": "false" + }, + { + "command": "fake.targetsOutline.runSingleTarget", + "when": "false" + }, + { + "command": "fake.targetsOutline.debugSingleTarget", + "when": "false" } ], "view/title": [ @@ -761,6 +831,16 @@ "command": "fsharp.explorer.clearCache", "group": "modification@5", "when": "view == ionide.projectExplorerInActivity" + }, + { + "command": "fake.targetsOutline.reloadTargets", + "group": "navigation@1", + "when": "view == fake.targetsOutline" + }, + { + "command": "fake.targetsOutline.reloadTargets", + "group": "navigation@1", + "when": "view == fake.targetsOutlineInActivity" } ], "view/item/context": [ @@ -1021,6 +1101,24 @@ "command": "fsharp.explorer.project.setDefault", "when": "viewItem == ionide.projectExplorer.projectExe", "group": "1_run@3" + }, + { + "command": "fake.targetsOutline.runTarget", + "when": "view =~ /(fake.targetsOutline|fake.targetsOutlineInActivity)/ && viewItem == fake.targetsOutline.target", + "group": "inline" + }, + { + "command": "fake.targetsOutline.debugTarget", + "when": "view =~ /(fake.targetsOutline|fake.targetsOutlineInActivity)/ && viewItem == fake.targetsOutline.target", + "group": "inline" + }, + { + "command": "fake.targetsOutline.runSingleTarget", + "when": "view =~ /(fake.targetsOutline|fake.targetsOutlineInActivity)/ && viewItem == fake.targetsOutline.target" + }, + { + "command": "fake.targetsOutline.debugSingleTarget", + "when": "view =~ /(fake.targetsOutline|fake.targetsOutlineInActivity)/ && viewItem == fake.targetsOutline.target" } ], "editor/context": [ @@ -1288,7 +1386,6 @@ "description": "The prefix displayed before the signature", "default": " // " }, - "FSharp.disableFailedProjectNotifications": { "type": "boolean", "default": false, @@ -1388,6 +1485,22 @@ "type": "boolean", "default": false, "description": "Logs additional information to F# output channel. Requires restart." + }, + "FAKE.targetsOutline": { + "type": "boolean", + "default": true, + "description": "Enables the Targets Outline tree view.", + "scope": "resource" + }, + "FAKE.showTargetsOutlineIn": { + "type": "string", + "default": "explorer", + "description": "Set the activity (left bar) where the FAKE targets outline view will be displayed.\nRequires restart.", + "enum": [ + "explorer", + "fsharp" + ], + "scope": "application" } } }, diff --git a/src/Components/FakeTargetsOutline.fs b/src/Components/FakeTargetsOutline.fs new file mode 100644 index 00000000..185757df --- /dev/null +++ b/src/Components/FakeTargetsOutline.fs @@ -0,0 +1,402 @@ +namespace Ionide.VSCode.FSharp + +open System +open Fable.Core +open Fable.Core.JsInterop +open Fable.Import +open Fable.Import.vscode +open Fable.Import.Node +open Ionide.VSCode.Helpers +open System.Collections.Generic + +open DTO +open DTO.FakeSupport +open Ionide.VSCode.Helpers +module node = Fable.Import.Node.Exports + +module FakeTargetsOutline = + + let private configurationKey = "FAKE.targetsOutline" + let private isEnabledFor uri = configurationKey |> Configuration.getInContext uri true + + type NodeEntry = + { Key : string + Children : Dictionary + Symbol : Symbol } + + type DependencyType = + | SoftDependency + | HardDependency + member x.Arrow = + match x with + | SoftDependency -> "<=?" + | HardDependency -> "<==" + type ModelType = + | TargetModel + | ErrorOrWarning + | DependencyModel of DependencyType + + type Model = + { AllTargets : Target [] + Label : string + Description : string + Declaration : Declaration option + Type : ModelType + getChildren : unit -> ResizeArray } + member x.IsTarget = + match x.Type with + | TargetModel -> true + | _ -> false + + let refresh = EventEmitter () + let private reallyRefresh = EventEmitter () + let mutable private currentDocument : string option = None + + let private getIconPath light dark = + let plugPath = + try + (VSCode.getPluginPath "Ionide.ionide-fsharp") + with + | _ -> (VSCode.getPluginPath "Ionide.Ionide-fsharp") + + let p = createEmpty + p.dark <- node.path.join(plugPath, "images", dark) |> U3.Case1 + p.light <- node.path.join(plugPath, "images", light) |> U3.Case1 + p + + let rec add' (state : NodeEntry) (symbol : Symbol) index = + let sep = "." + + let entry = symbol.Name + + if index >= entry.Length then + state + else + let endIndex = entry.IndexOf(sep, index) + let endIndex = if endIndex = -1 then entry.Length else endIndex + + let key = entry.Substring(index, endIndex - index) + if String.IsNullOrEmpty key then + state + else + if state.Children.ContainsKey key |> not then + let x = {Key = key; Children = new Dictionary<_,_>(); Symbol = symbol} + state.Children.Add(key,x) + let item = state.Children.[key] + add' item symbol (endIndex + 1) + + let getIcon (node:Model) = + // TODO use other icon for dependencies + match node.Type with + | ModelType.ErrorOrWarning -> + if node.AllTargets.Length = 0 then // error + Some <| getIconPath "error-inverse.svg" "error.svg" + else + Some <| getIconPath "warning-inverse.svg" "warning.svg" + | ModelType.TargetModel -> + Some <| getIconPath "icon-function-light.svg" "icon-function-dark.svg" + | ModelType.DependencyModel _ -> + Some <| getIconPath "auto-reveal-light.svg" "auto-reveal-dark.svg" + + let tryFindTarget (allTargets:Target[]) (name:string)= + allTargets |> Seq.tryFind (fun t -> t.Name.ToLowerInvariant() = name.ToLowerInvariant()) + + let rec depAsModel (allTargets:Target[]) (t:DependencyType) (d:Dependency) = + let mutable children = None + { + AllTargets = allTargets + Label = t.Arrow + " " + d.Name + Description = "A fake dependency" + Declaration = if isNull d.Declaration.File then None else Some d.Declaration + Type = ModelType.DependencyModel t + getChildren = fun () -> + match children with + | Some s -> s + | None -> + let n = + tryFindTarget allTargets d.Name + |> Option.map (targetAsModel allTargets) + |> Option.toArray + |> ResizeArray + children <- Some n + n + } + and targetAsModel (allTargets:Target[]) (t:Target) = + let mutable children = None + { + AllTargets = allTargets + Label = t.Name + Description = t.Description + Declaration = if isNull t.Declaration.File then None else Some t.Declaration + Type = ModelType.TargetModel + getChildren = fun () -> + match children with + | Some s -> s + | None -> + let n = + let hard = t.HardDependencies |> Seq.map (depAsModel allTargets DependencyType.HardDependency) + let soft = t.SoftDependencies |> Seq.map (depAsModel allTargets DependencyType.SoftDependency) + Seq.append hard soft + |> ResizeArray + children <- Some n + n + } + + let generateErrorEntry targets msg = + { + AllTargets = targets + Label = msg + Description = msg + Declaration = None + Type = ModelType.ErrorOrWarning + getChildren = fun () -> ResizeArray() + } + + let generateErrorRoot msg : ResizeArray = + [ generateErrorEntry [||] msg ] + |> ResizeArray + + let generateRootFromResponse (o:GetTargetsResult) : ResizeArray = + let items = + o.WarningsAndErrors + |> Seq.map (function + | GetTargetsWarningOrErrorType.NoFakeScript -> generateErrorEntry o.Targets "this script is not a FAKE script" + | GetTargetsWarningOrErrorType.MissingFakeCoreTargets -> generateErrorEntry o.Targets "this script does not use Fake.Core.Target" + | GetTargetsWarningOrErrorType.FakeCoreTargetsOlderThan5_15 -> generateErrorEntry o.Targets (sprintf "this script should be updated to at least Fake.Core.Target 5.15") + | GetTargetsWarningOrErrorType.MissingNavigationInfo -> generateErrorEntry o.Targets "navigation is missing, are you missing 'Target.initEnvironment()` at the top?" + | code -> generateErrorEntry o.Targets (sprintf "unknown error code %d" (int code))) + let realItems = + o.Targets + |> Seq.map (targetAsModel o.Targets) + + [ yield! items; yield! realItems] |> ResizeArray + + let createProvider () : TreeDataProvider = + { new TreeDataProvider with + member __.getParent = + None + + member this.onDidChangeTreeData = + reallyRefresh.event + + member this.getChildren(node) = + if JS.isDefined node then + node.getChildren() + else + let doc = window.activeTextEditor + if JS.isDefined doc + then + match currentDocument with + | None -> + currentDocument <- Some doc.document.fileName + | Some path -> + if path <> doc.document.fileName then + currentDocument <- Some doc.document.fileName + + match doc.document with + | Document.FSharp -> + promise { + let! o = LanguageService.FakeSupport.targetsInfo doc.document.fileName + if isNotNull o then + return generateRootFromResponse o + else + return generateErrorRoot "null response from fsac" + } |> unbox + | _ -> generateErrorRoot "No active F# document" + else generateErrorRoot "No active document" + + member this.getTreeItem(node) = + let children = node.getChildren() + let state = + if JS.isDefined children && children.Count > 0 then + Some TreeItemCollapsibleState.Collapsed + else None + + let ti = createEmpty + ti.label <- Some node.Label //getLabel node |> Some + ti.collapsibleState <- state + ti.iconPath <- getIcon node |> Option.map U4.Case3 + ti.contextValue <- + Some (match node.Type with + | ModelType.TargetModel -> "fake.targetsOutline.target" + | ModelType.DependencyModel _ -> "fake.targetsOutline.dependency" + | ModelType.ErrorOrWarning -> "fake.targetsOutline.error") + ti.tooltip <- Some node.Description + + let c = createEmpty + c.command <- "FAKE.targetsOutline.goTo" + c.title <- "open" + c.arguments <- Some (ResizeArray [| unbox node|]) + ti.command <- Some c + ti + } + + module private ShowInActivity = + let private setInFsharpActivity = Context.cachedSetter "fake.targetsOutline.showInFsharpActivity" + let private setInExplorerActivity = Context.cachedSetter "fake.targetsOutline.showInExplorerActivity" + + let showInFsharpActivity () = + let showIn = "FAKE.showTargetsOutlineIn" |> Configuration.get "explorer" + showIn = "fsharp" + + let initializeAndGetId () = + let inFsharpActivity = showInFsharpActivity () + setInFsharpActivity inFsharpActivity + setInExplorerActivity (not inFsharpActivity) + + if inFsharpActivity then "fake.targetsOutlineInActivity" else "fake.targetsOutline" + + let setShowCodeOutline = Context.cachedSetter "fake.targetsOutline.show" + + let inline private isFsharpFile (doc : TextDocument) = + match doc with + | Document.FSharp when doc.uri.scheme = "file" -> true + | _ -> false + + let private setShowTargetsOutlineForEditor (textEditor : TextEditor) = + let newValue = + if textEditor <> undefined then + if isFsharpFile textEditor.document || ShowInActivity.showInFsharpActivity() then + isEnabledFor textEditor.document.uri + else + false + else + false + setShowCodeOutline newValue + + let onDidChangeConfiguration (evt : ConfigurationChangeEvent) = + let textEditor = window.activeTextEditor + if textEditor <> undefined && evt.affectsConfiguration(configurationKey, textEditor.document.uri) then + setShowTargetsOutlineForEditor window.activeTextEditor + refresh.fire textEditor.document.uri + + let private onDidChangeActiveTextEditor (textEditor : TextEditor) = + setShowTargetsOutlineForEditor textEditor + if textEditor = undefined || (not (isFsharpFile textEditor.document)) then + reallyRefresh.fire(None) + + + type [] RequestLaunch = + { name : string + ``type`` : string + request : string + preLaunchTask : string option + program : string + args : string array + cwd : string + console : string + stopAtEntry : bool + justMyCode : bool + requireExactSource : bool } + + let activate (context : ExtensionContext) = + setShowTargetsOutlineForEditor window.activeTextEditor + + let treeViewId = ShowInActivity.initializeAndGetId () + + window.onDidChangeActiveTextEditor.Invoke(unbox onDidChangeActiveTextEditor) + |> context.subscriptions.Add + + refresh.event.Invoke(fun uri -> + if isEnabledFor uri then + reallyRefresh.fire(None) + createEmpty) + |> context.subscriptions.Add + + workspace.onDidChangeConfiguration.Invoke(unbox onDidChangeConfiguration) + |> context.subscriptions.Add + + commands.registerCommand("FAKE.targetsOutline.goTo", Func(fun n -> + let m = unbox n + match m.Declaration with + | Some decl -> + let line = decl.Line + let args = + createObj [ + "lineNumber" ==> line + "at" ==> "center" + ] + + vscode.commands.executeCommand("revealLine", args) + |> unbox + | None -> JS.undefined + )) |> context.subscriptions.Add + + let runFake doDebug onlySingleTarget targetName = + promise { + match currentDocument with + | Some scriptFile -> + let scriptDir = node.path.dirname scriptFile + let scriptName = node.path.basename scriptFile + let! fakeRuntime = LanguageService.FakeSupport.fakeRuntime() + let preArg = if onlySingleTarget then "-st" else "-t" + let args = + if doDebug then [| "run"; "--nocache"; "--fsiargs"; "--debug:portable --optimize-"; scriptName; preArg; targetName |] + else [| "run"; scriptName; preArg; targetName |] + let cfg : RequestLaunch = + { name = "Fake Script Debugging" + ``type`` = "coreclr" + request = "launch" + preLaunchTask = None + program = fakeRuntime + args = args + cwd = scriptDir + console = "externalTerminal" + stopAtEntry = false + justMyCode = false + requireExactSource = false } + if doDebug then + let! res = debug.startDebugging(JS.undefined, unbox cfg) + () + else + let! dotnet = Environment.dotnet + match dotnet with + | None -> + let! _ = window.showErrorMessage("Cannot start fake as no dotnet runtime was found. Consider configuring one in ionide settings.") + () + | Some dotnet -> + let taskDef = createEmpty + taskDef.``type`` <- Some "fakerun" + let opts = createEmpty + opts.cwd <- Some cfg.cwd + let procExp = ProcessExecution(dotnet, [| yield fakeRuntime; yield! args |], opts) + let task = Task(taskDef, ConfigurationTarget.Global, "fake run", "fake", procExp) + let exec = tasks.executeTask(task) + () + | None -> + let! _ = window.showErrorMessage("Cannot start fake as no script file is selected.") + () + } + + let debugTarget onlySingleTarget targetName = + runFake true onlySingleTarget targetName :> obj + + let runTarget onlySingleTarget targetName = + runFake false onlySingleTarget targetName :> obj + + commands.registerCommand("fake.targetsOutline.reloadTargets", Func(fun _ -> + refresh.fire undefined |> unbox + )) |> context.subscriptions.Add + commands.registerCommand("fake.targetsOutline.runTarget", Func(fun n -> + let item = unbox n + runTarget false item.Label + )) |> context.subscriptions.Add + commands.registerCommand("fake.targetsOutline.debugTarget", Func(fun n -> + let item = unbox n + debugTarget false item.Label + )) |> context.subscriptions.Add + commands.registerCommand("fake.targetsOutline.runSingleTarget", Func(fun n -> + let item = unbox n + runTarget true item.Label + )) |> context.subscriptions.Add + commands.registerCommand("fake.targetsOutline.debugSingleTarget", Func(fun n -> + let item = unbox n + debugTarget true item.Label + )) |> context.subscriptions.Add + + let provider = createProvider () + let treeOptions = createEmpty> + treeOptions.treeDataProvider <- provider + treeOptions.showCollapseAll <- Some true + let treeView = window.createTreeView(treeViewId, treeOptions) + context.subscriptions.Add treeView diff --git a/src/Core/DTO.fs b/src/Core/DTO.fs index 996bc50e..f7587804 100644 --- a/src/Core/DTO.fs +++ b/src/Core/DTO.fs @@ -372,3 +372,31 @@ module DTO = type CompileResult = Result type AnalyzerResult = Result type FsdnResult = Result + + + module FakeSupport = + type FakeContext = + { DotNetRuntime : string } + type TargetRequest = + { FileName : string; FakeContext : FakeContext } + + /// a target dependency, either a hard or a soft dependency. + type Dependency = + { Name : string + Declaration : Declaration } + /// a FAKE target, its description and its relations to other targets (dependencies), including the declaration lines of the target and the dependencies. + type Target = + { Name : string + HardDependencies : Dependency [] + SoftDependencies : Dependency [] + Declaration : Declaration + Description : string } + + type GetTargetsWarningOrErrorType = + | NoFakeScript = 1 + | MissingFakeCoreTargets = 2 + // Most likely due to missing `Target.initEnvironment()` + | MissingNavigationInfo = 4 + | FakeCoreTargetsOlderThan5_15 = 3 + + type GetTargetsResult = { WarningsAndErrors : GetTargetsWarningOrErrorType []; Targets : Target [] } \ No newline at end of file diff --git a/src/Core/LanguageService.fs b/src/Core/LanguageService.fs index 200deec4..3b11898a 100644 --- a/src/Core/LanguageService.fs +++ b/src/Core/LanguageService.fs @@ -301,6 +301,57 @@ module LanguageService = () ) + + module FakeSupport = + open DTO.FakeSupport + + let logger = ConsoleAndOutputChannelLogger(Some "FakeTargets", Level.DEBUG, None, Some Level.DEBUG) + + let fakeRuntime () = + match client with + | None -> + Promise.empty + | Some cl -> + cl.sendRequest("fake/runtimePath", null) + |> Promise.map (fun (res: Types.PlainNotification) -> + res.content |> ofJson> + ) + |> Promise.onFail (fun o -> + logger.Error("Error in fake/runtimePath request.", o) + ) + |> Promise.map (fun c -> c.Data) + + let targetsInfo (fn:string) = + match client with + | None -> + Promise.empty + | Some cl -> + Environment.dotnet + |> Promise.bind (fun dotnetRuntime -> + match dotnetRuntime with + | Some r -> + let req = { TargetRequest.FileName = handleUntitled fn; FakeContext = { DotNetRuntime = r }} + cl.sendRequest("fake/listTargets", req) + |> Promise.map (fun (res: Types.PlainNotification) -> + res.content |> ofJson + ) + |> Promise.onFail (fun o -> + logger.Error("Error in fake/listTargets request.", o) + ) + + | None -> + let msg = """ +Cannot request fake targets because `dotnet` was not found. +Consider: +* setting the `FSharp.dotnetLocation` settings key to a `dotnet` binary, +* including `dotnet` in your PATH, +* installing .NET Core into one of the default locations, or +""" + logger.Error(msg) + Promise.reject (msg) + ) + + let private fsacConfig () = compilerLocation () |> Promise.map (fun c -> c.Data) diff --git a/src/Core/Project.fs b/src/Core/Project.fs index 15619a46..96c98c9a 100644 --- a/src/Core/Project.fs +++ b/src/Core/Project.fs @@ -593,15 +593,17 @@ module Project = let private workspacePeek () = promise { - let! ws = LanguageService.workspacePeek (vscode.workspace.rootPath) deepLevel (excluded |> List.ofArray) - return - ws.Found - |> Array.sortBy (fun x -> - match x with - | WorkspacePeekFound.Solution sln -> countProjectsInSln sln - | WorkspacePeekFound.Directory _ -> -1) - |> Array.rev - |> List.ofArray + if isNull vscode.workspace.rootPath then return [] + else + let! ws = LanguageService.workspacePeek (vscode.workspace.rootPath) deepLevel (excluded |> List.ofArray) + return + ws.Found + |> Array.sortBy (fun x -> + match x with + | WorkspacePeekFound.Solution sln -> countProjectsInSln sln + | WorkspacePeekFound.Directory _ -> -1) + |> Array.rev + |> List.ofArray } let private getWorkspace () = diff --git a/src/Ionide.FSharp.fsproj b/src/Ionide.FSharp.fsproj index fd1b50a9..0062eec3 100644 --- a/src/Ionide.FSharp.fsproj +++ b/src/Ionide.FSharp.fsproj @@ -43,6 +43,7 @@ + diff --git a/src/fsharp.fs b/src/fsharp.fs index 4fc20176..10a7c0d6 100644 --- a/src/fsharp.fs +++ b/src/fsharp.fs @@ -66,6 +66,7 @@ let activate (context : ExtensionContext) : Fable.Import.JS.Promise = HtmlConverter.activate context InfoPanel.activate context CodeLensHelpers.activate context + FakeTargetsOutline.activate context let buildProject project = promise { let! exit = MSBuild.buildProjectPath "Build" project