diff --git a/Lombiq.Vsix.Orchard.sln b/Lombiq.Vsix.Orchard.sln index e018085..bcbbab0 100644 --- a/Lombiq.Vsix.Orchard.sln +++ b/Lombiq.Vsix.Orchard.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29905.134 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lombiq.Vsix.Orchard", "Lombiq.Vsix.Orchard\Lombiq.Vsix.Orchard.csproj", "{EF02401B-9EB3-4E06-8C34-8411700E4B58}" EndProject @@ -14,13 +14,19 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {EF02401B-9EB3-4E06-8C34-8411700E4B58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EF02401B-9EB3-4E06-8C34-8411700E4B58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF02401B-9EB3-4E06-8C34-8411700E4B58}.Debug|x86.ActiveCfg = Debug|x86 + {EF02401B-9EB3-4E06-8C34-8411700E4B58}.Debug|x86.Build.0 = Debug|x86 {EF02401B-9EB3-4E06-8C34-8411700E4B58}.Release|Any CPU.ActiveCfg = Release|Any CPU {EF02401B-9EB3-4E06-8C34-8411700E4B58}.Release|Any CPU.Build.0 = Release|Any CPU + {EF02401B-9EB3-4E06-8C34-8411700E4B58}.Release|x86.ActiveCfg = Release|x86 + {EF02401B-9EB3-4E06-8C34-8411700E4B58}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Lombiq.Vsix.Orchard/Commands/InjectDependencyCommand.cs b/Lombiq.Vsix.Orchard/Commands/InjectDependencyCommand.cs index 7105543..8ed25c1 100644 --- a/Lombiq.Vsix.Orchard/Commands/InjectDependencyCommand.cs +++ b/Lombiq.Vsix.Orchard/Commands/InjectDependencyCommand.cs @@ -60,6 +60,7 @@ private void MenuItemCallback(object sender, EventArgs e) => private async Task MenuItemCallbackAsync() { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); const string injectDependencyCaption = "Inject Dependency"; var dte = await _package.GetDteAsync().ConfigureAwait(true); diff --git a/Lombiq.Vsix.Orchard/Commands/OpenErrorLogCommand.cs b/Lombiq.Vsix.Orchard/Commands/OpenErrorLogCommand.cs index 4f119c2..87d556b 100644 --- a/Lombiq.Vsix.Orchard/Commands/OpenErrorLogCommand.cs +++ b/Lombiq.Vsix.Orchard/Commands/OpenErrorLogCommand.cs @@ -150,6 +150,7 @@ private void LogWatcherSettingsUpdatedCallback(object sender, LogWatcherSettings private async Task LogWatcherSettingsUpdatedCallbackAsync(LogWatcherSettingsUpdatedEventArgs e) { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var isEnabled = e.Settings.LogWatcherEnabled; var orchardLogWatcherToolbar = ((CommandBars)(await _package.GetDteAsync() .ConfigureAwait(true)).CommandBars)[CommandBarNames.OrchardLogWatcherToolbarName]; @@ -178,6 +179,7 @@ await Task.Run(() => private async Task UpdateOpenErrorLogCommandAccessibilityAndTextAsync(ILogFileStatus logFileStatus = null) { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var logWatcherSettings = await _logWatcherSettingsAccessor.GetSettingsAsync().ConfigureAwait(true); if (!(await _package.GetDteAsync().ConfigureAwait(true)).SolutionIsOpen()) diff --git a/Lombiq.Vsix.Orchard/Constants/DependencyInjectorErrorCodes.cs b/Lombiq.Vsix.Orchard/Constants/DependencyInjectorErrorCodes.cs index bd48b01..e079708 100644 --- a/Lombiq.Vsix.Orchard/Constants/DependencyInjectorErrorCodes.cs +++ b/Lombiq.Vsix.Orchard/Constants/DependencyInjectorErrorCodes.cs @@ -1,4 +1,4 @@ -namespace Lombiq.Vsix.Orchard.Constants +namespace Lombiq.Vsix.Orchard.Constants { internal static class DependencyInjectorErrorCodes { diff --git a/Lombiq.Vsix.Orchard/Constants/ExtensionVersion.cs b/Lombiq.Vsix.Orchard/Constants/ExtensionVersion.cs index 622a7eb..e4d3c22 100644 --- a/Lombiq.Vsix.Orchard/Constants/ExtensionVersion.cs +++ b/Lombiq.Vsix.Orchard/Constants/ExtensionVersion.cs @@ -1,7 +1,7 @@ -namespace Lombiq.Vsix.Orchard.Constants +namespace Lombiq.Vsix.Orchard.Constants { internal static class ExtensionVersion { - public const string Current = "1.5.4"; + public const string Current = "1.5.40"; } } diff --git a/Lombiq.Vsix.Orchard/Extensions/DTEExtensions.cs b/Lombiq.Vsix.Orchard/Extensions/DTEExtensions.cs index e3737e9..4f0a36d 100644 --- a/Lombiq.Vsix.Orchard/Extensions/DTEExtensions.cs +++ b/Lombiq.Vsix.Orchard/Extensions/DTEExtensions.cs @@ -1,7 +1,13 @@ -namespace EnvDTE -{ - public static class DteExtensions - { - public static bool SolutionIsOpen(this DTE dte) => dte.Solution.IsOpen; - } -} +namespace EnvDTE +{ + public static class DteExtensions + { + public static bool SolutionIsOpen(this DTE dte) + { + // We should never get an exception here. This is just to ensure we access DTE on the main thread and get + // rid of the VSTHRD010 violation. + Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); + return dte.Solution.IsOpen; + } + } +} diff --git a/Lombiq.Vsix.Orchard/Forms/InjectDependencyDialog.Designer.cs b/Lombiq.Vsix.Orchard/Forms/InjectDependencyDialog.Designer.cs index 51eecb4..9098e32 100644 --- a/Lombiq.Vsix.Orchard/Forms/InjectDependencyDialog.Designer.cs +++ b/Lombiq.Vsix.Orchard/Forms/InjectDependencyDialog.Designer.cs @@ -1,4 +1,4 @@ -namespace Lombiq.Vsix.Orchard.Forms +namespace Lombiq.Vsix.Orchard.Forms { partial class InjectDependencyDialog { @@ -394,4 +394,4 @@ private void InitializeComponent() private System.Windows.Forms.Label visualizeConstructorClosingParenthesisLabel; private System.Windows.Forms.Label visualizeInjectedNameLabel; } -} \ No newline at end of file +} diff --git a/Lombiq.Vsix.Orchard/Forms/InjectDependencyDialog.resx b/Lombiq.Vsix.Orchard/Forms/InjectDependencyDialog.resx index 29dcb1b..07e7760 100644 --- a/Lombiq.Vsix.Orchard/Forms/InjectDependencyDialog.resx +++ b/Lombiq.Vsix.Orchard/Forms/InjectDependencyDialog.resx @@ -1,4 +1,4 @@ - + + + + + 15.0 + 12.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + 12.0 + v3 + + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + Debug + AnyCPU + 2.0 + {EF02401B-9EB3-4E06-8C34-8411700E4B58} + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + Lombiq.Vsix.Orchard + Lombiq Orchard Visual Studio Extension + True + Key.snk + v4.7.2 + Program + $(DevEnvDir)\devenv.exe + /rootsuffix Exp + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + True + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + Always + true + + + Always + true + + + + + + + + + + + + + + + Form + + + InjectDependencyDialog.cs + + + + + + + + + + + + + + Component + + + + + + + + + + + + + + + + + + + + + + + + True + True + VSPackage.resx + + + + + InjectDependencyDialog.cs + + + true + VSPackage + ResXFileCodeGenerator + VSPackage.Designer.cs + Designer + + + + + true + + + true + + + Designer + + + + + + + + Menus.ctmenu + Designer + + + + + + + + False + Microsoft .NET Framework 4.5.2 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + 2.0.16 + + + 6.0.0 + + + 4.7.0 + + + 17.0.32112.339 + + + 17.0.32112.339 + + + 13.0.1 + + + 2.9.85 + + + 0.2.5 + + + 4.3.0 + + + 6.0.0 + + + 4.3.0 + + + 4.5.4 + + + + + + + + true + + + $([System.IO.Path]::GetFullPath( $(MSBuildProjectDirectory)\..\packages\TemplateBuilder.1.1.6.1\tools\ligershark.templates.targets )) + + + + + \ No newline at end of file diff --git a/Lombiq.Vsix.Orchard/LombiqOrchardVisualStudioExtension.vsct b/Lombiq.Vsix.Orchard/LombiqOrchardVisualStudioExtension.vsct index 8ad4398..5fbff44 100644 --- a/Lombiq.Vsix.Orchard/LombiqOrchardVisualStudioExtension.vsct +++ b/Lombiq.Vsix.Orchard/LombiqOrchardVisualStudioExtension.vsct @@ -1,4 +1,4 @@ - + @@ -113,4 +113,4 @@ - \ No newline at end of file + diff --git a/Lombiq.Vsix.Orchard/Models/LogChangedEventArgs.cs b/Lombiq.Vsix.Orchard/Models/LogChangedEventArgs.cs index ec717ed..fe90b44 100644 --- a/Lombiq.Vsix.Orchard/Models/LogChangedEventArgs.cs +++ b/Lombiq.Vsix.Orchard/Models/LogChangedEventArgs.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Lombiq.Vsix.Orchard.Models { diff --git a/Lombiq.Vsix.Orchard/Models/LogWatcherSettingsUpdatedEventArgs.cs b/Lombiq.Vsix.Orchard/Models/LogWatcherSettingsUpdatedEventArgs.cs index 850165f..2b21a62 100644 --- a/Lombiq.Vsix.Orchard/Models/LogWatcherSettingsUpdatedEventArgs.cs +++ b/Lombiq.Vsix.Orchard/Models/LogWatcherSettingsUpdatedEventArgs.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Lombiq.Vsix.Orchard.Models { diff --git a/Lombiq.Vsix.Orchard/Readme.md b/Lombiq.Vsix.Orchard/Readme.md index 429787e..9e0f93e 100644 --- a/Lombiq.Vsix.Orchard/Readme.md +++ b/Lombiq.Vsix.Orchard/Readme.md @@ -84,4 +84,4 @@ This project is developed by [Lombiq Technologies](https://lombiq.com/). Commerc - `IEnumerable` and other generic types are handled when generating injected dependency names; e.g. for `IEnumerable` the field name `_dependencies` will be generated. - 1.0, 24.05.2016 - Dependency Injector tool - - Templates for content part, shape template, injected dependency, Orchard 1.9 and 1.10 module \ No newline at end of file + - Templates for content part, shape template, injected dependency, Orchard 1.9 and 1.10 module diff --git a/Lombiq.Vsix.Orchard/Services/DependencyInjector/DependencyInjector.cs b/Lombiq.Vsix.Orchard/Services/DependencyInjector/DependencyInjector.cs index 07fd237..5d791e9 100644 --- a/Lombiq.Vsix.Orchard/Services/DependencyInjector/DependencyInjector.cs +++ b/Lombiq.Vsix.Orchard/Services/DependencyInjector/DependencyInjector.cs @@ -1,325 +1,339 @@ -using EnvDTE; -using Lombiq.Vsix.Orchard.Constants; -using Lombiq.Vsix.Orchard.Models; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Lombiq.Vsix.Orchard.Services.DependencyInjector -{ - /// - /// Injects dependency into the constructor and inserts the necessary code lines. - /// - public interface IDependencyInjector - { - /// - /// Injects the given dependency, creates the private readonly field and also inserts the assignment into the - /// constructor. - /// - /// Visual Studio document containing the class where the dependency needs to be - /// injected. - /// Field and constructor parameter type and name to be added to the - /// code. - /// Result of the dependency injection. - IResult Inject(Document document, DependencyInjectionData dependencyInjectionData); - - /// - /// Returns the expected class name where the dependency needs to be injected. - /// - /// Visual Studio document containing the class where the dependency needs to be injected. - /// Expected class name. - string GetExpectedClassName(Document document); - } - - public class DependencyInjector : IDependencyInjector - { - public IResult Inject(Document document, DependencyInjectionData dependencyInjectionData) +using EnvDTE; +using Lombiq.Vsix.Orchard.Constants; +using Lombiq.Vsix.Orchard.Models; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Lombiq.Vsix.Orchard.Services.DependencyInjector +{ + /// + /// Injects dependency into the constructor and inserts the necessary code lines. + /// + public interface IDependencyInjector + { + /// + /// Injects the given dependency, creates the private readonly field and also inserts the assignment into the + /// constructor. + /// + /// Visual Studio document containing the class where the dependency needs to be + /// injected. + /// Field and constructor parameter type and name to be added to the + /// code. + /// Result of the dependency injection. + IResult Inject(Document document, DependencyInjectionData dependencyInjectionData); + + /// + /// Returns the expected class name where the dependency needs to be injected. + /// + /// Visual Studio document containing the class where the dependency needs to be injected. + /// Expected class name. + string GetExpectedClassName(Document document); + } + + public class DependencyInjector : IDependencyInjector + { + public IResult Inject(Document document, DependencyInjectionData dependencyInjectionData) { - var context = new DependencyInjectionContext - { - FieldName = dependencyInjectionData.FieldName, - VariableName = dependencyInjectionData.ConstructorParameterName, - FieldType = dependencyInjectionData.FieldType, - VariableType = dependencyInjectionData.ConstructorParameterType, - ClassName = GetExpectedClassName(document), - Document = document, - }; - - // Get code lines from the document. - GetCodeLines(context); - - // Determine the brace styling. - DetermineBraceStyling(context); - - // Find the initial line of the class. - GetClassStartLineIndex(context); - if (context.ClassStartLineIndex == -1) - { - return Result.FailedResult(DependencyInjectorErrorCodes.ClassNotFound); - } - - // Find the initial line of the first constructor. If it hasn't been created yet then create it. - GetConstructorLineIndex(context); - if (context.ConstructorLineIndex == -1) - { - CreateConstructor(context); - } - - // Update inner code of the constructor first. - InsertConstructorCodeLine(context); - - // Update the parameters of the constructor. - InsertInjectionToConstructor(context); - - // Add the private field to the class. - InsertPrivateField(context); - - // Finally update the editor window and select the class name to be able to add usings if necessary. - UpdateCodeEditorAndSelectDependency(context); - - return Result.SuccessResult; - } - - public string GetExpectedClassName(Document document) => - Path.GetFileNameWithoutExtension(document.FullName); - - private static void GetCodeLines(DependencyInjectionContext context) + // We should never get an exception here. This is just to ensure we access DTE on the main thread and get + // rid of the VSTHRD010 violation. + Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); + var context = new DependencyInjectionContext + { + FieldName = dependencyInjectionData.FieldName, + VariableName = dependencyInjectionData.ConstructorParameterName, + FieldType = dependencyInjectionData.FieldType, + VariableType = dependencyInjectionData.ConstructorParameterType, + ClassName = GetExpectedClassName(document), + Document = document, + }; + + // Get code lines from the document. + GetCodeLines(context); + + // Determine the brace styling. + DetermineBraceStyling(context); + + // Find the initial line of the class. + GetClassStartLineIndex(context); + if (context.ClassStartLineIndex == -1) + { + return Result.FailedResult(DependencyInjectorErrorCodes.ClassNotFound); + } + + // Find the initial line of the first constructor. If it hasn't been created yet then create it. + GetConstructorLineIndex(context); + if (context.ConstructorLineIndex == -1) + { + CreateConstructor(context); + } + + // Update inner code of the constructor first. + InsertConstructorCodeLine(context); + + // Update the parameters of the constructor. + InsertInjectionToConstructor(context); + + // Add the private field to the class. + InsertPrivateField(context); + + // Finally update the editor window and select the class name to be able to add usings if necessary. + UpdateCodeEditorAndSelectDependency(context); + + return Result.SuccessResult; + } + + public string GetExpectedClassName(Document document) { - var textDocument = context.Document.Object() as TextDocument; - context.StartEditPoint = textDocument.StartPoint.CreateEditPoint(); - context.EndEditPoint = textDocument.EndPoint.CreateEditPoint(); - var codeText = context.StartEditPoint.GetText(context.EndEditPoint); - - context.CodeLines = new List(codeText.Split(new[] { Environment.NewLine, "\n", "\r" }, StringSplitOptions.None)); + // We should never get an exception here. This is just to ensure we access DTE on the main thread and get + // rid of the VSTHRD010 violation. + Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); + return Path.GetFileNameWithoutExtension(document.FullName); } - private static void DetermineBraceStyling(DependencyInjectionContext context) + private static void GetCodeLines(DependencyInjectionContext context) { - // Set new line styling as default. - context.BraceStyle = BraceStyles.OpenInNewLine; - foreach (var line in context.CodeLines) - { - var trimmedLine = line.Trim(); - - if (trimmedLine.StartsWith("{", StringComparison.InvariantCulture)) return; - - if (trimmedLine.EndsWith("{", StringComparison.InvariantCulture)) - { - context.BraceStyle = BraceStyles.OpenInSameLine; - - return; - } - } - } - - private static void GetClassStartLineIndex(DependencyInjectionContext context) - { - var expectedClassDefinition = "class " + context.ClassName; - context.ClassStartLineIndex = -1; - - for (int i = 0; i < context.CodeLines.Count; i++) - { - if (context.CodeLines[i].IndexOf(expectedClassDefinition, StringComparison.OrdinalIgnoreCase) >= 0) - { - context.ClassStartLineIndex = i; - - return; - } - } - } - - private static void GetConstructorLineIndex(DependencyInjectionContext context) - { - var expectedConstructorStart = "public " + context.ClassName + "("; - context.ConstructorLineIndex = -1; - - for (int i = 0; i < context.CodeLines.Count; i++) - { - if (context.CodeLines[i].Trim().StartsWith(expectedConstructorStart, StringComparison.OrdinalIgnoreCase)) - { - context.ConstructorLineIndex = i; - - return; - } - } - } - - private static void CreateConstructor(DependencyInjectionContext context) - { - var classStartIndentSize = GetIndentSizeOfLine(context.CodeLines[context.ClassStartLineIndex]); - var createConstructorFromIndex = context.ClassStartLineIndex + (context.BraceStyle == BraceStyles.OpenInNewLine ? 2 : 1); - - // Add two empty lines before and after to separate the constructor from the field and the other parts of the code. - var constructorCodeLines = context.BraceStyle == BraceStyles.OpenInNewLine ? - new[] - { - string.Empty, - IndentText(classStartIndentSize, 2, "public " + context.ClassName + "()"), - IndentText(classStartIndentSize, 2, "{"), - IndentText(classStartIndentSize, 2, "}"), - string.Empty, - } - : - new[] - { - string.Empty, - IndentText(classStartIndentSize, 2, "public " + context.ClassName + "() {"), - IndentText(classStartIndentSize, 2, "}"), - string.Empty, - }; - - for (int i = 0; i < constructorCodeLines.Length; i++) - { - context.CodeLines.Insert(createConstructorFromIndex + i, constructorCodeLines[i]); - } - - context.ConstructorLineIndex = createConstructorFromIndex + 1; - } - - private static void InsertConstructorCodeLine(DependencyInjectionContext context) + // We should never get an exception here. This is just to ensure we access DTE on the main thread and get + // rid of the VSTHRD010 violation. + Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); + var textDocument = context.Document.Object() as TextDocument; + context.StartEditPoint = textDocument.StartPoint.CreateEditPoint(); + context.EndEditPoint = textDocument.EndPoint.CreateEditPoint(); + var codeText = context.StartEditPoint.GetText(context.EndEditPoint); + + context.CodeLines = new List(codeText.Split(new[] { Environment.NewLine, "\n", "\r" }, StringSplitOptions.None)); + } + + private static void DetermineBraceStyling(DependencyInjectionContext context) + { + // Set new line styling as default. + context.BraceStyle = BraceStyles.OpenInNewLine; + foreach (var line in context.CodeLines) + { + var trimmedLine = line.Trim(); + + if (trimmedLine.StartsWith("{", StringComparison.InvariantCulture)) return; + + if (trimmedLine.EndsWith("{", StringComparison.InvariantCulture)) + { + context.BraceStyle = BraceStyles.OpenInSameLine; + + return; + } + } + } + + private static void GetClassStartLineIndex(DependencyInjectionContext context) + { + var expectedClassDefinition = "class " + context.ClassName; + context.ClassStartLineIndex = -1; + + for (int i = 0; i < context.CodeLines.Count; i++) + { + if (context.CodeLines[i].IndexOf(expectedClassDefinition, StringComparison.OrdinalIgnoreCase) >= 0) + { + context.ClassStartLineIndex = i; + + return; + } + } + } + + private static void GetConstructorLineIndex(DependencyInjectionContext context) + { + var expectedConstructorStart = "public " + context.ClassName + "("; + context.ConstructorLineIndex = -1; + + for (int i = 0; i < context.CodeLines.Count; i++) + { + if (context.CodeLines[i].Trim().StartsWith(expectedConstructorStart, StringComparison.OrdinalIgnoreCase)) + { + context.ConstructorLineIndex = i; + + return; + } + } + } + + private static void CreateConstructor(DependencyInjectionContext context) + { + var classStartIndentSize = GetIndentSizeOfLine(context.CodeLines[context.ClassStartLineIndex]); + var createConstructorFromIndex = context.ClassStartLineIndex + (context.BraceStyle == BraceStyles.OpenInNewLine ? 2 : 1); + + // Add two empty lines before and after to separate the constructor from the field and the other parts of the code. + var constructorCodeLines = context.BraceStyle == BraceStyles.OpenInNewLine ? + new[] + { + string.Empty, + IndentText(classStartIndentSize, 2, "public " + context.ClassName + "()"), + IndentText(classStartIndentSize, 2, "{"), + IndentText(classStartIndentSize, 2, "}"), + string.Empty, + } + : + new[] + { + string.Empty, + IndentText(classStartIndentSize, 2, "public " + context.ClassName + "() {"), + IndentText(classStartIndentSize, 2, "}"), + string.Empty, + }; + + for (int i = 0; i < constructorCodeLines.Length; i++) + { + context.CodeLines.Insert(createConstructorFromIndex + i, constructorCodeLines[i]); + } + + context.ConstructorLineIndex = createConstructorFromIndex + 1; + } + + private static void InsertConstructorCodeLine(DependencyInjectionContext context) + { + var constructorLine = context.CodeLines[context.ConstructorLineIndex]; + var constructorIndentSize = GetIndentSizeOfLine(constructorLine); + var constructorCodeLine = IndentText(constructorIndentSize, 1.5, context.FieldName + " = " + context.VariableName + ";"); + + var constructorCodeLineInserted = false; + var i = context.ConstructorLineIndex - 1; + var constructorCodeStartIndex = -1; + while (i < context.CodeLines.Count && !constructorCodeLineInserted) + { + i++; + // Need to find the inner part of the constructor first. + var trimmedLine = context.CodeLines[i].Trim(); + if (constructorCodeStartIndex == -1 && trimmedLine.Contains("{")) + { + constructorCodeStartIndex = i + 1; + + continue; + } + + if (constructorCodeStartIndex == -1) continue; + + // If the first line is empty skip this because it is probably empty conventionally. + if (constructorCodeStartIndex == i && string.IsNullOrEmpty(trimmedLine)) continue; + + // Insert the code line right after field assignments. + var isItFieldAssignment = trimmedLine.Length > 0 && + trimmedLine.Contains("=") && + (trimmedLine.StartsWith("_", StringComparison.InvariantCulture) + || char.IsLower(trimmedLine[0])); + + if (isItFieldAssignment) continue; + + context.CodeLines.Insert(i, constructorCodeLine); + constructorCodeLineInserted = true; + } + } + + private static void InsertInjectionToConstructor(DependencyInjectionContext context) + { + var constructorLine = context.CodeLines[context.ConstructorLineIndex]; + var indentSize = GetIndentSizeOfLine(constructorLine); + var injection = context.VariableType + " " + context.VariableName; + + // CASE 1: No parameters in constructor. + if (constructorLine.Contains("()")) + { + context.CodeLines.RemoveAt(context.ConstructorLineIndex); + context.CodeLines.Insert(context.ConstructorLineIndex, constructorLine.Replace("()", "(" + injection + ")")); + } + + // CASE 2: Has parameters in the same line as the constructor name. + else if (constructorLine.EndsWith(context.BraceStyle == BraceStyles.OpenInNewLine ? ")" : "{", StringComparison.InvariantCulture)) + { + context.CodeLines.RemoveAt(context.ConstructorLineIndex); + context.CodeLines.Insert(context.ConstructorLineIndex, constructorLine.Replace(")", ", " + injection + ")")); + } + + // CASE 3: Constructor has parameters in multiple lines. + else if (constructorLine.EndsWith("(", StringComparison.InvariantCulture)) + { + var i = context.ConstructorLineIndex - 1; + var injectionInserted = false; + while (i < context.CodeLines.Count && !injectionInserted) + { + i++; + var indexOfClosing = context.CodeLines[i].IndexOf(')'); + + if (indexOfClosing < 0) continue; + + var beforeClosing = context.CodeLines[i].Substring(0, indexOfClosing); + var afterClosing = context.CodeLines[i].Substring(indexOfClosing); + + context.CodeLines.RemoveAt(i); + context.CodeLines.Insert(i, IndentText(indentSize, 1.5, injection + afterClosing)); + context.CodeLines.Insert(i, beforeClosing + ","); + injectionInserted = true; + } + } + } + + private static void InsertPrivateField(DependencyInjectionContext context) + { + var classStartLine = context.CodeLines[context.ClassStartLineIndex]; + var indentSize = GetIndentSizeOfLine(classStartLine); + var privateFieldLine = new string(' ', indentSize * 2) + "private readonly " + context.FieldType + " " + context.FieldName + ";"; + + var i = context.ClassStartLineIndex + (context.BraceStyle == BraceStyles.OpenInNewLine ? 1 : 0); + var privateFieldInserted = false; + while (i < context.CodeLines.Count && !privateFieldInserted) + { + i++; + if (!context.CodeLines[i].Trim().StartsWith("private readonly", StringComparison.InvariantCulture)) + { + context.CodeLines.Insert(i, privateFieldLine); + privateFieldInserted = true; + } + } + } + + private static void UpdateCodeEditorAndSelectDependency(DependencyInjectionContext context) { - var constructorLine = context.CodeLines[context.ConstructorLineIndex]; - var constructorIndentSize = GetIndentSizeOfLine(constructorLine); - var constructorCodeLine = IndentText(constructorIndentSize, 1.5, context.FieldName + " = " + context.VariableName + ";"); - - var constructorCodeLineInserted = false; - var i = context.ConstructorLineIndex - 1; - var constructorCodeStartIndex = -1; - while (i < context.CodeLines.Count && !constructorCodeLineInserted) - { - i++; - // Need to find the inner part of the constructor first. - var trimmedLine = context.CodeLines[i].Trim(); - if (constructorCodeStartIndex == -1 && trimmedLine.Contains("{")) - { - constructorCodeStartIndex = i + 1; - - continue; - } - - if (constructorCodeStartIndex == -1) continue; - - // If the first line is empty skip this because it is probably empty conventionally. - if (constructorCodeStartIndex == i && string.IsNullOrEmpty(trimmedLine)) continue; - - // Insert the code line right after field assignments. - var isItFieldAssignment = trimmedLine.Length > 0 && - trimmedLine.Contains("=") && - (trimmedLine.StartsWith("_", StringComparison.InvariantCulture) - || char.IsLower(trimmedLine[0])); - - if (isItFieldAssignment) continue; - - context.CodeLines.Insert(i, constructorCodeLine); - constructorCodeLineInserted = true; - } - } - - private static void InsertInjectionToConstructor(DependencyInjectionContext context) - { - var constructorLine = context.CodeLines[context.ConstructorLineIndex]; - var indentSize = GetIndentSizeOfLine(constructorLine); - var injection = context.VariableType + " " + context.VariableName; - - // CASE 1: No parameters in constructor. - if (constructorLine.Contains("()")) - { - context.CodeLines.RemoveAt(context.ConstructorLineIndex); - context.CodeLines.Insert(context.ConstructorLineIndex, constructorLine.Replace("()", "(" + injection + ")")); - } - - // CASE 2: Has parameters in the same line as the constructor name. - else if (constructorLine.EndsWith(context.BraceStyle == BraceStyles.OpenInNewLine ? ")" : "{", StringComparison.InvariantCulture)) - { - context.CodeLines.RemoveAt(context.ConstructorLineIndex); - context.CodeLines.Insert(context.ConstructorLineIndex, constructorLine.Replace(")", ", " + injection + ")")); - } - - // CASE 3: Constructor has parameters in multiple lines. - else if (constructorLine.EndsWith("(", StringComparison.InvariantCulture)) - { - var i = context.ConstructorLineIndex - 1; - var injectionInserted = false; - while (i < context.CodeLines.Count && !injectionInserted) - { - i++; - var indexOfClosing = context.CodeLines[i].IndexOf(')'); - - if (indexOfClosing < 0) continue; - - var beforeClosing = context.CodeLines[i].Substring(0, indexOfClosing); - var afterClosing = context.CodeLines[i].Substring(indexOfClosing); - - context.CodeLines.RemoveAt(i); - context.CodeLines.Insert(i, IndentText(indentSize, 1.5, injection + afterClosing)); - context.CodeLines.Insert(i, beforeClosing + ","); - injectionInserted = true; - } - } - } - - private static void InsertPrivateField(DependencyInjectionContext context) - { - var classStartLine = context.CodeLines[context.ClassStartLineIndex]; - var indentSize = GetIndentSizeOfLine(classStartLine); - var privateFieldLine = new string(' ', indentSize * 2) + "private readonly " + context.FieldType + " " + context.FieldName + ";"; - - var i = context.ClassStartLineIndex + (context.BraceStyle == BraceStyles.OpenInNewLine ? 1 : 0); - var privateFieldInserted = false; - while (i < context.CodeLines.Count && !privateFieldInserted) - { - i++; - if (!context.CodeLines[i].Trim().StartsWith("private readonly", StringComparison.InvariantCulture)) - { - context.CodeLines.Insert(i, privateFieldLine); - privateFieldInserted = true; - } - } - } - - private static void UpdateCodeEditorAndSelectDependency(DependencyInjectionContext context) - { - context.StartEditPoint.ReplaceText(context.EndEditPoint, string.Join(Environment.NewLine, context.CodeLines), 0); - - var textSelection = context.Document.Selection as TextSelection; - textSelection.GotoLine(context.ClassStartLineIndex + 3); - textSelection.FindPattern(context.FieldType); - } - - private static int GetIndentSizeOfLine(string codeLine) - { - var indentSize = 0; - while (indentSize < codeLine.Length && codeLine[indentSize] == ' ') - { - indentSize++; - } - - return indentSize; - } - - private static string IndentText(int baseIndentSize, double indentSizeMultiplier, string text) => - new string(' ', Convert.ToInt32(baseIndentSize * indentSizeMultiplier)) + text; - - private enum BraceStyles - { - OpenInNewLine = 0, - OpenInSameLine, - } - - private sealed class DependencyInjectionContext - { - public Document Document { get; set; } - public EditPoint StartEditPoint { get; set; } - public EditPoint EndEditPoint { get; set; } - public BraceStyles BraceStyle { get; set; } - public IList CodeLines { get; set; } - public string FieldName { get; set; } - public string VariableName { get; set; } - public string VariableType { get; set; } - public string FieldType { get; set; } - public string ClassName { get; set; } - public int ClassStartLineIndex { get; set; } - public int ConstructorLineIndex { get; set; } - } - } -} + // We should never get an exception here. This is just to ensure we access DTE on the main thread and get + // rid of the VSTHRD010 violation. + Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread(); + context.StartEditPoint.ReplaceText(context.EndEditPoint, string.Join(Environment.NewLine, context.CodeLines), 0); + + var textSelection = context.Document.Selection as TextSelection; + textSelection.GotoLine(context.ClassStartLineIndex + 3); + textSelection.FindPattern(context.FieldType, 0, Tags: null); + } + + private static int GetIndentSizeOfLine(string codeLine) + { + var indentSize = 0; + while (indentSize < codeLine.Length && codeLine[indentSize] == ' ') + { + indentSize++; + } + + return indentSize; + } + + private static string IndentText(int baseIndentSize, double indentSizeMultiplier, string text) => + new string(' ', Convert.ToInt32(baseIndentSize * indentSizeMultiplier)) + text; + + private enum BraceStyles + { + OpenInNewLine = 0, + OpenInSameLine, + } + + private sealed class DependencyInjectionContext + { + public Document Document { get; set; } + public EditPoint StartEditPoint { get; set; } + public EditPoint EndEditPoint { get; set; } + public BraceStyles BraceStyle { get; set; } + public IList CodeLines { get; set; } + public string FieldName { get; set; } + public string VariableName { get; set; } + public string VariableType { get; set; } + public string FieldType { get; set; } + public string ClassName { get; set; } + public int ClassStartLineIndex { get; set; } + public int ConstructorLineIndex { get; set; } + } + } +} diff --git a/Lombiq.Vsix.Orchard/Services/LogWatcher/LogFileWatcherBase.cs b/Lombiq.Vsix.Orchard/Services/LogWatcher/LogFileWatcherBase.cs index 75a2dcc..76e76a3 100644 --- a/Lombiq.Vsix.Orchard/Services/LogWatcher/LogFileWatcherBase.cs +++ b/Lombiq.Vsix.Orchard/Services/LogWatcher/LogFileWatcherBase.cs @@ -1,223 +1,225 @@ -using EnvDTE; -using Lombiq.Vsix.Orchard.Models; -using Microsoft.VisualStudio.Shell; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Lombiq.Vsix.Orchard.Services.LogWatcher -{ - public abstract class LogFileWatcherBase : ILogFileWatcher - { - private const int DefaultLogWatcherTimerIntervalInMilliseconds = 1000; - - private readonly AsyncPackage _package; - protected readonly ILogWatcherSettingsAccessor _logWatcherSettingsAccessor; - [SuppressMessage( - "Usage", - "CA2213:Disposable fields should be disposed", - Justification = "The timer is Disposed from the StopWatching method when the LogFileWatcherBase class is Disposed")] - private Timer _timer; - private bool _isWatching; - private ILogFileStatus _previousLogFileStatus; - - public event EventHandler LogUpdated; - - protected LogFileWatcherBase(AsyncPackage package, ILogWatcherSettingsAccessor logWatcherSettingsAccessor) - { - _package = package; - _logWatcherSettingsAccessor = logWatcherSettingsAccessor; - } - - protected abstract Task GetLogFileNameAsync(); - - public virtual async System.Threading.Tasks.Task StartWatchingAsync() - { - if (_isWatching) return; - - _previousLogFileStatus = await GetLogFileStatusAsync().ConfigureAwait(true); - - // Using this pattern: https://stackoverflow.com/a/684208/220230 to prevent overlapping timer calls. - // Since Timer callbacks are executed in a ThreadPool thread - // (https://docs.microsoft.com/en-us/dotnet/standard/threading/timers) they won't block the UI thread. - _timer = new Timer(TimerCallback, state: null, 0, Timeout.Infinite); - - _isWatching = true; - } - - [SuppressMessage( - "Usage", - "VSTHRD102:Implement internal logic asynchronously", - Justification = "The event handler must return void. The JoinableTaskFactory.Run is required to run the tasks asynchronously.")] - private void TimerCallback(object state) => ThreadHelper.JoinableTaskFactory.Run(TimerCallbackAsync); - - private async System.Threading.Tasks.Task TimerCallbackAsync() - { - try - { - if (!(await _package.GetDteAsync().ConfigureAwait(true)).SolutionIsOpen()) return; - - var logFileStatus = await GetLogFileStatusAsync().ConfigureAwait(true); - - // Log file has been deleted. - if (logFileStatus == null && _previousLogFileStatus != null) - { - LogUpdated?.Invoke(this, new LogChangedEventArgs - { - LogFileStatus = new LogFileStatus - { - Exists = false, - LastUpdatedUtc = DateTime.UtcNow, - HasContent = false, - Path = _previousLogFileStatus.Path, - }, - }); - } - - // Log file has been added or changed. - else if ((_previousLogFileStatus == null && logFileStatus != null) || - (logFileStatus != null && !logFileStatus.Equals(_previousLogFileStatus))) - { - LogUpdated?.Invoke(this, new LogChangedEventArgs { LogFileStatus = logFileStatus }); - } - - _previousLogFileStatus = logFileStatus; - } - finally - { - try - { - _timer.Change(DefaultLogWatcherTimerIntervalInMilliseconds, Timeout.Infinite); - } - catch (ObjectDisposedException) - { - // This can happen when the Log Watcher is disabled. Just swallowing it not to cause any issues. - } - } - } - - [SuppressMessage( - "Critical Bug", - "S2952:Classes should \"Dispose\" of members from the classes' own \"Dispose\" methods", - Justification = "The timer will be disposed when the watcher is stopped.")] - public virtual void StopWatching() - { - if (!_isWatching) return; - - _timer.Dispose(); - - _isWatching = false; - - _previousLogFileStatus = null; - } - - public async Task GetLogFileStatusAsync() - { - var logFilePath = await GetExistingLogFilePathAsync().ConfigureAwait(true); - - if (string.IsNullOrEmpty(logFilePath)) return null; - - var fileInfo = new FileInfo(logFilePath); - - return new LogFileStatus - { - Exists = fileInfo.Exists, - HasContent = fileInfo.Exists && fileInfo.Length > 0, - Path = fileInfo.FullName, - LastUpdatedUtc = fileInfo.Exists ? (DateTime?)fileInfo.LastWriteTimeUtc : null, - }; - } - - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - StopWatching(); - } - } - - protected virtual async Task GetExistingLogFilePathAsync() - { - var logFilePaths = (await _logWatcherSettingsAccessor.GetSettingsAsync().ConfigureAwait(true)).GetLogFileFolderPaths(); - var dte = await _package.GetDteAsync().ConfigureAwait(true); - var solutionPath = dte.SolutionIsOpen() && !string.IsNullOrEmpty(dte.Solution.FileName) ? - Path.GetDirectoryName(dte.Solution.FileName) : string.Empty; - - var logFileName = await GetLogFileNameAsync().ConfigureAwait(true); - - if (string.IsNullOrEmpty(logFileName)) return null; - - return GetAllMatchingPaths(solutionPath, logFilePaths, logFileName).FirstOrDefault(); - } - - protected virtual IEnumerable GetAllMatchingPaths( - string root, - IEnumerable patterns, - string logFileName) - { - var fullPaths = patterns.Select(pattern => Path.Combine(root, pattern, logFileName).Replace("/", "\\")); - - return fullPaths.SelectMany(fullPath => - { - var parts = fullPath.Split(Path.DirectorySeparatorChar); - - return GetAllMatchingPathsInternal( - string.Join(Path.DirectorySeparatorChar.ToString(), parts.Skip(1)), - parts[0]); - }); - } - - private static IEnumerable GetAllMatchingPathsInternal(string pattern, string root) - { - var parts = pattern.Split(Path.DirectorySeparatorChar); - - for (var i = 0; i < parts.Length; i++) - { - // If this part of the path is a wildcard that needs expanding. - if (parts[i].Contains('*') || parts[i].Contains('?')) - { - var combined = root + - Path.DirectorySeparatorChar + - string.Join(Path.DirectorySeparatorChar.ToString(), parts.Take(i)); - - // Create an absolute path up to the current wildcard and check if it exists. - if (!Directory.Exists(combined)) return Enumerable.Empty(); - - // If this is the end of the path (a file name). - if (i == parts.Length - 1) - { - return Directory.EnumerateFiles(combined, parts[i], SearchOption.TopDirectoryOnly); - } - - // If this is in the middle of the path (a directory name). - var directories = Directory.EnumerateDirectories( - combined, - parts[i], - SearchOption.TopDirectoryOnly); - var paths = directories.SelectMany(directory => - GetAllMatchingPathsInternal( - string.Join(Path.DirectorySeparatorChar.ToString(), parts.Skip(i + 1)), - directory)); - - return paths; - } - } - - // If pattern ends in an absolute path with no wildcards in the filename. - var absolute = root + Path.DirectorySeparatorChar + string.Join(Path.DirectorySeparatorChar.ToString(), parts); - if (File.Exists(absolute)) return new[] { absolute }; - - return Enumerable.Empty(); - } - } -} +using EnvDTE; +using Lombiq.Vsix.Orchard.Models; +using Microsoft.VisualStudio.Shell; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Lombiq.Vsix.Orchard.Services.LogWatcher +{ + public abstract class LogFileWatcherBase : ILogFileWatcher + { + private const int DefaultLogWatcherTimerIntervalInMilliseconds = 1000; + + private readonly AsyncPackage _package; + protected readonly ILogWatcherSettingsAccessor _logWatcherSettingsAccessor; + [SuppressMessage( + "Usage", + "CA2213:Disposable fields should be disposed", + Justification = "The timer is Disposed from the StopWatching method when the LogFileWatcherBase class is Disposed")] + private Timer _timer; + private bool _isWatching; + private ILogFileStatus _previousLogFileStatus; + + public event EventHandler LogUpdated; + + protected LogFileWatcherBase(AsyncPackage package, ILogWatcherSettingsAccessor logWatcherSettingsAccessor) + { + _package = package; + _logWatcherSettingsAccessor = logWatcherSettingsAccessor; + } + + protected abstract Task GetLogFileNameAsync(); + + public virtual async Task StartWatchingAsync() + { + if (_isWatching) return; + + _previousLogFileStatus = await GetLogFileStatusAsync().ConfigureAwait(true); + + // Using this pattern: https://stackoverflow.com/a/684208/220230 to prevent overlapping timer calls. + // Since Timer callbacks are executed in a ThreadPool thread + // (https://docs.microsoft.com/en-us/dotnet/standard/threading/timers) they won't block the UI thread. + _timer = new Timer(TimerCallback, state: null, 0, Timeout.Infinite); + + _isWatching = true; + } + + [SuppressMessage( + "Usage", + "VSTHRD102:Implement internal logic asynchronously", + Justification = "The event handler must return void. The JoinableTaskFactory.Run is required to run the tasks asynchronously.")] + private void TimerCallback(object state) => ThreadHelper.JoinableTaskFactory.Run(TimerCallbackAsync); + + private async Task TimerCallbackAsync() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(_package.DisposalToken); + try + { + if (!(await _package.GetDteAsync().ConfigureAwait(true)).SolutionIsOpen()) return; + + var logFileStatus = await GetLogFileStatusAsync().ConfigureAwait(true); + + // Log file has been deleted. + if (logFileStatus == null && _previousLogFileStatus != null) + { + LogUpdated?.Invoke(this, new LogChangedEventArgs + { + LogFileStatus = new LogFileStatus + { + Exists = false, + LastUpdatedUtc = DateTime.UtcNow, + HasContent = false, + Path = _previousLogFileStatus.Path, + }, + }); + } + + // Log file has been added or changed. + else if ((_previousLogFileStatus == null && logFileStatus != null) || + (logFileStatus != null && !logFileStatus.Equals(_previousLogFileStatus))) + { + LogUpdated?.Invoke(this, new LogChangedEventArgs { LogFileStatus = logFileStatus }); + } + + _previousLogFileStatus = logFileStatus; + } + finally + { + try + { + _timer.Change(DefaultLogWatcherTimerIntervalInMilliseconds, Timeout.Infinite); + } + catch (ObjectDisposedException) + { + // This can happen when the Log Watcher is disabled. Just swallowing it not to cause any issues. + } + } + } + + [SuppressMessage( + "Critical Bug", + "S2952:Classes should \"Dispose\" of members from the classes' own \"Dispose\" methods", + Justification = "The timer will be disposed when the watcher is stopped.")] + public virtual void StopWatching() + { + if (!_isWatching) return; + + _timer.Dispose(); + + _isWatching = false; + + _previousLogFileStatus = null; + } + + public async Task GetLogFileStatusAsync() + { + var logFilePath = await GetExistingLogFilePathAsync().ConfigureAwait(true); + + if (string.IsNullOrEmpty(logFilePath)) return null; + + var fileInfo = new FileInfo(logFilePath); + + return new LogFileStatus + { + Exists = fileInfo.Exists, + HasContent = fileInfo.Exists && fileInfo.Length > 0, + Path = fileInfo.FullName, + LastUpdatedUtc = fileInfo.Exists ? (DateTime?)fileInfo.LastWriteTimeUtc : null, + }; + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + StopWatching(); + } + } + + protected virtual async Task GetExistingLogFilePathAsync() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(_package.DisposalToken); + var logFilePaths = (await _logWatcherSettingsAccessor.GetSettingsAsync().ConfigureAwait(true)).GetLogFileFolderPaths(); + var dte = await _package.GetDteAsync().ConfigureAwait(true); + var solutionPath = dte.SolutionIsOpen() && !string.IsNullOrEmpty(dte.Solution.FileName) ? + Path.GetDirectoryName(dte.Solution.FileName) : string.Empty; + + var logFileName = await GetLogFileNameAsync().ConfigureAwait(true); + + if (string.IsNullOrEmpty(logFileName)) return null; + + return GetAllMatchingPaths(solutionPath, logFilePaths, logFileName).FirstOrDefault(); + } + + protected virtual IEnumerable GetAllMatchingPaths( + string root, + IEnumerable patterns, + string logFileName) + { + var fullPaths = patterns.Select(pattern => Path.Combine(root, pattern, logFileName).Replace("/", "\\")); + + return fullPaths.SelectMany(fullPath => + { + var parts = fullPath.Split(Path.DirectorySeparatorChar); + + return GetAllMatchingPathsInternal( + string.Join(Path.DirectorySeparatorChar.ToString(), parts.Skip(1)), + parts[0]); + }); + } + + private static IEnumerable GetAllMatchingPathsInternal(string pattern, string root) + { + var parts = pattern.Split(Path.DirectorySeparatorChar); + + for (var i = 0; i < parts.Length; i++) + { + // If this part of the path is a wildcard that needs expanding. + if (parts[i].Contains('*') || parts[i].Contains('?')) + { + var combined = root + + Path.DirectorySeparatorChar + + string.Join(Path.DirectorySeparatorChar.ToString(), parts.Take(i)); + + // Create an absolute path up to the current wildcard and check if it exists. + if (!Directory.Exists(combined)) return Enumerable.Empty(); + + // If this is the end of the path (a file name). + if (i == parts.Length - 1) + { + return Directory.EnumerateFiles(combined, parts[i], SearchOption.TopDirectoryOnly); + } + + // If this is in the middle of the path (a directory name). + var directories = Directory.EnumerateDirectories( + combined, + parts[i], + SearchOption.TopDirectoryOnly); + var paths = directories.SelectMany(directory => + GetAllMatchingPathsInternal( + string.Join(Path.DirectorySeparatorChar.ToString(), parts.Skip(i + 1)), + directory)); + + return paths; + } + } + + // If pattern ends in an absolute path with no wildcards in the filename. + var absolute = root + Path.DirectorySeparatorChar + string.Join(Path.DirectorySeparatorChar.ToString(), parts); + if (File.Exists(absolute)) return new[] { absolute }; + + return Enumerable.Empty(); + } + } +} diff --git a/Lombiq.Vsix.Orchard/Services/LogWatcher/OrchardErrorLogFileWatcher.cs b/Lombiq.Vsix.Orchard/Services/LogWatcher/OrchardErrorLogFileWatcher.cs index 68181bf..d7c3f2e 100644 --- a/Lombiq.Vsix.Orchard/Services/LogWatcher/OrchardErrorLogFileWatcher.cs +++ b/Lombiq.Vsix.Orchard/Services/LogWatcher/OrchardErrorLogFileWatcher.cs @@ -1,18 +1,18 @@ -using Microsoft.VisualStudio.Shell; -using System; -using System.Globalization; -using System.Threading.Tasks; - -namespace Lombiq.Vsix.Orchard.Services.LogWatcher -{ - public sealed class OrchardErrorLogFileWatcher : LogFileWatcherBase - { - protected override Task GetLogFileNameAsync() => - System.Threading.Tasks.Task.FromResult( - $"orchard-error-{DateTime.Today.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}.log"); - - public OrchardErrorLogFileWatcher(AsyncPackage package, ILogWatcherSettingsAccessor logWatcherSettingsAccessor) - : base(package, logWatcherSettingsAccessor) - { } - } -} +using Microsoft.VisualStudio.Shell; +using System; +using System.Globalization; +using System.Threading.Tasks; + +namespace Lombiq.Vsix.Orchard.Services.LogWatcher +{ + public sealed class OrchardErrorLogFileWatcher : LogFileWatcherBase + { + protected override Task GetLogFileNameAsync() => + Task.FromResult( + $"orchard-error-{DateTime.Today.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)}.log"); + + public OrchardErrorLogFileWatcher(AsyncPackage package, ILogWatcherSettingsAccessor logWatcherSettingsAccessor) + : base(package, logWatcherSettingsAccessor) + { } + } +} diff --git a/Lombiq.Vsix.Orchard/VSPackage.Designer.cs b/Lombiq.Vsix.Orchard/VSPackage.Designer.cs index 8775d2e..c3e32aa 100644 --- a/Lombiq.Vsix.Orchard/VSPackage.Designer.cs +++ b/Lombiq.Vsix.Orchard/VSPackage.Designer.cs @@ -19,7 +19,7 @@ namespace Lombiq.Vsix.Orchard { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class VSPackage { diff --git a/Lombiq.Vsix.Orchard/VSPackage.resx b/Lombiq.Vsix.Orchard/VSPackage.resx index 1820813..adceb46 100644 --- a/Lombiq.Vsix.Orchard/VSPackage.resx +++ b/Lombiq.Vsix.Orchard/VSPackage.resx @@ -1,4 +1,4 @@ - +