Skip to content

Commit

Permalink
Fsi watcher (#1327)
Browse files Browse the repository at this point in the history
* FSI Watcher initial implementation

* Add types and functions to fsi watcher

* Add prompt when sometimes try to open watcher without enabling it
  • Loading branch information
Krzysztof-Cieslak committed Mar 22, 2020
1 parent c7b57d9 commit 0ae77f0
Show file tree
Hide file tree
Showing 5 changed files with 331 additions and 3 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Expand Up @@ -72,6 +72,10 @@ issueList.md
release/package-lock\.json
*.vsix
/release/bin_netcore/
/.ionide
/vendor/.paket/Paket.Restore.targets
/release-exp/
vars.txt
funcs.txt
types.txt
.ionide
.fake
17 changes: 15 additions & 2 deletions .vscode/tasks.json
Expand Up @@ -5,6 +5,20 @@
},
"type": "shell",
"tasks": [
{
"command": "${workspaceRoot}/build.sh",
"type": "shell",
"windows": {
"command": "${workspaceRoot}/build.cmd",
"type": "process" // some people have bash as default in windows
},
"label": "Extension Build",
"args": [],
"group": {
"isDefault": true,
"kind": "build"
}
},
{
"command": "${workspaceRoot}/build.sh",
"type": "shell",
Expand All @@ -24,14 +38,13 @@
"type": "shell",
"windows": {
"command": "${workspaceRoot}/build.cmd",
"type": "process" // some people have bash as default in windows
"type": "process"
},
"label": "Watch",
"args": [
"-t",
"Watch"
],
"group": "build",
"isBackground": true,
"problemMatcher": []
}
Expand Down
9 changes: 9 additions & 0 deletions release/package.json
Expand Up @@ -113,6 +113,10 @@
"command": "fsi.GenerateProjectReferences",
"title": "FSI: Generate script file with references from project"
},
{
"command": "fsi.OpenWatcher",
"title": "FSI: Open Watcher"
},
{
"command": "fsharp.scriptrunner.run",
"title": "F#: Run script",
Expand Down Expand Up @@ -1523,6 +1527,11 @@
"FSharp.dotNetRoot": {
"type": "string",
"description": "Sets the root path for finding dotnet SDK references. Primarily used for FSX Scripts."
},
"FSharp.addFsiWatcher": {
"type": "bool",
"default": false,
"description": "Enables the variables view for the fsi"
}
}
},
Expand Down
140 changes: 140 additions & 0 deletions release/watcher/watcher.fsx
@@ -0,0 +1,140 @@
fsi.AddPrinter (fun (_: obj) ->
let fsiAssembly =
System.AppDomain.CurrentDomain.GetAssemblies()
|> Seq.find (fun assm -> assm.GetName().Name = "FSI-ASSEMBLY")


let getWatchableVariables () =
fsiAssembly.GetTypes()//FSI types have the name pattern FSI_####, where #### is the order in which they were created
|> Seq.filter (fun ty -> ty.Name.StartsWith("FSI_"))
|> Seq.sortBy (fun ty -> ty.Name.Split('_').[1] |> int)
|> Seq.collect (fun ty ->
ty.GetProperties()
|> Seq.filter (fun pi -> pi.GetIndexParameters().Length > 0 |> not && pi.Name.Contains("@") |> not))
|> Seq.mapi (fun i pi -> pi.Name, (i, pi)) //remember the order
|> Map.ofSeq //remove leading duplicates (but now ordered by property name)
|> Map.toSeq //reconstitue
|> Seq.sortBy (fun (_,(i,_)) -> i) //order by original index
|> Seq.map (fun (_,(_,pi)) -> pi.Name, pi.GetValue(null, Array.empty), pi.PropertyType) //discard ordering index, project usuable watch value

let getRecords () =
fsiAssembly.GetTypes()
|> Seq.filter (fun ty -> ty.FullName.StartsWith("FSI"))
|> Seq.filter (Reflection.FSharpType.IsRecord)
|> Seq.map (fun ty ->
let flds =
Reflection.FSharpType.GetRecordFields ty
|> Seq.map (fun n -> n.Name, n.PropertyType.Name )
ty.Name, flds)

let getUnions () =
fsiAssembly.GetTypes()
|> Seq.filter (fun ty -> ty.FullName.StartsWith("FSI"))
|> Seq.filter (Reflection.FSharpType.IsUnion)
|> Seq.filter (fun ty -> ty.BaseType.Name = "Object") //find DU declaration not DU cases
|> Seq.map (fun ty ->
let flds =
Reflection.FSharpType.GetUnionCases ty
|> Seq.map (fun n ->
let props =
n.GetFields ()
|> Seq.map (fun n -> n.Name, n.PropertyType.Name )
n.Name, props )
ty.Name, flds)

let getFuncs () =
fsiAssembly.GetTypes()
|> Seq.filter (fun ty -> ty.FullName.StartsWith("FSI"))
|> Seq.filter (Reflection.FSharpType.IsModule)
|> Seq.choose (fun ty ->
let meth =
ty.GetMethods ()
|> Seq.filter (fun m ->
m.IsStatic && not (Seq.isEmpty (m.GetParameters())) && m.Name <> "set_it"
)
|> Seq.map (fun m ->
let parms =
m.GetParameters ()
|> Seq.map (fun p -> p.Name, if p.ParameterType.IsGenericParameter then "'" + p.ParameterType.Name else p.ParameterType.Name)
m.Name, parms, m.ReturnType.Name)
if Seq.isEmpty meth then None else Some (meth)
)
|> Seq.collect id

let variablesAction =
async {
try
let vars =
getWatchableVariables ()
|> Seq.map (fun (name, value, typ) ->
let x = sprintf "%s###IONIDESEP###%A###IONIDESEP###%s" name value typ.Name
x.Replace("\n",";")
)
|> String.concat "\n"
let path = System.IO.Path.Combine(__SOURCE_DIRECTORY__, "vars.txt")
System.IO.File.WriteAllText(path, vars)
with
| _ -> ()
}

let funcsAction =
async {
try
let vars =
getFuncs ()
|> Seq.map (fun (name, parms, typ) ->
let parms =
parms |> Seq.map (fun (n,t) -> n + ": " + t) |> String.concat "; "

let x = sprintf "%s###IONIDESEP###%s###IONIDESEP###%s" name parms typ
x.Replace("\n",";")
)
|> String.concat "\n"
let path = System.IO.Path.Combine(__SOURCE_DIRECTORY__, "funcs.txt")
System.IO.File.WriteAllText(path, vars)
with
| _ -> ()
}

let typesAction =
async {
try
let records =
getRecords ()
|> Seq.map (fun (name,flds) ->
let f =
flds
|> Seq.map (fun (x,y) -> x + ": " + y)
|> String.concat "; "
let x = sprintf "%s###IONIDESEP###%s" name f
x.Replace("\n",";")
)
|> String.concat "\n"
let unions =
getUnions ()
|> Seq.map (fun (name,flds) ->
let f =
flds
|> Seq.map (fun (x,props) ->
let y = props |> Seq.map (fun (x,y) -> sprintf "(%s: %s)" x y ) |> String.concat " * "

if System.String.IsNullOrWhiteSpace y then x else x + ": " + y)
|> String.concat "#|#"
let x = sprintf "%s###IONIDESEP###%s" name f
x.Replace("\n",";")
)
|> String.concat "\n"

let path = System.IO.Path.Combine(__SOURCE_DIRECTORY__, "types.txt")
System.IO.File.WriteAllText(path, records + "\n" + unions)
with
| _ -> ()
}


Async.Start variablesAction
Async.Start typesAction
Async.Start funcsAction

null
)

0 comments on commit 0ae77f0

Please sign in to comment.