From a2a2451a6bf029e425615958b2af0254d7e0aaaf Mon Sep 17 00:00:00 2001 From: Alexander Kislitsyn <4lex.kislitsyn@gmail.com> Date: Thu, 21 May 2020 17:02:59 +0300 Subject: [PATCH 01/11] Added options to configure window name to log into. Change window search mechanism. --- .../LoggingBuilderExtensions.cs | 11 +++++-- Notepad.Extensions.Logging/NotepadLogger.cs | 33 +++++++++++-------- .../NotepadLoggerProvider.cs | 8 +++-- .../NotepadProviderOptions.cs | 21 ++++++++++++ 4 files changed, 55 insertions(+), 18 deletions(-) create mode 100644 Notepad.Extensions.Logging/NotepadProviderOptions.cs diff --git a/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs b/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs index 77a6920..6ec155c 100644 --- a/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs +++ b/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs @@ -1,10 +1,17 @@ -namespace Microsoft.Extensions.Logging +using System; + +namespace Microsoft.Extensions.Logging { public static class LoggingBuilderExtensions { public static ILoggingBuilder AddNotepad(this ILoggingBuilder builder) + => AddNotepad(builder, null); + + public static ILoggingBuilder AddNotepad(this ILoggingBuilder builder, Action optionsConfiguration) { - builder.AddProvider(NotepadLoggerProvider.Instance); + var options = NotepadProviderOptions.Default; + optionsConfiguration?.Invoke(options); + builder.AddProvider(new NotepadLoggerProvider(options)); return builder; } } diff --git a/Notepad.Extensions.Logging/NotepadLogger.cs b/Notepad.Extensions.Logging/NotepadLogger.cs index 2636b13..a4f1dcf 100644 --- a/Notepad.Extensions.Logging/NotepadLogger.cs +++ b/Notepad.Extensions.Logging/NotepadLogger.cs @@ -7,14 +7,16 @@ namespace Microsoft.Extensions.Logging { class NotepadLogger : ILogger { - public NotepadLogger(ObjectPool stringBuilderPool, string categoryName) + public NotepadLogger(ObjectPool stringBuilderPool, string categoryName, string windowName = null) { this.stringBuilderPool = stringBuilderPool; this.categoryName = categoryName; + this.windowName = windowName; } readonly ObjectPool stringBuilderPool; readonly string categoryName; + readonly string windowName; public IDisposable BeginScope(TState state) => NullDisposable.Instance; @@ -82,30 +84,33 @@ namespace Microsoft.Extensions.Logging _ => throw new ArgumentOutOfRangeException(nameof(logLevel)), }; - static void WriteToNotepad(string message) + void WriteToNotepad(string message) { IntPtr hwnd = FindNotepadWindow(); + + if (hwnd.Equals(IntPtr.Zero)) + { + return; + } + IntPtr edit = NativeMethods.FindWindowEx(hwnd, IntPtr.Zero, "EDIT", null); NativeMethods.SendMessage(edit, NativeMethods.EM_REPLACESEL, (IntPtr)1, message); } - static IntPtr FindNotepadWindow() + IntPtr FindNotepadWindow() { IntPtr hwnd; - - hwnd = NativeMethods.FindWindow(null, "Untitled - Notepad"); - if (hwnd != IntPtr.Zero) - { - return hwnd; - } - hwnd = NativeMethods.FindWindow(null, "*Untitled - Notepad"); - if (hwnd != IntPtr.Zero) + hwnd = NativeMethods.FindWindow(null, windowName); + if (hwnd.Equals(IntPtr.Zero)) { - return hwnd; + // when the file changes, notepad changes the name to "* Window Name", so later created loggers cannot find window + var builder = stringBuilderPool.Get(); + builder.Append("*").Append(windowName); + hwnd = NativeMethods.FindWindow(null, builder.ToString()); + stringBuilderPool.Return(builder); } - - return IntPtr.Zero; + return hwnd; } } diff --git a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs index e8c87b6..b56734b 100644 --- a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs +++ b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs @@ -11,11 +11,15 @@ namespace Microsoft.Extensions.Logging stringBuilderPool = poolProvider.CreateStringBuilderPool(); } - public static ILoggerProvider Instance { get; } = new NotepadLoggerProvider(); + internal NotepadLoggerProvider(NotepadProviderOptions options) : this() + { + this.options = options; + } readonly ObjectPool stringBuilderPool; + readonly NotepadProviderOptions options; - public ILogger CreateLogger(string categoryName) => new NotepadLogger(stringBuilderPool, categoryName); + public ILogger CreateLogger(string categoryName) => new NotepadLogger(stringBuilderPool, categoryName, options.WindowName); public void Dispose() { diff --git a/Notepad.Extensions.Logging/NotepadProviderOptions.cs b/Notepad.Extensions.Logging/NotepadProviderOptions.cs new file mode 100644 index 0000000..8de59d5 --- /dev/null +++ b/Notepad.Extensions.Logging/NotepadProviderOptions.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Extensions.Logging +{ + public class NotepadProviderOptions + { + /// + /// Default options for . + /// + public static NotepadProviderOptions Default { get; } = new NotepadProviderOptions + { + WindowName = "Untitled - Notepad", + }; + /// + /// Name of window to search. + /// + public string WindowName { get; set; } + } +} From ed95e98a27efbbc1815ee3a88aafca8936bac7fe Mon Sep 17 00:00:00 2001 From: Alexander Kislitsyn <4lex.kislitsyn@gmail.com> Date: Thu, 21 May 2020 18:52:06 +0300 Subject: [PATCH 02/11] Removed default value and added exception. --- Notepad.Extensions.Logging/NotepadLogger.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Notepad.Extensions.Logging/NotepadLogger.cs b/Notepad.Extensions.Logging/NotepadLogger.cs index a4f1dcf..c0a0d6c 100644 --- a/Notepad.Extensions.Logging/NotepadLogger.cs +++ b/Notepad.Extensions.Logging/NotepadLogger.cs @@ -7,11 +7,11 @@ namespace Microsoft.Extensions.Logging { class NotepadLogger : ILogger { - public NotepadLogger(ObjectPool stringBuilderPool, string categoryName, string windowName = null) + public NotepadLogger(ObjectPool stringBuilderPool, string categoryName, string windowName) { this.stringBuilderPool = stringBuilderPool; this.categoryName = categoryName; - this.windowName = windowName; + this.windowName = windowName ?? throw new ArgumentNullException("Window name cannot be null."); } readonly ObjectPool stringBuilderPool; From 7b4969ce0d7cf32a77e368da6f18010498e9f4d3 Mon Sep 17 00:00:00 2001 From: Alexander Kislitsyn <4lex.kislitsyn@gmail.com> Date: Fri, 22 May 2020 08:19:42 +0300 Subject: [PATCH 03/11] Removed string builder from logger. Added support of IOptions. --- .../LoggingBuilderExtensions.cs | 27 ++++++++++++++----- .../Notepad.Extensions.Logging.csproj | 1 + Notepad.Extensions.Logging/NotepadLogger.cs | 10 +++---- .../NotepadLoggerOptions.cs | 14 ++++++++++ .../NotepadLoggerProvider.cs | 27 +++++++++++++++---- .../NotepadProviderOptions.cs | 21 --------------- 6 files changed, 61 insertions(+), 39 deletions(-) create mode 100644 Notepad.Extensions.Logging/NotepadLoggerOptions.cs delete mode 100644 Notepad.Extensions.Logging/NotepadProviderOptions.cs diff --git a/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs b/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs index 6ec155c..54a242c 100644 --- a/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs +++ b/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs @@ -1,17 +1,30 @@ -using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging.Configuration; +using Microsoft.Extensions.Options; +using System; namespace Microsoft.Extensions.Logging { public static class LoggingBuilderExtensions { public static ILoggingBuilder AddNotepad(this ILoggingBuilder builder) - => AddNotepad(builder, null); - - public static ILoggingBuilder AddNotepad(this ILoggingBuilder builder, Action optionsConfiguration) { - var options = NotepadProviderOptions.Default; - optionsConfiguration?.Invoke(options); - builder.AddProvider(new NotepadLoggerProvider(options)); + builder.AddConfiguration(); + + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); + LoggerProviderOptions.RegisterProviderOptions(builder.Services); + return builder; + } + + public static ILoggingBuilder AddNotepad(this ILoggingBuilder builder, Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + AddNotepad(builder); + builder.Services.Configure(configure); return builder; } } diff --git a/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj b/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj index 6249221..b7eefed 100644 --- a/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj +++ b/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj @@ -14,6 +14,7 @@ + diff --git a/Notepad.Extensions.Logging/NotepadLogger.cs b/Notepad.Extensions.Logging/NotepadLogger.cs index c0a0d6c..228046b 100644 --- a/Notepad.Extensions.Logging/NotepadLogger.cs +++ b/Notepad.Extensions.Logging/NotepadLogger.cs @@ -11,12 +11,14 @@ namespace Microsoft.Extensions.Logging { this.stringBuilderPool = stringBuilderPool; this.categoryName = categoryName; - this.windowName = windowName ?? throw new ArgumentNullException("Window name cannot be null."); + this.windowName = windowName ?? throw new ArgumentNullException(nameof(windowName)); + this.changedWindowName = $"*{windowName}"; } readonly ObjectPool stringBuilderPool; readonly string categoryName; readonly string windowName; + readonly string changedWindowName; public IDisposable BeginScope(TState state) => NullDisposable.Instance; @@ -104,11 +106,7 @@ namespace Microsoft.Extensions.Logging hwnd = NativeMethods.FindWindow(null, windowName); if (hwnd.Equals(IntPtr.Zero)) { - // when the file changes, notepad changes the name to "* Window Name", so later created loggers cannot find window - var builder = stringBuilderPool.Get(); - builder.Append("*").Append(windowName); - hwnd = NativeMethods.FindWindow(null, builder.ToString()); - stringBuilderPool.Return(builder); + hwnd = NativeMethods.FindWindow(null, changedWindowName); } return hwnd; } diff --git a/Notepad.Extensions.Logging/NotepadLoggerOptions.cs b/Notepad.Extensions.Logging/NotepadLoggerOptions.cs new file mode 100644 index 0000000..8894945 --- /dev/null +++ b/Notepad.Extensions.Logging/NotepadLoggerOptions.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Extensions.Logging +{ + public class NotepadLoggerOptions + { + /// + /// Name of window to search. + /// + public string WindowName { get; set; } = "Untitled - Notepad"; + } +} diff --git a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs index b56734b..f291622 100644 --- a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs +++ b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs @@ -1,28 +1,45 @@ -using System.Text; +using System; +using System.Text; using Microsoft.Extensions.ObjectPool; +using Microsoft.Extensions.Options; namespace Microsoft.Extensions.Logging { + [ProviderAlias("Notepad")] class NotepadLoggerProvider : ILoggerProvider { + readonly ObjectPool stringBuilderPool; + readonly IDisposable optionsReloadToken; + NotepadLoggerOptions options; + NotepadLoggerProvider() { var poolProvider = new DefaultObjectPoolProvider(); stringBuilderPool = poolProvider.CreateStringBuilderPool(); } - internal NotepadLoggerProvider(NotepadProviderOptions options) : this() + public NotepadLoggerProvider(IOptionsMonitor options) : this() + { + // Filter would be applied on LoggerFactory level + optionsReloadToken = options.OnChange(ReloadLoggerOptions); + ReloadLoggerOptions(options.CurrentValue); + } + + public NotepadLoggerProvider(NotepadLoggerOptions options) : this() { this.options = options; } - readonly ObjectPool stringBuilderPool; - readonly NotepadProviderOptions options; - public ILogger CreateLogger(string categoryName) => new NotepadLogger(stringBuilderPool, categoryName, options.WindowName); public void Dispose() { + optionsReloadToken?.Dispose(); + } + + private void ReloadLoggerOptions(NotepadLoggerOptions options) + { + this.options = options; } } } diff --git a/Notepad.Extensions.Logging/NotepadProviderOptions.cs b/Notepad.Extensions.Logging/NotepadProviderOptions.cs deleted file mode 100644 index 8de59d5..0000000 --- a/Notepad.Extensions.Logging/NotepadProviderOptions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.Extensions.Logging -{ - public class NotepadProviderOptions - { - /// - /// Default options for . - /// - public static NotepadProviderOptions Default { get; } = new NotepadProviderOptions - { - WindowName = "Untitled - Notepad", - }; - /// - /// Name of window to search. - /// - public string WindowName { get; set; } - } -} From c658b437b58bdb21faa22a937b2f81959e331ee6 Mon Sep 17 00:00:00 2001 From: Yaakov Date: Sat, 23 May 2020 19:05:09 +1000 Subject: [PATCH 04/11] Remove statics and threadstatic so that we can pass options around --- Notepad.Extensions.Logging/IWindowFinder.cs | 7 ++ Notepad.Extensions.Logging/NativeMethods.cs | 4 +- Notepad.Extensions.Logging/NotepadLogger.cs | 17 ++-- .../NotepadLoggerProvider.cs | 4 +- .../WindowEnumerationState.cs | 72 ++++++++++++++++ .../WindowEnumerationStatePoolingPolicy.cs | 17 ++++ Notepad.Extensions.Logging/WindowFinder.cs | 84 ++++--------------- Notepad.Extensions.Logging/WindowInfo.cs | 16 ++++ Notepad.Extensions.Logging/WindowKind.cs | 2 +- 9 files changed, 143 insertions(+), 80 deletions(-) create mode 100644 Notepad.Extensions.Logging/IWindowFinder.cs create mode 100644 Notepad.Extensions.Logging/WindowEnumerationState.cs create mode 100644 Notepad.Extensions.Logging/WindowEnumerationStatePoolingPolicy.cs create mode 100644 Notepad.Extensions.Logging/WindowInfo.cs diff --git a/Notepad.Extensions.Logging/IWindowFinder.cs b/Notepad.Extensions.Logging/IWindowFinder.cs new file mode 100644 index 0000000..b8d446a --- /dev/null +++ b/Notepad.Extensions.Logging/IWindowFinder.cs @@ -0,0 +1,7 @@ +namespace Notepad.Extensions.Logging +{ + interface IWindowFinder + { + WindowInfo FindNotepadWindow(); + } +} \ No newline at end of file diff --git a/Notepad.Extensions.Logging/NativeMethods.cs b/Notepad.Extensions.Logging/NativeMethods.cs index 873b5de..af5abb8 100644 --- a/Notepad.Extensions.Logging/NativeMethods.cs +++ b/Notepad.Extensions.Logging/NativeMethods.cs @@ -22,10 +22,10 @@ namespace Notepad.Extensions.Logging [DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); - public delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lParam); + public delegate bool EnumWindowsDelegate(IntPtr hWnd, object lParam); [DllImport("user32.dll")] - public static extern bool EnumWindows(EnumWindowsDelegate lpEnumFunc, IntPtr lParam); + public static extern bool EnumWindows(EnumWindowsDelegate lpEnumFunc, object lParam); [DllImport("User32.dll")] public static extern int GetWindowText(IntPtr hWndParent, StringBuilder sb, int maxCount); diff --git a/Notepad.Extensions.Logging/NotepadLogger.cs b/Notepad.Extensions.Logging/NotepadLogger.cs index 7679e7b..3eab97f 100644 --- a/Notepad.Extensions.Logging/NotepadLogger.cs +++ b/Notepad.Extensions.Logging/NotepadLogger.cs @@ -1,6 +1,5 @@ using System; using System.Buffers; -using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; @@ -12,13 +11,15 @@ namespace Notepad.Extensions.Logging { class NotepadLogger : ILogger { - public NotepadLogger(ObjectPool stringBuilderPool, string categoryName) + public NotepadLogger(ObjectPool stringBuilderPool, IWindowFinder windowFinder, string categoryName) { - this.stringBuilderPool = stringBuilderPool; + this.stringBuilderPool = stringBuilderPool ?? throw new ArgumentNullException(nameof(stringBuilderPool)); + this.windowFinder = windowFinder ?? throw new ArgumentNullException(nameof(windowFinder)); this.categoryName = categoryName; } readonly ObjectPool stringBuilderPool; + readonly IWindowFinder windowFinder; readonly string categoryName; public IDisposable BeginScope(TState state) => NullDisposable.Instance; @@ -87,18 +88,18 @@ namespace Notepad.Extensions.Logging _ => throw new ArgumentOutOfRangeException(nameof(logLevel)), }; - static void WriteToNotepad(string message) + void WriteToNotepad(string message) { - var (kind, hwnd) = WindowFinder.FindNotepadWindow(); - switch (kind) + var info = windowFinder.FindNotepadWindow(); + switch (info.Kind) { case WindowKind.Notepad: - SendMessage(hwnd, EM_REPLACESEL, (IntPtr)1, message); + SendMessage(info.Handle, EM_REPLACESEL, (IntPtr)1, message); break; case WindowKind.NotepadPlusPlus: { - WriteToNotepadPlusPlus(hwnd, message); + WriteToNotepadPlusPlus(info.Handle, message); break; } } diff --git a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs index 601cb8a..5c479b8 100644 --- a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs +++ b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs @@ -10,13 +10,15 @@ namespace Notepad.Extensions.Logging { var poolProvider = new DefaultObjectPoolProvider(); stringBuilderPool = poolProvider.CreateStringBuilderPool(); + windowFinder = new WindowFinder(); } public static ILoggerProvider Instance { get; } = new NotepadLoggerProvider(); readonly ObjectPool stringBuilderPool; + readonly IWindowFinder windowFinder; - public ILogger CreateLogger(string categoryName) => new NotepadLogger(stringBuilderPool, categoryName); + public ILogger CreateLogger(string categoryName) => new NotepadLogger(stringBuilderPool, windowFinder, categoryName); public void Dispose() { diff --git a/Notepad.Extensions.Logging/WindowEnumerationState.cs b/Notepad.Extensions.Logging/WindowEnumerationState.cs new file mode 100644 index 0000000..2d13e75 --- /dev/null +++ b/Notepad.Extensions.Logging/WindowEnumerationState.cs @@ -0,0 +1,72 @@ +using System; +using System.ComponentModel; +using System.Text; +using System.Text.RegularExpressions; + +namespace Notepad.Extensions.Logging +{ + class WindowEnumerationState + { + public WindowEnumerationState() + { + sb = new StringBuilder(capacity: 4096); + } + + readonly StringBuilder sb; + + public IntPtr Handle { get; private set; } + public WindowKind WindowKind { get; private set; } + + public void Reset() + { + Handle = default; + WindowKind = default; + sb.Clear(); + } + + public bool ExamineWindow(IntPtr hWnd) + { + var result = NativeMethods.GetWindowText(hWnd, sb, sb.Capacity); + if (result < 0) + { + throw new Win32Exception(result); + } + + Handle = hWnd; + + if (sb.Length > 0 && sb[0] == '*') + { + // Notepad and Notepad++ both mark dirty documents by adding a leading asterisk to the window name. + sb.Remove(0, 1); + } + + if (IsKnownNotepadWindow(sb.ToString())) + { + return false; + } + return true; + } + + static Regex notepadPlusPlusRegex = new Regex(@"^new \d+ - Notepad\+\+$", RegexOptions.Compiled); + + bool IsKnownNotepadWindow(string titleText) + { + switch (titleText) + { + case "Untitled - Notepad": + WindowKind = WindowKind.Notepad; + Handle = NativeMethods.FindWindowEx(Handle, IntPtr.Zero, "EDIT", null); + return true; + } + + if (notepadPlusPlusRegex.IsMatch(titleText)) + { + WindowKind = WindowKind.NotepadPlusPlus; + Handle = NativeMethods.FindWindowEx(Handle, IntPtr.Zero, "Scintilla", null); + return true; + } + + return false; + } + } +} diff --git a/Notepad.Extensions.Logging/WindowEnumerationStatePoolingPolicy.cs b/Notepad.Extensions.Logging/WindowEnumerationStatePoolingPolicy.cs new file mode 100644 index 0000000..7056cab --- /dev/null +++ b/Notepad.Extensions.Logging/WindowEnumerationStatePoolingPolicy.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.ObjectPool; + +namespace Notepad.Extensions.Logging +{ + class WindowEnumerationStatePoolingPolicy : IPooledObjectPolicy + { + public static IPooledObjectPolicy Instance { get; } = new WindowEnumerationStatePoolingPolicy(); + + public WindowEnumerationState Create() => new WindowEnumerationState(); + + public bool Return(WindowEnumerationState obj) + { + obj.Reset(); + return true; + } + } +} diff --git a/Notepad.Extensions.Logging/WindowFinder.cs b/Notepad.Extensions.Logging/WindowFinder.cs index b716ec9..ce56966 100644 --- a/Notepad.Extensions.Logging/WindowFinder.cs +++ b/Notepad.Extensions.Logging/WindowFinder.cs @@ -1,89 +1,37 @@ using System; -using System.ComponentModel; -using System.Text; -using System.Text.RegularExpressions; +using Microsoft.Extensions.ObjectPool; namespace Notepad.Extensions.Logging { - static class WindowFinder + class WindowFinder : IWindowFinder { - public static (WindowKind kind, IntPtr hwnd) FindNotepadWindow() + public WindowFinder() { - sb ??= new StringBuilder(4096); + statePool = new DefaultObjectPool(WindowEnumerationStatePoolingPolicy.Instance); + } + readonly ObjectPool statePool; + + public WindowInfo FindNotepadWindow() + { + var stateObject = statePool.Get(); try { - FindMainWindow(); - return (windowKind, handle); + NativeMethods.EnumWindows(enumWindowsDelegate, stateObject); + return new WindowInfo(stateObject.WindowKind, stateObject.Handle); } finally { - handle = IntPtr.Zero; - sb.Clear(); - windowKind = WindowKind.Invalid; + statePool.Return(stateObject); } } - static IntPtr FindMainWindow() - { - NativeMethods.EnumWindows(enumWindowsDelegate, IntPtr.Zero); - return handle; - } - static NativeMethods.EnumWindowsDelegate enumWindowsDelegate = new NativeMethods.EnumWindowsDelegate(EnumWindowsCallback); - static bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam) + static bool EnumWindowsCallback(IntPtr hWnd, object lParam) { - var result = NativeMethods.GetWindowText(hWnd, sb, sb.Capacity); - if (result < 0) - { - throw new Win32Exception(result); - } - - WindowFinder.handle = hWnd; - - if (sb.Length > 0 && sb[0] == '*') - { - // Notepad and Notepad++ both mark dirty documents by adding a leading asterisk to the window name. - sb.Remove(0, 1); - } - - if (IsKnownNotepadWindow(sb.ToString())) - { - return false; - } - return true; - } - - [ThreadStatic] - static IntPtr handle; - - [ThreadStatic] - static WindowKind windowKind; - - [ThreadStatic] - static StringBuilder sb; - - static Regex notepadPlusPlusRegex = new Regex(@"^new \d+ - Notepad\+\+$", RegexOptions.Compiled); - - static bool IsKnownNotepadWindow(string titleText) - { - switch (titleText) - { - case "Untitled - Notepad": - windowKind = WindowKind.Notepad; - handle = NativeMethods.FindWindowEx(handle, IntPtr.Zero, "EDIT", null); - return true; - } - - if (notepadPlusPlusRegex.IsMatch(titleText)) - { - windowKind = WindowKind.NotepadPlusPlus; - handle = NativeMethods.FindWindowEx(handle, IntPtr.Zero, "Scintilla", null); - return true; - } - - return false; + var state = (WindowEnumerationState)lParam; + return state.ExamineWindow(hWnd); } } } diff --git a/Notepad.Extensions.Logging/WindowInfo.cs b/Notepad.Extensions.Logging/WindowInfo.cs new file mode 100644 index 0000000..6629774 --- /dev/null +++ b/Notepad.Extensions.Logging/WindowInfo.cs @@ -0,0 +1,16 @@ +using System; + +namespace Notepad.Extensions.Logging +{ + struct WindowInfo + { + public WindowInfo(WindowKind kind, IntPtr handle) + { + Kind = kind; + Handle = handle; + } + + public WindowKind Kind { get; } + public IntPtr Handle { get; } + } +} \ No newline at end of file diff --git a/Notepad.Extensions.Logging/WindowKind.cs b/Notepad.Extensions.Logging/WindowKind.cs index d79e383..cd8c57d 100644 --- a/Notepad.Extensions.Logging/WindowKind.cs +++ b/Notepad.Extensions.Logging/WindowKind.cs @@ -1,6 +1,6 @@ namespace Notepad.Extensions.Logging { - public enum WindowKind + enum WindowKind { Invalid, Notepad, From d2e66538e0a992f4048fef72a168a21337325280 Mon Sep 17 00:00:00 2001 From: Alexander Kislitsyn <4lex.kislitsyn@gmail.com> Date: Sat, 23 May 2020 13:00:55 +0300 Subject: [PATCH 05/11] Added support of window name from options in WindowEnumerationState class. --- .../LoggingBuilderExtensions.cs | 1 + Notepad.Extensions.Logging/NotepadLogger.cs | 5 +- .../NotepadLoggerProvider.cs | 4 ++ .../WindowEnumerationState.cs | 52 ++++++++++++++----- Notepad.Extensions.Logging/WindowFinder.cs | 3 ++ 5 files changed, 48 insertions(+), 17 deletions(-) diff --git a/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs b/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs index 54a242c..af2dc4b 100644 --- a/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs +++ b/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging.Configuration; using Microsoft.Extensions.Options; +using Notepad.Extensions.Logging; using System; namespace Microsoft.Extensions.Logging diff --git a/Notepad.Extensions.Logging/NotepadLogger.cs b/Notepad.Extensions.Logging/NotepadLogger.cs index 3eab97f..94ccff6 100644 --- a/Notepad.Extensions.Logging/NotepadLogger.cs +++ b/Notepad.Extensions.Logging/NotepadLogger.cs @@ -94,14 +94,11 @@ namespace Notepad.Extensions.Logging switch (info.Kind) { case WindowKind.Notepad: - SendMessage(info.Handle, EM_REPLACESEL, (IntPtr)1, message); + SendMessage(info.Handle, EM_REPLACESEL, (IntPtr)1, message); break; - case WindowKind.NotepadPlusPlus: - { WriteToNotepadPlusPlus(info.Handle, message); break; - } } } diff --git a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs index c5cc50f..fa41322 100644 --- a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs +++ b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs @@ -42,6 +42,10 @@ namespace Notepad.Extensions.Logging private void ReloadLoggerOptions(NotepadLoggerOptions options) { this.options = options; + if (windowFinder is WindowFinder finder) + { + finder.WindowName = this.options.WindowName; + } } } } diff --git a/Notepad.Extensions.Logging/WindowEnumerationState.cs b/Notepad.Extensions.Logging/WindowEnumerationState.cs index 2d13e75..9eddf2e 100644 --- a/Notepad.Extensions.Logging/WindowEnumerationState.cs +++ b/Notepad.Extensions.Logging/WindowEnumerationState.cs @@ -16,11 +16,13 @@ namespace Notepad.Extensions.Logging public IntPtr Handle { get; private set; } public WindowKind WindowKind { get; private set; } + public string WindowName { get; internal set; } public void Reset() { Handle = default; WindowKind = default; + WindowName = default; sb.Clear(); } @@ -47,26 +49,50 @@ namespace Notepad.Extensions.Logging return true; } - static Regex notepadPlusPlusRegex = new Regex(@"^new \d+ - Notepad\+\+$", RegexOptions.Compiled); + static Regex notepadPlusPlusRegex = new Regex(@" - Notepad\+\+$", RegexOptions.Compiled); bool IsKnownNotepadWindow(string titleText) { - switch (titleText) + if (!string.IsNullOrWhiteSpace(WindowName)) { - case "Untitled - Notepad": - WindowKind = WindowKind.Notepad; - Handle = NativeMethods.FindWindowEx(Handle, IntPtr.Zero, "EDIT", null); - return true; + if (WindowName.Equals(titleText, StringComparison.Ordinal)) + { + WindowKind = notepadPlusPlusRegex.IsMatch(titleText) ? WindowKind.NotepadPlusPlus : WindowKind.Notepad; + } + } + else + { + switch (titleText) + { + case "Untitled - Notepad": + WindowKind = WindowKind.Notepad; + break; + default: + if (notepadPlusPlusRegex.IsMatch(titleText)) + { + WindowKind = WindowKind.NotepadPlusPlus; + } + break; + + } } - if (notepadPlusPlusRegex.IsMatch(titleText)) - { - WindowKind = WindowKind.NotepadPlusPlus; - Handle = NativeMethods.FindWindowEx(Handle, IntPtr.Zero, "Scintilla", null); - return true; - } + Handle = FindInnerWindow(WindowKind); - return false; + return WindowKind != default; + } + + IntPtr FindInnerWindow(WindowKind windowKind) + { + switch (windowKind) + { + case WindowKind.Notepad: + return NativeMethods.FindWindowEx(Handle, IntPtr.Zero, "EDIT", null); + case WindowKind.NotepadPlusPlus: + return NativeMethods.FindWindowEx(Handle, IntPtr.Zero, "Scintilla", null); + default: + return Handle; + } } } } diff --git a/Notepad.Extensions.Logging/WindowFinder.cs b/Notepad.Extensions.Logging/WindowFinder.cs index ce56966..c5d5a17 100644 --- a/Notepad.Extensions.Logging/WindowFinder.cs +++ b/Notepad.Extensions.Logging/WindowFinder.cs @@ -12,11 +12,14 @@ namespace Notepad.Extensions.Logging readonly ObjectPool statePool; + public string WindowName { get; internal set; } + public WindowInfo FindNotepadWindow() { var stateObject = statePool.Get(); try { + stateObject.WindowName = WindowName; NativeMethods.EnumWindows(enumWindowsDelegate, stateObject); return new WindowInfo(stateObject.WindowKind, stateObject.Handle); } From bf96d0622a88117c2f642ddda581a32915d329cf Mon Sep 17 00:00:00 2001 From: Alexander Kislitsyn <4lex.kislitsyn@gmail.com> Date: Sat, 23 May 2020 13:07:49 +0300 Subject: [PATCH 06/11] Removed private keyword. --- Notepad.Extensions.Logging/NotepadLoggerProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs index fa41322..99425a5 100644 --- a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs +++ b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs @@ -39,7 +39,7 @@ namespace Notepad.Extensions.Logging optionsReloadToken?.Dispose(); } - private void ReloadLoggerOptions(NotepadLoggerOptions options) + void ReloadLoggerOptions(NotepadLoggerOptions options) { this.options = options; if (windowFinder is WindowFinder finder) From f1eafc15dbb17cdff788d54b5fcde9f16e703e4e Mon Sep 17 00:00:00 2001 From: Alexander Kislitsyn <4lex.kislitsyn@gmail.com> Date: Sat, 23 May 2020 14:54:13 +0300 Subject: [PATCH 07/11] Moved window name to IWindowFinder method parameter from property. --- Notepad.Extensions.Logging/IWindowFinder.cs | 2 +- Notepad.Extensions.Logging/NotepadLogger.cs | 6 +++-- .../NotepadLoggerOptions.cs | 2 +- .../NotepadLoggerProvider.cs | 6 +---- .../WindowEnumerationState.cs | 24 +++++++------------ Notepad.Extensions.Logging/WindowFinder.cs | 6 ++--- 6 files changed, 17 insertions(+), 29 deletions(-) diff --git a/Notepad.Extensions.Logging/IWindowFinder.cs b/Notepad.Extensions.Logging/IWindowFinder.cs index b8d446a..76dd275 100644 --- a/Notepad.Extensions.Logging/IWindowFinder.cs +++ b/Notepad.Extensions.Logging/IWindowFinder.cs @@ -2,6 +2,6 @@ { interface IWindowFinder { - WindowInfo FindNotepadWindow(); + WindowInfo FindNotepadWindow(string windowName); } } \ No newline at end of file diff --git a/Notepad.Extensions.Logging/NotepadLogger.cs b/Notepad.Extensions.Logging/NotepadLogger.cs index 94ccff6..a896938 100644 --- a/Notepad.Extensions.Logging/NotepadLogger.cs +++ b/Notepad.Extensions.Logging/NotepadLogger.cs @@ -11,16 +11,18 @@ namespace Notepad.Extensions.Logging { class NotepadLogger : ILogger { - public NotepadLogger(ObjectPool stringBuilderPool, IWindowFinder windowFinder, string categoryName) + public NotepadLogger(ObjectPool stringBuilderPool, IWindowFinder windowFinder, string categoryName, string windowName) { this.stringBuilderPool = stringBuilderPool ?? throw new ArgumentNullException(nameof(stringBuilderPool)); this.windowFinder = windowFinder ?? throw new ArgumentNullException(nameof(windowFinder)); this.categoryName = categoryName; + this.windowName = windowName; } readonly ObjectPool stringBuilderPool; readonly IWindowFinder windowFinder; readonly string categoryName; + readonly string windowName; public IDisposable BeginScope(TState state) => NullDisposable.Instance; @@ -90,7 +92,7 @@ namespace Notepad.Extensions.Logging void WriteToNotepad(string message) { - var info = windowFinder.FindNotepadWindow(); + var info = windowFinder.FindNotepadWindow(windowName); switch (info.Kind) { case WindowKind.Notepad: diff --git a/Notepad.Extensions.Logging/NotepadLoggerOptions.cs b/Notepad.Extensions.Logging/NotepadLoggerOptions.cs index 8894945..b5eb08f 100644 --- a/Notepad.Extensions.Logging/NotepadLoggerOptions.cs +++ b/Notepad.Extensions.Logging/NotepadLoggerOptions.cs @@ -9,6 +9,6 @@ namespace Microsoft.Extensions.Logging /// /// Name of window to search. /// - public string WindowName { get; set; } = "Untitled - Notepad"; + public string WindowName { get; set; } } } diff --git a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs index 99425a5..7407e5b 100644 --- a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs +++ b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs @@ -32,7 +32,7 @@ namespace Notepad.Extensions.Logging readonly IDisposable optionsReloadToken; NotepadLoggerOptions options; - public ILogger CreateLogger(string categoryName) => new NotepadLogger(stringBuilderPool, windowFinder, categoryName); + public ILogger CreateLogger(string categoryName) => new NotepadLogger(stringBuilderPool, windowFinder, categoryName, options.WindowName); public void Dispose() { @@ -42,10 +42,6 @@ namespace Notepad.Extensions.Logging void ReloadLoggerOptions(NotepadLoggerOptions options) { this.options = options; - if (windowFinder is WindowFinder finder) - { - finder.WindowName = this.options.WindowName; - } } } } diff --git a/Notepad.Extensions.Logging/WindowEnumerationState.cs b/Notepad.Extensions.Logging/WindowEnumerationState.cs index 9eddf2e..bd1a5ad 100644 --- a/Notepad.Extensions.Logging/WindowEnumerationState.cs +++ b/Notepad.Extensions.Logging/WindowEnumerationState.cs @@ -49,7 +49,7 @@ namespace Notepad.Extensions.Logging return true; } - static Regex notepadPlusPlusRegex = new Regex(@" - Notepad\+\+$", RegexOptions.Compiled); + static Regex notepadPlusPlusRegex = new Regex(@"^new \d+ - Notepad\+\+$", RegexOptions.Compiled); bool IsKnownNotepadWindow(string titleText) { @@ -57,24 +57,16 @@ namespace Notepad.Extensions.Logging { if (WindowName.Equals(titleText, StringComparison.Ordinal)) { - WindowKind = notepadPlusPlusRegex.IsMatch(titleText) ? WindowKind.NotepadPlusPlus : WindowKind.Notepad; + WindowKind = titleText.EndsWith(" - Notepad++") ? WindowKind.NotepadPlusPlus : WindowKind.Notepad; } } - else + else if (titleText.Equals("Untitled - Notepad", StringComparison.Ordinal)) { - switch (titleText) - { - case "Untitled - Notepad": - WindowKind = WindowKind.Notepad; - break; - default: - if (notepadPlusPlusRegex.IsMatch(titleText)) - { - WindowKind = WindowKind.NotepadPlusPlus; - } - break; - - } + WindowKind = WindowKind.Notepad; + } + else if (notepadPlusPlusRegex.IsMatch(titleText)) + { + WindowKind = WindowKind.NotepadPlusPlus; } Handle = FindInnerWindow(WindowKind); diff --git a/Notepad.Extensions.Logging/WindowFinder.cs b/Notepad.Extensions.Logging/WindowFinder.cs index c5d5a17..54d0ff8 100644 --- a/Notepad.Extensions.Logging/WindowFinder.cs +++ b/Notepad.Extensions.Logging/WindowFinder.cs @@ -12,14 +12,12 @@ namespace Notepad.Extensions.Logging readonly ObjectPool statePool; - public string WindowName { get; internal set; } - - public WindowInfo FindNotepadWindow() + public WindowInfo FindNotepadWindow(string windowName) { var stateObject = statePool.Get(); try { - stateObject.WindowName = WindowName; + stateObject.WindowName = windowName; NativeMethods.EnumWindows(enumWindowsDelegate, stateObject); return new WindowInfo(stateObject.WindowKind, stateObject.Handle); } From 1116c4b54c0be96874717610aab10b9b5a4a49ea Mon Sep 17 00:00:00 2001 From: Yaakov Date: Sat, 23 May 2020 23:55:51 +1000 Subject: [PATCH 08/11] Introduce code analysis, clean up code to comply --- .editorconfig | 139 ++++++++++++++++++ ...d.Extensions.Logging.FunctionalTest.csproj | 14 +- .../Program.cs | 6 +- Notepad.Extensions.Logging.sln | 9 +- .../LoggingBuilderExtensions.cs | 18 ++- Notepad.Extensions.Logging/NativeMethods.cs | 2 +- .../Notepad.Extensions.Logging.csproj | 16 ++ Notepad.Extensions.Logging/NotepadLogger.cs | 30 ++-- .../NotepadLoggerProvider.cs | 29 ++-- .../WindowEnumerationState.cs | 7 +- Notepad.Extensions.Logging/WindowInfo.cs | 1 + Notepad.Extensions.Logging/WindowKind.cs | 2 +- 12 files changed, 231 insertions(+), 42 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..015702f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,139 @@ +[*.cs] + +# SA1200: Using directives should be placed correctly +dotnet_diagnostic.SA1200.severity = none + +# SA1600: Elements should be documented +dotnet_diagnostic.SA1600.severity = none + +# SA1400: Access modifier should be declared +dotnet_diagnostic.SA1400.severity = none + +# SA1633: File should have header +dotnet_diagnostic.SA1633.severity = none + +# SA1101: Prefix local calls with this +dotnet_diagnostic.SA1101.severity = none + +# SA1201: Elements should appear in the correct order +dotnet_diagnostic.SA1201.severity = none + +# SA1204: Static elements should appear before instance elements +dotnet_diagnostic.SA1204.severity = none + +# SA1602: Enumeration items should be documented +dotnet_diagnostic.SA1602.severity = none + +# CA1303: Do not pass literals as localized parameters +dotnet_diagnostic.CA1303.severity = none + +# SA0001: XML comment analysis is disabled due to project configuration +dotnet_diagnostic.SA0001.severity = none + +# WTG1001: Do not use the 'private' keyword. +dotnet_diagnostic.WTG1001.severity = warning + +# WTG1002: Use the 'var' keyword instead of an explicit type where possible. +dotnet_diagnostic.WTG1002.severity = warning + +# WTG1003: Do not leave whitespace on the end of the line. +dotnet_diagnostic.WTG1003.severity = warning + +# WTG1004: Indent with tabs rather than spaces. +dotnet_diagnostic.WTG1004.severity = none + +# WTG1005: Use consistent line endings. +dotnet_diagnostic.WTG1005.severity = warning + +# WTG1006: Do not use the 'internal' keyword for non-nested type definitions. +dotnet_diagnostic.WTG1006.severity = warning + +# WTG1007: Do not compare bool to a constant value. +dotnet_diagnostic.WTG1007.severity = warning + +# WTG1008: Do not compare bool to a constant value in an expression. +dotnet_diagnostic.WTG1008.severity = warning + +# WTG1009: Using directives must be ordered by kind. +dotnet_diagnostic.WTG1009.severity = warning + +# WTG1010: Use the 'var' keyword instead of an explicit type where possible. +dotnet_diagnostic.WTG1010.severity = warning + +# WTG1011: Use the 'var' keyword once when deconstructing an object. +dotnet_diagnostic.WTG1011.severity = warning + +# WTG1012: Use the 'var' keyword instead of an explicit type where possible. +dotnet_diagnostic.WTG1012.severity = warning + +# WTG1013: Don't use tuple types in public interfaces. +dotnet_diagnostic.WTG1013.severity = warning + +# WTG1014: Don't nest conditional operators. +dotnet_diagnostic.WTG1014.severity = warning + +# WTG1015: Conditional operators should not have multiline values. +dotnet_diagnostic.WTG1015.severity = warning + +# WTG1016: Avoid the discard-coalesce-throw pattern. +dotnet_diagnostic.WTG1016.severity = warning + +# WTG1017: Don't define variables that could be confused with discards. +dotnet_diagnostic.WTG1017.severity = warning + +# WTG2001: Do not use ConfigureAwait from an async void method. +dotnet_diagnostic.WTG2001.severity = warning + +# WTG2002: Avoid conditional compilation based on DEBUG. +dotnet_diagnostic.WTG2002.severity = warning + +# WTG2003: Flags enums should specify explicit values. +dotnet_diagnostic.WTG2003.severity = warning + +# WTG2004: This project does not use Code Contracts. +dotnet_diagnostic.WTG2004.severity = warning + +# WTG2005: Use Correct Emit Overload +dotnet_diagnostic.WTG2005.severity = warning + +# WTG2006: Do not pass the Compiled option into static methods on Regex. +dotnet_diagnostic.WTG2006.severity = warning + +# WTG3001: Remove orphaned suppressions. +dotnet_diagnostic.WTG3001.severity = warning + +# WTG3002: Prefer direct member access over linq. +dotnet_diagnostic.WTG3002.severity = warning + +# WTG3003: Prefer direct member access over linq in an expression. +dotnet_diagnostic.WTG3003.severity = warning + +# WTG3004: Prefer Array.Empty() over creating a new empty array. +dotnet_diagnostic.WTG3004.severity = warning + +# WTG3005: Don't call ToString() on a string. +dotnet_diagnostic.WTG3005.severity = warning + +# WTG3006: Prefer nameof over calling ToString on an enum literal. +dotnet_diagnostic.WTG3006.severity = warning + +# WTG3007: Overrides should not simply call base. +dotnet_diagnostic.WTG3007.severity = warning + +# WTG3008: Do not compare value types with null. +dotnet_diagnostic.WTG3008.severity = warning + +# WTG3009: Prefer Task.CompletedTask when applicable. +dotnet_diagnostic.WTG3009.severity = warning + +# WTG3010: Don't await a trivially completed task. +dotnet_diagnostic.WTG3010.severity = warning + +# WTG3101: Do not nest regions. +dotnet_diagnostic.WTG3101.severity = warning + +# WTG3102: Regions should not split structures. +dotnet_diagnostic.WTG3102.severity = warning + +# WTG3103: Conditional compilation directives should not split structures. +dotnet_diagnostic.WTG3103.severity = warning diff --git a/Notepad.Extensions.Logging.FunctionalTest/Notepad.Extensions.Logging.FunctionalTest.csproj b/Notepad.Extensions.Logging.FunctionalTest/Notepad.Extensions.Logging.FunctionalTest.csproj index 4e854f5..3630bcf 100644 --- a/Notepad.Extensions.Logging.FunctionalTest/Notepad.Extensions.Logging.FunctionalTest.csproj +++ b/Notepad.Extensions.Logging.FunctionalTest/Notepad.Extensions.Logging.FunctionalTest.csproj @@ -1,4 +1,4 @@ - + Exe @@ -6,8 +6,20 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Notepad.Extensions.Logging.FunctionalTest/Program.cs b/Notepad.Extensions.Logging.FunctionalTest/Program.cs index b8deb88..d59181e 100644 --- a/Notepad.Extensions.Logging.FunctionalTest/Program.cs +++ b/Notepad.Extensions.Logging.FunctionalTest/Program.cs @@ -6,13 +6,13 @@ namespace Notepad.Extensions.Logging.FunctionalTest { class Program { - static void Main(string[] args) + static void Main() { var sc = new ServiceCollection(); sc.AddLogging(lb => { lb.AddConsole(); - lb.AddNotepad(); + lb.AddNotepad(o => o.WindowName = "mylog - Notepad++"); }); var sp = sc.BuildServiceProvider(); @@ -29,7 +29,7 @@ namespace Notepad.Extensions.Logging.FunctionalTest { throw new InvalidOperationException("Wheeeeeeee"); } - catch (Exception ex) + catch (InvalidOperationException ex) { return ex; } diff --git a/Notepad.Extensions.Logging.sln b/Notepad.Extensions.Logging.sln index 8b0ca14..e6f47f8 100644 --- a/Notepad.Extensions.Logging.sln +++ b/Notepad.Extensions.Logging.sln @@ -3,9 +3,14 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30114.105 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Notepad.Extensions.Logging", "Notepad.Extensions.Logging\Notepad.Extensions.Logging.csproj", "{859E3EAB-04EB-42EE-86B2-A6FE90D71731}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Notepad.Extensions.Logging", "Notepad.Extensions.Logging\Notepad.Extensions.Logging.csproj", "{859E3EAB-04EB-42EE-86B2-A6FE90D71731}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Notepad.Extensions.Logging.FunctionalTest", "Notepad.Extensions.Logging.FunctionalTest\Notepad.Extensions.Logging.FunctionalTest.csproj", "{82E1E9EF-9284-4E7B-B350-0318F9B7B411}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Notepad.Extensions.Logging.FunctionalTest", "Notepad.Extensions.Logging.FunctionalTest\Notepad.Extensions.Logging.FunctionalTest.csproj", "{82E1E9EF-9284-4E7B-B350-0318F9B7B411}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1B0EB23C-7148-453B-BC03-AD132DF7E13B}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs b/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs index af2dc4b..2b76a45 100644 --- a/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs +++ b/Notepad.Extensions.Logging/LoggingBuilderExtensions.cs @@ -1,9 +1,8 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging.Configuration; -using Microsoft.Extensions.Options; using Notepad.Extensions.Logging; -using System; namespace Microsoft.Extensions.Logging { @@ -11,6 +10,11 @@ namespace Microsoft.Extensions.Logging { public static ILoggingBuilder AddNotepad(this ILoggingBuilder builder) { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + builder.AddConfiguration(); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); @@ -20,10 +24,16 @@ namespace Microsoft.Extensions.Logging public static ILoggingBuilder AddNotepad(this ILoggingBuilder builder, Action configure) { - if (configure == null) + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (configure is null) { throw new ArgumentNullException(nameof(configure)); } + AddNotepad(builder); builder.Services.Configure(configure); return builder; diff --git a/Notepad.Extensions.Logging/NativeMethods.cs b/Notepad.Extensions.Logging/NativeMethods.cs index af5abb8..86de38f 100644 --- a/Notepad.Extensions.Logging/NativeMethods.cs +++ b/Notepad.Extensions.Logging/NativeMethods.cs @@ -27,7 +27,7 @@ namespace Notepad.Extensions.Logging [DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsDelegate lpEnumFunc, object lParam); - [DllImport("User32.dll")] + [DllImport("User32.dll", CharSet = CharSet.Unicode)] public static extern int GetWindowText(IntPtr hWndParent, StringBuilder sb, int maxCount); [DllImport("kernel32.dll", SetLastError = true)] diff --git a/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj b/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj index 427e3ed..4ede967 100644 --- a/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj +++ b/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj @@ -15,9 +15,25 @@ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/Notepad.Extensions.Logging/NotepadLogger.cs b/Notepad.Extensions.Logging/NotepadLogger.cs index a896938..655b585 100644 --- a/Notepad.Extensions.Logging/NotepadLogger.cs +++ b/Notepad.Extensions.Logging/NotepadLogger.cs @@ -77,18 +77,18 @@ namespace Notepad.Extensions.Logging { stringBuilderPool.Return(builder); } - } - static string GetLogLevelString(LogLevel logLevel) => logLevel switch - { - LogLevel.Trace => "trce", - LogLevel.Debug => "dbug", - LogLevel.Information => "info", - LogLevel.Warning => "warn", - LogLevel.Error => "fail", - LogLevel.Critical => "crit", - _ => throw new ArgumentOutOfRangeException(nameof(logLevel)), - }; + static string GetLogLevelString(LogLevel logLevel) => logLevel switch + { + LogLevel.Trace => "trce", + LogLevel.Debug => "dbug", + LogLevel.Information => "info", + LogLevel.Warning => "warn", + LogLevel.Error => "fail", + LogLevel.Critical => "crit", + _ => throw new ArgumentOutOfRangeException(nameof(logLevel)), + }; + } void WriteToNotepad(string message) { @@ -104,16 +104,14 @@ namespace Notepad.Extensions.Logging } } - unsafe static void WriteToNotepadPlusPlus(IntPtr hwnd, string message) + static void WriteToNotepadPlusPlus(IntPtr hwnd, string message) { var dataLength = Encoding.UTF8.GetByteCount(message); - // // HERE BE DRAGONS // We need to copy the message into Notepad++'s memory so that it can read it. // Look away now, before its too late. - // - + /* unused thread ID */ _ = GetWindowThreadProcessId(hwnd, out var remoteProcessId); using var remoteProcess = Process.GetProcessById(remoteProcessId); var mem = VirtualAllocEx(remoteProcess.Handle, IntPtr.Zero, (IntPtr)dataLength, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); @@ -129,7 +127,7 @@ namespace Notepad.Extensions.Logging { var idx = Encoding.UTF8.GetBytes(message, 0, message.Length, data, 0); - WriteProcessMemory(remoteProcess.Handle, mem, data, (IntPtr)dataLength, out var bytesWritten); + _ = WriteProcessMemory(remoteProcess.Handle, mem, data, (IntPtr)dataLength, out var bytesWritten); SendMessage(hwnd, SCI_ADDTEXT, (IntPtr)dataLength, mem); } finally diff --git a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs index 7407e5b..e3b40c0 100644 --- a/Notepad.Extensions.Logging/NotepadLoggerProvider.cs +++ b/Notepad.Extensions.Logging/NotepadLoggerProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ObjectPool; @@ -7,8 +8,23 @@ using Microsoft.Extensions.Options; namespace Notepad.Extensions.Logging { [ProviderAlias("Notepad")] + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Instantiated via DI container")] class NotepadLoggerProvider : ILoggerProvider { + public NotepadLoggerProvider(IOptionsMonitor options) + : this() + { + // Filter would be applied on LoggerFactory level + optionsReloadToken = options.OnChange(ReloadLoggerOptions); + ReloadLoggerOptions(options.CurrentValue); + } + + public NotepadLoggerProvider(NotepadLoggerOptions options) + : this() + { + this.options = options; + } + NotepadLoggerProvider() { var poolProvider = new DefaultObjectPoolProvider(); @@ -16,22 +32,11 @@ namespace Notepad.Extensions.Logging windowFinder = new WindowFinder(); } - public NotepadLoggerProvider(IOptionsMonitor options) : this() - { - // Filter would be applied on LoggerFactory level - optionsReloadToken = options.OnChange(ReloadLoggerOptions); - ReloadLoggerOptions(options.CurrentValue); - } - - public NotepadLoggerProvider(NotepadLoggerOptions options) : this() - { - this.options = options; - } readonly ObjectPool stringBuilderPool; readonly IWindowFinder windowFinder; readonly IDisposable optionsReloadToken; NotepadLoggerOptions options; - + public ILogger CreateLogger(string categoryName) => new NotepadLogger(stringBuilderPool, windowFinder, categoryName, options.WindowName); public void Dispose() diff --git a/Notepad.Extensions.Logging/WindowEnumerationState.cs b/Notepad.Extensions.Logging/WindowEnumerationState.cs index bd1a5ad..0f7803c 100644 --- a/Notepad.Extensions.Logging/WindowEnumerationState.cs +++ b/Notepad.Extensions.Logging/WindowEnumerationState.cs @@ -15,8 +15,10 @@ namespace Notepad.Extensions.Logging readonly StringBuilder sb; public IntPtr Handle { get; private set; } + public WindowKind WindowKind { get; private set; } - public string WindowName { get; internal set; } + + public string WindowName { get; set; } public void Reset() { @@ -46,6 +48,7 @@ namespace Notepad.Extensions.Logging { return false; } + return true; } @@ -57,7 +60,7 @@ namespace Notepad.Extensions.Logging { if (WindowName.Equals(titleText, StringComparison.Ordinal)) { - WindowKind = titleText.EndsWith(" - Notepad++") ? WindowKind.NotepadPlusPlus : WindowKind.Notepad; + WindowKind = titleText.EndsWith(" - Notepad++", StringComparison.Ordinal) ? WindowKind.NotepadPlusPlus : WindowKind.Notepad; } } else if (titleText.Equals("Untitled - Notepad", StringComparison.Ordinal)) diff --git a/Notepad.Extensions.Logging/WindowInfo.cs b/Notepad.Extensions.Logging/WindowInfo.cs index 6629774..b7081db 100644 --- a/Notepad.Extensions.Logging/WindowInfo.cs +++ b/Notepad.Extensions.Logging/WindowInfo.cs @@ -11,6 +11,7 @@ namespace Notepad.Extensions.Logging } public WindowKind Kind { get; } + public IntPtr Handle { get; } } } \ No newline at end of file diff --git a/Notepad.Extensions.Logging/WindowKind.cs b/Notepad.Extensions.Logging/WindowKind.cs index cd8c57d..90a56cb 100644 --- a/Notepad.Extensions.Logging/WindowKind.cs +++ b/Notepad.Extensions.Logging/WindowKind.cs @@ -4,6 +4,6 @@ { Invalid, Notepad, - NotepadPlusPlus + NotepadPlusPlus, } } From 690e6bd4c367d56110ad1301cb1033d0abe1e0ed Mon Sep 17 00:00:00 2001 From: Alexander Kislitsyn <4lex.kislitsyn@gmail.com> Date: Sat, 23 May 2020 18:31:44 +0300 Subject: [PATCH 09/11] Added support of notepad2. --- Notepad.Extensions.Logging/NotepadLogger.cs | 1 + .../WindowEnumerationState.cs | 24 ++++++++++++++++++- Notepad.Extensions.Logging/WindowKind.cs | 1 + 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Notepad.Extensions.Logging/NotepadLogger.cs b/Notepad.Extensions.Logging/NotepadLogger.cs index 655b585..6400044 100644 --- a/Notepad.Extensions.Logging/NotepadLogger.cs +++ b/Notepad.Extensions.Logging/NotepadLogger.cs @@ -98,6 +98,7 @@ namespace Notepad.Extensions.Logging case WindowKind.Notepad: SendMessage(info.Handle, EM_REPLACESEL, (IntPtr)1, message); break; + case WindowKind.Notepad2: case WindowKind.NotepadPlusPlus: WriteToNotepadPlusPlus(info.Handle, message); break; diff --git a/Notepad.Extensions.Logging/WindowEnumerationState.cs b/Notepad.Extensions.Logging/WindowEnumerationState.cs index 0f7803c..982fbf8 100644 --- a/Notepad.Extensions.Logging/WindowEnumerationState.cs +++ b/Notepad.Extensions.Logging/WindowEnumerationState.cs @@ -44,6 +44,12 @@ namespace Notepad.Extensions.Logging sb.Remove(0, 1); } + if (sb.Length > 0 && sb[0] == ' ') + { + // Notepad2 added space after asterisk + sb.Remove(0, 1); + } + if (IsKnownNotepadWindow(sb.ToString())) { return false; @@ -60,13 +66,28 @@ namespace Notepad.Extensions.Logging { if (WindowName.Equals(titleText, StringComparison.Ordinal)) { - WindowKind = titleText.EndsWith(" - Notepad++", StringComparison.Ordinal) ? WindowKind.NotepadPlusPlus : WindowKind.Notepad; + if (titleText.EndsWith(" - Notepad++", StringComparison.Ordinal)) + { + WindowKind = WindowKind.NotepadPlusPlus; + } + else if (titleText.EndsWith(" - Notepad2", StringComparison.Ordinal)) + { + WindowKind = WindowKind.Notepad2; + } + else + { + WindowKind = WindowKind.Notepad; + } } } else if (titleText.Equals("Untitled - Notepad", StringComparison.Ordinal)) { WindowKind = WindowKind.Notepad; } + else if (titleText.Equals("Untitled - Notepad2", StringComparison.Ordinal)) + { + WindowKind = WindowKind.Notepad2; + } else if (notepadPlusPlusRegex.IsMatch(titleText)) { WindowKind = WindowKind.NotepadPlusPlus; @@ -83,6 +104,7 @@ namespace Notepad.Extensions.Logging { case WindowKind.Notepad: return NativeMethods.FindWindowEx(Handle, IntPtr.Zero, "EDIT", null); + case WindowKind.Notepad2: case WindowKind.NotepadPlusPlus: return NativeMethods.FindWindowEx(Handle, IntPtr.Zero, "Scintilla", null); default: diff --git a/Notepad.Extensions.Logging/WindowKind.cs b/Notepad.Extensions.Logging/WindowKind.cs index 90a56cb..23fe66d 100644 --- a/Notepad.Extensions.Logging/WindowKind.cs +++ b/Notepad.Extensions.Logging/WindowKind.cs @@ -5,5 +5,6 @@ Invalid, Notepad, NotepadPlusPlus, + Notepad2, } } From 6b2d78a3c8973388f9ffebf0ab2f809d1f47f02d Mon Sep 17 00:00:00 2001 From: Yaakov Date: Sun, 24 May 2020 22:34:49 +1000 Subject: [PATCH 10/11] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3540414..3e1216a 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ public void ConfigureServices(IServiceCollection services) } ``` -3. Open a new Notepad window. +3. Open a new Notepad, Notepad++, or Notepad2 window. 4. Run your application. ## Source Material From 181fec9e3720f554df5afbe6a1b4a245319ba207 Mon Sep 17 00:00:00 2001 From: Yaakov Date: Sun, 21 Jun 2020 14:20:59 +1000 Subject: [PATCH 11/11] Fixed a memory leak in Scintilla injection --- Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj | 2 +- Notepad.Extensions.Logging/NotepadLogger.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj b/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj index 4ede967..c6023b6 100644 --- a/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj +++ b/Notepad.Extensions.Logging/Notepad.Extensions.Logging.csproj @@ -11,7 +11,7 @@ Apache-2.0 true true - 1.0.1 + 1.0.3 diff --git a/Notepad.Extensions.Logging/NotepadLogger.cs b/Notepad.Extensions.Logging/NotepadLogger.cs index 6400044..a591365 100644 --- a/Notepad.Extensions.Logging/NotepadLogger.cs +++ b/Notepad.Extensions.Logging/NotepadLogger.cs @@ -138,7 +138,7 @@ namespace Notepad.Extensions.Logging } finally { - VirtualFreeEx(remoteProcess.Handle, IntPtr.Zero, IntPtr.Zero, MEM_RELEASE); + VirtualFreeEx(remoteProcess.Handle, mem, IntPtr.Zero, MEM_RELEASE); } } }