├── .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> -> "None" 142 | | null -> "null" 143 | | _ -> 144 | if typeof.IsAssignableFrom(ty) then 145 | sprintf "typeof<%s>" (value :?> Type).FSharpName 146 | else 147 | sprintf "%A" value |> cleanString 148 | 149 | ///Create lazy seq of children s for a typical valued 150 | let rec createChildren ownerValue (ownerTy:Type) = 151 | if ownerValue =& null then Seq.empty 152 | else 153 | let getMembers bindingFlags = 154 | let allMembers = seq { 155 | ///yield all ownerTy members 156 | yield! ownerTy.GetMembers(bindingFlags) 157 | 158 | let mapMembers tys = 159 | tys 160 | |> Seq.map (fun (ty:Type) -> ty.GetMembers(bindingFlags)) 161 | |> Seq.concat 162 | //yield all ownerTy interface members 163 | yield! ownerTy.GetInterfaces() |> mapMembers 164 | //yield all ownerTy base type (recursive) members 165 | yield! 166 | ownerTy 167 | |> Seq.unfold(fun ty -> 168 | let bty = ty.BaseType 169 | if bty = null then None else Some(bty, bty)) 170 | |> mapMembers } 171 | 172 | let isDebuggerBrowserNeverAttribute (x:obj) = 173 | match x with 174 | | :? System.Diagnostics.DebuggerBrowsableAttribute as db -> db.State = System.Diagnostics.DebuggerBrowsableState.Never 175 | | _ -> false 176 | 177 | let validMemberTypes = 178 | allMembers 179 | |> Seq.filter (fun mi -> 180 | match mi with 181 | | :? PropertyInfo as pi -> 182 | pi.CanRead && //issue 19 183 | pi.GetIndexParameters() = Array.empty && 184 | pi.GetCustomAttributes(false) |> Array.exists isDebuggerBrowserNeverAttribute |> not 185 | | :? MethodInfo as meth -> //a simple method taking no arguments and returning a value 186 | meth.GetParameters() = Array.empty && 187 | meth.ReturnType <> typeof && 188 | meth.ReturnType <> typeof && 189 | meth.ContainsGenericParameters |> not && 190 | meth.Name.StartsWith("get_") |> not //F# does not mark properties as having a "Special Name", so need to filter by prefix 191 | | :? FieldInfo as fi -> 192 | fi.GetCustomAttributes(false) |> Array.exists isDebuggerBrowserNeverAttribute |> not 193 | | _ -> false) 194 | 195 | let nonRedundantMembers = 196 | validMemberTypes 197 | |> Seq.distinctByResolve 198 | (fun mi -> mi.Name.ToLower()) 199 | (fun mi1 mi2 -> 200 | let ty1, ty2 = mi1.DeclaringType, mi2.DeclaringType 201 | if ty1.IsAssignableFrom(ty2) then -1 202 | elif ty2.IsAssignableFrom(ty1) then 1 203 | else 0) 204 | 205 | let sortedMembers = 206 | nonRedundantMembers 207 | |> Seq.sortBy (fun mi -> mi.Name.ToLower()) 208 | 209 | sortedMembers 210 | 211 | let createResultWatches (value:System.Collections.IEnumerator) = 212 | let createChild index value = 213 | //Would like to be able to always get the type 214 | //but if is non-Custom IEnumerable, then can't 215 | let ty = if value =& null then typeof else value.GetType() 216 | let valueText = sprintValue value ty 217 | let expression = sprintf "[%i]" index 218 | let text = sprintf "%s : %s = %s" expression ty.FSharpName valueText 219 | let children = createChildren value ty 220 | EnumeratorElement { Text=text 221 | Children=children 222 | ValueInfo={Text=valueText; Value=value; Type=ty} 223 | ExpressionInfo={Expression=expression; ExplicitInterfaceName=None; IsPublic=true}} 224 | 225 | //yield 100 chunks 226 | let rec calcRest pos (ie:System.Collections.IEnumerator) = seq { 227 | if ie.MoveNext() then 228 | let nextResult = createChild pos ie.Current 229 | if pos % 100 = 0 && pos <> 0 then 230 | let rest = seq { yield nextResult; yield! calcRest (pos+1) ie } 231 | yield Organizer { OrganizerKind=Rest 232 | Children=rest } 233 | else 234 | yield nextResult; 235 | yield! calcRest (pos+1) ie } 236 | 237 | //must cache the enumerator is created outside of the seq 238 | seq { yield! calcRest 0 value } |> Seq.cache //should use "use" when getting enumerator? 239 | 240 | //if member is inherited from base type or explicit interface, fully qualify 241 | let getMemberName (mi:Reflection.MemberInfo) = 242 | if mi.ReflectedType <> ownerTy then 243 | mi.ReflectedType.FSharpName + "." + mi.Name 244 | else 245 | mi.Name 246 | 247 | let getMemberExpressionInfo (mi:Reflection.MemberInfo) = 248 | let explicitInterfaceName = 249 | if mi.ReflectedType.IsInterface then 250 | Some(mi.ReflectedType.FSharpName) 251 | else 252 | None 253 | 254 | let expression = 255 | if mi :? MethodInfo then 256 | sprintf "%s()" mi.Name 257 | else 258 | mi.Name 259 | 260 | let isPublic = 261 | match mi with 262 | | :? FieldInfo as fi -> fi.IsPublic 263 | | :? MethodInfo as mi -> mi.IsPublic 264 | | :? PropertyInfo as pi -> pi.GetGetMethod(true).IsPublic 265 | | _ -> false //shouldn't be possible: we should log this, but not fail hard. 266 | 267 | { Expression=expression; ExplicitInterfaceName=explicitInterfaceName; IsPublic=isPublic } 268 | 269 | let makeMemberValue (value:obj) valueTy pretext = 270 | if typeof.IsAssignableFrom(valueTy) then 271 | { MemberValue.LoadedText=pretext valueTy.FSharpName "" 272 | Children=(createResultWatches (value :?> System.Collections.IEnumerator)) 273 | ValueInfo=None } 274 | else 275 | let valueText = sprintValue value valueTy 276 | { LoadedText=pretext valueTy.FSharpName (" = " + valueText) 277 | Children=(createChildren value valueTy) 278 | ValueInfo=Some({Text=valueText; Value=value; Type=valueTy}) } 279 | 280 | let loadingText = " = Loading..." 281 | 282 | ///Create a Watch node from the given PropertyInfo. Precondition: PropertyInfo.CanRead is true. 283 | let getPropertyWatch (pi:PropertyInfo) = 284 | let pretext = sprintf "%s : %s%s" (getMemberName pi) 285 | let delayed = lazy( 286 | let value, valueTy = 287 | try 288 | let value = pi.GetValue(ownerValue, Array.empty) 289 | value, if value <>& null then value.GetType() else pi.PropertyType //use the actual type if we can 290 | with e -> 291 | box e, e.GetType() 292 | makeMemberValue value valueTy pretext) 293 | 294 | let image = 295 | let meth = pi.GetGetMethod(true) 296 | if meth.IsPublic then 297 | if meth.IsFinal then 298 | ImageResource.SealedProperty 299 | else 300 | ImageResource.Property 301 | elif meth.IsFamily || meth.IsFamilyAndAssembly || meth.IsFamilyOrAssembly then 302 | ImageResource.ProtectedProperty 303 | else //assume private 304 | ImageResource.PrivateProperty 305 | 306 | DataMember { LoadingText=(pretext pi.PropertyType.FSharpName loadingText) 307 | LazyMemberValue=delayed 308 | Image=image 309 | MemberInfo=pi 310 | ExpressionInfo= getMemberExpressionInfo pi } 311 | 312 | let getFieldWatch (fi:FieldInfo) = 313 | let pretext = sprintf "%s : %s%s" (getMemberName fi) 314 | let delayed = lazy( 315 | let value, valueTy = 316 | try 317 | let value = fi.GetValue(ownerValue) 318 | value, if value <>& null then value.GetType() else fi.FieldType //use the actual type if we can 319 | with e -> 320 | box e, e.GetType() 321 | makeMemberValue value valueTy pretext) 322 | 323 | let image = 324 | if fi.IsPublic then 325 | ImageResource.Field //I don't think there is a such a thing as "sealed" fields 326 | elif fi.IsFamily || fi.IsFamilyAndAssembly || fi.IsFamilyOrAssembly then 327 | ImageResource.ProtectedField 328 | else //assume private 329 | ImageResource.PrivateField 330 | 331 | DataMember { LoadingText=pretext fi.FieldType.FSharpName loadingText 332 | LazyMemberValue=delayed 333 | Image=image 334 | MemberInfo=fi 335 | ExpressionInfo= getMemberExpressionInfo fi } 336 | 337 | let getMethodWatch (mi:MethodInfo) = 338 | let pretext = sprintf "%s() : %s%s" (getMemberName mi) 339 | let delayed = lazy( 340 | let value, valueTy = 341 | try 342 | let value = mi.Invoke(ownerValue, Array.empty) 343 | value, if value <>& null then value.GetType() else mi.ReturnType //use the actual type if we can 344 | with e -> 345 | box e, e.GetType() 346 | makeMemberValue value valueTy pretext) 347 | 348 | let image = 349 | if mi.IsPublic then 350 | if mi.IsFinal then 351 | ImageResource.SealedMethod 352 | else 353 | ImageResource.Method 354 | elif mi.IsFamily || mi.IsFamilyAndAssembly || mi.IsFamilyOrAssembly then 355 | ImageResource.ProtectedMethod 356 | else //assume private 357 | ImageResource.PrivateMethod 358 | 359 | CallMember { InitialText=pretext mi.ReturnType.FSharpName "" 360 | LoadingText=pretext mi.ReturnType.FSharpName loadingText 361 | LazyMemberValue=delayed 362 | Image=image 363 | MemberInfo=mi 364 | ExpressionInfo= getMemberExpressionInfo mi} 365 | 366 | let getMemberWatches bindingFlags = seq { 367 | let members = getMembers bindingFlags 368 | for m in members do 369 | match m with 370 | | :? PropertyInfo as x -> yield getPropertyWatch x 371 | | :? FieldInfo as x -> yield getFieldWatch x 372 | | :? MethodInfo as x -> yield getMethodWatch x 373 | | _ -> () } 374 | 375 | let publicBindingFlags = BindingFlags.Instance ||| BindingFlags.Public 376 | let nonPublicBindingFlags = BindingFlags.Instance ||| BindingFlags.NonPublic 377 | 378 | seq { 379 | let nonPublicMemberWatches = getMemberWatches nonPublicBindingFlags 380 | yield Organizer { OrganizerKind=NonPublic 381 | Children=nonPublicMemberWatches } 382 | yield! getMemberWatches publicBindingFlags 383 | } 384 | ///Create a watch root. If value is not null, then value.GetType() is used as the watch Type instead of 385 | ///ty. Else if ty is not null ty is used. Else typeof is used. 386 | let createRootWatch (name:string) (value:obj) (ty:Type) = 387 | let ty = 388 | if value <> null then value.GetType() 389 | elif ty <> null then ty 390 | else typeof 391 | 392 | let valueText = sprintValue value ty 393 | let text = sprintf "%s : %s = %s" name ty.FSharpName valueText 394 | let children = createChildren value ty 395 | Root { Text=text 396 | Children=children 397 | ValueInfo={Text=valueText; Value=value; Type=ty} 398 | Name=name 399 | ExpressionInfo= {Expression=name; IsPublic=true; ExplicitInterfaceName=None}} -------------------------------------------------------------------------------- /FsEye/Win32.fs: -------------------------------------------------------------------------------- 1 | namespace Swensen.FsEye 2 | 3 | open System.Reflection 4 | open System.Runtime.InteropServices 5 | open System.Drawing 6 | 7 | open Microsoft.FSharp.NativeInterop 8 | 9 | module Win32 = 10 | let WM_MOUSEWHEEL = 0x20a 11 | let TVM_SETEXTENDEDSTYLE = 0x1100 + 0x2c 12 | let TVM_GETEXTENDEDSTYLE = 0x1100 + 0x2d; 13 | let TVS_EX_DOUBLEBUFFER = 0x0004; 14 | 15 | [] 16 | extern nativeint WindowFromPoint(Point pt) 17 | 18 | [] 19 | extern nativeint SendMessage(nativeint hWnd, int msg, nativeint wp, nativeint lp) 20 | -------------------------------------------------------------------------------- /FsEye/todo.txt: -------------------------------------------------------------------------------- 1 | TODO 2 | 3 | Issue 25, Plugin Architecture: 4 | * DONE Decouple TabContainer from PluginManager 5 | * DONE Create PluginTabContainer control 6 | * DONE Add optional ValueInfo property to Watch to include Value, Type, and Text 7 | * DONE Add ExpressionInfo property to Watch to include Expression (e.g. [0], GetBla(), PropHere), 8 | optional InterfaceName, IsNonPublic (or maybe IsPublic to avoid the double negative). 9 | * Refactor PluginManager: not happy yet, look for appropriate design patterns (i.e. we have the 10 | PluginManager, ManagedPlugins, and ManagedWatchViewers that are tightly couple but we want to 11 | limit mutable surface) 12 | * DONE Consider whether we want IPlugin.IsWatchable to be Type -> bool or obj -> Type -> bool (where obj is 13 | the actual obj instance of the watch candidate. 14 | STATUS: chose second option 15 | * Per Howards latest feedback, consider renaming IWatchViewer to something more general like IWatchReceiver with the following signature: 16 | 17 | type IWatchReceiver = 18 | ///Add or update a the receiver with the given label, value, and type. Note: you can choose to 19 | ///disregard the label and type if desired, but will almost certainly need the value. 20 | abstract Receive : string * 'a * System.Type -> unit 21 | ///An optional control which provides a viewer for the receiver. If Some, then 22 | ///will be displayed in a tab by FsEye and can be updated. If None, then is for one-time use 23 | ///and will not have any visual representation in FsEye. 24 | abstract Control : Control option 25 | 26 | * Per Howards latest feedback, related to the previous item, consider renaming IPlugin.IsWatchable to IPlugin.IsSupported and prefer the following signature: 27 | 28 | ///Used by plugin authors to indicate how the plugin should be displayed in the context menu based on a watch instance and type. 29 | type Supported = 30 | | Enabled 31 | | Disabled 32 | | Hidden 33 | 34 | type IPlugin = 35 | //The name of the plugin 36 | abstract Name : string 37 | ///Create an instance of this plugin's watch receiver 38 | abstract CreateWatchReceiver : unit -> IWatchReceiver 39 | ///Returns the Supported choice depending on whether the given instance and its type (which we may need if the instance is null) 40 | ///is supported (receivable): if false, then FsEye will not allow creating a watch for a value of the given type 41 | abstract IsSupported : obj -> Type -> Supported 42 | 43 | CURRENT THOUGHTS regarding previous two items: 44 | 45 | I worry about growing the complexity of the plugin interfaces, for fear it will 46 | become prohibitive for would-be plugin authors and become less-intuitive for plugin users. For example, the tri-state Support 47 | value may be confusing for plugin users since it would not be transparent to them why a given plugin should be Enabled, Disabled, or Hidden. 48 | 49 | So in that regard I think we should stick to just Enabled / Disabled, preferring Disabled to Hidden since the availability is determined 50 | DYNAMICALLY by all WATCHABLE values. I believe in this classification it leaves a user in less wonder to see a menu item disabled versus 51 | hidden (principle of least surprise: "is my plugin loaded?"). On the other hand, we hide menu items for STATIC availability of nodes of a given 52 | clasification (e.g. Archive nodes are never watchable no matter what their state, so we never show them the "Send To" menu item). But I do 53 | see the concern that the list of plugins may be very large and cluttered if we merely disable rather than hide... perhaps we making this a Settings option 54 | (i.e. user choice rather than plugin author's choice -- default to disabled). 55 | 56 | Regarding expanding the generality of plugins, i.e. IWatchReceiver with an optional control which indicates it is not a visual "watch", that 57 | again increases complexity. It seems to me it would be perfectly usable to have an e.g. "Send To Excel" plugin that also exposes a Control 58 | for a watch that e.g. displays info about or allows configuration for the excel file that is to be generated. 59 | 60 | * If one plugin throws an exception while loading, should continue to try to load the rest 61 | 62 | * DONE Should it be IWatchViewer.IsWatchable obj * Type -> bool or IWatchViewer.IsWatchable 'a * Type -> bool? The latter is consistent but seems 63 | like an anti pattern we adopted to give benifit in F#'s type system. 64 | SOLUTION: keeping obj * Type -> bool, changed WatchTreeView.Watch to string * obj * Type 65 | 66 | * ON HOLD Add Send To Active under Send To -> context menu item for conviences (navigating 3 levels deeps is a bit burdonsome.) 67 | 68 | * Write tests for add / remove plugin functionality on PluginManager. 69 | 70 | Issue 26, PropertyGrid: 71 | * Consider SelectedObjects vs. SelectedObject. Maybe save for future enhancement. 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2011-2012 Stephen Swensen 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | FsEye 2 | Copyright 2011-2014 Stephen Swensen 3 | 4 | The FsEye icon was designed by Priya Swensen 5 | 6 | This software includes Microsoft Visual Studio 2010 Babel Icons 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [User Guide](../../wiki/UserGuide) | [Downloads](http://www.swensensoftware.com/fseye) | [Release Notes](../../wiki/ReleaseNotes) | [Issues](../../issues) 2 | 3 | --- 4 | 5 | FsEye is a visual object tree inspector for the F# Interactive. Taking advantage of the built-in WinForms event loop, it 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. 6 | 7 | ![screen shot](http://www.swensensoftware.com/static/fseye/front-page-example.png) 8 | 9 | Features 10 | * Monitors FSI for watch additions and updates 11 | * Asynchronous, parallel, lazy loading of child nodes 12 | * Asynchronous Break and Continue debugging 13 | * View large or infinite sequences in 100 element lazy loaded chunks 14 | * View public and non-public member values, including fields, properties, and lazily forced return values for zero-arg non-void call members 15 | * Programmatic control of FsEye watches 16 | * Pretty F# name printing 17 | * Copy watch values to the Clipboard with the right-click context menu 18 | * Support for plugins with PropertyGrid, DataGridView, and TreeView-based plugins provided out-of-the box 19 | 20 | --- 21 | 22 | [![Build status](https://ci.appveyor.com/api/projects/status/mmy4kyngu0d8lxu4?svg=true)](https://ci.appveyor.com/project/stephen-swensen/fseye) 23 | 24 | You are welcome to [Pay What You Want](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BFNS7ZMAL3JZQ) for FsEye via PayPal. 25 | 26 | Copyright 2011-2016 [Swensen Software](http://www.swensensoftware.com) 27 | -------------------------------------------------------------------------------- /Test.FsEye/DataGridViewPluginTests.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 DataGridViewTests 17 | 18 | open Swensen.Unquote 19 | open Xunit 20 | 21 | open Swensen.FsEye 22 | open Swensen.FsEye.Plugins 23 | 24 | open Swensen.Utils 25 | open System.Windows.Forms 26 | 27 | [] 28 | let ``seq is synthetic watchable type`` () = 29 | let p = new DataGridViewPlugin() :> IPlugin 30 | let xs = seq [1;2;3] 31 | test <@ p.IsWatchable(xs, xs.GetType()) @> 32 | 33 | let wv = p.CreateWatchViewer() 34 | wv.Watch("", xs, xs.GetType()) 35 | let dgv = wv.Control.Controls |> Seq.cast |> Seq.find (fun c -> c :? DataGridView) :?> DataGridView 36 | let xsWrapper = dgv.DataSource 37 | test <@ xsWrapper :? System.Collections.Generic.List @> 38 | let xsWrapper = xsWrapper :?> System.Collections.Generic.List 39 | test <@ xsWrapper.Count = 3 @> 40 | test <@ xsWrapper.[0] :?> int = 1 @> 41 | test <@ xsWrapper.[1] :?> int = 2 @> 42 | test <@ xsWrapper.[2] :?> int = 3 @> 43 | 44 | [] 45 | let ``ResizeArray is a natively watchable type`` () = 46 | let p = new DataGridViewPlugin() :> IPlugin 47 | let xl = new System.Collections.Generic.List([1;2;3]) 48 | test <@ p.IsWatchable(xl, xl.GetType()) @> 49 | 50 | let wv = p.CreateWatchViewer() 51 | wv.Watch("", xl, xl.GetType()) 52 | let dgv = wv.Control.Controls |> Seq.cast |> Seq.find (fun c -> c :? DataGridView) :?> DataGridView 53 | test <@ obj.ReferenceEquals(xl, dgv.DataSource) @> -------------------------------------------------------------------------------- /Test.FsEye/PluginManagerTests.fs: -------------------------------------------------------------------------------- 1 | module PluginManagerTests 2 | 3 | open Swensen.Unquote 4 | open Xunit 5 | 6 | open Swensen.FsEye.WatchModel 7 | open Swensen.FsEye.Forms 8 | open Swensen.FsEye 9 | open Swensen.FsEye.Plugins 10 | 11 | open Swensen.Utils 12 | open System.Windows.Forms 13 | 14 | open System 15 | open System.Reflection 16 | open ImpromptuInterface.FSharp 17 | 18 | let mkPlugin name mkControl = 19 | { 20 | new IPlugin with 21 | member __.Name = name 22 | member __.IsWatchable(_,_) = true 23 | member __.CreateWatchViewer() = 24 | { 25 | new IWatchViewer with 26 | member __.Watch(_,_,_) = () 27 | member __.Control = mkControl() 28 | } 29 | } 30 | 31 | let mkControl = fun () -> new Control() 32 | 33 | [] 34 | let ``SendTo plugin creates watch viewers with absolute count ids`` () = 35 | let plugin = mkPlugin "Plugin" mkControl 36 | let pm = new PluginManager(false) 37 | let mp = pm.RegisterPlugin(plugin) 38 | test <@ pm.SendTo(mp, "", null, typeof).ID = "Plugin 1" @> 39 | test <@ pm.SendTo(mp, "", null, typeof).ID = "Plugin 2" @> 40 | test <@ pm.SendTo(mp, "", null, typeof).ID = "Plugin 3" @> 41 | pm.RemoveManagedWatchViewer("Plugin 3") 42 | test <@ pm.SendTo(mp, "", null, typeof).ID = "Plugin 4" @> 43 | 44 | [] 45 | let ``removing a plugin removes its watch viewers`` () = 46 | let pluginA = mkPlugin "PluginA" mkControl 47 | let pluginB = mkPlugin "PluginB" mkControl 48 | 49 | let pm = new PluginManager(false) 50 | let mpA = pm.RegisterPlugin(pluginA) 51 | let mpB = pm.RegisterPlugin(pluginB) 52 | pm.SendTo(mpA, "", null, typeof) |> ignore 53 | pm.SendTo(mpA, "", null, typeof) |> ignore 54 | pm.SendTo(mpB, "", null, typeof) |> ignore 55 | 56 | pm.RemoveManagedPlugin(mpB) 57 | 58 | test <@ pm.ManagedWatchViewers |> Seq.map (fun x -> x.ID) |> Seq.toList = ["PluginA 1"; "PluginA 2"] @> 59 | 60 | [] 61 | let ``registering a plugin of the same name removes the previous version of the plugin`` () = 62 | let pluginA = mkPlugin "PluginA" mkControl 63 | let pluginB = mkPlugin "PluginB" mkControl 64 | 65 | let pm = new PluginManager(false) 66 | let mpA = pm.RegisterPlugin(pluginA) 67 | let mpB = pm.RegisterPlugin(pluginB) 68 | pm.SendTo(mpA, "", null, typeof) |> ignore 69 | pm.SendTo(mpA, "", null, typeof) |> ignore 70 | pm.SendTo(mpB, "", null, typeof) |> ignore 71 | 72 | let pluginB' = mkPlugin "PluginB" mkControl 73 | let mpB' = pm.RegisterPlugin(pluginB') 74 | 75 | ///i.e. all of the original PluginB watch viewers were removed when the new version of registered 76 | test <@ pm.ManagedWatchViewers |> Seq.map (fun x -> x.ID) |> Seq.toList = ["PluginA 1"; "PluginA 2"] @> 77 | 78 | test <@ pm.ManagedPlugins |> Seq.map (fun x -> x.Plugin) |> Seq.toList = [pluginA; pluginB'] @> 79 | 80 | [] 81 | let ``removing a managed watch viewer disposes of its control`` () = 82 | let control = new Control() 83 | let pluginA = mkPlugin "PluginA" (fun () -> control) 84 | 85 | let pm = new PluginManager(false) 86 | let mpA = pm.RegisterPlugin(pluginA) 87 | let mwv = pm.SendTo(mpA, "", null, typeof) 88 | 89 | test <@ not mwv.WatchViewer.Control.IsDisposed @> 90 | pm.RemoveManagedWatchViewer(mwv) 91 | test <@ mwv.WatchViewer.Control.IsDisposed @> 92 | 93 | [] 94 | let ``ManagedPlugin ManagedWatchViewers filtered correctly`` () = 95 | let pluginA = mkPlugin "PluginA" mkControl 96 | let pluginB = mkPlugin "PluginB" mkControl 97 | 98 | let pm = new PluginManager(false) 99 | let mpA = pm.RegisterPlugin(pluginA) 100 | let mpB = pm.RegisterPlugin(pluginB) 101 | pm.SendTo(mpA, "", null, typeof) |> ignore 102 | pm.SendTo(mpA, "", null, typeof) |> ignore 103 | pm.SendTo(mpB, "", null, typeof) |> ignore 104 | 105 | test <@ mpA.ManagedWatchViewers |> Seq.map (fun x -> x.ID) |> Seq.toList = ["PluginA 1"; "PluginA 2"] @> 106 | test <@ mpB.ManagedWatchViewers |> Seq.map (fun x -> x.ID) |> Seq.toList = ["PluginB 1";] @> 107 | 108 | let dumpAssemblyInfo asm kind = 109 | stdout.WriteLine("{0} assembly location: {1}", kind, asm) 110 | let fi = new System.IO.FileInfo(asm) 111 | let files = 112 | fi.Directory.GetFiles() 113 | //|> Seq.filter (fun x -> x.ToString().EndsWith(".dll")) 114 | |> Seq.fold (fun acc x -> acc + " " + x.ToString()) "" 115 | stdout.WriteLine("{0} assembly files: {1}", kind, files) 116 | 117 | [] 118 | let ``PluginManager finds plugins with scanForPlugins, the default, true`` () = 119 | let pm = new PluginManager() 120 | dumpAssemblyInfo (Assembly.GetExecutingAssembly().Location) "executing" 121 | dumpAssemblyInfo (Assembly.GetCallingAssembly().Location) "calling" 122 | 123 | //we assert that the plugins referenced in this project (the out-of-the-box plugins we provided) are available 124 | test <@ pm.ManagedPlugins |> Seq.length = 3 @> 125 | test <@ pm.ManagedPlugins |> Seq.exists (fun x -> x.Plugin.GetType() = typeof) @> 126 | test <@ pm.ManagedPlugins |> Seq.exists (fun x -> x.Plugin.GetType() = typeof) @> 127 | test <@ pm.ManagedPlugins |> Seq.exists (fun x -> x.Plugin.GetType() = typeof) @> -------------------------------------------------------------------------------- /Test.FsEye/Test.FsEye.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {5f774bcf-2bed-428e-b2f5-2165eb7862e0} 9 | Library 10 | Test.FsEye 11 | Test.FsEye 12 | v4.0 13 | Test.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\Test.FsEye.XML 26 | 27 | 28 | pdbonly 29 | true 30 | true 31 | bin\Release\ 32 | TRACE 33 | 3 34 | bin\Release\Test.FsEye.XML 35 | 36 | 37 | true 38 | full 39 | false 40 | true 41 | bin\Debug\ 42 | DEBUG;TRACE 43 | 3 44 | bin\Debug\Test.FsEye.XML 45 | x86 46 | 47 | 48 | pdbonly 49 | true 50 | true 51 | bin\Release\ 52 | TRACE 53 | 3 54 | bin\Release\Test.FsEye.XML 55 | x86 56 | 57 | 58 | 59 | ..\packages\ImpromptuInterface.5.6.7\lib\net40\ImpromptuInterface.dll 60 | True 61 | 62 | 63 | ..\packages\ImpromptuInterface.FSharp.1.1.0\lib\net40\ImpromptuInterface.FSharp.dll 64 | True 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ..\packages\Unquote.2.2.2\lib\net40\Unquote.dll 76 | True 77 | 78 | 79 | ..\packages\xunit.1.9.1\lib\net20\xunit.dll 80 | True 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | FsEye.DataGridView.Plugin 94 | {cc563dcc-2eaf-4822-b4ce-9d80acca6b5e} 95 | True 96 | 97 | 98 | FsEye.PropertyGrid.Plugin 99 | {1f9b8cb8-8c0c-424c-9035-6562b637a233} 100 | True 101 | 102 | 103 | FsEye.TreeView.Plugin 104 | {669bc59d-958a-4096-affe-7776cf68b46e} 105 | True 106 | 107 | 108 | FsEye 109 | {20de2466-d7b1-4f72-b8f8-51f10f5f186e} 110 | True 111 | 112 | 113 | Utils 114 | {46b72fed-e4b4-4ce3-ae7b-97e43837ba2b} 115 | True 116 | 117 | 118 | 119 | 120 | 121 | 128 | -------------------------------------------------------------------------------- /Test.FsEye/WatchModelTests.fs: -------------------------------------------------------------------------------- 1 | module WatchModelTests 2 | 3 | open Swensen.Unquote 4 | open Xunit 5 | 6 | open Swensen.FsEye.WatchModel 7 | 8 | open Swensen.Utils 9 | 10 | ///Create a root watch, fast 11 | let watch x = createRootWatch "x" x null 12 | let findChildByName memberInfoName (w:Watch) = 13 | w.Children 14 | |> Seq.find (fun x -> x.MemberInfo |> function Some(mi) -> mi.Name = memberInfoName | _ -> false) 15 | 16 | let tryFindChildByName memberInfoName (w:Watch) = 17 | w.Children 18 | |> Seq.tryFind (fun x -> x.MemberInfo |> function Some(mi) -> mi.Name = memberInfoName | _ -> false) 19 | 20 | ///Get the last element of a sequence with 1 or more elements 21 | let last xs = xs |> Seq.skip ((xs |> Seq.length) - 1) |> Seq.head 22 | 23 | [] 24 | let ``Issue 13 Fix: GetEnumerator may be called multiple times without exhaustion bug fix`` () = 25 | let w = watch {1..100} |> findChildByName "GetEnumerator" 26 | test <@ w.Children |> Seq.length = 100 @> 27 | test <@ w.Children |> Seq.isEmpty |> not @> 28 | 29 | [] 30 | let ``sequence with less than or equal to 100 elements has no Rest sub node`` () = 31 | let w = watch {1..100} |> findChildByName "GetEnumerator" 32 | 33 | test <@ w.Children |> Seq.length = 100 @> 34 | test <@ (w.Children |> last).DefaultText <> "Rest" @> 35 | 36 | [] 37 | let ``sequence with 101 elements has Rest node in 101st position which itself contains the remaining last element node nodes`` () = 38 | let w = watch {1..101} |> findChildByName "GetEnumerator" 39 | 40 | test <@ w.Children |> Seq.length = 101 @> 41 | test <@ (w.Children |> last).DefaultText = "Rest" @> 42 | test <@ (w.Children |> last).Children |> Seq.length = 1 @> 43 | 44 | [] 45 | let ``sequence with 110 elements has Rest node in 101st position which itself contains the remaining 10 element nodes`` () = 46 | let w = watch {1..110} |> findChildByName "GetEnumerator" 47 | 48 | test <@ w.Children |> Seq.length = 101 @> 49 | test <@ (w.Children |> last).DefaultText = "Rest" @> 50 | test <@ (w.Children |> last).Children |> Seq.length = 10 @> 51 | 52 | [] 53 | let ``infinate sequences are handled lazily without problem`` () = 54 | let w = watch (Seq.initInfinite id) |> findChildByName "GetEnumerator" 55 | 56 | test <@ w.Children |> Seq.length = 101 @> 57 | test <@ (((w.Children |> last).Children |> last).Children |> last).DefaultText = "Rest" @> 58 | 59 | [] 60 | let ``child watches are ordered by case-insensitive name`` () = 61 | let w = watch [1..5] 62 | let getWatchNames (w:Watch) = w.Children |> Seq.choose (fun x -> x.MemberInfo) |> Seq.map (fun x -> x.Name) |> Seq.toList 63 | let publicWatchNames = getWatchNames w 64 | let nonPublicWatchNames = w.Children |> Seq.head |> getWatchNames 65 | test <@ publicWatchNames = (publicWatchNames |> List.sortBy (fun x -> x.ToLower())) @> 66 | test <@ nonPublicWatchNames = (nonPublicWatchNames |> List.sortBy (fun x -> x.ToLower())) @> 67 | 68 | open System.Collections 69 | open System.Collections.Generic 70 | [] 71 | let ``when there is a conflict, the generic most member is displayed``() = 72 | let xl = [1..5] 73 | test <@ box xl :? IEnumerable @> 74 | test <@ box xl :? IEnumerable @> 75 | 76 | let w = watch [1..5] |> findChildByName "GetEnumerator" 77 | test <@ w.MemberInfo.Value.DeclaringType = typeof> @> 78 | 79 | [] 80 | let ``when not overriden the member is qualified by the base class name``() = 81 | let w = watch (fun () -> ()) |> findChildByName "ToString" 82 | test <@ w.MemberInfo.Value.DeclaringType = typeof @> 83 | test <@ w.DefaultText.StartsWith("obj.ToString()") @> 84 | 85 | [] 86 | let ``when overriden the member is not qualified``() = 87 | let w = watch [1..5] |> findChildByName "ToString" 88 | test <@ w.MemberInfo.Value.DeclaringType = typeof @> 89 | test <@ w.DefaultText.StartsWith("ToString()") @> 90 | 91 | open System.Diagnostics 92 | type Foo = 93 | [] 94 | val mutable public showField : int 95 | 96 | [] 97 | [] 98 | val mutable public hideField : int 99 | 100 | new() = {} 101 | 102 | member public __.ShowProperty = () 103 | [] 104 | member public __.HideProperty = () 105 | 106 | [] 107 | let ``do not show properties marked with DebuggerBrowsableState.Never attribute``() = 108 | let foo = Foo() 109 | 110 | let hideProperty = watch foo |> tryFindChildByName "HideProperty" 111 | test <@ hideProperty.IsNone @> 112 | 113 | [] 114 | let ``do not show fields marked with DebuggerBrowsableState.Never attribute``() = 115 | let foo = Foo() 116 | 117 | let hideField = watch foo |> tryFindChildByName "hideField" 118 | test <@ hideField.IsNone @> 119 | 120 | [] 121 | let ``Issue 19: Crashes when trying to see details of WebBrowser``() = 122 | let w = watch (new System.Windows.Forms.WebBrowser()) 123 | test <@ w.Children |> Seq.iter ignore ; true @> -------------------------------------------------------------------------------- /Test.FsEye/WatchTreeViewLabelCalculatorTests.fs: -------------------------------------------------------------------------------- 1 | module WatchTreeViewLabelCalculatorTests 2 | 3 | open Swensen.Unquote 4 | open Xunit 5 | 6 | open Swensen.FsEye.WatchModel 7 | open Swensen.FsEye.Forms 8 | open Swensen.FsEye 9 | 10 | open Swensen.Utils 11 | open System.Windows.Forms 12 | 13 | open System 14 | open System.Reflection 15 | open ImpromptuInterface.FSharp 16 | 17 | ///Parses the path to find a tree node in the Tree, expanding nodes as needed. Path has form: 18 | ///root/child1/child2/child3 where t%he root and each child is a text starts with match 19 | let findTreeNode (tree:TreeView) (path:string) = 20 | let parts = path.Split('/') |> Seq.toList 21 | 22 | let tryFind (nodes:TreeNodeCollection) p = nodes |> Seq.cast |> Seq.tryFind (fun x -> x.Text.StartsWith(p)) 23 | 24 | let rec loop tn parts = 25 | match parts with 26 | | [] -> tn 27 | | p::parts' when tn = null -> //the root 28 | match tryFind tree.Nodes p with 29 | | Some(tn') -> loop tn' parts' 30 | | None -> failwith "unexpected condition" 31 | | p::parts' -> 32 | //expand tn nodes in another thread so thread sleeping on this thread doesn't block loading work 33 | async { 34 | do! Async.SwitchToNewThread() 35 | tree?OnAfterExpand(new TreeViewEventArgs(tn)) 36 | } |> Async.RunSynchronously 37 | 38 | let rec waitLoop = function 39 | | None -> 40 | System.Threading.Thread.Sleep(120) 41 | waitLoop (tryFind tn.Nodes p) 42 | | Some(tn') -> tn' 43 | 44 | let tn' = waitLoop None 45 | loop tn' parts' 46 | 47 | loop null parts 48 | 49 | [] 50 | let ``watch label: root watch`` () = 51 | let tree = new WatchTreeView() 52 | tree.Watch("watch", [1;2;3;4;5]) 53 | let tn = tree.Nodes.[0] 54 | test <@ tree?calcNodeLabel(tn) = "watch" @> 55 | 56 | [] 57 | let ``watch label: non virtual method`` () = 58 | let tree = new WatchTreeView() 59 | tree.Watch("watch", ResizeArray({1..5})) 60 | let tn = findTreeNode tree "watch/ToArray()" 61 | test <@ tree?calcNodeLabel(tn) = "watch.ToArray()" @> 62 | 63 | [] 64 | let ``watch label: base class virtual method`` () = 65 | let tree = new WatchTreeView() 66 | tree.Watch("watch", ResizeArray({1..5})) 67 | let tn = findTreeNode tree "watch/obj.ToString()" 68 | test <@ tree?calcNodeLabel(tn) = "watch.ToString()" @> 69 | 70 | [] 71 | let ``watch label: non virtual property`` () = 72 | let tree = new WatchTreeView() 73 | tree.Watch("watch", ResizeArray({1..5})) 74 | let tn = findTreeNode tree "watch/Count" 75 | test <@ tree?calcNodeLabel(tn) = "watch.Count" @> 76 | 77 | [] 78 | let ``watch label: interface virtual property`` () = 79 | let tree = new WatchTreeView() 80 | tree.Watch("watch", ResizeArray({1..5})) 81 | let tn = findTreeNode tree "watch/IList.IsFixedSize" 82 | test <@ tree?calcNodeLabel(tn) = "(watch :> IList).IsFixedSize" @> 83 | 84 | [] 85 | let ``watch label: non virtual method of interface virtual property`` () = 86 | let tree = new WatchTreeView() 87 | tree.Watch("watch", ResizeArray({1..5})) 88 | let tn = findTreeNode tree "watch/IList.IsFixedSize/GetHashCode()" 89 | test <@ tree?calcNodeLabel(tn) = "(watch :> IList).IsFixedSize.GetHashCode()" @> 90 | 91 | [] 92 | let ``watch label: enumerator element`` () = 93 | let tree = new WatchTreeView() 94 | tree.Watch("watch", ResizeArray({1..5})) 95 | let tn = findTreeNode tree "watch/Non-public/obj.MemberwiseClone()" 96 | test <@ tree?calcNodeLabel(tn) = "watch?MemberwiseClone()" @> 97 | 98 | [] 99 | let ``watch label: enumerator rest element`` () = 100 | let tree = new WatchTreeView() 101 | tree.Watch("watch", ResizeArray({1..110})) 102 | let tn = findTreeNode tree "watch/GetEnumerator()/Rest/[103]" 103 | test <@ tree?calcNodeLabel(tn) = "watch.GetEnumerator().[103]" @> 104 | 105 | [] 106 | let ``watch label: private method`` () = 107 | let tree = new WatchTreeView() 108 | tree.Watch("watch", ResizeArray({1..110})) 109 | let tn = findTreeNode tree "watch/GetEnumerator()/Rest/[103]" 110 | test <@ tree?calcNodeLabel(tn) = "watch.GetEnumerator().[103]" @> 111 | 112 | [] 113 | let ``watch label: archive`` () = 114 | let tree = new WatchTreeView() 115 | tree.Watch("watch", ResizeArray({1..5})) 116 | tree.Archive() 117 | let tn = findTreeNode tree "Archive (0)/watch" 118 | test <@ tree?calcNodeLabel(tn) = "[Archive (0)] watch" @> -------------------------------------------------------------------------------- /Test.FsEye/WatchTreeViewTests.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 WatchTreeViewTests 17 | 18 | open Swensen.Unquote 19 | open Xunit 20 | 21 | open Swensen.FsEye.WatchModel 22 | open Swensen.FsEye.Forms 23 | 24 | open Swensen.Utils 25 | open System.Windows.Forms 26 | 27 | [] 28 | let ``calling Watch with new name adds a node`` () = 29 | let tree = new WatchTreeView() 30 | tree.Watch("w1", 1) 31 | 32 | test <@ tree.Nodes.Count = 1 @> 33 | test <@ tree.Nodes.Find("w1", false) <> null @> 34 | 35 | [] 36 | let ``calling Watch two times with different names creates two nodes`` () = 37 | let tree = new WatchTreeView() 38 | tree.Watch("w1", 1) 39 | tree.Watch("w2", 2) 40 | 41 | test <@ tree.Nodes.Count = 2 @> 42 | test <@ tree.Nodes.Find("w1", false).Length = 1 @> 43 | test <@ tree.Nodes.Find("w2", false).Length = 1 @> 44 | 45 | let asRoot (tn:TreeNode) = 46 | match tn.Tag with 47 | | :? Watch as w -> 48 | match w with 49 | | Root(info) -> info 50 | | _ -> failwith "Watch is not a Root" 51 | | _ -> failwith "TreeNode is not a Watch" 52 | 53 | [] 54 | let ``calling Watch with an existing name and different value replaces previous watch node`` () = 55 | let tree = new WatchTreeView() 56 | tree.Watch("w1", 1) 57 | tree.Watch("w1", 2) 58 | 59 | test <@ tree.Nodes.Count = 1 @> 60 | test <@ tree.Nodes.Find("w1", false).Length = 1 @> 61 | test <@ ((tree.Nodes.Find("w1", false).[0] |> asRoot).ValueInfo.Value :?> int) = 2 @> 62 | 63 | [] 64 | let ``calling Watch with an existing name and same reference does nothing`` () = 65 | let tree = new WatchTreeView() 66 | let value = "hello" 67 | tree.Watch("w1", value) 68 | tree.Watch("w1", value) 69 | 70 | test <@ tree.Nodes.Count = 1 @> 71 | test <@ tree.Nodes.Find("w1", false).Length = 1 @> 72 | test <@ ((tree.Nodes.Find("w1", false).[0] |> asRoot).ValueInfo.Value :?> string) =& value @> 73 | 74 | [] 75 | let ``create empty Archive with explicit label`` () = 76 | let tree = new WatchTreeView() 77 | let label = "Archive with label" 78 | tree.Archive(label) 79 | 80 | test <@ tree.Nodes.Count = 1 @> 81 | test <@ tree.Nodes.[0].Text = label @> 82 | 83 | [] 84 | let ``create mutliple empty Archives with same explicit label`` () = 85 | let tree = new WatchTreeView() 86 | let label = "Archive with label" 87 | tree.Archive(label) 88 | tree.Archive(label) 89 | 90 | test <@ tree.Nodes.Count = 2 @> 91 | test <@ tree.Nodes.[0].Text = label @> 92 | test <@ tree.Nodes.[1].Text = label @> 93 | 94 | [] 95 | let ``create empty archives with auto increment default labels`` () = 96 | let tree = new WatchTreeView() 97 | tree.Archive() 98 | tree.Archive() 99 | 100 | test <@ tree.Nodes.Count = 2 @> 101 | test <@ tree.Nodes.[0].Text = "Archive (0)" @> 102 | test <@ tree.Nodes.[1].Text = "Archive (1)" @> 103 | 104 | [] 105 | let ``Archive two watches removes watches from root and puts them under new Archive node`` () = 106 | let tree = new WatchTreeView() 107 | let w1, w2 = "w1", "w2" 108 | tree.Watch(w1, 1) 109 | tree.Watch(w2, 2) 110 | let label = "Archive with label" 111 | tree.Archive(label) 112 | 113 | test <@ tree.Nodes.Count = 1 @> 114 | test <@ tree.Nodes.[0].Nodes.Count = 2 @> 115 | test <@ (tree.Nodes.[0].Nodes.[0] |> asRoot).Name = "w1" @> 116 | test <@ (tree.Nodes.[0].Nodes.[1] |> asRoot).Name = "w2" @> 117 | 118 | 119 | [] 120 | let ``Archive does not archive archives`` () = 121 | let tree = new WatchTreeView() 122 | let w1, w2, w3 = "w1", "w2", "w3" 123 | tree.Watch(w1, 1) 124 | tree.Watch(w2, 2) 125 | tree.Archive() 126 | tree.Watch(w3, 3) 127 | tree.Archive() 128 | 129 | test <@ tree.Nodes.Count = 2 @> 130 | test <@ tree.Nodes.[0].Text = "Archive (0)" @> 131 | test <@ tree.Nodes.[1].Text = "Archive (1)" @> 132 | 133 | ///watch, watch, archive, watch, archive, watch, watch 134 | let mockForClear() = 135 | let tree = new WatchTreeView() 136 | let w1, w2, w3, w4, w5 = "w1", "w2", "w3", "w4", "w5" 137 | tree.Watch(w1, 1) 138 | tree.Watch(w2, 2) 139 | tree.Archive() 140 | tree.Watch(w3, 3) 141 | tree.Archive() 142 | tree.Watch(w4, 4) 143 | tree.Watch(w5, 5) 144 | tree 145 | 146 | [] 147 | let ``ClearArchives clears Archives but not Watches`` () = 148 | let tree = mockForClear() 149 | tree.ClearArchives() 150 | 151 | test <@ tree.Nodes.Count = 2 @> 152 | test <@ tree.Nodes.[0].Tag :? Watch @> 153 | test <@ tree.Nodes.[1].Tag :? Watch @> 154 | 155 | [] 156 | let ``ClearWatches clears Watches but not Archives`` () = 157 | let tree = mockForClear() 158 | tree.ClearWatches() 159 | 160 | test <@ tree.Nodes.Count = 2 @> 161 | test <@ tree.Nodes.[0].Text = "Archive (0)" @> 162 | test <@ tree.Nodes.[1].Text = "Archive (1)" @> 163 | 164 | [] 165 | let ``ClearAll clears all watches and archives`` () = 166 | let tree = mockForClear() 167 | tree.ClearAll() 168 | 169 | test <@ tree.Nodes.Count = 0 @> 170 | 171 | [] 172 | let ``ClearAll resets archive count for default archive label`` () = 173 | let tree = new WatchTreeView() 174 | tree.Archive() 175 | tree.Archive() 176 | tree.Archive() 177 | tree.ClearAll() 178 | tree.Archive() 179 | 180 | test <@ tree.Nodes.Count = 1 @> 181 | test <@ tree.Nodes.[0].Text = "Archive (0)" @> 182 | 183 | [] 184 | let ``ClearArchives resets archive count for default archive label`` () = 185 | let tree = new WatchTreeView() 186 | tree.Archive() 187 | tree.Archive() 188 | tree.Archive() 189 | tree.ClearArchives() 190 | tree.Archive() 191 | 192 | test <@ tree.Nodes.Count = 1 @> 193 | test <@ tree.Nodes.[0].Text = "Archive (0)" @> 194 | 195 | let dummyNodeText = "dummy" 196 | 197 | [] 198 | let ``new Watch initially adds dummy child node for lazy loading`` () = 199 | let tree = new WatchTreeView() 200 | tree.Watch("watch", 1) 201 | 202 | test <@ tree.Nodes.[0].Nodes.Count = 1 @> 203 | test <@ tree.Nodes.[0].Nodes.[0].Text = dummyNodeText @> 204 | 205 | open ImpromptuInterface.FSharp 206 | 207 | [] 208 | let ``after expanded, dummy watch child replaced with real children`` () = 209 | let tree = new WatchTreeView() 210 | tree.Watch("watch", [1;2;3;4;5]) 211 | 212 | tree?OnAfterSelect(new TreeViewEventArgs(tree.Nodes.[0])) 213 | tree?OnAfterExpand(new TreeViewEventArgs(tree.Nodes.[0])) 214 | 215 | test <@ tree.Nodes.[0].Nodes.Count >= 1 @> 216 | test <@ tree.Nodes.[0].Nodes.[0].Text <> dummyNodeText @> 217 | test <@ tree.Nodes.[0].Nodes |> Seq.cast |> Seq.forall (fun tn -> tn.Tag :? Watch) @> 218 | 219 | -------------------------------------------------------------------------------- /Test.FsEye/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Utils/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.Utils 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 | [] 34 | 35 | // Setting ComVisible to false makes the types in this assembly not visible 36 | // to COM components. If you need to access a type in this assembly from 37 | // COM, set the ComVisible attribute to true on that type. 38 | [] 39 | 40 | // Version information for an assembly consists of the following four values: 41 | // 42 | // Major Version 43 | // Minor Version 44 | // Build Number 45 | // Revision 46 | // 47 | // You can specify all the values or you can default the Build and Revision Numbers 48 | // by using the '*' as shown below: 49 | // [assembly: AssemblyVersion("1.0.*")] 50 | [] //the fourth position is for beta release numbers 51 | [] 52 | 53 | () 54 | -------------------------------------------------------------------------------- /Utils/ControlExt.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 | 17 | namespace Swensen.Utils 18 | 19 | module Control = 20 | let inline update< ^a when ^a :(member BeginUpdate: unit -> unit) and ^a : (member EndUpdate: unit -> unit)> (this: ^a) f = 21 | (^a : (member BeginUpdate: unit -> unit) (this)) 22 | f() 23 | (^a : (member EndUpdate: unit -> unit) (this)) -------------------------------------------------------------------------------- /Utils/MiscUtils.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 | [] 17 | module Swensen.Utils.MiscUtils 18 | 19 | ///Reference comparison 20 | let inline (=&) a b = obj.ReferenceEquals(a,b) 21 | 22 | ///Not reference equals 23 | let inline (<>&) a b = a =& b |> not 24 | -------------------------------------------------------------------------------- /Utils/RegexUtils.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 | 17 | //note: some of this code was collaboratively developed with the folks in the FSharpx project, and has 18 | //founds its way as a contribution to that project. 19 | 20 | namespace Swensen.Utils 21 | open System.Text.RegularExpressions 22 | 23 | ///Regex extensions 24 | module internal Regex = 25 | type ActiveMatch = 26 | { 27 | Match: Match 28 | MatchValue: string 29 | Groups: Group list 30 | OptionalGroups: (Group option) list 31 | GroupValues: string list 32 | OptionalGroupValues: (string option) list 33 | } 34 | 35 | /// 36 | ///Test an input string against a regex pattern using the given RegexOptions flags. 37 | ///If the match succeeds, returns an ActiveMatch instance, which can be used for further pattern matching. 38 | ///Note that the implementation takes advantage of the .NET Regex cache. 39 | /// 40 | /// 41 | ///The first argument allows you pass in RegexOptions flags. 42 | /// 43 | /// 44 | ///The second argument is the regex pattern. Cannot be null. 45 | /// 46 | /// 47 | ///The last argument is the input string to test. The input 48 | ///may be null which would result in a no-match. 49 | /// 50 | let (|Match|_|) flags pattern input = 51 | match input with 52 | | null -> None //Regex.Match will throw with null input, we return None instead 53 | | _ -> 54 | //using the static Regex.Match takes advantage of Regex caching 55 | match Regex.Match(input, pattern, flags) with 56 | | m when m.Success -> 57 | //n.b. the head value of m.Groups is the match itself, which we discard 58 | //n.b. if a group is optional and doesn't match, it's Value is "" 59 | let groups = [for x in m.Groups -> x].Tail 60 | let optionalGroups = groups |> List.map (fun x -> if x.Success then Some(x) else None) 61 | let groupValues = groups |> List.map (fun x -> x.Value) 62 | let optionalGroupValues = optionalGroups |> List.map (function None -> None | Some(x) -> Some(x.Value)) 63 | 64 | Some({ Match=m 65 | MatchValue=m.Value 66 | Groups=groups 67 | OptionalGroups=optionalGroups 68 | GroupValues=groupValues 69 | OptionalGroupValues=optionalGroupValues }) 70 | | _ -> None 71 | 72 | ///Convenience versions of our regex active patterns using RegexOptions.Compiled flag. 73 | ///If SILVERLIGHT compiler directive defined, then RegexOptions.None flag used. 74 | module Compiled = 75 | ///When silverlight mode is None, else is Compiled 76 | let private compiledRegexOption = 77 | #if SILVERLIGHT 78 | RegexOptions.None 79 | #else 80 | RegexOptions.Compiled 81 | #endif 82 | 83 | let (|Match|_|) = (|Match|_|) compiledRegexOption 84 | 85 | ///Convenience versions of our regex active patterns using RegexOptions.None flag 86 | module Interpreted = 87 | let (|Match|_|) = (|Match|_|) RegexOptions.None -------------------------------------------------------------------------------- /Utils/SeqExt.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 | 17 | namespace Swensen.Utils 18 | 19 | module internal Seq = 20 | let distinctByResolve primaryKey resolveCollision values = seq { //make lazy seq? (YES, Seq.distinct does) 21 | let cache = System.Collections.Generic.Dictionary<_,_>(HashIdentity.Structural) 22 | for canidateValue in values do 23 | let key = primaryKey canidateValue 24 | match cache.TryGetValue(key) with 25 | | true, existingValue -> //collision 26 | match resolveCollision existingValue canidateValue with 27 | | x when x >= 0 -> () //if existing equal or greater, keep it 28 | | _ -> //canidate key wins in collision resolution 29 | cache.Remove(key) |> ignore 30 | cache.Add(key,canidateValue) 31 | | false, _ -> 32 | cache.Add(key, canidateValue) 33 | 34 | yield! cache.Values } 35 | 36 | let partition condition values = 37 | let pairs = seq { 38 | for i in values do 39 | if condition i then 40 | yield Some(i), None 41 | else 42 | yield None, Some(i) } 43 | 44 | pairs |> Seq.choose fst, pairs |> Seq.choose snd 45 | -------------------------------------------------------------------------------- /Utils/TypeExt.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 | 17 | [] 18 | module internal Swensen.Utils.TypeExt 19 | 20 | open System 21 | open System.Reflection 22 | 23 | ///Implementation taken (copied and pasted!) from Unquote 24 | module internal Impl = 25 | ///get the source name for the Module or F# Function represented by the given MemberInfo 26 | let sourceName (mi:MemberInfo) = 27 | mi.GetCustomAttributes(true) 28 | |> Array.tryPick 29 | (function 30 | | :? CompilationSourceNameAttribute as csna -> Some(csna.SourceName) 31 | | :? CompilationRepresentationAttribute as cra -> 32 | //seems sufficient, but may not be as robust as FSharpEntity.DisplayName 33 | if cra.Flags = CompilationRepresentationFlags.ModuleSuffix then 34 | Some(mi.Name.Substring(0, mi.Name.Length - 6)) 35 | else 36 | None 37 | | _ -> None) 38 | |> (function | Some(sourceName) -> sourceName | None -> mi.Name) 39 | 40 | let inline private applyParensForPrecInContext context prec s = if prec > context then s else sprintf "(%s)" s 41 | 42 | //the usefullness of this function makes me think to open up Sprint module (currently just added TypeExt with this feature) 43 | ///Sprint the F#-style type signature of the given Type. Handles known type abbreviations, 44 | ///simple types, arbitrarily complex generic types (multiple parameters and nesting), 45 | ///lambdas, tuples, and arrays. 46 | let sprintSig (outerTy:Type) = 47 | //list of F# type abbrs: http://207.46.16.248/en-us/library/ee353649.aspx 48 | ///Get the type abbr name or short name from the "clean" name 49 | let displayName = function 50 | | "System.Object" -> "obj" 51 | | "System.String" -> "string" 52 | | "System.Char" -> "char" 53 | | "System.Boolean" -> "bool" 54 | | "System.Decimal" -> "decimal" 55 | 56 | | "System.Int16" -> "int16" 57 | | "System.Int32" -> "int"//int32 58 | | "System.Int64" -> "int64" 59 | 60 | | "System.UInt16" -> "uint16" 61 | | "System.UInt32" -> "uint32" 62 | | "System.UInt64" -> "uint64" 63 | 64 | | "System.Single" -> "float32"//single 65 | | "System.Double" -> "float"//double 66 | 67 | | "System.Byte" -> "byte"//uint8 68 | | "System.SByte" -> "sbyte"//int8 69 | 70 | | "System.IntPtr" -> "nativeint" 71 | | "System.UIntPtr" -> "unativeint" 72 | 73 | | "System.Numerics.BigInteger" -> "bigint" 74 | | "Microsoft.FSharp.Core.Unit" -> "unit" 75 | | "Microsoft.FSharp.Math.BigRational" -> "BigNum" 76 | | "Microsoft.FSharp.Core.FSharpRef" -> "ref" 77 | | "Microsoft.FSharp.Core.FSharpOption" -> "option" 78 | | "Microsoft.FSharp.Collections.FSharpList" -> "list" 79 | | "Microsoft.FSharp.Collections.FSharpMap" -> "Map" 80 | | "System.Collections.Generic.IEnumerable" -> "seq" 81 | | Regex.Compiled.Match @"[\.\+]?([^\.\+]*)$" { GroupValues=[name] }-> name //short name 82 | | cleanName -> failwith "failed to lookup type display name from it's \"clean\" name: " + cleanName 83 | 84 | let rec sprintSig context (ty:Type) = 85 | let applyParens = applyParensForPrecInContext context 86 | let cleanName, arrSig = 87 | //if is generic type, then doesn't have FullName, need to use just Name 88 | match (if String.IsNullOrEmpty(ty.FullName) then ty.Name else ty.FullName) with 89 | | Regex.Compiled.Match @"^([^`\[]*)`?.*?(\[[\[\],]*\])?$" { GroupValues=[cleanName;arrSig] }-> //long name type encoding left of `, array encoding at end 90 | cleanName, arrSig 91 | | _ -> 92 | failwith ("failed to parse type name: " + ty.FullName) 93 | 94 | match ty.GetGenericArguments() with 95 | | args when args.Length = 0 -> 96 | (if outerTy.IsGenericTypeDefinition then "'" else "") + (displayName cleanName) + arrSig 97 | | args when cleanName = "System.Tuple" -> 98 | (applyParens (if arrSig.Length > 0 then 0 else 3) (sprintf "%s" (args |> Array.map (sprintSig 3) |> String.concat " * "))) + arrSig 99 | | [|lhs;rhs|] when cleanName = "Microsoft.FSharp.Core.FSharpFunc" -> //right assoc, binding not as strong as tuples 100 | (applyParens (if arrSig.Length > 0 then 0 else 2) (sprintf "%s -> %s" (sprintSig 2 lhs) (sprintSig 1 rhs))) + arrSig 101 | | args -> 102 | sprintf "%s<%s>%s" (displayName cleanName) (args |> Array.map (sprintSig 1) |> String.concat ", ") arrSig 103 | 104 | sprintSig 0 outerTy 105 | 106 | type System.Type with 107 | ///The F#-style signature. Note: this property is out-of-place in this assembly and may be moved elsewhere in future versions. 108 | member this.FSharpName = 109 | Impl.sprintSig this -------------------------------------------------------------------------------- /Utils/Utils.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {46b72fed-e4b4-4ce3-ae7b-97e43837ba2b} 9 | Library 10 | Utils 11 | Utils 12 | v4.0 13 | Utils 14 | ..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | true 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | 3 25 | bin\Debug\Utils.xml 26 | 27 | 28 | pdbonly 29 | true 30 | true 31 | bin\Release\ 32 | TRACE 33 | 3 34 | bin\Release\Utils.xml 35 | 36 | 37 | true 38 | full 39 | false 40 | true 41 | bin\Debug\ 42 | DEBUG;TRACE 43 | 3 44 | bin\Debug\Utils.xml 45 | x86 46 | 47 | 48 | pdbonly 49 | true 50 | true 51 | bin\Release\ 52 | TRACE 53 | 3 54 | bin\Release\Utils.xml 55 | x86 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 82 | -------------------------------------------------------------------------------- /package.bat: -------------------------------------------------------------------------------- 1 | set /p versionNumber= 2 | 3 | REM clean up 4 | del builds\FsEye-%versionNumber%.zip 5 | del builds\FsEye.%versionNumber%.nupkg 6 | rd /q /s builds\FsEye-%versionNumber% 7 | rd /q /s builds\FsEye 8 | 9 | REM preparing staging dir 10 | mkdir staging 11 | mkdir staging\plugins 12 | 13 | copy LICENSE staging 14 | copy NOTICE staging 15 | 16 | copy FsEye\bin\Release\FsEye.fsx staging 17 | copy FsEye\bin\Release\FsEye.dll staging 18 | copy FsEye\bin\Release\FsEye.xml staging 19 | 20 | copy "FsEye.PropertyGrid.Plugin\bin\Release\FsEye.PropertyGrid.Plugin.dll" staging\plugins 21 | copy "FsEye.TreeView.Plugin\bin\Release\FsEye.TreeView.Plugin.dll" staging\plugins 22 | copy "FsEye.DataGridView.Plugin\bin\Release\FsEye.DataGridView.Plugin.dll" staging\plugins 23 | 24 | REM zip staging files 25 | cd staging 26 | "..\tools\7z\7za.exe" a -tzip "..\builds\FsEye-%versionNumber%.zip" * 27 | cd .. 28 | 29 | REM extract build 30 | "tools\7z\7za.exe" x "builds\FsEye-%versionNumber%.zip" -o"builds\FsEye-%versionNumber%" 31 | "tools\7z\7za.exe" x "builds\FsEye-%versionNumber%.zip" -o"builds\FsEye" 32 | 33 | REM preparing nuget dirs 34 | 35 | mkdir nuget 36 | mkdir nuget\content 37 | mkdir nuget\lib 38 | mkdir nuget\lib\net40 39 | mkdir nuget\lib\net40\plugins 40 | copy FsEye.nuspec nuget 41 | 42 | REM copy staging builds to nuget package... 43 | 44 | copy staging\FsEye.dll nuget\lib\net40\FsEye.dll 45 | copy staging\plugins\* nuget\lib\net40\plugins\ 46 | copy FsEye\bin\Release\FsEye.NuGet.fsx nuget\content\FsEye.fsx 47 | 48 | REM create nuget package... 49 | 50 | ".nuget\nuget.exe" pack nuget\FsEye.nuspec -Version %versionNumber% 51 | copy FsEye.%versionNumber%.nupkg builds 52 | del FsEye.%versionNumber%.nupkg 53 | 54 | REM cleanup... 55 | 56 | rd /q /s staging 57 | rd /q /s nuget 58 | 59 | pause 60 | -------------------------------------------------------------------------------- /tools/7z/7za.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwensenSoftware/fseye/70f2bd92aa471a3b4e2bbe45b69e0010e85df73c/tools/7z/7za.exe -------------------------------------------------------------------------------- /tools/7z/license.txt: -------------------------------------------------------------------------------- 1 | 7-Zip Command line version 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | License for use and distribution 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | 7-Zip Copyright (C) 1999-2010 Igor Pavlov. 7 | 8 | 7za.exe is distributed under the GNU LGPL license 9 | 10 | Notes: 11 | You can use 7-Zip on any computer, including a computer in a commercial 12 | organization. You don't need to register or pay for 7-Zip. 13 | 14 | 15 | GNU LGPL information 16 | -------------------- 17 | 18 | This library is free software; you can redistribute it and/or 19 | modify it under the terms of the GNU Lesser General Public 20 | License as published by the Free Software Foundation; either 21 | version 2.1 of the License, or (at your option) any later version. 22 | 23 | This library is distributed in the hope that it will be useful, 24 | but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 26 | Lesser General Public License for more details. 27 | 28 | You can receive a copy of the GNU Lesser General Public License from 29 | http://www.gnu.org/ 30 | -------------------------------------------------------------------------------- /tools/7z/readme.txt: -------------------------------------------------------------------------------- 1 | 7-Zip Command line version 9.20 2 | ------------------------------- 3 | 4 | 7-Zip is a file archiver with high compression ratio. 5 | 7za.exe is a standalone command line version of 7-Zip. 6 | 7 | 7-Zip Copyright (C) 1999-2010 Igor Pavlov. 8 | 9 | Features of 7za.exe: 10 | - High compression ratio in new 7z format 11 | - Supported formats: 12 | - Packing / unpacking: 7z, xz, ZIP, GZIP, BZIP2 and TAR 13 | - Unpacking only: Z, lzma 14 | - Highest compression ratio for ZIP and GZIP formats. 15 | - Fast compression and decompression 16 | - Strong AES-256 encryption in 7z and ZIP formats. 17 | 18 | 7za.exe is a free software distributed under the GNU LGPL. 19 | Read license.txt for more information. 20 | 21 | Source code of 7za.exe and 7-Zip can be found at 22 | http://www.7-zip.org/ 23 | 24 | 7za.exe can work in Windows 95/98/ME/NT/2000/2003/2008/XP/Vista/7. 25 | 26 | There is also port of 7za.exe for POSIX systems like Unix (Linux, Solaris, OpenBSD, 27 | FreeBSD, Cygwin, AIX, ...), MacOS X and BeOS: 28 | 29 | http://p7zip.sourceforge.net/ 30 | 31 | 32 | This distributive packet contains the following files: 33 | 34 | 7za.exe - 7-Zip standalone command line version. 35 | readme.txt - This file. 36 | license.txt - License information. 37 | 7-zip.chm - User's Manual in HTML Help format. 38 | 39 | 40 | --- 41 | End of document 42 | --------------------------------------------------------------------------------