Remove statics and threadstatic so that we can pass options around

This commit is contained in:
Yaakov 2020-05-23 19:05:09 +10:00
parent b817c75720
commit c658b437b5
9 changed files with 143 additions and 80 deletions

View file

@ -0,0 +1,7 @@
namespace Notepad.Extensions.Logging
{
interface IWindowFinder
{
WindowInfo FindNotepadWindow();
}
}

View file

@ -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);

View file

@ -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<StringBuilder> stringBuilderPool, string categoryName)
public NotepadLogger(ObjectPool<StringBuilder> 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<StringBuilder> stringBuilderPool;
readonly IWindowFinder windowFinder;
readonly string categoryName;
public IDisposable BeginScope<TState>(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;
}
}

View file

@ -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<StringBuilder> 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()
{

View file

@ -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;
}
}
}

View file

@ -0,0 +1,17 @@
using Microsoft.Extensions.ObjectPool;
namespace Notepad.Extensions.Logging
{
class WindowEnumerationStatePoolingPolicy : IPooledObjectPolicy<WindowEnumerationState>
{
public static IPooledObjectPolicy<WindowEnumerationState> Instance { get; } = new WindowEnumerationStatePoolingPolicy();
public WindowEnumerationState Create() => new WindowEnumerationState();
public bool Return(WindowEnumerationState obj)
{
obj.Reset();
return true;
}
}
}

View file

@ -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<WindowEnumerationState>(WindowEnumerationStatePoolingPolicy.Instance);
}
readonly ObjectPool<WindowEnumerationState> 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);
}
}
}

View file

@ -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; }
}
}

View file

@ -1,6 +1,6 @@
namespace Notepad.Extensions.Logging
{
public enum WindowKind
enum WindowKind
{
Invalid,
Notepad,