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 @@
+
+
+
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 @@
+
+
+
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