diff --git a/.editorconfig b/.editorconfig
index 654123bb..6c05deba 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,138 +1,116 @@
-# editorconfig.org
-
-# top-most EditorConfig file
-root = true
-
-# Default settings:
-# A newline ending every file
-# Use 4 spaces as indentation
-[*]
-insert_final_newline = true
-indent_style = space
-indent_size = 4
-
-# C# files
-[*.cs]
-
-# "This." and "Me." Qualification
-dotnet_style_qualification_for_field = false:suggestion
-dotnet_style_qualification_for_property = false:suggestion
-dotnet_style_qualification_for_method = false:suggestion
-dotnet_style_qualification_for_event = false:suggestion
-
-# Language keywords (int, string, etc.) vs framework type names for type references
-dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
-dotnet_style_predefined_type_for_member_access = true:suggestion
-dotnet_style_object_initializer = true:suggestion
-dotnet_style_collection_initializer = true:suggestion
-dotnet_style_explicit_tuple_names = true:warning
-dotnet_style_coalesce_expression = true:suggestion
-dotnet_style_null_propagation true:warning
-
-# "var" and Explicit Types
-csharp_style_var_for_built_in_types = true:suggestion
-csharp_style_var_when_type_is_apparent = true:suggestion
-csharp_style_var_elsewhere = true:suggestion
-
-# Expression-bodied Members
-csharp_style_expression_bodied_methods = false:none
-csharp_style_expression_bodied_constructors = false:none
-csharp_style_expression_bodied_operators = false:none
-csharp_style_expression_bodied_properties = true:none
-csharp_style_expression_bodied_indexers = true:none
-csharp_style_expression_bodied_accessors = true:none
-
-# Pattern matching
-csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
-csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
-csharp_style_inlined_variable_declaration = true:suggestion
-
-# Expression-level Preferences
-csharp_prefer_simple_default_expression = true:suggestion
-
-# "Null" Checking Preferences
-csharp_style_throw_expression = true:suggestion
-csharp_style_conditional_delegate_call = true:suggestion
-
-# Code Block Preferences
-csharp_prefer_braces = false:suggestion
-
-# Organize Usings
-dotnet_sort_system_directives_first = true
-
-# Newline Options
-csharp_new_line_before_open_brace = all
-csharp_new_line_before_else = true
-csharp_new_line_before_catch = true
-csharp_new_line_before_finally = true
-csharp_new_line_before_members_in_object_initializers = true
-csharp_new_line_before_members_in_anonymous_types = true
-csharp_new_line_between_query_expression_clauses = true
-
-# Indentation Options
-csharp_indent_case_contents = true
-csharp_indent_switch_labels = true
-csharp_indent_labels = no_change
-
-# Spacing Options
-csharp_space_after_cast = true
-csharp_space_between_method_declaration_parameter_list_parentheses = false
-csharp_space_between_method_call_parameter_list_parentheses = false
-csharp_space_between_parentheses = false
-
-# Wrapping Options
-csharp_preserve_single_line_statements = false
-csharp_preserve_single_line_blocks = true
-
-#####
-# Naming Convention (Custom)
-#####
-
-## Name all private fields with camelCase
-dotnet_naming_symbols.private_field_symbol.applicable_kinds = field
-dotnet_naming_symbols.private_field_symbol.applicable_accessibilities = private
-dotnet_naming_style.private_field_style.capitalization = camel_case
-dotnet_naming_rule.private_fields_are_camel_case.severity = warning
-dotnet_naming_rule.private_fields_are_camel_case.symbols = private_field_symbol
-dotnet_naming_rule.private_fields_are_camel_case.style = private_field_style
-
-## Name all non-private fields with PascalCase
-dotnet_naming_symbols.non_private_field_symbol.applicable_kinds = field
-dotnet_naming_symbols.non_private_field_symbol.applicable_accessibilities = public,internal,friend,protected,protected_internal,protected_friend
-dotnet_naming_style.non_private_field_style.capitalization = pascal_case
-dotnet_naming_rule.non_private_fields_are_pascal_case.severity = error
-dotnet_naming_rule.non_private_fields_are_pascal_case.symbols = non_private_field_symbol
-dotnet_naming_rule.non_private_fields_are_pascal_case.style = non_private_field_style
-
-## Names of parameters must be CamelCase
-dotnet_naming_symbols.parameter_symbol.applicable_kinds = parameter
-dotnet_naming_style.parameter_style.capitalization = camel_case
-dotnet_naming_rule.parameters_are_camel_case.severity = error
-dotnet_naming_rule.parameters_are_camel_case.symbols = parameter_symbol
-dotnet_naming_rule.parameters_are_camel_case.style = parameter_style
-
-## Non-interface types must use PascalCase
-dotnet_naming_symbols.non_interface_type_symbol.applicable_kinds = class,struct,enum,delegate
-dotnet_naming_style.non_interface_type_style.capitalization = pascal_case
-dotnet_naming_rule.non_interface_types_are_pascal_case.severity = error
-dotnet_naming_rule.non_interface_types_are_pascal_case.symbols = non_interface_type_symbol
-dotnet_naming_rule.non_interface_types_are_pascal_case.style = non_interface_type_style
-
-## Interfaces must use PascalCase and start with a prefix of 'I'
-dotnet_naming_symbols.interface_type_symbol.applicable_kinds = interface
-dotnet_naming_style.interface_type_style.capitalization = pascal_case
-dotnet_naming_style.interface_type_style.required_prefix = I
-dotnet_naming_rule.interface_types_must_be_prefixed_with_I.severity = error
-dotnet_naming_rule.interface_types_must_be_prefixed_with_I.symbols = interface_type_symbol
-dotnet_naming_rule.interface_types_must_be_prefixed_with_I.style = interface_type_style
-
-## Methods, Properties, and Events must use PascalCase
-dotnet_naming_symbols.member_symbol.applicable_kinds = method,property,event
-dotnet_naming_style.member_style.capitalization = pascal_case
-dotnet_naming_rule.members_are_pascal_case.severity = error
-dotnet_naming_rule.members_are_pascal_case.symbols = member_symbol
-dotnet_naming_rule.members_are_pascal_case.style = member_style
-
-# Xml project files
-[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
-indent_size = 2
+# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
+###############################
+# Core EditorConfig Options #
+###############################
+# All files
+[*]
+indent_style = space
+# Code files
+[*.{cs,csx,vb,vbx}]
+indent_size = 4
+insert_final_newline = true
+charset = utf-8-bom
+###############################
+# .NET Coding Conventions #
+###############################
+[*.{cs,vb}]
+# Organize usings
+dotnet_sort_system_directives_first = true
+# this. preferences
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+dotnet_style_readonly_field = true:suggestion
+# Expression-level preferences
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_prefer_inferred_tuple_names = true:suggestion
+dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+###############################
+# Naming Conventions #
+###############################
+# Style Definitions
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+# Use PascalCase for constant fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = warning
+dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
+dotnet_naming_symbols.constant_fields.applicable_kinds = field
+dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
+dotnet_naming_symbols.constant_fields.required_modifiers = const
+###############################
+# C# Coding Conventions #
+###############################
+[*.cs]
+# var preferences
+csharp_style_var_for_built_in_types = true:silent
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = true:silent
+# Expression-bodied members
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+# Pattern matching preferences
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+# Null-checking preferences
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+# Modifier preferences
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
+# Expression-level preferences
+csharp_prefer_braces = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_pattern_local_over_anonymous_function = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+###############################
+# C# Formatting Rules #
+###############################
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+# Indentation preferences
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = flush_left
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+# Wrapping preferences
+csharp_preserve_single_line_statements = true
+csharp_preserve_single_line_blocks = true
diff --git a/BuildVision.sln b/BuildVision.sln
index d7427501..11426b07 100644
--- a/BuildVision.sln
+++ b/BuildVision.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.28307.136
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.28803.202
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Misc", "Misc", "{A8C25F90-8847-4EBE-A66B-02E5D1B42EC0}"
ProjectSection(SolutionItems) = preProject
@@ -19,48 +19,84 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AE904AFF-1B2
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildVision", "src\BuildVision\BuildVision.csproj", "{9925A635-1827-4BB4-9C31-FE0FC87A6265}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildVision.Common", "src\BuildVision.Common\BuildVision.Common.csproj", "{848412D1-95BF-4E56-A9EF-2926AF5C6D67}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildVision.Common", "src\BuildVision.Common\BuildVision.Common.csproj", "{848412D1-95BF-4E56-A9EF-2926AF5C6D67}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildVision.Contracts", "src\BuildVision.Contracts\BuildVision.Contracts.csproj", "{13D64A57-5DB3-4CC7-AC2B-9034E767D754}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildVision.Contracts", "src\BuildVision.Contracts\BuildVision.Contracts.csproj", "{13D64A57-5DB3-4CC7-AC2B-9034E767D754}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildVision.UI", "src\BuildVision.UI\BuildVision.UI.csproj", "{84E8BA65-9A4B-4C50-A115-6EF3208E4058}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1DE272AA-D641-45F2-AEB9-934B3BAA6FBD}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildVision.IntegrationTests", "test\BuildVision.IntegrationTests\BuildVision.IntegrationTests.csproj", "{FBB4F3ED-B1B8-4401-8667-5180194BAA54}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildVision.IntegrationTests", "test\BuildVision.IntegrationTests\BuildVision.IntegrationTests.csproj", "{FBB4F3ED-B1B8-4401-8667-5180194BAA54}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildVision.UnitTests", "test\BuildVision.UnitTests\BuildVision.UnitTests.csproj", "{2A7DE186-A1FA-4BA8-B393-3CA9ECBF444F}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildVision.Exports", "src\BuildVision.Exports\BuildVision.Exports.csproj", "{F16E6593-DDF9-4E9E-A2F8-56A3C43A643E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Marketplace|Any CPU = Marketplace|Any CPU
Release|Any CPU = Release|Any CPU
+ VsixGallery|Any CPU = VsixGallery|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9925A635-1827-4BB4-9C31-FE0FC87A6265}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9925A635-1827-4BB4-9C31-FE0FC87A6265}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9925A635-1827-4BB4-9C31-FE0FC87A6265}.Marketplace|Any CPU.ActiveCfg = Marketplace|Any CPU
+ {9925A635-1827-4BB4-9C31-FE0FC87A6265}.Marketplace|Any CPU.Build.0 = Marketplace|Any CPU
{9925A635-1827-4BB4-9C31-FE0FC87A6265}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9925A635-1827-4BB4-9C31-FE0FC87A6265}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9925A635-1827-4BB4-9C31-FE0FC87A6265}.VsixGallery|Any CPU.ActiveCfg = VsixGallery|Any CPU
+ {9925A635-1827-4BB4-9C31-FE0FC87A6265}.VsixGallery|Any CPU.Build.0 = VsixGallery|Any CPU
{848412D1-95BF-4E56-A9EF-2926AF5C6D67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{848412D1-95BF-4E56-A9EF-2926AF5C6D67}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {848412D1-95BF-4E56-A9EF-2926AF5C6D67}.Marketplace|Any CPU.ActiveCfg = Marketplace|Any CPU
+ {848412D1-95BF-4E56-A9EF-2926AF5C6D67}.Marketplace|Any CPU.Build.0 = Marketplace|Any CPU
{848412D1-95BF-4E56-A9EF-2926AF5C6D67}.Release|Any CPU.ActiveCfg = Release|Any CPU
{848412D1-95BF-4E56-A9EF-2926AF5C6D67}.Release|Any CPU.Build.0 = Release|Any CPU
+ {848412D1-95BF-4E56-A9EF-2926AF5C6D67}.VsixGallery|Any CPU.ActiveCfg = VsixGallery|Any CPU
+ {848412D1-95BF-4E56-A9EF-2926AF5C6D67}.VsixGallery|Any CPU.Build.0 = VsixGallery|Any CPU
{13D64A57-5DB3-4CC7-AC2B-9034E767D754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{13D64A57-5DB3-4CC7-AC2B-9034E767D754}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {13D64A57-5DB3-4CC7-AC2B-9034E767D754}.Marketplace|Any CPU.ActiveCfg = Marketplace|Any CPU
+ {13D64A57-5DB3-4CC7-AC2B-9034E767D754}.Marketplace|Any CPU.Build.0 = Marketplace|Any CPU
{13D64A57-5DB3-4CC7-AC2B-9034E767D754}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13D64A57-5DB3-4CC7-AC2B-9034E767D754}.Release|Any CPU.Build.0 = Release|Any CPU
+ {13D64A57-5DB3-4CC7-AC2B-9034E767D754}.VsixGallery|Any CPU.ActiveCfg = VsixGallery|Any CPU
+ {13D64A57-5DB3-4CC7-AC2B-9034E767D754}.VsixGallery|Any CPU.Build.0 = VsixGallery|Any CPU
{84E8BA65-9A4B-4C50-A115-6EF3208E4058}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84E8BA65-9A4B-4C50-A115-6EF3208E4058}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {84E8BA65-9A4B-4C50-A115-6EF3208E4058}.Marketplace|Any CPU.ActiveCfg = Marketplace|Any CPU
+ {84E8BA65-9A4B-4C50-A115-6EF3208E4058}.Marketplace|Any CPU.Build.0 = Marketplace|Any CPU
{84E8BA65-9A4B-4C50-A115-6EF3208E4058}.Release|Any CPU.ActiveCfg = Release|Any CPU
{84E8BA65-9A4B-4C50-A115-6EF3208E4058}.Release|Any CPU.Build.0 = Release|Any CPU
+ {84E8BA65-9A4B-4C50-A115-6EF3208E4058}.VsixGallery|Any CPU.ActiveCfg = VsixGallery|Any CPU
+ {84E8BA65-9A4B-4C50-A115-6EF3208E4058}.VsixGallery|Any CPU.Build.0 = VsixGallery|Any CPU
{FBB4F3ED-B1B8-4401-8667-5180194BAA54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FBB4F3ED-B1B8-4401-8667-5180194BAA54}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FBB4F3ED-B1B8-4401-8667-5180194BAA54}.Marketplace|Any CPU.ActiveCfg = Marketplace|Any CPU
+ {FBB4F3ED-B1B8-4401-8667-5180194BAA54}.Marketplace|Any CPU.Build.0 = Marketplace|Any CPU
{FBB4F3ED-B1B8-4401-8667-5180194BAA54}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FBB4F3ED-B1B8-4401-8667-5180194BAA54}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FBB4F3ED-B1B8-4401-8667-5180194BAA54}.VsixGallery|Any CPU.ActiveCfg = VsixGallery|Any CPU
+ {FBB4F3ED-B1B8-4401-8667-5180194BAA54}.VsixGallery|Any CPU.Build.0 = VsixGallery|Any CPU
{2A7DE186-A1FA-4BA8-B393-3CA9ECBF444F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2A7DE186-A1FA-4BA8-B393-3CA9ECBF444F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2A7DE186-A1FA-4BA8-B393-3CA9ECBF444F}.Marketplace|Any CPU.ActiveCfg = Marketplace|Any CPU
+ {2A7DE186-A1FA-4BA8-B393-3CA9ECBF444F}.Marketplace|Any CPU.Build.0 = Marketplace|Any CPU
{2A7DE186-A1FA-4BA8-B393-3CA9ECBF444F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A7DE186-A1FA-4BA8-B393-3CA9ECBF444F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2A7DE186-A1FA-4BA8-B393-3CA9ECBF444F}.VsixGallery|Any CPU.ActiveCfg = VsixGallery|Any CPU
+ {2A7DE186-A1FA-4BA8-B393-3CA9ECBF444F}.VsixGallery|Any CPU.Build.0 = VsixGallery|Any CPU
+ {F16E6593-DDF9-4E9E-A2F8-56A3C43A643E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F16E6593-DDF9-4E9E-A2F8-56A3C43A643E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F16E6593-DDF9-4E9E-A2F8-56A3C43A643E}.Marketplace|Any CPU.ActiveCfg = Marketplace|Any CPU
+ {F16E6593-DDF9-4E9E-A2F8-56A3C43A643E}.Marketplace|Any CPU.Build.0 = Marketplace|Any CPU
+ {F16E6593-DDF9-4E9E-A2F8-56A3C43A643E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F16E6593-DDF9-4E9E-A2F8-56A3C43A643E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F16E6593-DDF9-4E9E-A2F8-56A3C43A643E}.VsixGallery|Any CPU.ActiveCfg = VsixGallery|Any CPU
+ {F16E6593-DDF9-4E9E-A2F8-56A3C43A643E}.VsixGallery|Any CPU.Build.0 = VsixGallery|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -72,6 +108,7 @@ Global
{84E8BA65-9A4B-4C50-A115-6EF3208E4058} = {AE904AFF-1B2B-4D8F-9826-57BC13DC4AC5}
{FBB4F3ED-B1B8-4401-8667-5180194BAA54} = {1DE272AA-D641-45F2-AEB9-934B3BAA6FBD}
{2A7DE186-A1FA-4BA8-B393-3CA9ECBF444F} = {1DE272AA-D641-45F2-AEB9-934B3BAA6FBD}
+ {F16E6593-DDF9-4E9E-A2F8-56A3C43A643E} = {AE904AFF-1B2B-4D8F-9826-57BC13DC4AC5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1485240E-4A28-4FA4-8D69-3D8151C3E1F6}
diff --git a/Directory.Build.props b/Directory.Build.props
index 19922ea1..f8117bd5 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,8 +2,16 @@
- 2.2.33
+ 2.3.138
all
+
+
+ $(DefineConstants);VSIXGallery
+ $(DefineConstants);MARKETPLACE
+ $(DefineConstants);VSIX
+ $(DefineConstants);VSIX
+
+
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
index 34f2aaa5..8371b4bf 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
- Copyright © 2017 Aleksey Nagovitsyn, Stefan Kert and Tom Schmiedlechner
+ Copyright © 2019 Stefan Kert
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index ac693dce..61b79f0b 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -2,60 +2,78 @@ trigger:
branches:
include:
- master
- - develop
pr:
branches:
include:
- master
- - develop
-
-pool:
- vmImage: 'VS2017-Win2016'
variables:
BuildPlatform: 'Any CPU'
BuildConfiguration: 'Release'
-steps:
-- task: DotNetCoreCLI@2
- inputs:
- command: custom
- custom: tool
- arguments: install --tool-path . nbgv
- displayName: Install NBGV tool
-
-- script: nbgv cloud -a
- displayName: Set Version
-
-- task: NuGetToolInstaller@0
-
-- task: NuGetCommand@2
- inputs:
- restoreSolution: BuildVision.sln
-
-- task: MSBuild@1
- displayName: Build BuildVision.sln
- inputs:
- solution: BuildVision.sln
- platform: $(BuildPlatform)
- configuration: $(BuildConfiguration)
-
-- task: CopyFiles@2
- inputs:
- contents: '**/bin/$(BuildConfiguration)/*.vsix'
- targetFolder: $(Build.ArtifactStagingDirectory)/artifacts
- flattenFolders: true
-
-- task: DotNetCoreCLI@2
- inputs:
- command: test
- projects: 'test/**/*.csproj'
- arguments: -c $(BuildConfiguration) --no-build --no-restore
- displayName: Run Tests
-
-- task: PublishBuildArtifacts@1
- inputs:
- PathtoPublish: $(Build.ArtifactStagingDirectory)/artifacts
- ArtifactName: artifacts
- publishLocation: Container
+jobs:
+ - job: Build
+ pool:
+ vmImage: vs2017-win2016
+ strategy:
+ matrix:
+ Config_Release:
+ BuildConfiguration: Release
+ BuildOutputDirectory: Vsix
+ Config_Marketplace:
+ BuildConfiguration: Marketplace
+ BuildOutputDirectory: Marketplace
+ Config_VSIXGallery:
+ BuildConfiguration: VsixGallery
+ BuildOutputDirectory: VsixGallery
+
+ steps:
+ - task: DotNetCoreCLI@2
+ inputs:
+ command: custom
+ custom: tool
+ arguments: install --tool-path . nbgv
+ displayName: Install NBGV tool
+ continueOnError: true
+
+ - powershell: |
+ mkdir $(Build.ArtifactStagingDirectory)\$(BuildOutputDirectory)
+
+ - script: nbgv cloud -a
+ displayName: Set Version
+
+ - task: VisualStudioTestPlatformInstaller@1
+ displayName: VsTest Platform Installer
+
+ - task: NuGetToolInstaller@0
+
+ - task: NuGetCommand@2
+ inputs:
+ restoreSolution: BuildVision.sln
+
+ - task: MSBuild@1
+ displayName: Build BuildVision.sln
+ inputs:
+ solution: BuildVision.sln
+ platform: $(BuildPlatform)
+ configuration: $(BuildConfiguration)
+
+ - task: CopyFiles@2
+ inputs:
+ contents: '**/bin/$(BuildConfiguration)/*.vsix'
+ targetFolder: $(Build.ArtifactStagingDirectory)/$(BuildOutputDirectory)
+ flattenFolders: true
+
+ - task: DotNetCoreCLI@2
+ inputs:
+ command: test
+ projects: 'test/**/BuildVision.UnitTests.csproj'
+ arguments: -c $(BuildConfiguration) --no-build --no-restore
+ displayName: Run Unittests
+
+ - task: PublishBuildArtifacts@1
+ inputs:
+ PathtoPublish: $(Build.ArtifactStagingDirectory)/$(BuildOutputDirectory)
+ ArtifactName: $(BuildOutputDirectory)
+ publishLocation: Container
\ No newline at end of file
diff --git a/libs/2017/Microsoft.VSSDK.TestHostFramework.dll b/libs/2017/Microsoft.VSSDK.TestHostFramework.dll
new file mode 100644
index 00000000..9ad0b181
Binary files /dev/null and b/libs/2017/Microsoft.VSSDK.TestHostFramework.dll differ
diff --git a/libs/2017/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll b/libs/2017/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll
new file mode 100644
index 00000000..4f1fa2e7
Binary files /dev/null and b/libs/2017/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll differ
diff --git a/libs/2017/Microsoft.VisualStudioTools.VSTestHost.15.0.dll b/libs/2017/Microsoft.VisualStudioTools.VSTestHost.15.0.dll
new file mode 100644
index 00000000..03fdd043
Binary files /dev/null and b/libs/2017/Microsoft.VisualStudioTools.VSTestHost.15.0.dll differ
diff --git a/src/BuildVision.Common/AppVersionInfo.cs b/src/BuildVision.Common/AppVersionInfo.cs
index 1b98f9c5..b5707524 100644
--- a/src/BuildVision.Common/AppVersionInfo.cs
+++ b/src/BuildVision.Common/AppVersionInfo.cs
@@ -1,6 +1,8 @@
-using System;
+using System;
+using System.Diagnostics;
using System.IO;
using System.Reflection;
+using System.Runtime.InteropServices;
namespace BuildVision.Common
{
@@ -8,47 +10,14 @@ public class AppVersionInfo
{
public string AppVersion { get; set; }
public string BuildVersion { get; set; }
- public DateTime BuildDateTime { get; set; }
public AppVersionInfo()
{
- Assembly assembly = Assembly.GetExecutingAssembly();
- AssemblyName assemblyName = assembly.GetName();
- Version version = assemblyName.Version;
+ var assembly = Assembly.GetExecutingAssembly();
+ var versionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
- AppVersion = version.ToString(3);
- BuildVersion = version.ToString(4);
- BuildDateTime = RetrieveLinkerTimestamp(assembly);
- }
-
- ///
- /// Get last build datetime.
- ///
- private static DateTime RetrieveLinkerTimestamp(Assembly assembly)
- {
- string filePath = assembly.Location;
- const int PeHeaderOffset = 60;
- const int LinkerTimestampOffset = 8;
- var buffer = new byte[2048];
- Stream stream = null;
-
- try
- {
- stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
- stream.Read(buffer, 0, 2048);
- }
- finally
- {
- if (stream != null)
- stream.Close();
- }
-
- int i = BitConverter.ToInt32(buffer, PeHeaderOffset);
- int secondsSince1970 = BitConverter.ToInt32(buffer, i + LinkerTimestampOffset);
- var dt = new DateTime(1970, 1, 1, 0, 0, 0);
- dt = dt.AddSeconds(secondsSince1970);
- dt = dt.AddHours(TimeZone.CurrentTimeZone.GetUtcOffset(dt).Hours);
- return dt;
+ AppVersion = versionInfo.ProductVersion.ToString();
+ BuildVersion = new Version(versionInfo.FileVersion).ToString();
}
}
}
diff --git a/src/BuildVision.Common/ApplicationInfo.cs b/src/BuildVision.Common/ApplicationInfo.cs
new file mode 100644
index 00000000..78e92de3
--- /dev/null
+++ b/src/BuildVision.Common/ApplicationInfo.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Diagnostics;
+using System.Reflection;
+
+namespace BuildVision.Common
+{
+ public static class ApplicationInfo
+ {
+ public const string ApplicationName = "BuildVision";
+
+ public static FileVersionInfo GetHostVersionInfo()
+ {
+ return Process.GetCurrentProcess().MainModule.FileVersionInfo;
+ }
+
+ public static string GetPackageVersion(object package)
+ {
+ var versionInfo = FileVersionInfo.GetVersionInfo(package.GetType().Assembly.Location);
+ return versionInfo.ProductVersion;
+ }
+
+ public static string GetProductVersion()
+ {
+ var versionInfo = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location);
+ return versionInfo.ProductVersion;
+ }
+ }
+}
diff --git a/src/BuildVision.Common/BindableBase.cs b/src/BuildVision.Common/BindableBase.cs
index 5efa5848..5d2ecec8 100644
--- a/src/BuildVision.Common/BindableBase.cs
+++ b/src/BuildVision.Common/BindableBase.cs
@@ -11,7 +11,9 @@ public abstract class BindableBase : INotifyPropertyChanged
public virtual bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage, value))
+ {
return false;
+ }
storage = value;
OnPropertyChanged(propertyName);
@@ -19,12 +21,14 @@ public virtual bool SetProperty(ref T storage, T value, [CallerMemberName] st
return true;
}
- public virtual bool SetProperty(Func storage, Action set, T value, [CallerMemberName] string propertyName = null)
+ public virtual bool SetProperty(Func storage, Action setAction, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(storage(), value))
+ {
return false;
+ }
- set(value);
+ setAction(value);
OnPropertyChanged(propertyName);
return true;
diff --git a/src/BuildVision.Common/BuildVision.Common.csproj b/src/BuildVision.Common/BuildVision.Common.csproj
index 4f779ccd..de2843bf 100644
--- a/src/BuildVision.Common/BuildVision.Common.csproj
+++ b/src/BuildVision.Common/BuildVision.Common.csproj
@@ -1,80 +1,35 @@
-
-
-
+
- Debug
- AnyCPU
- {848412D1-95BF-4E56-A9EF-2926AF5C6D67}
- Library
- Properties
- BuildVision.Common
- BuildVision.Common
- v4.7.2
- 512
-
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
- true
+ net472
+ Debug;Release;Marketplace;VsixGallery
+
Key.snk
+ true
+
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
+
+
-
- {13D64A57-5DB3-4CC7-AC2B-9034E767D754}
- BuildVision.Contracts
-
+
-
+
\ No newline at end of file
diff --git a/src/BuildVision.Common/CustomStringFormatProvider.cs b/src/BuildVision.Common/CustomStringFormatProvider.cs
index 73909163..0703ede6 100644
--- a/src/BuildVision.Common/CustomStringFormatProvider.cs
+++ b/src/BuildVision.Common/CustomStringFormatProvider.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
namespace BuildVision.Common
{
@@ -19,11 +19,13 @@ public object GetFormat(Type formatType)
public string Format(string format, object arg, IFormatProvider formatProvider)
{
if (arg == null)
+ {
return null;
+ }
if (format != null && arg is string)
{
- var strArg = (string) arg;
+ var strArg = (string)arg;
switch (format.ToUpper())
{
case "U":
@@ -34,7 +36,7 @@ public string Format(string format, object arg, IFormatProvider formatProvider)
}
return arg is IFormattable
- ? ((IFormattable) arg).ToString(format, formatProvider)
+ ? ((IFormattable)arg).ToString(format, formatProvider)
: arg.ToString();
}
}
diff --git a/src/BuildVision.Common/Diagnostics/DiagnosticsClient.cs b/src/BuildVision.Common/Diagnostics/DiagnosticsClient.cs
new file mode 100644
index 00000000..1ab889ff
--- /dev/null
+++ b/src/BuildVision.Common/Diagnostics/DiagnosticsClient.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Microsoft.ApplicationInsights;
+using Microsoft.ApplicationInsights.Extensibility;
+
+namespace BuildVision.Common.Diagnostics
+{
+ public static class DiagnosticsClient
+ {
+ private static bool _initialized;
+ private static TelemetryClient _client;
+
+ public static bool ParticipateInTelemetry { get; set; } = true;
+
+ public static void Initialize(string edition, string vsVersion, string apiKey)
+ {
+ if (!string.IsNullOrWhiteSpace(apiKey))
+ {
+ TelemetryConfiguration.Active.InstrumentationKey = apiKey;
+ TelemetryConfiguration.Active.TelemetryChannel.DeveloperMode = Debugger.IsAttached;
+ TelemetryConfiguration.Active.TelemetryInitializers.Add(new VersionTelemetry());
+ TelemetryConfiguration.Active.TelemetryInitializers.Add(new SessionTelemetry(vsVersion, edition));
+
+ _initialized = true;
+ _client = new TelemetryClient();
+ }
+ }
+
+ public static void Flush()
+ {
+ if (!_initialized || !ParticipateInTelemetry)
+ {
+ return;
+ }
+
+ _client.Flush();
+ // Allow time for flushing:
+ System.Threading.Thread.Sleep(1000);
+ }
+
+ public static void TrackEvent(string eventName, IDictionary properties = null, IDictionary metrics = null)
+ {
+ if (!_initialized || !ParticipateInTelemetry)
+ {
+ return;
+ }
+
+ _client.TrackEvent(eventName, properties, metrics);
+ }
+
+ public static void TrackTrace(string trace)
+ {
+ if (!_initialized || !ParticipateInTelemetry)
+ {
+ return;
+ }
+
+ _client.TrackTrace(trace);
+ }
+
+ public static void TrackException(Exception exception)
+ {
+ if (!_initialized || !ParticipateInTelemetry)
+ {
+ return;
+ }
+
+ _client.TrackException(exception);
+ }
+
+ public static void TrackPageView(string pageName)
+ {
+ if (!_initialized || !ParticipateInTelemetry)
+ {
+ return;
+ }
+
+ _client.TrackPageView(pageName);
+ }
+ }
+}
diff --git a/src/BuildVision.Common/Diagnostics/SessionTelemetry.cs b/src/BuildVision.Common/Diagnostics/SessionTelemetry.cs
new file mode 100644
index 00000000..fc4fac9b
--- /dev/null
+++ b/src/BuildVision.Common/Diagnostics/SessionTelemetry.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Text;
+using Microsoft.ApplicationInsights.Channel;
+using Microsoft.ApplicationInsights.Extensibility;
+
+namespace BuildVision.Common.Diagnostics
+{
+ class SessionTelemetry : ITelemetryInitializer
+ {
+ private readonly string _userName;
+ private readonly string _operatingSystem = RuntimeInformation.OSDescription?.Replace("Microsoft ", ""); // Shorter description
+ private readonly string _session = Guid.NewGuid().ToString();
+ private readonly string _vsVersion;
+ private readonly string _dteEdition;
+
+#if VSIXGallery
+ private const string Channel = "vsixgallery";
+#elif MARKETPLACE
+ private const string Channel = "marketplace";
+#elif VSIX
+ private const string Channel = "vsix";
+#else
+ private const string Channel = "vsix";
+#endif
+
+ public SessionTelemetry(string vsVersion, string dteEdition)
+ {
+ try
+ {
+ using (var hash = SHA256.Create())
+ {
+ var hashBytes = hash.ComputeHash(Encoding.UTF8.GetBytes(Environment.MachineName + Environment.UserDomainName + Environment.UserName));
+ _userName = Convert.ToBase64String(hashBytes);
+ }
+ }
+ catch
+ {
+ // No user id
+ }
+
+ _vsVersion = vsVersion;
+ _dteEdition = dteEdition;
+ }
+
+ public void Initialize(ITelemetry telemetry)
+ {
+ telemetry.Context.GlobalProperties["Environment"] = Channel;
+ // Always default to development if we're in the debugger
+ if (Debugger.IsAttached)
+ {
+ telemetry.Context.GlobalProperties["Environment"] = "development";
+ }
+
+ if (_userName != null)
+ {
+ telemetry.Context.User.Id = _userName;
+ }
+
+ telemetry.Context.Session.Id = _session;
+ telemetry.Context.Device.OperatingSystem = _operatingSystem;
+ telemetry.Context.Cloud.RoleInstance = "";
+ telemetry.Context.Cloud.RoleName = "";
+ telemetry.Context.Device.Model = _vsVersion;
+ telemetry.Context.Device.Type = _dteEdition;
+ }
+ }
+}
diff --git a/src/BuildVision.Common/Diagnostics/VersionTelemetry.cs b/src/BuildVision.Common/Diagnostics/VersionTelemetry.cs
new file mode 100644
index 00000000..1fcc320f
--- /dev/null
+++ b/src/BuildVision.Common/Diagnostics/VersionTelemetry.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using Microsoft.ApplicationInsights.Channel;
+using Microsoft.ApplicationInsights.Extensibility;
+
+namespace BuildVision.Common.Diagnostics
+{
+ class VersionTelemetry : ITelemetryInitializer
+ {
+ private readonly string _appVersion;
+
+ public VersionTelemetry()
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ var versionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
+
+ _appVersion = versionInfo.ProductVersion.ToString();
+ }
+
+ public void Initialize(ITelemetry telemetry)
+ {
+ telemetry.Context.Component.Version = _appVersion;
+ }
+ }
+}
diff --git a/src/BuildVision.Common/Extensions/DateTimeExtensions.cs b/src/BuildVision.Common/Extensions/DateTimeExtensions.cs
index 6d15ea35..6d789e96 100644
--- a/src/BuildVision.Common/Extensions/DateTimeExtensions.cs
+++ b/src/BuildVision.Common/Extensions/DateTimeExtensions.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace BuildVision.Common.Extensions
{
@@ -10,9 +6,7 @@ public static class DateTimeExtensions
{
public static DateTime Truncate(this DateTime dateTime, TimeSpan timeSpan)
{
- if (timeSpan <= TimeSpan.Zero)
- return dateTime;
- return dateTime.AddTicks(-(dateTime.Ticks % timeSpan.Ticks));
+ return timeSpan <= TimeSpan.Zero ? dateTime : dateTime.AddTicks(-(dateTime.Ticks % timeSpan.Ticks));
}
}
}
diff --git a/src/BuildVision.Common/FilePathHelper.cs b/src/BuildVision.Common/FilePathHelper.cs
index d7c52811..12e72f8c 100644
--- a/src/BuildVision.Common/FilePathHelper.cs
+++ b/src/BuildVision.Common/FilePathHelper.cs
@@ -120,7 +120,7 @@ public static string ShortenPath(string path, int maxLength)
}
}
- if (lastPart == "")
+ if (string.IsNullOrEmpty(lastPart))
{
//the filename (and root path) in itself was longer than maxLength, shorten it
lastPart = pathParts[pathParts.Length - 1];//"pathParts[pathParts.Length -1]" is the equivalent of "Path.GetFileName(pathToShorten)"
diff --git a/src/BuildVision.Common/GenericXmlSerializer.cs b/src/BuildVision.Common/GenericXmlSerializer.cs
index 49e70b00..c0a2ea2a 100644
--- a/src/BuildVision.Common/GenericXmlSerializer.cs
+++ b/src/BuildVision.Common/GenericXmlSerializer.cs
@@ -1,5 +1,4 @@
-using System;
-using System.IO;
+using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
diff --git a/src/BuildVision.Common/GithubHelper.cs b/src/BuildVision.Common/GithubHelper.cs
index feb9821e..b6679ac6 100644
--- a/src/BuildVision.Common/GithubHelper.cs
+++ b/src/BuildVision.Common/GithubHelper.cs
@@ -1,15 +1,12 @@
-using BuildVision.Common;
-using System;
-using System.Collections.Generic;
+using System;
using System.Diagnostics;
-using System.Linq;
+using System.Globalization;
using System.Net;
-using System.Text;
-using System.Threading.Tasks;
+using BuildVision.Common;
namespace BuildVision.Helpers
{
- public class GithubHelper
+ public static class GithubHelper
{
const string ASSIGNEE = "stefankert";
const string URL_TEMPLATE = "https://github.com/StefanKert/BuildVision/issues/new?labels={0}&title={1}&assignee={2}&body={3}";
@@ -28,13 +25,13 @@ public static void OpenBrowserWithPrefilledIssue()
{
var appVersion = new AppVersionInfo();
- var url = GetUrlForNewBug(string.Format(template, VsVersion.FullVersion, appVersion.BuildVersion, Environment.OSVersion));
- Process.Start(new ProcessStartInfo(url));
+ var url = GetUrlForNewBug(string.Format(CultureInfo.CurrentCulture, template, VsVersion.FullVersion, appVersion.BuildVersion, Environment.OSVersion));
+ Process.Start(new ProcessStartInfo(url.ToString()));
}
- public static string GetUrlForNewBug(string body)
+ public static Uri GetUrlForNewBug(string body)
{
- return string.Format(URL_TEMPLATE, "Bug", "", ASSIGNEE, WebUtility.UrlEncode(body));
+ return new Uri(string.Format(CultureInfo.CurrentCulture, URL_TEMPLATE, "Bug", "", ASSIGNEE, WebUtility.UrlEncode(body)));
}
}
}
diff --git a/src/BuildVision.Common/Logging/ILoggerExtensions.cs b/src/BuildVision.Common/Logging/ILoggerExtensions.cs
new file mode 100644
index 00000000..7e5490e1
--- /dev/null
+++ b/src/BuildVision.Common/Logging/ILoggerExtensions.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Globalization;
+using System.Threading.Tasks;
+using Serilog;
+
+namespace BuildVision.Common.Logging
+{
+ public static class ILoggerExtensions
+ {
+ public static void Assert(this ILogger logger, bool condition, string messageTemplate)
+ {
+ if (!condition)
+ {
+ messageTemplate = "Assertion Failed: " + messageTemplate;
+#pragma warning disable Serilog004 // propertyValues might not be strings
+ logger.Warning(messageTemplate);
+#pragma warning restore Serilog004
+ }
+ }
+
+ public static void Assert(this ILogger logger, bool condition, string messageTemplate, params object[] propertyValues)
+ {
+ if (!condition)
+ {
+ messageTemplate = "Assertion Failed: " + messageTemplate;
+#pragma warning disable Serilog004 // propertyValues might not be strings
+ logger.Warning(messageTemplate, propertyValues);
+#pragma warning restore Serilog004
+ }
+ }
+
+ public static void Time(this ILogger logger, string name, Action method)
+ {
+ var startTime = DateTime.Now;
+ method();
+ logger.Verbose("{Name} took {Seconds} seconds", name, FormatSeconds(DateTime.Now - startTime));
+ }
+
+ public static T Time(this ILogger logger, string name, Func method)
+ {
+ var startTime = DateTime.Now;
+ var value = method();
+ logger.Verbose("{Name} took {Seconds} seconds", name, FormatSeconds(DateTime.Now - startTime));
+ return value;
+ }
+
+ public static async Task TimeAsync(this ILogger logger, string name, Func methodAsync)
+ {
+ var startTime = DateTime.Now;
+ await methodAsync().ConfigureAwait(false);
+ logger.Verbose("{Name} took {Seconds} seconds", name, FormatSeconds(DateTime.Now - startTime));
+ }
+
+ public static async Task TimeAsync(this ILogger logger, string name, Func> methodAsync)
+ {
+ var startTime = DateTime.Now;
+ var value = await methodAsync().ConfigureAwait(false);
+ logger.Verbose("{Name} took {Seconds} seconds", name, FormatSeconds(DateTime.Now - startTime));
+ return value;
+ }
+
+ static string FormatSeconds(TimeSpan timeSpan) => timeSpan.TotalSeconds.ToString("0.##", CultureInfo.InvariantCulture);
+ }
+}
diff --git a/src/BuildVision.Common/Logging/LogManager.cs b/src/BuildVision.Common/Logging/LogManager.cs
new file mode 100644
index 00000000..c224f6a6
--- /dev/null
+++ b/src/BuildVision.Common/Logging/LogManager.cs
@@ -0,0 +1,56 @@
+using System;
+using System.IO;
+using Serilog;
+using Serilog.Core;
+using Serilog.Events;
+
+namespace BuildVision.Common.Logging
+{
+ public static class LogManager
+ {
+#if DEBUG
+ private static LogEventLevel DefaultLoggingLevel = LogEventLevel.Debug;
+#else
+ private static LogEventLevel DefaultLoggingLevel = LogEventLevel.Information;
+#endif
+
+ private static LoggingLevelSwitch LoggingLevelSwitch = new LoggingLevelSwitch(DefaultLoggingLevel);
+
+ static Logger CreateLogger()
+ {
+ var logPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+ ApplicationInfo.ApplicationName,
+ "extension.log");
+
+ const string outputTemplate =
+ "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{ProcessId:00000}] {Level:u4} [{ThreadId:00}] {ShortSourceContext,-25} {Message:lj}{NewLine}{Exception}";
+
+ return new LoggerConfiguration()
+ .Enrich.WithProcessId()
+ .Enrich.WithThreadId()
+ .MinimumLevel.ControlledBy(LoggingLevelSwitch)
+ .WriteTo.File(logPath,
+ fileSizeLimitBytes: null,
+ outputTemplate: outputTemplate,
+ shared: true)
+ .CreateLogger();
+ }
+
+ public static void EnableTraceLogging(bool enable)
+ {
+ var logEventLevel = enable ? LogEventLevel.Verbose : DefaultLoggingLevel;
+ if (LoggingLevelSwitch.MinimumLevel != logEventLevel)
+ {
+ ForContext(typeof(LogManager)).Information("Set Logging Level: {LogEventLevel}", logEventLevel);
+ LoggingLevelSwitch.MinimumLevel = logEventLevel;
+ }
+ }
+
+ static Lazy Logger { get; } = new Lazy(CreateLogger);
+
+ public static ILogger ForContext() => ForContext(typeof(T));
+
+ public static ILogger ForContext(Type type) => Logger.Value.ForContext(type).ForContext("ShortSourceContext", type.Name);
+ }
+}
diff --git a/src/BuildVision.Common/ObservableCollectionExtensions.cs b/src/BuildVision.Common/ObservableCollectionExtensions.cs
deleted file mode 100644
index 98f2b01a..00000000
--- a/src/BuildVision.Common/ObservableCollectionExtensions.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-
-namespace BuildVision.Common
-{
- public static class ObservableCollectionExtensions
- {
- public static void AddRange(this ObservableCollection collection, IEnumerable range)
- {
- foreach (T item in range)
- {
- collection.Add(item);
- }
- }
- }
-}
diff --git a/src/BuildVision.Common/Properties/AssemblyInfo.cs b/src/BuildVision.Common/Properties/AssemblyInfo.cs
deleted file mode 100644
index 4d3afc79..00000000
--- a/src/BuildVision.Common/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using BuildVision;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("BuildVision.Common")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("BuildVision.Common")]
-[assembly: AssemblyCopyright("Copyright © 2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("848412d1-95bf-4e56-a9ef-2926af5c6d67")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
diff --git a/src/BuildVision.Common/Properties/GlobalSuppressions.cs b/src/BuildVision.Common/Properties/GlobalSuppressions.cs
new file mode 100644
index 00000000..f29fc8c0
--- /dev/null
+++ b/src/BuildVision.Common/Properties/GlobalSuppressions.cs
@@ -0,0 +1,6 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:BuildVision.Common.FilePathHelper.ShortenPath(System.String,System.Int32)~System.String")]
+[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "", Scope = "member", Target = "~M:BuildVision.Common.Diagnostics.SessionTelemetry.#ctor")]
diff --git a/src/BuildVision.Common/PropertyColumnSorter.cs b/src/BuildVision.Common/PropertyColumnSorter.cs
deleted file mode 100644
index f5c69e67..00000000
--- a/src/BuildVision.Common/PropertyColumnSorter.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using BuildVision.Contracts;
-using System;
-using System.Collections;
-using System.ComponentModel;
-using System.Reflection;
-
-namespace BuildVision.Common
-{
- public class PropertyColumnSorter : IComparer
- where T : class
- {
- private readonly int _direction;
- private readonly PropertyInfo _propertyInfo;
-
- public PropertyColumnSorter(ListSortDirection direction, string propertyName)
- {
- _direction = (direction == ListSortDirection.Ascending) ? 1 : -1;
- _propertyInfo = typeof(T).GetProperty(propertyName);
-
- if (_propertyInfo == null)
- throw new PropertyNotFoundException(propertyName, typeof(T));
- }
-
- int IComparer.Compare(object x, object y)
- {
- return Compare((T) x, (T) y);
- }
-
- protected int Compare(T x, T y)
- {
- var x1 = _propertyInfo.GetValue(x, null) as IComparable;
- var y1 = _propertyInfo.GetValue(y, null) as IComparable;
-
- if (x1 != null && y1 != null)
- return x1.CompareTo(y1) * _direction;
-
- if (x1 == null && y1 == null)
- return 0;
-
- // Null values always in the bottom.
- return (x1 == null) ? 1 : -1;
- }
- }
-}
diff --git a/src/BuildVision.Common/RelayCommand.cs b/src/BuildVision.Common/RelayCommand.cs
index cec77435..3d8c3080 100644
--- a/src/BuildVision.Common/RelayCommand.cs
+++ b/src/BuildVision.Common/RelayCommand.cs
@@ -32,10 +32,7 @@ public RelayCommand(Action