├── .nuget
├── nuget.config
├── nuget.exe
└── nuget.targets
├── Debug.FsEye
├── App.config
├── AssemblyInfo.fs
├── Debug.FsEye.fsproj
└── Program.fs
├── FsEye.DataGridView.Plugin
├── AssemblyInfo.fs
├── DataGridViewPlugin.fs
└── FsEye.DataGridView.Plugin.fsproj
├── FsEye.PropertyGrid.Plugin
├── AssemblyInfo.fs
├── FsEye.PropertyGrid.Plugin.fsproj
└── PropertyGridPlugin.fs
├── FsEye.TreeView.Plugin
├── AssemblyInfo.fs
├── FsEye.TreeView.Plugin.fsproj
└── TreeViewPlugin.fs
├── FsEye.nuspec
├── FsEye.sln
├── FsEye
├── AssemblyInfo.fs
├── Forms
│ ├── EyeForm.fs
│ ├── EyePanel.fs
│ ├── EyeSplitContainer.fs
│ ├── PluginTabControl.fs
│ └── WatchTreeView.fs
├── FsEye.NuGet.fsx
├── FsEye.fsproj
├── FsEye.fsx
├── Fsi
│ ├── Eye.fs
│ └── SessionQueries.fs
├── IconResource.fs
├── ImageResource.fs
├── PluginSystem.fs
├── Resources
│ ├── FsEye.ico
│ ├── VSObject_Field.bmp
│ ├── VSObject_Field_Friend.bmp
│ ├── VSObject_Field_Private.bmp
│ ├── VSObject_Field_Protected.bmp
│ ├── VSObject_Field_Sealed.bmp
│ ├── VSObject_Method.bmp
│ ├── VSObject_Method_Friend.bmp
│ ├── VSObject_Method_Private.bmp
│ ├── VSObject_Method_Protected.bmp
│ ├── VSObject_Method_Sealed.bmp
│ ├── VSObject_Properties.bmp
│ ├── VSObject_Properties_Friend.bmp
│ ├── VSObject_Properties_Private.bmp
│ ├── VSObject_Properties_Protected.bmp
│ └── VSObject_Properties_Sealed.bmp
├── Script.fsx
├── Settings.fs
├── WatchModel.fs
├── Win32.fs
└── todo.txt
├── LICENSE
├── NOTICE
├── README.md
├── Test.FsEye
├── DataGridViewPluginTests.fs
├── PluginManagerTests.fs
├── Test.FsEye.fsproj
├── WatchModelTests.fs
├── WatchTreeViewLabelCalculatorTests.fs
├── WatchTreeViewTests.fs
└── packages.config
├── Utils
├── AssemblyInfo.fs
├── ControlExt.fs
├── MiscUtils.fs
├── RegexUtils.fs
├── SeqExt.fs
├── TypeExt.fs
└── Utils.fsproj
├── package.bat
└── tools
└── 7z
├── 7za.exe
├── license.txt
└── readme.txt
/.nuget/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.nuget/nuget.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/.nuget/nuget.exe
--------------------------------------------------------------------------------
/.nuget/nuget.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildProjectDirectory)\..\
5 |
6 |
7 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
8 | $([System.IO.Path]::Combine($(ProjectDir), "packages.config"))
9 | $([System.IO.Path]::Combine($(SolutionDir), "packages"))
10 |
11 |
12 | $(SolutionDir).nuget
13 | packages.config
14 | $(SolutionDir)packages
15 |
16 |
17 | $(NuGetToolsPath)\nuget.exe
18 | "$(NuGetExePath)"
19 | mono --runtime=v4.0.30319 $(NuGetExePath)
20 |
21 | $(TargetDir.Trim('\\'))
22 |
23 |
24 | ""
25 |
26 |
27 | false
28 |
29 |
30 | false
31 |
32 |
33 | $(NuGetCommand) install "$(PackagesConfig)" -source $(PackageSources) -o "$(PackagesDir)"
34 | $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols
35 |
36 |
37 |
38 | RestorePackages;
39 | $(BuildDependsOn);
40 |
41 |
42 |
43 |
44 | $(BuildDependsOn);
45 | BuildPackage;
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
57 |
58 |
61 |
62 |
63 |
64 |
66 |
67 |
70 |
71 |
--------------------------------------------------------------------------------
/Debug.FsEye/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Debug.FsEye/AssemblyInfo.fs:
--------------------------------------------------------------------------------
1 | namespace Debug.FsEye
2 |
3 | open System.Reflection
4 | open System.Runtime.CompilerServices
5 | open System.Runtime.InteropServices
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | []
11 | []
12 | []
13 | []
14 | []
15 | []
16 | []
17 |
18 | // Setting ComVisible to false makes the types in this assembly not visible
19 | // to COM components. If you need to access a type in this assembly from
20 | // COM, set the ComVisible attribute to true on that type.
21 | []
22 |
23 | // The following GUID is for the ID of the typelib if this project is exposed to COM
24 | []
25 |
26 | // Version information for an assembly consists of the following four values:
27 | //
28 | // Major Version
29 | // Minor Version
30 | // Build Number
31 | // Revision
32 | //
33 | // You can specify all the values or you can default the Build and Revision Numbers
34 | // by using the '*' as shown below:
35 | // [assembly: AssemblyVersion("1.0.*")]
36 | []
37 | []
38 |
39 | ()
40 |
--------------------------------------------------------------------------------
/Debug.FsEye/Debug.FsEye.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | x86
6 | 8.0.30703
7 | 2.0
8 | {fd9691cc-8069-4c02-b3b6-f990fbd6114d}
9 | WinExe
10 | Debug.FsEye
11 | Debug.FsEye
12 | v4.0
13 |
14 |
15 | Debug.FsEye
16 |
17 |
18 | true
19 | full
20 | false
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | 3
25 | x86
26 | bin\Debug\Debug.FsEye.XML
27 |
28 |
29 | pdbonly
30 | true
31 | true
32 | bin\Release\
33 | TRACE
34 | 3
35 | x86
36 | bin\Release\Debug.FsEye.XML
37 |
38 |
39 | true
40 | full
41 | false
42 | false
43 | bin\Debug\
44 | DEBUG;TRACE
45 | 3
46 | bin\Debug\Debug.FsEye.XML
47 | AnyCPU
48 |
49 |
50 | pdbonly
51 | true
52 | true
53 | bin\Release\
54 | TRACE
55 | 3
56 | bin\Release\Debug.FsEye.XML
57 | AnyCPU
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | Always
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | FsEye.DataGridView.Plugin
82 | {cc563dcc-2eaf-4822-b4ce-9d80acca6b5e}
83 | True
84 |
85 |
86 | FsEye.PropertyGrid.Plugin
87 | {1f9b8cb8-8c0c-424c-9035-6562b637a233}
88 | True
89 |
90 |
91 | FsEye.TreeView.Plugin
92 | {669bc59d-958a-4096-affe-7776cf68b46e}
93 | True
94 |
95 |
96 | FsEye
97 | {20de2466-d7b1-4f72-b8f8-51f10f5f186e}
98 | True
99 |
100 |
101 |
108 |
--------------------------------------------------------------------------------
/Debug.FsEye/Program.fs:
--------------------------------------------------------------------------------
1 | namespace Debug.FsEye
2 |
3 | open System
4 | open System.Drawing
5 | open System.Windows.Forms
6 | open System.Xml
7 | open Swensen.FsEye
8 | open Swensen.FsEye.Forms
9 |
10 | type IHuman =
11 | abstract member Name : string
12 |
13 | type Student =
14 | { Name:string; Age:int }
15 | interface IHuman with
16 | member this.Name = this.Name
17 |
18 | type Professor =
19 | { Name:string; Age:int; Rank: int }
20 | interface IHuman with
21 | member this.Name = this.Name
22 |
23 | module Main =
24 | let initEye() =
25 | let eye = new Swensen.FsEye.Forms.EyeForm(new PluginManager())
26 | eye.Watch("x", 3)
27 | eye.Watch("y", new System.Collections.Generic.List(Seq.init 200 id))
28 | eye.Watch("some null value", null, typeof>)
29 | let value = ([|3.2; 2.; 1.; -3.; 23.|],[|"a";"b";"c";"d";"e"|])
30 | eye.Watch("series", value, value.GetType())
31 | let value = [{Student.Name="Tom"; Age=3};{Name="Jane"; Age=9}]
32 | eye.Watch("series2", value, value.GetType())
33 |
34 | let value = [
35 | {Professor.Name="Jane"; Age=9; Rank=23} :> IHuman;
36 | {Student.Name="Tom"; Age=3} :> IHuman; ]
37 | eye.Watch("series3", value, value.GetType())
38 |
39 | let doc = new XmlDocument()
40 | doc.AppendChild(doc.CreateProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\"")) |> ignore
41 | let root = doc.CreateElement("Root")
42 | root.AppendChild(doc.CreateElement("Apple"))|>ignore
43 | doc.AppendChild(root)|>ignore
44 | eye.Watch("xmldoc", doc)
45 |
46 | eye.Watch("eye", eye, eye.GetType())
47 | eye
48 |
49 |
50 | []
51 | []
52 | do
53 | Application.EnableVisualStyles()
54 | Application.SetCompatibleTextRenderingDefault(false)
55 | Application.Run(initEye())
56 |
--------------------------------------------------------------------------------
/FsEye.DataGridView.Plugin/AssemblyInfo.fs:
--------------------------------------------------------------------------------
1 | namespace FsEye.DataGridView.Plugin
2 |
3 | open System.Reflection
4 | open System.Runtime.CompilerServices
5 | open System.Runtime.InteropServices
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | []
11 | []
12 | []
13 | []
14 | []
15 | []
16 | []
17 |
18 | // Setting ComVisible to false makes the types in this assembly not visible
19 | // to COM components. If you need to access a type in this assembly from
20 | // COM, set the ComVisible attribute to true on that type.
21 | []
22 |
23 | // The following GUID is for the ID of the typelib if this project is exposed to COM
24 | []
25 |
26 | // Version information for an assembly consists of the following four values:
27 | //
28 | // Major Version
29 | // Minor Version
30 | // Build Number
31 | // Revision
32 | //
33 | //the fourth position is for beta release numbers
34 | []
35 |
36 | ()
--------------------------------------------------------------------------------
/FsEye.DataGridView.Plugin/DataGridViewPlugin.fs:
--------------------------------------------------------------------------------
1 | namespace Swensen.FsEye.Plugins
2 |
3 | open System
4 | open Swensen.FsEye
5 | open System.Windows.Forms
6 | open System.Drawing
7 |
8 | //todo:share common code between this plugin and the PropertyGrid plugin
9 |
10 | module private Helpers =
11 | //see http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.datasource.aspx
12 | let ValidDataSourceTypes = [
13 | typeof
14 | typeof
15 | typeof
16 | typeof]
17 |
18 | ///A DataGridView-based watch viewer
19 | type DataGridViewWatchViewer() =
20 | let panel = new Panel()
21 |
22 | let dgv = new DataGridView(Dock=DockStyle.Fill)
23 | do
24 | //swallow dataerror events: these can happen when we have a non-homogeneous collection
25 | dgv.DataError.Add(fun target -> ())
26 | panel.Controls.Add(dgv)
27 |
28 | let labelPanel = new FlowLayoutPanel(Dock=DockStyle.Top, AutoSize=true, Padding=Padding(0,3,3,5))
29 | let labelLabel = new Label(Text="Source Expression:", AutoSize=true)
30 | let expressionLabel = new Label(AutoSize=true)
31 | do
32 | labelLabel.Font <- new Font(labelLabel.Font, FontStyle.Bold)
33 | labelPanel.Controls.Add(labelLabel)
34 |
35 | labelPanel.Controls.Add(expressionLabel)
36 |
37 | panel.Controls.Add(labelPanel)
38 |
39 | interface IWatchViewer with
40 | ///Set or refresh the DataGridView DataSource with the given value.
41 | member this.Watch(label, value, ty) =
42 | expressionLabel.Text <- label
43 | let value =
44 | if not (Helpers.ValidDataSourceTypes |> List.exists (fun validTy -> validTy.IsAssignableFrom(ty))) then //must be non-generic IEnumerable
45 | //note: List works but T[] does not
46 | System.Collections.Generic.List(value :?> System.Collections.IEnumerable |> Seq.cast) :> obj
47 | else
48 | value
49 | dgv.DataSource <- value
50 | ///Get the underlying Control of this watch view
51 | member this.Control = panel :> Control
52 |
53 | ///A Plugin that creates DataGridViewWatchViewers
54 | type DataGridViewPlugin() =
55 | interface IPlugin with
56 | ///"Property Grid"
57 | member __.Name = "Data Grid"
58 | ///Create a new instance of a DataGridViewWatchViewer
59 | member __.CreateWatchViewer() = new DataGridViewWatchViewer() :> IWatchViewer
60 | ///Returns true if and only if the given value is not null and the given type implements one of the interfaces supported by DataGridView.DataSource.
61 | member this.IsWatchable(value:obj, ty:Type) =
62 | //we convert non-generic IEnumerables to ILists for convenience
63 | let validTys = typeof :: Helpers.ValidDataSourceTypes
64 | value <> null && (validTys |> List.exists (fun validTy -> validTy.IsAssignableFrom(ty)))
--------------------------------------------------------------------------------
/FsEye.DataGridView.Plugin/FsEye.DataGridView.Plugin.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 8.0.30703
7 | 2.0
8 | {cc563dcc-2eaf-4822-b4ce-9d80acca6b5e}
9 | Library
10 | FsEye.DataGridView.Plugin
11 | FsEye.DataGridView.Plugin
12 | v4.0
13 | FsEye.DataGridView.Plugin
14 |
15 |
16 | true
17 | full
18 | false
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | 3
23 | bin\Debug\FsEye.DataGridView.Plugin.XML
24 |
25 |
26 | pdbonly
27 | true
28 | true
29 | bin\Release\
30 | TRACE
31 | 3
32 | bin\Release\FsEye.DataGridView.Plugin.XML
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | FsEye
50 | {20de2466-d7b1-4f72-b8f8-51f10f5f186e}
51 | True
52 |
53 |
54 |
55 |
56 |
63 |
--------------------------------------------------------------------------------
/FsEye.PropertyGrid.Plugin/AssemblyInfo.fs:
--------------------------------------------------------------------------------
1 | namespace FsEye.PropertyGrid.Plugin
2 |
3 | open System.Reflection
4 | open System.Runtime.CompilerServices
5 | open System.Runtime.InteropServices
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | []
11 | []
12 | []
13 | []
14 | []
15 | []
16 | []
17 |
18 | // Setting ComVisible to false makes the types in this assembly not visible
19 | // to COM components. If you need to access a type in this assembly from
20 | // COM, set the ComVisible attribute to true on that type.
21 | []
22 |
23 | // The following GUID is for the ID of the typelib if this project is exposed to COM
24 | []
25 |
26 | // Version information for an assembly consists of the following four values:
27 | //
28 | // Major Version
29 | // Minor Version
30 | // Build Number
31 | // Revision
32 | //
33 | //the fourth position is for beta release numbers
34 | []
35 |
36 | ()
37 |
--------------------------------------------------------------------------------
/FsEye.PropertyGrid.Plugin/FsEye.PropertyGrid.Plugin.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | x86
6 | 8.0.30703
7 | 2.0
8 | {1f9b8cb8-8c0c-424c-9035-6562b637a233}
9 | Library
10 | FsEye.PropertyGrid.Plugin
11 | FsEye.PropertyGrid.Plugin
12 | v4.0
13 |
14 |
15 | FsEye.PropertyGrid.Plugin
16 |
17 |
18 | true
19 | full
20 | false
21 | false
22 | bin\Debug\plugins
23 | DEBUG;TRACE
24 | 3
25 | x86
26 | bin\Debug\FsEye.PropertyGrid.Plugin.XML
27 |
28 |
29 | pdbonly
30 | true
31 | true
32 | bin\Release\plugins
33 | TRACE
34 | 3
35 | x86
36 | bin\Release\FsEye.PropertyGrid.Plugin.xml
37 |
38 |
39 | true
40 | full
41 | false
42 | true
43 | bin\Debug\
44 | DEBUG;TRACE
45 | 3
46 | bin\Debug\FsEye.PropertyGrid.Plugin.xml
47 | AnyCPU
48 |
49 |
50 | pdbonly
51 | true
52 | true
53 | bin\Release\
54 | TRACE
55 | 3
56 | bin\Release\FsEye.PropertyGrid.Plugin.xml
57 | AnyCPU
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | FsEye
76 | {20de2466-d7b1-4f72-b8f8-51f10f5f186e}
77 | True
78 |
79 |
80 |
81 |
82 |
89 |
--------------------------------------------------------------------------------
/FsEye.PropertyGrid.Plugin/PropertyGridPlugin.fs:
--------------------------------------------------------------------------------
1 | namespace Swensen.FsEye.Plugins
2 |
3 | open System
4 | open Swensen.FsEye
5 | open System.Windows.Forms
6 | open System.Drawing
7 |
8 | ///A PropertyGrid-based watch viewer
9 | type PropertyGridWatchViewer() =
10 | let panel = new Panel()
11 |
12 | let propGrid = new PropertyGrid(Dock=DockStyle.Fill)
13 | do
14 | panel.Controls.Add(propGrid)
15 |
16 | let labelPanel = new FlowLayoutPanel(Dock=DockStyle.Top, AutoSize=true, Padding=Padding(0,3,3,5))
17 | let labelLabel = new Label(Text="Source Expression:", AutoSize=true)
18 | let expressionLabel = new Label(AutoSize=true)
19 | do
20 | labelLabel.Font <- new Font(labelLabel.Font, FontStyle.Bold)
21 | labelPanel.Controls.Add(labelLabel)
22 |
23 | labelPanel.Controls.Add(expressionLabel)
24 |
25 | panel.Controls.Add(labelPanel)
26 |
27 | interface IWatchViewer with
28 | ///Set or refresh the PropertyGrid SelectedObject with the given value.
29 | member this.Watch(label, value, _) =
30 | expressionLabel.Text <- label
31 | propGrid.SelectedObject <- value
32 | ///Get the underlying Control of this watch view
33 | member this.Control = panel :> Control
34 |
35 | ///A Plugin that creates PropertyGridWatchViewers
36 | type PropertyGridPlugin() =
37 | interface IPlugin with
38 | ///"Property Grid"
39 | member __.Name = "Property Grid"
40 | ///Create a new instance of a PropertyGridWatchViewer
41 | member __.CreateWatchViewer() = new PropertyGridWatchViewer() :> IWatchViewer
42 | ///Returns true if and only if the given type has any public properties.
43 | member this.IsWatchable(value:obj, ty:Type) =
44 | //see Aseem Gautam (http://stackoverflow.com/users/213469/aseem-gautam) answer at http://stackoverflow.com/a/11458601/236255
45 | value <> null && System.ComponentModel.TypeDescriptor.GetProperties(ty).Count > 0
--------------------------------------------------------------------------------
/FsEye.TreeView.Plugin/AssemblyInfo.fs:
--------------------------------------------------------------------------------
1 | namespace FsEye.TreeView.Plugin
2 |
3 | open System.Reflection
4 | open System.Runtime.CompilerServices
5 | open System.Runtime.InteropServices
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | []
11 | []
12 | []
13 | []
14 | []
15 | []
16 | []
17 |
18 | // Setting ComVisible to false makes the types in this assembly not visible
19 | // to COM components. If you need to access a type in this assembly from
20 | // COM, set the ComVisible attribute to true on that type.
21 | []
22 |
23 | // The following GUID is for the ID of the typelib if this project is exposed to COM
24 | []
25 |
26 | // Version information for an assembly consists of the following four values:
27 | //
28 | // Major Version
29 | // Minor Version
30 | // Build Number
31 | // Revision
32 | //
33 | //the fourth position is for beta release numbers
34 | []
35 |
36 | ()
37 |
--------------------------------------------------------------------------------
/FsEye.TreeView.Plugin/FsEye.TreeView.Plugin.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | x86
6 | 8.0.30703
7 | 2.0
8 | {669bc59d-958a-4096-affe-7776cf68b46e}
9 | Library
10 | FsEye.TreeView.Plugin
11 | FsEye.TreeView.Plugin
12 | v4.0
13 |
14 |
15 | FsEye.TreeView.Plugin
16 |
17 |
18 | true
19 | full
20 | false
21 | false
22 | bin\Debug\plugins
23 | DEBUG;TRACE
24 | 3
25 | x86
26 | bin\Debug\FsEye.TreeView.Plugin.XML
27 |
28 |
29 | pdbonly
30 | true
31 | true
32 | bin\Release\plugins
33 | TRACE
34 | 3
35 | x86
36 | bin\Release\FsEye.TreeView.Plugin.XML
37 |
38 |
39 | true
40 | full
41 | false
42 | true
43 | bin\Debug\
44 | DEBUG;TRACE
45 | 3
46 | bin\Debug\FsEye.TreeView.Plugin.xml
47 | AnyCPU
48 |
49 |
50 | pdbonly
51 | true
52 | true
53 | bin\Release\
54 | TRACE
55 | 3
56 | bin\Release\FsEye.TreeView.Plugin.xml
57 | AnyCPU
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | FsEye
76 | {20de2466-d7b1-4f72-b8f8-51f10f5f186e}
77 | True
78 |
79 |
80 |
81 |
82 |
89 |
--------------------------------------------------------------------------------
/FsEye.TreeView.Plugin/TreeViewPlugin.fs:
--------------------------------------------------------------------------------
1 | namespace Swensen.FsEye.Plugins
2 |
3 | open System
4 | open Swensen.FsEye
5 | open Swensen.FsEye.Forms
6 | open System.Windows.Forms
7 |
8 | type TreeViewWatchViewer() =
9 | let watchTreeView = new WatchTreeView()
10 | interface IWatchViewer with
11 | member this.Watch(label, value, ty) =
12 | watchTreeView.Watch(label, value, ty)
13 |
14 | member this.Control = watchTreeView :> Control
15 |
16 | type TreeViewPlugin() =
17 | interface IPlugin with
18 | ///"Tree View"
19 | member this.Name = "Tree View"
20 | ///Creates and returns a new instance of a TreeViewWatchViewer
21 | member this.CreateWatchViewer() = new TreeViewWatchViewer() :> IWatchViewer
22 | ///Always returns true.
23 | member this.IsWatchable(value:obj, ty:Type) = true
--------------------------------------------------------------------------------
/FsEye.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FsEye
5 | 0.0.0
6 | Stephen Swensen
7 | Stephen Swensen
8 | https://fseye.googlecode.com/svn/images/logo.png
9 | http://www.apache.org/licenses/LICENSE-2.0
10 | http://code.google.com/p/fseye/
11 | F# fsharp fsi
12 | false
13 | A visual object tree inspector for the F# Interactive.
14 | FsEye listens for additions and updates to variables within FSI sessions, allowing you to reflectively examine properties of captured values through a visual interface. It also allows you to programmatically add and update eye watches, effectively ending the era of printf REPL debugging.
15 |
16 |
17 |
--------------------------------------------------------------------------------
/FsEye.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 11.00
3 | # Visual Studio 2010
4 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsEye", "FsEye\FsEye.fsproj", "{20DE2466-D7B1-4F72-B8F8-51F10F5F186E}"
5 | EndProject
6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Utils", "Utils\Utils.fsproj", "{46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}"
7 | EndProject
8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Test.FsEye", "Test.FsEye\Test.FsEye.fsproj", "{5F774BCF-2BED-428E-B2F5-2165EB7862E0}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{AB939582-CB0E-4FAE-B5EA-E2B7CD66C005}"
11 | ProjectSection(SolutionItems) = preProject
12 | .nuget\NuGet.Config = .nuget\NuGet.Config
13 | .nuget\NuGet.exe = .nuget\NuGet.exe
14 | .nuget\NuGet.targets = .nuget\NuGet.targets
15 | EndProjectSection
16 | EndProject
17 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Debug.FsEye", "Debug.FsEye\Debug.FsEye.fsproj", "{FD9691CC-8069-4C02-B3B6-F990FBD6114D}"
18 | EndProject
19 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsEye.TreeView.Plugin", "FsEye.TreeView.Plugin\FsEye.TreeView.Plugin.fsproj", "{669BC59D-958A-4096-AFFE-7776CF68B46E}"
20 | EndProject
21 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsEye.PropertyGrid.Plugin", "FsEye.PropertyGrid.Plugin\FsEye.PropertyGrid.Plugin.fsproj", "{1F9B8CB8-8C0C-424C-9035-6562B637A233}"
22 | EndProject
23 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsEye.DataGridView.Plugin", "FsEye.DataGridView.Plugin\FsEye.DataGridView.Plugin.fsproj", "{CC563DCC-2EAF-4822-B4CE-9D80ACCA6B5E}"
24 | EndProject
25 | Global
26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
27 | Debug|Any CPU = Debug|Any CPU
28 | Debug|Mixed Platforms = Debug|Mixed Platforms
29 | Debug|x86 = Debug|x86
30 | Release|Any CPU = Release|Any CPU
31 | Release|Mixed Platforms = Release|Mixed Platforms
32 | Release|x86 = Release|x86
33 | EndGlobalSection
34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
35 | {20DE2466-D7B1-4F72-B8F8-51F10F5F186E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {20DE2466-D7B1-4F72-B8F8-51F10F5F186E}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {20DE2466-D7B1-4F72-B8F8-51F10F5F186E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
38 | {20DE2466-D7B1-4F72-B8F8-51F10F5F186E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
39 | {20DE2466-D7B1-4F72-B8F8-51F10F5F186E}.Debug|x86.ActiveCfg = Debug|x86
40 | {20DE2466-D7B1-4F72-B8F8-51F10F5F186E}.Debug|x86.Build.0 = Debug|x86
41 | {20DE2466-D7B1-4F72-B8F8-51F10F5F186E}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {20DE2466-D7B1-4F72-B8F8-51F10F5F186E}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {20DE2466-D7B1-4F72-B8F8-51F10F5F186E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
44 | {20DE2466-D7B1-4F72-B8F8-51F10F5F186E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
45 | {20DE2466-D7B1-4F72-B8F8-51F10F5F186E}.Release|x86.ActiveCfg = Release|x86
46 | {20DE2466-D7B1-4F72-B8F8-51F10F5F186E}.Release|x86.Build.0 = Release|x86
47 | {46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
48 | {46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
49 | {46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
50 | {46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
51 | {46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}.Debug|x86.ActiveCfg = Debug|x86
52 | {46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}.Debug|x86.Build.0 = Debug|x86
53 | {46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
54 | {46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}.Release|Any CPU.Build.0 = Release|Any CPU
55 | {46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
56 | {46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
57 | {46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}.Release|x86.ActiveCfg = Release|x86
58 | {46B72FED-E4B4-4CE3-AE7B-97E43837BA2B}.Release|x86.Build.0 = Release|x86
59 | {5F774BCF-2BED-428E-B2F5-2165EB7862E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
60 | {5F774BCF-2BED-428E-B2F5-2165EB7862E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
61 | {5F774BCF-2BED-428E-B2F5-2165EB7862E0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
62 | {5F774BCF-2BED-428E-B2F5-2165EB7862E0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
63 | {5F774BCF-2BED-428E-B2F5-2165EB7862E0}.Debug|x86.ActiveCfg = Debug|x86
64 | {5F774BCF-2BED-428E-B2F5-2165EB7862E0}.Debug|x86.Build.0 = Debug|x86
65 | {5F774BCF-2BED-428E-B2F5-2165EB7862E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
66 | {5F774BCF-2BED-428E-B2F5-2165EB7862E0}.Release|Any CPU.Build.0 = Release|Any CPU
67 | {5F774BCF-2BED-428E-B2F5-2165EB7862E0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
68 | {5F774BCF-2BED-428E-B2F5-2165EB7862E0}.Release|Mixed Platforms.Build.0 = Release|Any CPU
69 | {5F774BCF-2BED-428E-B2F5-2165EB7862E0}.Release|x86.ActiveCfg = Release|x86
70 | {5F774BCF-2BED-428E-B2F5-2165EB7862E0}.Release|x86.Build.0 = Release|x86
71 | {FD9691CC-8069-4C02-B3B6-F990FBD6114D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
72 | {FD9691CC-8069-4C02-B3B6-F990FBD6114D}.Debug|Any CPU.Build.0 = Debug|Any CPU
73 | {FD9691CC-8069-4C02-B3B6-F990FBD6114D}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
74 | {FD9691CC-8069-4C02-B3B6-F990FBD6114D}.Debug|Mixed Platforms.Build.0 = Debug|x86
75 | {FD9691CC-8069-4C02-B3B6-F990FBD6114D}.Debug|x86.ActiveCfg = Debug|x86
76 | {FD9691CC-8069-4C02-B3B6-F990FBD6114D}.Debug|x86.Build.0 = Debug|x86
77 | {FD9691CC-8069-4C02-B3B6-F990FBD6114D}.Release|Any CPU.ActiveCfg = Release|Any CPU
78 | {FD9691CC-8069-4C02-B3B6-F990FBD6114D}.Release|Any CPU.Build.0 = Release|Any CPU
79 | {FD9691CC-8069-4C02-B3B6-F990FBD6114D}.Release|Mixed Platforms.ActiveCfg = Release|x86
80 | {FD9691CC-8069-4C02-B3B6-F990FBD6114D}.Release|Mixed Platforms.Build.0 = Release|x86
81 | {FD9691CC-8069-4C02-B3B6-F990FBD6114D}.Release|x86.ActiveCfg = Release|x86
82 | {FD9691CC-8069-4C02-B3B6-F990FBD6114D}.Release|x86.Build.0 = Release|x86
83 | {669BC59D-958A-4096-AFFE-7776CF68B46E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
84 | {669BC59D-958A-4096-AFFE-7776CF68B46E}.Debug|Any CPU.Build.0 = Debug|Any CPU
85 | {669BC59D-958A-4096-AFFE-7776CF68B46E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
86 | {669BC59D-958A-4096-AFFE-7776CF68B46E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
87 | {669BC59D-958A-4096-AFFE-7776CF68B46E}.Debug|x86.ActiveCfg = Debug|x86
88 | {669BC59D-958A-4096-AFFE-7776CF68B46E}.Debug|x86.Build.0 = Debug|x86
89 | {669BC59D-958A-4096-AFFE-7776CF68B46E}.Release|Any CPU.ActiveCfg = Release|Any CPU
90 | {669BC59D-958A-4096-AFFE-7776CF68B46E}.Release|Any CPU.Build.0 = Release|Any CPU
91 | {669BC59D-958A-4096-AFFE-7776CF68B46E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
92 | {669BC59D-958A-4096-AFFE-7776CF68B46E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
93 | {669BC59D-958A-4096-AFFE-7776CF68B46E}.Release|x86.ActiveCfg = Release|x86
94 | {669BC59D-958A-4096-AFFE-7776CF68B46E}.Release|x86.Build.0 = Release|x86
95 | {1F9B8CB8-8C0C-424C-9035-6562B637A233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
96 | {1F9B8CB8-8C0C-424C-9035-6562B637A233}.Debug|Any CPU.Build.0 = Debug|Any CPU
97 | {1F9B8CB8-8C0C-424C-9035-6562B637A233}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
98 | {1F9B8CB8-8C0C-424C-9035-6562B637A233}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
99 | {1F9B8CB8-8C0C-424C-9035-6562B637A233}.Debug|x86.ActiveCfg = Debug|x86
100 | {1F9B8CB8-8C0C-424C-9035-6562B637A233}.Debug|x86.Build.0 = Debug|x86
101 | {1F9B8CB8-8C0C-424C-9035-6562B637A233}.Release|Any CPU.ActiveCfg = Release|Any CPU
102 | {1F9B8CB8-8C0C-424C-9035-6562B637A233}.Release|Any CPU.Build.0 = Release|Any CPU
103 | {1F9B8CB8-8C0C-424C-9035-6562B637A233}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
104 | {1F9B8CB8-8C0C-424C-9035-6562B637A233}.Release|Mixed Platforms.Build.0 = Release|Any CPU
105 | {1F9B8CB8-8C0C-424C-9035-6562B637A233}.Release|x86.ActiveCfg = Release|x86
106 | {1F9B8CB8-8C0C-424C-9035-6562B637A233}.Release|x86.Build.0 = Release|x86
107 | {CC563DCC-2EAF-4822-B4CE-9D80ACCA6B5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
108 | {CC563DCC-2EAF-4822-B4CE-9D80ACCA6B5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
109 | {CC563DCC-2EAF-4822-B4CE-9D80ACCA6B5E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
110 | {CC563DCC-2EAF-4822-B4CE-9D80ACCA6B5E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
111 | {CC563DCC-2EAF-4822-B4CE-9D80ACCA6B5E}.Debug|x86.ActiveCfg = Debug|Any CPU
112 | {CC563DCC-2EAF-4822-B4CE-9D80ACCA6B5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
113 | {CC563DCC-2EAF-4822-B4CE-9D80ACCA6B5E}.Release|Any CPU.Build.0 = Release|Any CPU
114 | {CC563DCC-2EAF-4822-B4CE-9D80ACCA6B5E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
115 | {CC563DCC-2EAF-4822-B4CE-9D80ACCA6B5E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
116 | {CC563DCC-2EAF-4822-B4CE-9D80ACCA6B5E}.Release|x86.ActiveCfg = Release|Any CPU
117 | EndGlobalSection
118 | GlobalSection(SolutionProperties) = preSolution
119 | HideSolutionNode = FALSE
120 | EndGlobalSection
121 | EndGlobal
122 |
--------------------------------------------------------------------------------
/FsEye/AssemblyInfo.fs:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright 2011 Stephen Swensen
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | *)
16 | namespace Swensen.FsEye
17 |
18 | open System.Reflection
19 | open System.Runtime.CompilerServices
20 | open System.Runtime.InteropServices
21 |
22 | // General Information about an assembly is controlled through the following
23 | // set of attributes. Change these attribute values to modify the information
24 | // associated with an assembly.
25 | []
26 | []
27 | []
28 | []
29 | []
30 | []
31 | []
32 |
33 | // Setting ComVisible to false makes the types in this assembly not visible
34 | // to COM components. If you need to access a type in this assembly from
35 | // COM, set the ComVisible attribute to true on that type.
36 | []
37 |
38 | // The following GUID is for the ID of the typelib if this project is exposed to COM
39 | []
40 |
41 | // Version information for an assembly consists of the following four values:
42 | //
43 | // Major Version
44 | // Minor Version
45 | // Build Number
46 | // Revision
47 | //
48 | //the fourth position is for beta release numbers
49 | []
50 |
51 | ()
52 |
--------------------------------------------------------------------------------
/FsEye/Forms/EyeForm.fs:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright 2011 Stephen Swensen
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | *)
16 | namespace Swensen.FsEye.Forms
17 | open Swensen.FsEye
18 | open System.Windows.Forms
19 | open System
20 |
21 | type EyeForm(pluginManager:PluginManager) as this =
22 | inherit Form(
23 | Name = "FsEye",
24 | Icon = IconResource.FsEye.Icon,
25 | Text = (
26 | let version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version
27 | sprintf "FsEye v%i.%i.%i by Stephen Swensen" version.Major version.Minor version.Build
28 | ),
29 | Size = (
30 | let size = SystemInformation.PrimaryMonitorSize
31 | System.Drawing.Size((2 * size.Width) / 3, size.Height / 2)
32 | )
33 | )
34 |
35 | let eyePanel = new EyePanel(pluginManager, Dock=DockStyle.Fill)
36 |
37 | do
38 | this.Controls.Add(eyePanel)
39 | Application.AddMessageFilter this
40 |
41 | /// Implementing message filter here allows to propagate MouseWheel messages
42 | /// to the control under mouse pointer. This is needed because otherwise - only control
43 | /// which currently has focus will receive these events.
44 | interface IMessageFilter with
45 | member __.PreFilterMessage message =
46 | if message.Msg <> Win32.WM_MOUSEWHEEL
47 | then
48 | false
49 | else
50 | let hWnd = Win32.WindowFromPoint Cursor.Position
51 |
52 | if hWnd <> IntPtr.Zero && hWnd <> message.HWnd && (Control.FromHandle hWnd <> null) then
53 | Win32.SendMessage (hWnd,message.Msg,message.WParam,message.LParam) |> ignore
54 | true
55 | else
56 | false
57 | with
58 | //a lot of delegation to treeView below -- not sure how to do this better
59 |
60 | ///Add or update a watch with the given name, value, and type.
61 | member this.Watch(name, value:obj, ty) =
62 | eyePanel.Watch(name, value, ty)
63 |
64 | ///Add or update a watch with the given name and value (where the type is derived from the type paramater of the value).
65 | member this.Watch(name, value) =
66 | eyePanel.Watch(name,value)
67 |
68 | ///Take archival snap shot of all current watches using the given label.
69 | member this.Archive(label: string) =
70 | eyePanel.Archive(label)
71 |
72 | ///Take archival snap shot of all current watches using a default label based on an archive count.
73 | member this.Archive() =
74 | eyePanel.Archive()
75 |
76 | ///Clear all archives and reset the archive count.
77 | member this.ClearArchives() =
78 | eyePanel.ClearArchives()
79 |
80 | ///Clear all watches (doesn't include archive nodes).
81 | member this.ClearWatches() =
82 | eyePanel.ClearWatches()
83 |
84 | ///Clear all archives (reseting archive count) and watches.
85 | member this.ClearAll() =
86 | eyePanel.ClearAll()
87 |
88 | ///
89 | ///Use this in an async block with do! to pause execution and activate the watch viewer, e.g.
90 | ///
91 | ///async {
92 | /// for i in 1..100 do
93 | /// watch.Watch("i", i, typeof<int>)
94 | /// watch.Archive()
95 | /// if i = 50 then
96 | /// do! watch.AsyncBreak()
97 | ///} |> Async.StartImmediate
98 | ///
99 | member this.AsyncBreak() =
100 | let asyncBreak = eyePanel.AsyncBreak()
101 | if this.Visible |> not then
102 | this.Show()
103 |
104 | this.Activate()
105 |
106 | asyncBreak
107 |
108 | ///Continue from an AsyncBreak()
109 | member this.AsyncContinue() =
110 | eyePanel.AsyncContinue()
--------------------------------------------------------------------------------
/FsEye/Forms/EyePanel.fs:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright 2011 Stephen Swensen
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | *)
16 | namespace Swensen.FsEye.Forms
17 | open System.Windows.Forms
18 | open System.Reflection
19 | open Swensen.FsEye
20 |
21 | type EyePanel(pluginManager:PluginManager) as this =
22 | inherit Panel()
23 | let continueButton = new Button(Text="Async Continue", AutoSize=true, Enabled=false)
24 | let asyncBreak = async {
25 | let! _ = Async.AwaitEvent continueButton.Click
26 | ()
27 | }
28 |
29 | let splitContainer = new EyeSplitContainer(pluginManager, Dock=DockStyle.Fill)
30 |
31 | //must add splitContainer (with dockstyle fill) first in order for it to be flush with button panel
32 | //see: http://www.pcreview.co.uk/forums/setting-control-dock-fill-you-have-menustrip-t3240577.html
33 | do this.Controls.Add(splitContainer)
34 |
35 | do //build button panel and add it to this panel
36 | let buttonPanel = new FlowLayoutPanel(Dock=DockStyle.Top, AutoSize=true)
37 |
38 | let archiveButton = new Button(Text="Archive Watches", AutoSize=true)
39 | archiveButton.Click.Add(fun _ -> this.Archive())
40 | buttonPanel.Controls.Add(archiveButton)
41 |
42 | let clearButton = new Button(Text="Clear Archives", AutoSize=true)
43 | clearButton.Click.Add(fun _ -> this.ClearArchives() )
44 | buttonPanel.Controls.Add(clearButton)
45 |
46 | let clearButton = new Button(Text="Clear Watches", AutoSize=true)
47 | clearButton.Click.Add(fun _ -> this.ClearWatches())
48 | buttonPanel.Controls.Add(clearButton)
49 |
50 | let clearButton = new Button(Text="Clear All", AutoSize=true)
51 | clearButton.Click.Add(fun _ -> this.ClearAll())
52 | buttonPanel.Controls.Add(clearButton)
53 |
54 | continueButton.Click.Add(fun _ -> continueButton.Enabled <- false)
55 | buttonPanel.Controls.Add(continueButton)
56 |
57 | this.Controls.Add(buttonPanel)
58 | with
59 | //a lot of delegation to treeView below -- not sure how to do this better
60 |
61 | ///Add or update a watch with the given name, value, and type.
62 | member this.Watch(name, value:obj, ty) =
63 | splitContainer.TreeView.Watch(name, value, ty)
64 |
65 | ///Add or update a watch with the given name and value (where the type is derived from the type paramater of the value).
66 | member this.Watch(name, value) =
67 | splitContainer.TreeView.Watch(name,value)
68 |
69 | ///Take archival snap shot of all current watches using the given label.
70 | member this.Archive(label: string) =
71 | splitContainer.TreeView.Archive(label)
72 |
73 | ///Take archival snap shot of all current watches using a default label based on an archive count.
74 | member this.Archive() =
75 | splitContainer.TreeView.Archive()
76 |
77 | ///Clear all archives and reset the archive count.
78 | member this.ClearArchives() =
79 | splitContainer.TreeView.ClearArchives()
80 |
81 | ///Clear all watches (doesn't include archive nodes).
82 | member this.ClearWatches() =
83 | splitContainer.TreeView.ClearWatches()
84 |
85 | ///Clear all archives (reseting archive count) and watches.
86 | member this.ClearAll() =
87 | splitContainer.TreeView.ClearAll()
88 |
89 | //note: would like to use []
90 | //on the following two Async methods but the attribute is not valid on methods
91 | //maybe we should introduce our own "MethodDebuggerAttribute"
92 |
93 | ///
94 | ///Use this in a sync block with do!, e.g.
95 | ///
96 | ///async {
97 | /// for i in 1..100 do
98 | /// watch.Watch("i", i, typeof<int>)
99 | /// watch.Archive()
100 | /// if i = 50 then
101 | /// do! watch.AsyncBreak()
102 | ///} |> Async.StartImmediate
103 | ///
104 | member this.AsyncBreak() =
105 | continueButton.Enabled <- true
106 | asyncBreak
107 |
108 | ///Continue from an AsyncBreak()
109 | member this.AsyncContinue() =
110 | //the Click event for continueButton.PerformClick() doesn't fire when form is closed
111 | //but it does fire using InvokeOnClick
112 | this.InvokeOnClick(continueButton, System.EventArgs.Empty)
--------------------------------------------------------------------------------
/FsEye/Forms/EyeSplitContainer.fs:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright 2011 Stephen Swensen
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | *)
16 | namespace Swensen.FsEye.Forms
17 | open System.Windows.Forms
18 | open System.Reflection
19 | open Swensen.FsEye
20 | open System.Drawing
21 |
22 | ///A vertical split container which manages the interaction between the left WatchTreeView and the right PluginTabControl.
23 | ///When no plugin watch viewers are active, the right panel is hidden, otherwise it is shown, reacting to tabs as they
24 | ///are added and remove. By default the size of each is 50% on each size, but it persists the percentage set by the
25 | ///user scaling on resize. This internal component is used by the EyePanel.
26 | type internal EyeSplitContainer(pluginManager:PluginManager) as this =
27 | inherit SplitContainer(Orientation=Orientation.Vertical, SplitterWidth=6)
28 |
29 | let treeView = new WatchTreeView(Some(pluginManager), Dock=DockStyle.Fill)
30 | let tabControl = new PluginTabControl(pluginManager, Dock=DockStyle.Fill)
31 |
32 | let hidePanel2 () =
33 | this.Panel2Collapsed <- true
34 | this.Panel2.Hide()
35 |
36 | let showPanel2 () =
37 | this.Panel2Collapsed <- false
38 | this.Panel2.Show()
39 |
40 | do tabControl.TabAdded.Add(fun _ ->
41 | if tabControl.TabCount > 0 && this.Panel2Collapsed then
42 | showPanel2()
43 | )
44 |
45 | do tabControl.TabRemoved.Add(fun _ ->
46 | if tabControl.TabCount = 0 && not this.Panel2Collapsed then
47 | hidePanel2()
48 | )
49 |
50 | //Auto-update splitter distance to a percentage on resize
51 | let mutable splitterDistancePercentage = 0.5
52 | do
53 | let inline ensureBounds lower upper num =
54 | if num > upper then upper
55 | elif num < lower then lower
56 | else num
57 | let updateSplitterDistancePercentage() =
58 | if this.Width > 0 then
59 | splitterDistancePercentage <-
60 | let pct = (float this.SplitterDistance) / (float this.Width)
61 | ensureBounds 0. 100. pct
62 | let updateSplitterDistance() =
63 | if this.Width > 0 then
64 | this.SplitterDistance <-
65 | let dist = int ((float this.Width) * splitterDistancePercentage)
66 | ensureBounds this.Panel1MinSize (this.Width - this.Panel2MinSize) dist
67 | updateSplitterDistance() //since SplitterMoved fires first, need to establish default splitter distance
68 | this.SplitterMoved.Add(fun _ -> updateSplitterDistancePercentage())
69 | this.SizeChanged.Add(fun _ -> updateSplitterDistance())
70 |
71 | hidePanel2()
72 |
73 | //build up the component tree
74 | do
75 | this.Panel1.Controls.Add(treeView)
76 | this.Panel2.Controls.Add(tabControl)
77 |
78 | //http://stackoverflow.com/a/10412371/236255 (try to emphasize splitter)
79 | // do
80 | // this.Paint.Add(fun e ->
81 | // let s = this
82 | // let top = 5;
83 | // let bottom = s.Height - 5;
84 | // let left = s.SplitterDistance;
85 | // let right = left + s.SplitterWidth - 1;
86 | // e.Graphics.DrawLine(Pens.Silver, left, top, left, bottom);
87 | // e.Graphics.DrawLine(Pens.Silver, right, top, right, bottom);
88 | // )
89 |
90 | ///The left panel control
91 | member this.TreeView = treeView
92 | ///The right panel control
93 | member this.TabControl = tabControl
94 |
95 |
96 |
--------------------------------------------------------------------------------
/FsEye/Forms/PluginTabControl.fs:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright 2011 Stephen Swensen
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | *)
16 | namespace Swensen.FsEye.Forms
17 | open System.Windows.Forms
18 | open System.Reflection
19 | open Swensen.FsEye
20 |
21 | ///The tab-based visual surface of the PluginManager. Exposes TabAdded and TabRemoved events
22 | ///which are fired when plugin watch view tabs are added and removed (do not directly add / remove
23 | ///tab pages, this is initiated via tab right-click context menu calls which do add / remove ops
24 | ///on the tab plugin itself. This decoupling always direct, programatic calls to add / remove
25 | ///plugin manager watch views to propagate events that are handled by this and higher up visual components.
26 | ///This internal component is used by the WatchPluginSplitContainer which in turn is used by the EyePanel.
27 | type internal PluginTabControl(pluginManager:PluginManager) as this =
28 | inherit TabControl()
29 |
30 | let tabAdded = new Event()
31 | let tabRemoved = new Event()
32 |
33 | //wire up tab closing, coordinating with the plugin manager.
34 | let closeTab (tab:TabPage) =
35 | pluginManager.RemoveManagedWatchViewer(tab.Name)
36 |
37 | let closeOtherTabs (tab:TabPage) =
38 | this.TabPages
39 | |> Seq.cast
40 | |> Seq.filter (fun x -> x.Name <> tab.Name)
41 | |> Seq.map (fun x -> x.Name)
42 | |> Seq.toList
43 | |> Seq.iter (fun id -> pluginManager.RemoveManagedWatchViewer(id))
44 |
45 | let closeAllTabs () =
46 | this.TabPages
47 | |> Seq.cast
48 | |> Seq.map (fun x -> x.Name)
49 | |> Seq.toList
50 | |> Seq.iter (fun id -> pluginManager.RemoveManagedWatchViewer(id))
51 |
52 | //we may want to have WatchUpdating event and trigger select at that point rather than after
53 | do pluginManager.WatchUpdated.Add (fun mwv ->
54 | this.SelectTab(mwv.ID)
55 | )
56 |
57 | do pluginManager.WatchAdded.Add (fun mwv ->
58 | //display the watch viewer
59 | let tab = new TabPage(mwv.ID, Name=mwv.ID)
60 | let wvControl = mwv.WatchViewer.Control
61 | wvControl.Dock <- DockStyle.Fill
62 | tab.Controls.Add(wvControl)
63 | this.TabPages.Add(tab)
64 |
65 | this.SelectTab(tab)
66 |
67 | tabAdded.Trigger(tab)
68 | )
69 |
70 | do pluginManager.WatchRemoved.Add (fun mwv ->
71 | let tab = this.TabPages.[mwv.ID]
72 | tab.Dispose() //http://stackoverflow.com/a/1970158/236255
73 | this.TabPages.Remove(tab)
74 |
75 | tabRemoved.Trigger(tab)
76 | )
77 |
78 | let createTabContextMenu (tab:TabPage) =
79 | new ContextMenu [|
80 | let mi = new MenuItem("Close Tab")
81 | mi.Click.Add(fun _ -> closeTab tab)
82 | yield mi
83 |
84 | let mi = new MenuItem("Close Other Tabs")
85 | if this.TabCount > 1 then mi.Click.Add(fun _ -> closeOtherTabs tab)
86 | else mi.Enabled <- false
87 | yield mi
88 |
89 | let mi = new MenuItem("Close All Tabs")
90 | mi.Click.Add(fun _ -> closeAllTabs ())
91 | yield mi
92 | |]
93 |
94 | //show the context menu on right-click
95 | do this.MouseClick.Add (fun e ->
96 | if e.Button = MouseButtons.Right then
97 | let clickedTab =
98 | this.TabPages
99 | |> Seq.cast
100 | |> Seq.mapi (fun i tab -> (i,tab))
101 | |> Seq.find (fun (i,tab) -> this.GetTabRect(i).Contains(e.Location))
102 | |> snd
103 | (createTabContextMenu clickedTab).Show(this, e.Location)
104 | )
105 |
106 | []
107 | ///Fires when a plugin tab has been added (but not if you manually add a tab)
108 | member __.TabAdded = tabAdded.Publish
109 | []
110 | ///Fires when a plugin tab has been removed (but not if you manually add a tab)
111 | member __.TabRemoved = tabRemoved.Publish
--------------------------------------------------------------------------------
/FsEye/Forms/WatchTreeView.fs:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright 2011 Stephen Swensen
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | *)
16 | namespace Swensen.FsEye.Forms
17 | open System.Windows.Forms
18 | open System.Drawing
19 | open System.Reflection
20 | open Microsoft.FSharp.NativeInterop
21 |
22 | open Swensen.Utils
23 | open Swensen.FsEye
24 | open Swensen.FsEye.WatchModel
25 |
26 |
27 | //Copy / Copy Value context Menu
28 |
29 | //for thoughts on cancellation:
30 | //http://stackoverflow.com/questions/5852317/how-to-cancel-individual-async-computation-being-run-in-parallel-with-others-fr
31 |
32 | ///A TreeView which binds to and manipulates a Watch model.
33 | ///The PluginManager argument is only used for the primary FsEye WatchTreeView,
34 | ///it should be None for WatchTreeViews hosted as plugins.
35 | type WatchTreeView(pluginManager: PluginManager option) as this =
36 | inherit TreeView()
37 |
38 | static let requiresUIThread (ty:System.Type) =
39 | [typeof
40 | System.Type.GetType("System.Windows.UIElement", false)] //mono does not support WPF
41 | |> Seq.filter ((<>&) null)
42 | |> Seq.exists ty.IsAssignableFrom
43 |
44 | static let (|Archive|Watch|) (tn:TreeNode) =
45 | match tn.Tag with
46 | | :? Watch as w -> Watch(w)
47 | | _ -> Archive
48 |
49 | static let isWatch = function
50 | | Watch _ -> true
51 | | _ -> false
52 |
53 | static let isArchive = function
54 | | Archive -> true
55 | | _ -> false
56 |
57 | ///The text used for constructing "dummy" TreeNodes used as a place-holder until childnodes are
58 | ///loaded lazily.
59 | static let dummyText = "dummy"
60 |
61 | ///Checks whether the given TreeNode contains the default "dummy" TreeNode as it's only child.
62 | static let hasDummyChild (tn:TreeNode) =
63 | tn.Nodes.Count = 1 && tn.Nodes.[0].Text = dummyText
64 |
65 | ///The action to perform on the given TreeNode after the "Refresh" menu item has
66 | ///been clicked via the right-click context menu (which can only be performed on root TreeNode's
67 | ///for Root Watches).
68 | let refresh (node:TreeNode) =
69 | match node with
70 | | Watch(Root({ValueInfo=vi})) ->
71 | this.UpdateWatch(node, vi.Value, vi.Type)
72 | | _ -> failwith "TreeNode was not a Root Watch"
73 |
74 | ///Calculate the "label" for the given node. i.e., the expression, starting from the root, from which the current node value is obtained. Used by the SendTo plugin.
75 | let calcNodeLabel (tn:TreeNode) =
76 | //todo: should not fail hard in unexpected node cases, instead return something like "!error!" and write detailed message to a local log file
77 | //use Trace API for now to help keep 3rd party libs out for now.
78 | let rec loop (cur:TreeNode) =
79 | match cur with
80 | | null -> ""
81 | | Archive -> sprintf "[%s] " cur.Text
82 | | Watch (Root {ExpressionInfo=ei}) -> sprintf "%s%s" (loop cur.Parent) ei.Expression
83 | | Watch w ->
84 | match w.ExpressionInfo with
85 | | None -> loop cur.Parent
86 | | Some(ei) ->
87 | let separator =
88 | if ei.IsPublic then "."
89 | else "?"
90 |
91 | match ei.ExplicitInterfaceName with
92 | | Some(iface) ->
93 | sprintf "(%s :> %s)%s%s" (loop cur.Parent) iface separator ei.Expression
94 | | None ->
95 | sprintf "%s%s%s" (loop cur.Parent) separator ei.Expression
96 | loop tn
97 |
98 | let createNodeContextMenu (tn:TreeNode) =
99 | new ContextMenu [|
100 | match tn with
101 | | Watch(Root(_)) ->
102 | let mi = new MenuItem("Refresh")
103 | mi.Click.Add(fun args -> refresh tn)
104 | yield mi
105 | | _ -> ()
106 |
107 | match tn with
108 | | Watch(Root(_)) | Archive ->
109 | let mi = new MenuItem("Remove")
110 | mi.Click.Add(fun args -> this.Nodes.Remove(tn))
111 | yield mi
112 | | _ -> ()
113 |
114 | match tn with
115 | | Watch(Organizer _ ) -> ()
116 | | Watch(w) ->
117 | match w with
118 | | Root _ ->
119 | yield new MenuItem("-") //n.b. for root watches, ValueInfo is always Some
120 | | _ -> ()
121 |
122 | let mi = new MenuItem("Copy Value")
123 | match w.ValueInfo with
124 | | Some({Text=vtext}) -> mi.Click.Add(fun _ -> Clipboard.SetText(vtext))
125 | | None -> mi.Enabled <- false
126 | yield mi
127 |
128 | match pluginManager with
129 | | Some(pluginManager) ->
130 | //issues 25 and 26 (plugin architecture and view property grid)
131 | let miSendTo = new MenuItem("Send To")
132 | match w.ValueInfo with
133 | | Some(vi) when pluginManager.ManagedPlugins |> Seq.length > 0 ->
134 | let label = lazy(calcNodeLabel tn) //lazy since we need it 0 to x times depending on Plugin.IsWatchable
135 | miSendTo.MenuItems.AddRange [|
136 | let managedPlugins =
137 | pluginManager.ManagedPlugins
138 | |> Seq.map (fun mp -> mp.Plugin.IsWatchable(vi.Value, vi.Type), mp)
139 |
140 | //send to a new watch
141 | for (enabled, managedPlugin) in managedPlugins do
142 | let miWatchViewer = new MenuItem(managedPlugin.Plugin.Name + " (new)")
143 | miWatchViewer.Enabled <- enabled
144 | miWatchViewer.Click.Add(fun _ -> pluginManager.SendTo(managedPlugin, label.Force(), vi.Value, vi.Type) |> ignore)
145 | yield miWatchViewer
146 |
147 | //send to an existing watch
148 | let managedWatchViewers = [|
149 | for (enabled, managedPlugin) in managedPlugins do
150 | if managedPlugin.ManagedWatchViewers |> Seq.length > 0 then
151 | for managedWatchViewer in managedPlugin.ManagedWatchViewers do
152 | let miWatchViewer = new MenuItem(managedWatchViewer.ID)
153 | miWatchViewer.Enabled <- enabled
154 | miWatchViewer.Click.Add(fun _ -> pluginManager.SendTo(managedWatchViewer, label.Force(), vi.Value, vi.Type))
155 | yield miWatchViewer
156 | |]
157 | if managedWatchViewers |> Seq.length > 0 then
158 | yield new MenuItem("-")
159 | yield! managedWatchViewers
160 | |]
161 | | _ ->
162 | miSendTo.Enabled <- false
163 | yield miSendTo
164 | | None -> ()
165 | | _ -> () |]
166 |
167 | let mutable archiveCounter = 0
168 |
169 | ///Constructs an async expression used for updating the given TreeNode with values from the given Lazy.
170 | let loadWatchAsync guiContext (tn:TreeNode) (lz:Lazy) addDummy =
171 | async {
172 | let original = System.Threading.SynchronizationContext.Current //always null - don't understand the point
173 | let text = lz.Value.LoadedText
174 | do! Async.SwitchToContext guiContext
175 | Control.update this <| fun () ->
176 | tn.Text <- text
177 | if addDummy then tn.Nodes.Add(dummyText) |> ignore
178 | do! Async.SwitchToContext original
179 | }
180 |
181 | ///Create a TreeNode from the given Watch model. If the Watch is a DataMember, then
182 | ///the guiContext must be provided and is used to construct an async expression returned
183 | ///as the second element of the tuple. Otherwise guiContext may be null and the second
184 | ///element of the tuple is None.
185 | let createWatchTreeNode guiContext (watch:Watch) =
186 | let tn = new TreeNode(Text=watch.DefaultText, Tag=watch, ImageKey=watch.Image.Name, SelectedImageKey=watch.Image.Name)
187 |
188 | match watch with
189 | | Root info ->
190 | tn.Name <- info.Name
191 | tn.Nodes.Add(dummyText) |> ignore
192 | tn, None
193 | | DataMember(info) -> //need to make this not clickable, Lazy is not thread safe
194 | if info.MemberInfo.DeclaringType |> requiresUIThread then //issue 20
195 | tn.Text <- info.LazyMemberValue.Value.LoadedText
196 | tn.Nodes.Add(dummyText) |> ignore
197 | tn, None
198 | else
199 | tn, Some(loadWatchAsync guiContext tn info.LazyMemberValue true)
200 | | _ ->
201 | tn.Nodes.Add(dummyText) |> ignore
202 | tn, None
203 |
204 | ///The action to perform on the given TreeNode after it has been selected:
205 | ///if the Tag of the given TreeNode is a CallMember Watch which hasn't yet been loaded,
206 | ///Asyncrounously execute the method and show it's return value (but do not load it's children).
207 | let afterSelect (tn:TreeNode) =
208 | match tn.Tag with
209 | | :? Watch as watch when hasDummyChild tn ->
210 | match watch with
211 | | CallMember(info) when info.LazyMemberValue.IsValueCreated |> not ->
212 | if info.MemberInfo.DeclaringType |> requiresUIThread then //issue 20
213 | Control.update this <| fun () ->
214 | tn.Text <- info.LazyMemberValue.Value.LoadedText
215 | //note that the dummy node is already added
216 | else
217 | Control.update this <| fun () ->
218 | tn.Nodes.Clear() //so don't try click while still async loading
219 | tn.Text <- info.LoadingText
220 |
221 | let guiContext = System.Threading.SynchronizationContext.Current
222 | loadWatchAsync guiContext tn info.LazyMemberValue true |> Async.Start
223 | | _ -> ()
224 | | _ -> ()
225 |
226 | //note: FSharpRefactor doesn't rename variables in when clause of a pattern match
227 | ///The action to perform on the given TreeNode after it has been expanded:
228 | ///Load all the child watches of the TreeNode's Watch; load all DataMember child watches
229 | ///asyncronously in parallel. If the TreeNode's Watch is a CallMember and it's value has not
230 | ///yet been loaded and displayed (via previous selection), asycronously load and display it's value,
231 | ///and then load all of it's children as normal.
232 | let afterExpand (node:TreeNode) =
233 | match node with
234 | | Watch(watch) when hasDummyChild node -> //need to harden this check for loaded vs. not
235 | let loadWatches context (node:TreeNode) (watch:Watch) =
236 | this.BeginUpdate()
237 | node.Nodes.Clear() //clear dummy node
238 | let createWatchTreeNode = createWatchTreeNode context
239 | let asyncNodes = [|
240 | for (tn, a) in watch.Children |> Seq.map createWatchTreeNode do
241 | node.Nodes.Add(tn) |> ignore
242 | match a with
243 | | Some(a) -> yield a
244 | | _ -> () |]
245 | this.EndUpdate()
246 | //N.B. deliberately excluding Asyn.Start pipe-line from begin/end update
247 | //so child nodes have chance to expand before parallel updates start kicking off
248 | asyncNodes
249 | |> Async.Parallel
250 | |> Async.Ignore
251 | |> Async.Start
252 |
253 | let guiContext = System.Threading.SynchronizationContext.Current //gui thread
254 | match watch with
255 | | CallMember(info) when info.LazyMemberValue.IsValueCreated |> not (* not i.e. already loaded via after select event *) ->
256 | node.Nodes.Clear()
257 | node.Text <- info.LoadingText
258 | async {
259 | let original = System.Threading.SynchronizationContext.Current
260 | do! loadWatchAsync guiContext node info.LazyMemberValue false
261 | do! Async.SwitchToContext guiContext
262 | do loadWatches guiContext node watch
263 | do! Async.SwitchToContext original
264 | } |> Async.Start
265 | | _ ->
266 | loadWatches guiContext node watch
267 | | _ -> () //either an Archive node or IWatchNode children already expanded
268 |
269 | do
270 | this.NodeMouseClick.Add <| fun args ->
271 | if args.Button = MouseButtons.Right then
272 | this.SelectedNode <- args.Node //right click causing node to become selected is std. windows behavior
273 | let nodeContextMenu = createNodeContextMenu args.Node
274 | if nodeContextMenu.MenuItems.Count > 0 then
275 | nodeContextMenu.Show(this, args.Location)
276 | else ()
277 |
278 | this.AfterSelect.Add (fun args -> afterSelect args.Node)
279 | this.AfterExpand.Add (fun args -> afterExpand args.Node)
280 |
281 | this.ImageList <-
282 | let il = new ImageList()
283 | il.TransparentColor <- System.Drawing.Color.Magenta
284 |
285 | for ir in ImageResource.WatchImages do
286 | il.Images.Add(ir.Name, ir.Image)
287 |
288 | il
289 |
290 | this.Font <- Font (this.Font.FontFamily, this.Font.Size, this.Font.Style)
291 |
292 | this.MouseWheel
293 | |> Event.filter (fun args ->
294 | Control.ModifierKeys = Keys.Control && args.Delta <> 0)
295 | |> Event.map (fun args -> if args.Delta > 0 then 1.0f else -1.0f)
296 | |> Event.add (fun fontSizeDelta ->
297 | let oldFont = this.Font
298 | let newSize =
299 | let s = oldFont.Size + fontSizeDelta
300 | if s <= 0.0f then 1.0f else s
301 | let newFont = Font (oldFont.FontFamily, newSize, oldFont.Style)
302 | this.Font <- newFont
303 | oldFont.Dispose ())
304 | with
305 | ///Initialize an instance of a WatchTreeView without a PluginManager (e.g. when a WatchTreeView is used as the basis for a plugin!).
306 | new() = new WatchTreeView(None)
307 |
308 | override this.OnHandleCreated e =
309 | // This way we enable double-buffering on tree view control - eliminates flickering while hovering, selecting nodes etc.
310 | Win32.SendMessage(this.Handle, Win32.TVM_SETEXTENDEDSTYLE, Win32.TVS_EX_DOUBLEBUFFER |> nativeint, Win32.TVS_EX_DOUBLEBUFFER |> nativeint) |> ignore
311 | base.OnHandleCreated e
312 |
313 | member private this.UpdateWatch(tn:TreeNode, value, ty) =
314 | Control.update this <| fun () ->
315 | let watch = createRootWatch tn.Name value ty
316 | tn.Text <- watch.DefaultText
317 | tn.Tag <- watch
318 | tn.Nodes.Clear()
319 | tn.Nodes.Add(dummyText) |> ignore
320 | tn.Collapse()
321 |
322 | member private this.AddWatch(name, value:obj, ty) =
323 | Control.update this <| fun () ->
324 | createRootWatch name value ty
325 | |> createWatchTreeNode null //don't like passing null here...
326 | |> fst
327 | |> this.Nodes.Add
328 | |> ignore
329 |
330 | ///Add or update a watch with the given name, value, and type.
331 | member this.Watch(name, value:obj, ty) =
332 | let objNode =
333 | this.Nodes
334 | |> Seq.cast
335 | |> Seq.tryFind (fun tn -> tn.Name = name)
336 |
337 | match objNode with
338 | | Some(Watch(Root({ValueInfo=vi})) as tn) when vi.Value <>& value ->
339 | this.UpdateWatch(tn, value, ty)
340 | | None -> this.AddWatch(name, value, ty)
341 | | _ -> ()
342 |
343 | ///Add or update a watch with the given name and value (where the type is derived from the type paramater of the value).
344 | member this.Watch(name: string, value: 'a) =
345 | this.Watch(name, value, typeof<'a>)
346 |
347 | ///Clear all nodes satisfying the given predicate.
348 | member this.ClearAll(predicate) =
349 | Control.update this <| fun () ->
350 | //N.B. can't remove node while iterating Nodes, so need to make array first
351 | [| for tn in this.Nodes do if predicate tn then yield tn |]
352 | |> Seq.iter (fun tn -> this.Nodes.Remove(tn))
353 |
354 | ///Take archival snap shot of all current watches using the given label.
355 | member this.Archive(label: string) =
356 | Control.update this <| fun () ->
357 | let nodesToArchiveCloned = [|
358 | for tn in this.Nodes do
359 | if tn |> isWatch then
360 | yield tn.Clone() :?> TreeNode |]
361 |
362 | this.ClearAll isWatch
363 |
364 | let archiveNode = TreeNode(Text=label)
365 | archiveNode.Nodes.AddRange(nodesToArchiveCloned)
366 | this.Nodes.Add(archiveNode) |> ignore
367 | archiveCounter <- archiveCounter + 1
368 |
369 | ///Take archival snap shot of all current watches using a default label based on an archive count.
370 | member this.Archive() =
371 | this.Archive(sprintf "Archive (%i)" archiveCounter)
372 |
373 | ///Clear all archives and reset the archive count.
374 | member this.ClearArchives() =
375 | this.ClearAll isArchive
376 | archiveCounter <- 0
377 |
378 | ///Clear all watches (doesn't include archive nodes).
379 | member this.ClearWatches() =
380 | this.ClearAll isWatch
381 |
382 | ///Clear all archives (reseting archive count) and watches.
383 | member this.ClearAll() =
384 | this.Nodes.Clear()
385 | archiveCounter <- 0
--------------------------------------------------------------------------------
/FsEye/FsEye.NuGet.fsx:
--------------------------------------------------------------------------------
1 | //reference FsEye.dll (e.g. "Send to F# Interactive" on the project reference availabe in some IDE versions)
2 | //#r @"\FsEye.dll"
3 |
4 | //Execute the following two lines of code to bring the eye singleton into scope and bind it to FSI
5 | open Swensen.FsEye.Fsi //bring the eye singleton into scope
6 | fsi.AddPrintTransformer eye.Listener //attached the listener
--------------------------------------------------------------------------------
/FsEye/FsEye.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 8.0.30703
7 | 2.0
8 | {20de2466-d7b1-4f72-b8f8-51f10f5f186e}
9 | Library
10 | Swensen.FsEye
11 | FsEye
12 | v4.0
13 | FsEye
14 | ..\
15 | true
16 |
17 |
18 | true
19 | full
20 | false
21 | true
22 | bin\Debug\
23 | DEBUG;TRACE
24 | 3
25 | bin\Debug\FsEye.XML
26 | --staticlink:Utils
27 |
28 |
29 | pdbonly
30 | true
31 | true
32 | bin\Release\
33 | TRACE
34 | 3
35 | bin\Release\FsEye.xml
36 | --staticlink:Utils
37 |
38 |
39 | true
40 | full
41 | false
42 | true
43 | bin\Debug\
44 | DEBUG;TRACE
45 | 3
46 | bin\Debug\FsEye.XML
47 | --staticlink:Utils
48 | x86
49 |
50 |
51 | pdbonly
52 | true
53 | true
54 | bin\Release\
55 | TRACE
56 | 3
57 | bin\Release\FsEye.xml
58 | --staticlink:Utils
59 | x86
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | Always
97 |
98 |
99 |
100 |
101 | Always
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | Utils
114 | {46b72fed-e4b4-4ce3-ae7b-97e43837ba2b}
115 | True
116 |
117 |
118 |
119 |
126 |
--------------------------------------------------------------------------------
/FsEye/FsEye.fsx:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright 2011 Stephen Swensen
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | *)
16 | []
17 | module StartupScript
18 |
19 | #I __SOURCE_DIRECTORY__ //learned from http://stackoverflow.com/questions/4860991/f-for-scripting-location-of-script-file
20 |
21 | //should do dynamic action while building to toggle between this local path and the relative path
22 | #r "FsEye.dll" //release deployment expects this file next to the dll
23 | //#r "bin/Release/FsEye.dll"
24 |
25 |
26 | open Swensen.FsEye.Fsi
27 | let eye = eye
28 | fsi.AddPrintTransformer eye.Listener //attached the listener
29 |
--------------------------------------------------------------------------------
/FsEye/Fsi/Eye.fs:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright 2011 Stephen Swensen
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | *)
16 | namespace Swensen.FsEye.Fsi
17 | open Swensen.FsEye
18 | open Swensen.FsEye.Forms
19 |
20 | type private ManagedEyeResources = { EyeForm: EyeForm; PluginManager: PluginManager }
21 |
22 | ///Manages a EyeForm in the context of an FSI session listening for watch additions and updates and reflecting those in the EyeForm.
23 | type Eye() as this =
24 | let initResources () =
25 | let pluginManager = new PluginManager()
26 | let eyeForm = new EyeForm(pluginManager)
27 | {EyeForm=eyeForm; PluginManager=pluginManager}
28 |
29 |
30 | let mutable resources = initResources()
31 | do
32 | ///prevent form from disposing when closing
33 | resources.EyeForm.Closing.Add(fun args -> args.Cancel <- true ; this.Hide())
34 |
35 | ///Indicates whether or not FSI session listening is turned on
36 | let mutable listen = true
37 |
38 | let mutable listenerCts = new System.Threading.CancellationTokenSource()
39 |
40 | ///The listener event handler. Takes care to throttle fast repeated calls (discards those leading up to the last in <100ms succession).
41 | let listener (_:obj) =
42 | if not listen then
43 | null
44 | else
45 | listenerCts.Cancel()
46 | listenerCts <- new System.Threading.CancellationTokenSource()
47 | let gui = System.Threading.SynchronizationContext.Current
48 | let computation = async {
49 | let original = System.Threading.SynchronizationContext.Current
50 |
51 | do! Async.Sleep(100)
52 | do! async.Return () //nop to force cancellation check
53 |
54 | let watchVars = SessionQueries.getWatchableVariables()
55 |
56 | do! Async.SwitchToContext gui
57 |
58 | this.Show()
59 | watchVars |> Seq.iter resources.EyeForm.Watch
60 |
61 | do! Async.SwitchToContext original
62 | }
63 | Async.Start(computation, listenerCts.Token)
64 | null
65 |
66 | ///Add or update a watch with the given name, value, and type.
67 | member __.Watch(name, value:obj, ty) =
68 | resources.EyeForm.Watch(name, value, ty)
69 |
70 | ///Add or update a watch with the given name and value (where the type is derived from the type paramater of the value).
71 | member __.Watch(name, value) =
72 | resources.EyeForm.Watch(name, value)
73 |
74 | ///Take archival snap shot of all current watches using the given label.
75 | member this.Archive(label) =
76 | resources.EyeForm.Archive(label)
77 |
78 | ///Take archival snap shot of all current watches using a default label based on an archive count.
79 | member __.Archive() =
80 | resources.EyeForm.Archive()
81 |
82 | ///Clear all watches (doesn't include archive nodes).
83 | member __.ClearArchives() =
84 | resources.EyeForm.ClearArchives()
85 |
86 | ///Clear all watches (doesn't include archive nodes).
87 | member __.ClearWatches() =
88 | resources.EyeForm.ClearWatches()
89 |
90 | ///Clear all archives (reseting archive count) and watches.
91 | member __.ClearAll() =
92 | resources.EyeForm.ClearAll()
93 |
94 | ///
95 | ///Use this in a sync block with do!, e.g.
96 | ///
97 | ///async {
98 | /// for i in 1..100 do
99 | /// watch.Watch("i", i, typeof<int>)
100 | /// watch.Archive()
101 | /// if i = 50 then
102 | /// do! watch.AsyncBreak()
103 | ///} |> Async.StartImmediate
104 | ///
105 | member __.AsyncBreak() =
106 | resources.EyeForm.AsyncBreak()
107 |
108 | ///Continue from an AsyncBreak()
109 | member __.AsyncContinue() =
110 | resources.EyeForm.AsyncContinue()
111 |
112 | ///Indicates whether or not FSI session listening is turned on.
113 | member __.Listen
114 | with get() = listen
115 | and set(value) = listen <- value
116 |
117 | ///The listener to attached to FSI.
118 | member __.Listener =
119 | listener
120 |
121 | ///Show the Watch form.
122 | member __.Show() =
123 | if resources.EyeForm.IsDisposed then
124 | resources <- initResources()
125 |
126 | if resources.EyeForm.Visible |> not then
127 | resources.EyeForm.Show()
128 | resources.EyeForm.Activate()
129 |
130 | ///Hide the Watch form.
131 | member __.Hide() =
132 | resources.EyeForm.Hide()
133 |
134 | ///Manages plugins and plugin watch viewers
135 | member this.PluginManager = resources.PluginManager
136 |
137 | []
138 | []
139 | ///Holds the Eye singleton for the active FSI session
140 | module Eye =
141 | ///The Eye singleton for the active FSI session
142 | let eye = new Eye()
--------------------------------------------------------------------------------
/FsEye/Fsi/SessionQueries.fs:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright 2011 Stephen Swensen
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | *)
16 | ///Queries against the active FSI session
17 | module internal Swensen.FsEye.Fsi.SessionQueries
18 |
19 | open System.Reflection
20 |
21 | //The following method for extracting FSI session variables using reflection was
22 | //adapted from Tomas Petricek's (http://stackoverflow.com/users/33518/tomas-petricek) answer at
23 | //http://stackoverflow.com/questions/4997028/f-interactive-how-to-see-all-the-variables-defined-in-current-session/4998232#4998232
24 | let getWatchableVariables =
25 | let fsiAssembly =
26 | System.AppDomain.CurrentDomain.GetAssemblies()
27 | |> Seq.find (fun assm -> assm.GetName().Name = "FSI-ASSEMBLY")
28 |
29 | fun () ->
30 | fsiAssembly.GetTypes()//FSI types have the name pattern FSI_####, where #### is the order in which they were created
31 | |> Seq.filter (fun ty -> ty.Name.StartsWith("FSI_"))
32 | |> Seq.sortBy (fun ty -> ty.Name.Split('_').[1] |> int)
33 | |> Seq.collect (fun ty ->
34 | let flags = BindingFlags.Static ||| BindingFlags.NonPublic ||| BindingFlags.Public
35 | ty.GetProperties(flags)
36 | |> Seq.filter (fun pi -> pi.GetIndexParameters().Length > 0 |> not && pi.Name.Contains("@") |> not))
37 | //|> Seq.map (fun pi -> printfn "%A" (pi.Name, pi.GetValue(null, Array.empty), pi.PropertyType); pi)
38 | //the next sequence of pipes removes leading duplicates
39 | |> Seq.mapi (fun i pi -> pi.Name, (i, pi)) //remember the order
40 | |> Map.ofSeq //remove leading duplicates (but now ordered by property name)
41 | |> Map.toSeq //reconstitue
42 | |> Seq.sortBy (fun (_,(i,_)) -> i) //order by original index
43 | |> Seq.map (fun (_,(_,pi)) -> pi.Name, pi.GetValue(null, Array.empty), pi.PropertyType) //discard ordering index, project usuable watch value
44 | //|> Seq.map (fun it -> printfn "%A" it; it)
45 |
--------------------------------------------------------------------------------
/FsEye/IconResource.fs:
--------------------------------------------------------------------------------
1 | namespace Swensen.FsEye
2 | open System
3 | open System.Reflection
4 |
5 | ///Icon resource used to identify tree view node member classifications and provide FsEye form icon.
6 | type IconResource (name:string) = //should probably make IDisposible to dispose of Icon
7 | let loadIconResource =
8 | let assm = Assembly.GetExecutingAssembly()
9 | fun name -> new System.Drawing.Icon(assm.GetManifestResourceStream(name))
10 |
11 | let image = loadIconResource name
12 |
13 | member __.Name = name
14 | member __.Icon = image
15 |
16 | //F# doesn't allow public static fields so we save our selves a lot of boiler-plate using a module pretending to be the static part of the class
17 | []
18 | []
19 | ///Icon resource used to identify tree view node member classifications and provide FsEye form icon.
20 | module IconResource =
21 | let FsEye = IconResource "FsEye.ico"
--------------------------------------------------------------------------------
/FsEye/ImageResource.fs:
--------------------------------------------------------------------------------
1 | namespace Swensen.FsEye
2 | open System
3 | open System.Reflection
4 |
5 | ///Image resource used to identify tree view node member classifications and provide FsEye form icon.
6 | type ImageResource (name:string) =
7 | let loadImageResource =
8 | let assm = Assembly.GetExecutingAssembly()
9 | fun name -> System.Drawing.Image.FromStream(assm.GetManifestResourceStream(name))
10 |
11 | let image = loadImageResource name
12 |
13 | member __.Name = name
14 | member __.Image = image
15 |
16 | //F# doesn't allow public static fields so we save our selves a lot of boiler-plate using a module pretending to be the static part of the class
17 | []
18 | []
19 | ///Image resource used to identify tree view node member classifications and provide FsEye form icon.
20 | module ImageResource =
21 | let Default = ImageResource "VSObject_Field.bmp" //VS also used "field" as default for everything!
22 |
23 | let Field = ImageResource "VSObject_Field.bmp"
24 | let Property = ImageResource "VSObject_Properties.bmp"
25 | let Method = ImageResource "VSObject_Method.bmp"
26 |
27 | let FriendField = ImageResource "VSObject_Field_Friend.bmp"
28 | let FriendProperty = ImageResource "VSObject_Properties_Friend.bmp"
29 | let FriendMethod = ImageResource "VSObject_Method_Friend.bmp"
30 |
31 | let PrivateField = ImageResource "VSObject_Field_Private.bmp"
32 | let PrivateProperty = ImageResource "VSObject_Properties_Private.bmp"
33 | let PrivateMethod = ImageResource "VSObject_Method_Private.bmp"
34 |
35 | let ProtectedField = ImageResource "VSObject_Field_Protected.bmp"
36 | let ProtectedProperty = ImageResource "VSObject_Properties_Protected.bmp"
37 | let ProtectedMethod = ImageResource "VSObject_Method_Protected.bmp"
38 |
39 | let SealedField = ImageResource "VSObject_Field_Sealed.bmp"
40 | let SealedProperty = ImageResource "VSObject_Properties_Sealed.bmp"
41 | let SealedMethod = ImageResource "VSObject_Method_Sealed.bmp"
42 |
43 | let WatchImages = [
44 | Default
45 | Field; Property; Method
46 | FriendField; FriendProperty; FriendMethod
47 | PrivateField; PrivateProperty; PrivateMethod
48 | ProtectedField; ProtectedProperty; ProtectedMethod
49 | SealedField; SealedProperty; SealedMethod]
--------------------------------------------------------------------------------
/FsEye/PluginSystem.fs:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright 2011 Stephen Swensen
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | *)
16 | namespace Swensen.FsEye
17 | open System.Windows.Forms
18 | open System.Reflection
19 | open System.IO
20 | open System
21 | open Microsoft.FSharp.Collections
22 |
23 | ///Specifies a watch viewer interface, an instance which can add or update one or more watches with
24 | ///a custom watch viewer control
25 | type IWatchViewer =
26 | ///Add or update a watch with the given label, value, and type. Note: you can choose to
27 | ///disregard the label and type if desired, but will almost certainly need the value.
28 | abstract Watch : string * obj * System.Type -> unit
29 | ///The underlying watch viewer control. Exists as a property of IWatchViewer
30 | ///since you may or may not own the control (i.e. you cannot directly implement IWatchViewer on the control).
31 | abstract Control : Control
32 |
33 | ///Specificies a watch view plugin, capable of creating watch viewer instances
34 | type IPlugin =
35 | //The name of the plugin
36 | abstract Name : string
37 | ///Create an instance of this plugin's watch viewer
38 | abstract CreateWatchViewer : unit -> IWatchViewer
39 | ///Returns true or false depending on whether the given instance and its type (which we may need if
40 | ///the instance is null) are watchable: if false, then FsEye will not allow creating a watch for a value
41 | ///of the given type. Plugin authors should be mindful of the performance impact this method may have.
42 | abstract IsWatchable : obj * Type -> bool
43 |
44 | ///Represents a plugin watch viewer being managed by the PluginManager
45 | type ManagedWatchViewer = {
46 | ///The unique ID of the watch viewer instance
47 | ID:string
48 | ///The watch viewer instance which is being managed
49 | WatchViewer:IWatchViewer
50 | ///The owning ManagedPlugin
51 | ManagedPlugin:ManagedPlugin
52 | }
53 |
54 | ///Represents a plugin being managed by the PluginManager
55 | and ManagedPlugin = {
56 | ///The plugin being managed
57 | Plugin:IPlugin
58 | ///The owning plugin manager
59 | PluginManager:PluginManager
60 | }
61 | with
62 | ///The list of active watch viewers (i.e. watch viewers may be added and removed by the plugin manager)
63 | member this.ManagedWatchViewers = this.PluginManager.ManagedWatchViewers |> Seq.filter (fun (x:ManagedWatchViewer) -> x.ManagedPlugin = this)
64 |
65 | ///Manages FsEye watch viewer plugins
66 | and PluginManager(?scanForPlugins:bool) as this =
67 | let scanForPlugins = defaultArg scanForPlugins true
68 | let watchAdded = new Event()
69 | let watchUpdated = new Event()
70 | let watchRemoved = new Event()
71 |
72 | let showErrorDialog (text:string) (caption:string) =
73 | MessageBox.Show(text, caption, MessageBoxButtons.OK, MessageBoxIcon.Error) |> ignore
74 |
75 | let showPluginErrorDialog text = showErrorDialog text "FsEye Plugin Loading Error"
76 |
77 | let managedPlugins =
78 | if scanForPlugins then
79 | try
80 | let executingAsm = Assembly.GetExecutingAssembly()
81 | let executingAsmLocation =
82 | if System.AppDomain.CurrentDomain.ShadowCopyFiles then
83 | (new System.Uri (executingAsm.EscapedCodeBase)).LocalPath
84 | else executingAsm.Location
85 | let executingDir = Path.GetDirectoryName(executingAsmLocation)
86 | let pluginDir = sprintf "%s%cplugins" executingDir Path.DirectorySeparatorChar
87 | let assemblyExclude = ["FsEye.dll"] //maybe exclude all of executingAsm.GetReferencedAssemblies() as well.
88 |
89 | let pluginSeq =
90 | [pluginDir; executingDir]
91 | |> Seq.filter Directory.Exists
92 | |> Seq.collect (fun pluginDir ->
93 | Directory.GetFiles(pluginDir)
94 | |> Seq.filter(fun assemblyFile ->
95 | assemblyFile.EndsWith(".dll") && assemblyExclude |> List.exists (fun exclude -> assemblyFile.EndsWith(exclude)) |> not)
96 | //Issue 36: need to use Assembly.UnsafeLoadFrom to avoid plugin loading errors
97 | |> Seq.map (fun assemblyFile -> Assembly.UnsafeLoadFrom(assemblyFile))
98 | |> Seq.collect (fun assembly ->
99 | try
100 | assembly.GetTypes()
101 | with
102 | | :? ReflectionTypeLoadException as x ->
103 | for le in x.LoaderExceptions do
104 | let msg = sprintf "Error loading types in assembly '%s'.\n\n%s" assembly.FullName le.Message
105 | showPluginErrorDialog msg
106 | [||]
107 | )
108 | |> Seq.filter (fun ty -> typeof.IsAssignableFrom(ty))
109 | |> Seq.map (fun pluginTy ->
110 | let plugin = Activator.CreateInstance(pluginTy) :?> IPlugin
111 | {Plugin=plugin; PluginManager=this}))
112 | ResizeArray(pluginSeq)
113 | with
114 | | x ->
115 | showPluginErrorDialog x.Message
116 | ResizeArray()
117 | else
118 | ResizeArray()
119 |
120 | let managedWatchViewers = ResizeArray()
121 |
122 | ///absolute number of watch viewer instances created for the given plugin
123 | let absoluteCounts =
124 | let dic = new System.Collections.Generic.Dictionary()
125 | for plugin in managedPlugins do
126 | dic.[plugin] <- 0
127 |
128 | dic
129 |
130 | []
131 | member __.WatchAdded = watchAdded.Publish
132 | []
133 | member __.WatchUpdated = watchUpdated.Publish
134 | []
135 | member __.WatchRemoved = watchRemoved.Publish
136 |
137 | member __.ManagedPlugins = managedPlugins |> Seq.readonly
138 | member __.ManagedWatchViewers = managedWatchViewers |> Seq.readonly
139 |
140 | ///Create a new watch viewer for the given managed plugin, sending the given label, value and type.
141 | ///Returns the ManagedWatchViewer which wraps the created watch viewer.
142 | member this.SendTo(managedPlugin:ManagedPlugin, label: string, value: obj, valueTy:System.Type) =
143 | //create the new watch viewer
144 | let watchViewer = managedPlugin.Plugin.CreateWatchViewer()
145 | watchViewer.Watch(label, value, valueTy)
146 |
147 | //create the container control
148 | let id =
149 | let next = absoluteCounts.[managedPlugin] + 1
150 | absoluteCounts.[managedPlugin] <- next
151 | sprintf "%s %i" managedPlugin.Plugin.Name next
152 |
153 |
154 | //create the managed watch viewer and add it to this managed plugin's collection
155 | let mwv = {ID=id;WatchViewer=watchViewer;ManagedPlugin=managedPlugin}
156 | managedWatchViewers.Add(mwv)
157 |
158 | watchAdded.Trigger(mwv)
159 | mwv
160 |
161 | ///Send the given label, value and type to the given, existing managed watch viewer.
162 | member this.SendTo(mwv:ManagedWatchViewer, label: string, value: obj, valueTy:System.Type) =
163 | mwv.WatchViewer.Watch(label, value, valueTy)
164 |
165 | watchUpdated.Trigger(mwv)
166 |
167 | ///Remove the given managed watch viewer, disposing the watch viewer's Control.
168 | member this.RemoveManagedWatchViewer(mwv) =
169 | mwv.WatchViewer.Control.Dispose()
170 | managedWatchViewers.Remove(mwv) |> ignore
171 | watchRemoved.Trigger(mwv)
172 |
173 | ///Remove the managed watch viewer by id, disposing the watch viewer's Control.
174 | member this.RemoveManagedWatchViewer(id:string) =
175 | let mwv = managedWatchViewers |> Seq.find(fun x -> x.ID = id)
176 | this.RemoveManagedWatchViewer(mwv)
177 |
178 | ///Remove the given managed plugin (and all of its managed watch viewers).
179 | member this.RemoveManagedPlugin(mp) =
180 | managedWatchViewers
181 | |> Seq.filter (fun x -> x.ManagedPlugin = mp)
182 | |> Seq.toList
183 | |> Seq.iter (fun x -> this.RemoveManagedWatchViewer(x))
184 |
185 | absoluteCounts.Remove(mp) |> ignore
186 | managedPlugins.Remove(mp) |> ignore
187 |
188 | ///Remove the managed plugin (and all of its managed watch viewers) by name.
189 | member this.RemoveManagedPlugin(name:string) =
190 | let mp = managedPlugins |> Seq.find(fun x -> x.Plugin.Name = name)
191 | this.RemoveManagedPlugin(mp)
192 |
193 | ///Register the given plugin and return the managed plugin wrapping it. If a managed plugin wrapping a plugin of the same name exists,
194 | ///removes it (and all of its associated managed watch viewers).
195 | member this.RegisterPlugin(plugin:IPlugin) =
196 | match managedPlugins |> Seq.tryFind (fun x -> x.Plugin.Name = plugin.Name) with
197 | | Some(old_mp) -> this.RemoveManagedPlugin(old_mp)
198 | | None -> ()
199 |
200 | let mp = {Plugin=plugin; PluginManager=this}
201 | managedPlugins.Add(mp)
202 | absoluteCounts.[mp] <- 0
203 | mp
--------------------------------------------------------------------------------
/FsEye/Resources/FsEye.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/FsEye.ico
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Field.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Field.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Field_Friend.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Field_Friend.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Field_Private.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Field_Private.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Field_Protected.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Field_Protected.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Field_Sealed.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Field_Sealed.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Method.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Method.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Method_Friend.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Method_Friend.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Method_Private.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Method_Private.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Method_Protected.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Method_Protected.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Method_Sealed.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Method_Sealed.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Properties.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Properties.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Properties_Friend.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Properties_Friend.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Properties_Private.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Properties_Private.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Properties_Protected.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Properties_Protected.bmp
--------------------------------------------------------------------------------
/FsEye/Resources/VSObject_Properties_Sealed.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/FsEye/Resources/VSObject_Properties_Sealed.bmp
--------------------------------------------------------------------------------
/FsEye/Script.fsx:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright 2011 Stephen Swensen
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | *)
16 |
17 | //we use Debug.FsEye since it has the plugins.
18 | #r "../Debug.FsEye/bin/Release/FsEye.dll"
19 | #load @"..\Debug.FsEye\bin\Release\FsEye.fsx"
20 |
21 |
22 | //----NEW FEATURE IDEAS
23 | //add "Refresh Watches" button to top left
24 | //file menu?
25 | //drag and drop root nodes to archives
26 | //add Archive Watches... which will prompt for archive name
27 | //right click context menu on archive node allows you to change archive name
28 | //data visualization: simple graph, or plug into existing visualization tools
29 | //multi-select nodes (for drag-drop, archiving, etc.)
30 |
31 | //Simple example of how we can "break" during evaluation!
32 | async {
33 | for i in 1..40 do
34 | eye.Watch("i", i)
35 | eye.Watch("i*2", i*2)
36 | eye.Archive()
37 | if i % 10 = 0 then
38 | do! eye.AsyncBreak()
39 | } |> Async.StartImmediate
40 |
41 | eye.AsyncContinue()
42 | eye.Show()
43 |
44 |
45 | let work loops =
46 | for i in 1I..(loops*1000000I) do ()
47 | loops
48 |
49 | type SlowType() =
50 | member this.AMethod() = work 2I ; new SlowType()
51 | member this.One = work 1I
52 | member this.Two = work 2I
53 | member this.Three = work 1I |> ignore ; failwith "Some exception occurred" ; 3
54 | member this.Four = work 1I
55 | member this.Four3 = work 1I
56 | member this.Four2 = work 2I
57 | member this.Four1 = work 4I
58 | member this.Fou5 = work 3I
59 | member private this.Fou23 = work 5I
60 | member this.Fou234 = work 6I
61 | member this.Fousd = work 1I
62 | member private this.Fous =work 3I
63 | member this.Foug = work 2I
64 |
65 | eye.Watch("sl", SlowType())
66 | eye.Show()
67 |
68 |
69 | //also try to avoid display immediately seq's which are lazy (e.g. known concrete type, or perhaps having Item.[int] property lookup)
70 |
71 | //features
72 | //Monitors FSI for watch additions and updates
73 | //Asycronous, parallel, lazy loading of child nodes
74 | //Asyncronouse Break and Continue debugging
75 | //View large or infinite sequences in 100 element lazy loaded chunks
76 | //View Public and Non-public value members
77 | //Programatic access to Gui commands and watch addition and updates through FSI
78 | //Pretty F# name printing via Unquote
--------------------------------------------------------------------------------
/FsEye/Settings.fs:
--------------------------------------------------------------------------------
1 | namespace Swensen.FsEye
2 |
3 | open System
4 | //open System.Xml
5 | //open System.Xml.Serialization
6 |
7 | //type Settings() =
8 | // member
--------------------------------------------------------------------------------
/FsEye/WatchModel.fs:
--------------------------------------------------------------------------------
1 | (*
2 | Copyright 2011 Stephen Swensen
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | *)
16 | module Swensen.FsEye.WatchModel
17 | open System
18 | open System.Reflection
19 | open Microsoft.FSharp.Reflection
20 | open Swensen.Utils
21 |
22 |
23 | ///let l be the lazy:'a arg, returns Some(l.Value) if l.IsValueCreated, else returns None.
24 | let (|CreatedValue|_|) (l:'a Lazy) =
25 | if l.IsValueCreated then Some(l.Value)
26 | else None
27 |
28 | //how to add icons to tree view: http://msdn.microsoft.com/en-us/library/aa983725(v=vs.71).aspx
29 |
30 | type Root = {
31 | Text: string
32 | Children:seq
33 | ValueInfo:ValueInfo
34 | Name: String
35 | ExpressionInfo:ExpressionInfo }
36 |
37 | and Organizer = {
38 | OrganizerKind: OrganizerKind
39 | Children:seq }
40 |
41 | and OrganizerKind =
42 | | Rest
43 | | NonPublic
44 |
45 | and EnumeratorElement = {
46 | Text:string
47 | Children:seq
48 | ValueInfo:ValueInfo
49 | ExpressionInfo:ExpressionInfo }
50 |
51 | and DataMember = {
52 | LoadingText: string
53 | LazyMemberValue: Lazy
54 | Image:ImageResource
55 | MemberInfo:MemberInfo
56 | ExpressionInfo:ExpressionInfo }
57 |
58 | and CallMember = {
59 | InitialText: string
60 | LoadingText: string
61 | LazyMemberValue: Lazy
62 | Image:ImageResource
63 | MemberInfo:MemberInfo
64 | ExpressionInfo:ExpressionInfo }
65 |
66 | and MemberValue = {
67 | LoadedText: string
68 | Children:seq
69 | ValueInfo: ValueInfo option }
70 |
71 | and ValueInfo = {
72 | Text: string
73 | Value: obj
74 | Type : Type }
75 |
76 | and ExpressionInfo = {
77 | Expression: string
78 | IsPublic: bool
79 | ExplicitInterfaceName: string option }
80 |
81 | and Watch =
82 | | Root of Root
83 | | DataMember of DataMember
84 | | CallMember of CallMember
85 | | Organizer of Organizer
86 | | EnumeratorElement of EnumeratorElement
87 | ///Get the "default text" of this Watch (i.e. the text which is displayed when a watch node is first created which may or may not be updated later).
88 | member this.DefaultText =
89 | match this with
90 | | Root {Text=text}
91 | | DataMember {LoadingText=text}
92 | | CallMember {InitialText=text}
93 | | EnumeratorElement {Text=text}-> text
94 | | Organizer {OrganizerKind=Rest} -> "Rest"
95 | | Organizer {OrganizerKind=NonPublic} -> "Non-public"
96 | ///Get the children of this Watch. If the children are taken from a Lazy property,
97 | ///evaluation is forced.
98 | member this.Children =
99 | match this with
100 | | Root {Children=children}
101 | | Organizer {Children=children}
102 | | EnumeratorElement {Children=children} -> children
103 | | CallMember {LazyMemberValue=l}
104 | | DataMember {LazyMemberValue=l} -> l.Value.Children
105 | ///Returns the value info for this watch if a) the watch is fully initialized (it is not forced), b) it is even supported
106 | member this.ValueInfo =
107 | match this with
108 | | Root {ValueInfo=vi}
109 | | EnumeratorElement {ValueInfo=vi}-> Some(vi)
110 | | DataMember {LazyMemberValue=CreatedValue({ValueInfo=optionalVi})}
111 | | CallMember {LazyMemberValue=CreatedValue({ValueInfo=optionalVi})} -> optionalVi
112 | | _ -> None
113 | member this.Image =
114 | match this with
115 | | DataMember {Image=image}
116 | | CallMember {Image=image} -> image
117 | | _ -> ImageResource.Default
118 | member this.MemberInfo =
119 | match this with
120 | | DataMember { MemberInfo=mi }
121 | | CallMember { MemberInfo=mi } -> Some(mi)
122 | | _ -> None
123 | member this.ExpressionInfo =
124 | match this with
125 | | Root {ExpressionInfo=ei}
126 | | EnumeratorElement {ExpressionInfo=ei}
127 | | CallMember {ExpressionInfo=ei}
128 | | DataMember {ExpressionInfo=ei} -> Some(ei)
129 | | Organizer _ -> None
130 |
131 | open System.Text.RegularExpressions
132 | ///Sprint the given value with the given Type. Precondition: Type cannot be null.
133 | let private sprintValue (value:obj) (ty:Type) =
134 | if ty =& null then
135 | nullArg "ty cannot be null"
136 |
137 | //remove white space since e.g. sprint "%A" uses a lot of whitespace for deeply nested records and DUs.
138 | let cleanString str = Regex.Replace(str, @"[\t\r\n]", "", RegexOptions.Compiled)
139 |
140 | match value with
141 | | null when ty.IsGenericType && ty.GetGenericTypeDefinition() = typedefof