diff --git a/Example/ExampleService.cs b/Example/ExampleService.cs
index 3011830..382b6a3 100644
--- a/Example/ExampleService.cs
+++ b/Example/ExampleService.cs
@@ -1,23 +1,9 @@
-using System.Collections.Generic;
-
-namespace Example;
-
-///
-/// Service that demonstrates various Serilog logging features and syntax highlighting capabilities.
-///
-///
-/// This service showcases all the different ways to use Serilog logging with properties,
-/// destructuring, formatting, and expressions. It serves as a comprehensive example
-/// for testing the SerilogSyntax Visual Studio extension.
-///
+namespace Example;
+
public class ExampleService(ILogger logger)
{
- private static readonly string[] consoleLoggerScopes = ["Main", "ConsoleLoggerEmulationExample()"];
+ private static readonly string[] consoleLoggerScopes = [nameof(RunExamplesAsync), nameof(ConsoleLoggerEmulationExample)];
- ///
- /// Runs all example logging scenarios to demonstrate the full range of Serilog features.
- ///
- /// A task that completes when all examples have been executed.
public async Task RunExamplesAsync()
{
SelfLog.Enable(Console.Error);
@@ -36,14 +22,6 @@ public async Task RunExamplesAsync()
await ConsoleLoggerEmulationExample();
}
- ///
- /// Demonstrates all syntax highlighting features in a single comprehensive example.
- ///
- ///
- /// This method showcases standard properties, destructuring, stringification, formatting,
- /// alignment, verbatim strings, raw string literals, and Serilog.Expressions syntax.
- ///
- /// A task that completes when the showcase example has been logged.
private async Task ShowcaseExample()
{
logger.LogInformation("=== Serilog Syntax Showcase ===");
@@ -109,14 +87,6 @@ With properties like {UserId} and {@Order}
await Task.Delay(100);
}
- ///
- /// Demonstrates basic Serilog logging patterns with simple property substitution.
- ///
- ///
- /// Shows how to log with single and multiple properties at different log levels
- /// including Debug, Information, Warning, and Error.
- ///
- /// A task that completes when the basic examples have been logged.
private async Task BasicLoggingExamples()
{
logger.LogInformation("=== Basic Logging Examples ===");
@@ -140,14 +110,6 @@ private async Task BasicLoggingExamples()
await Task.Delay(100); // Simulate some work
}
- ///
- /// Demonstrates object destructuring and stringification in Serilog templates.
- ///
- ///
- /// Shows the difference between destructuring with @ (captures object structure)
- /// and stringification with $ (captures ToString() representation).
- ///
- /// A task that completes when the destructuring examples have been logged.
private async Task DestructuringExamples()
{
logger.LogInformation("=== Destructuring Examples ===");
@@ -171,7 +133,7 @@ private async Task DestructuringExamples()
{
new { Product = "Laptop", Price = 999.99m, Quantity = 1 },
new { Product = "Mouse", Price = 29.99m, Quantity = 2 }
- },
+ },
Total = 1059.97m
};
logger.LogInformation("Order created {@Order}", order);
@@ -183,14 +145,6 @@ private async Task DestructuringExamples()
await Task.Delay(100);
}
- ///
- /// Demonstrates format specifiers and alignment options in Serilog templates.
- ///
- ///
- /// Shows various formatting options for dates, numbers, currency, percentages,
- /// and how to use alignment to create tabular output.
- ///
- /// A task that completes when the formatting examples have been logged.
private async Task FormattingExamples()
{
logger.LogInformation("=== Formatting Examples ===");
@@ -213,7 +167,7 @@ private async Task FormattingExamples()
new { Name = "Laptop", Price = 999.99m, Stock = 15 },
new { Name = "Mouse", Price = 29.99m, Stock = 147 },
new { Name = "Keyboard", Price = 79.50m, Stock = 23 }
- };
+ };
logger.LogInformation("Inventory Report:");
foreach (var item in items)
@@ -229,14 +183,6 @@ private async Task FormattingExamples()
await Task.Delay(100);
}
- ///
- /// Demonstrates Serilog properties within C# verbatim string literals (@"...").
- ///
- ///
- /// Tests various edge cases including multi-line verbatim strings, escaped quotes,
- /// positional parameters, and complex property combinations within verbatim strings.
- ///
- /// A task that completes when the verbatim string examples have been logged.
private async Task VerbatimStringExamples()
{
logger.LogInformation("=== Additional Verbatim String Tests ===");
@@ -285,14 +231,6 @@ private async Task VerbatimStringExamples()
await Task.Delay(100);
}
- ///
- /// Demonstrates Serilog properties within C# 11 raw string literals ("""...""").
- ///
- ///
- /// Shows how the extension handles properties in single-line and multi-line raw strings,
- /// including custom delimiter counts and embedded quotes without escaping.
- ///
- /// A task that completes when the raw string literal examples have been logged.
private async Task RawStringLiteralExamples()
{
logger.LogInformation("=== Raw String Literal Tests (C# 11+) ===");
@@ -362,14 +300,6 @@ This allows literal triple quotes in the string
await Task.Delay(100);
}
- ///
- /// Demonstrates Serilog.Expressions syntax including filters, conditionals, and expression templates.
- ///
- ///
- /// Shows filter expressions with ByExcluding/ByIncludingOnly, conditional enrichment,
- /// computed properties, conditional writes, and expression template control flow directives.
- ///
- /// A task that completes when the expression examples have been logged.
private async Task SerilogExpressionsExamples()
{
logger.LogInformation("=== Serilog.Expressions Syntax Examples ===");
@@ -426,14 +356,6 @@ private async Task SerilogExpressionsExamples()
await Task.Delay(100);
}
- ///
- /// Demonstrates logging exceptions and error scenarios with structured data.
- ///
- ///
- /// Shows how to log exceptions with LogError, including exception properties
- /// and legacy positional parameter formats.
- ///
- /// A task that completes when the error handling examples have been logged.
private async Task ErrorHandlingExamples()
{
logger.LogInformation("=== Error Handling Examples ===");
@@ -460,14 +382,6 @@ private async Task ErrorHandlingExamples()
await Task.Delay(100);
}
- ///
- /// Demonstrates performance metrics logging with timing and throughput data.
- ///
- ///
- /// Shows how to log structured performance data, use logging scopes for context,
- /// and track operation durations with detailed metrics.
- ///
- /// A task that completes when the performance examples have been logged.
private async Task PerformanceLoggingExamples()
{
logger.LogInformation("=== Performance Logging Examples ===");
@@ -587,12 +501,6 @@ private async Task ConsoleLoggerEmulationExample()
await Task.Delay(100);
}
- ///
- /// Simulates a file operation that throws an exception for error logging demonstration.
- ///
- /// The name of the file to simulate processing.
- /// A task that fails with a FileNotFoundException.
- /// Always thrown to demonstrate error logging.
private async Task SimulateOperationAsync(string fileName)
{
logger.LogDebug("Attempting to process file {FileName}", fileName);
diff --git a/Example/GlobalUsings.cs b/Example/GlobalUsings.cs
index 9cc637e..970925f 100644
--- a/Example/GlobalUsings.cs
+++ b/Example/GlobalUsings.cs
@@ -6,6 +6,7 @@
global using Serilog.Templates;
global using Serilog.Templates.Themes;
global using System;
+global using System.Collections.Generic;
global using System.Diagnostics;
global using System.IO;
global using System.Threading.Tasks;
diff --git a/README.md b/README.md
index 24acae7..1f44f91 100644
--- a/README.md
+++ b/README.md
@@ -9,24 +9,27 @@ A Visual Studio 2022 extension that provides syntax highlighting, brace matching
### 🎨 Syntax Highlighting
#### Message Templates
-- **Property names** highlighted in blue: `{UserId}`, `{UserName}`
-- **Destructuring operator** `@` highlighted in dark goldenrod: `{@User}`
-- **Stringification operator** `$` highlighted in dark goldenrod: `{$Settings}`
-- **Format specifiers** highlighted in teal: `{Timestamp:yyyy-MM-dd}`
+- **Property names** highlighted in theme-appropriate blue: `{UserId}`, `{UserName}`
+- **Destructuring operator** `@` highlighted in warm orange/red: `{@User}`
+- **Stringification operator** `$` highlighted in warm orange/red: `{$Settings}`
+- **Format specifiers** highlighted in green: `{Timestamp:yyyy-MM-dd}`
- **Alignment** highlighted in red: `{Name,10}`, `{Price,-8}`
-- **Positional parameters** highlighted in dark violet: `{0}`, `{1}`
-- **Property braces** highlighted in purple for structure
+- **Positional parameters** highlighted in purple: `{0}`, `{1}`
+- **Property braces** highlighted for structure
- **Multi-line verbatim strings** fully supported with proper highlighting across lines
- **C# 11 raw string literals** supported with `"""` delimiters for complex templates
+- **Automatic theme adaptation** - All colors automatically adjust for Light/Dark themes
#### Serilog.Expressions
- **Filter expressions** in `Filter.ByExcluding()` and `Filter.ByIncludingOnly()`
- **Expression templates** with control flow directives
-- **Operators** highlighted distinctly: `and`, `or`, `not`, `like`, `in`, `is null`
-- **Functions** highlighted: `StartsWith()`, `Contains()`, `Length()`, etc.
-- **Literals** properly colored: strings, numbers, booleans, null
-- **Directives** highlighted: `{#if}`, `{#each}`, `{#else}`, `{#end}`
-- **Built-in properties**: `@t`, `@m`, `@l`, `@x`, `@i`, `@p`
+- **Operators** highlighted in red: `and`, `or`, `not`, `like`, `in`, `is null`
+- **Functions** highlighted in purple: `StartsWith()`, `Contains()`, `Length()`, etc.
+- **Keywords** highlighted in blue: conditional and control flow keywords
+- **Literals** highlighted in cyan/teal: strings, numbers, booleans, null
+- **Directives** highlighted in magenta: `{#if}`, `{#each}`, `{#else}`, `{#end}`
+- **Built-in properties** highlighted in teal: `@t`, `@m`, `@l`, `@x`, `@i`, `@p`
+- **Theme-aware colors** - All expression elements adapt to Light/Dark themes
### 🔗 Smart Detection
- Works with any logger variable name (not just `_logger` or `log`)
@@ -69,8 +72,19 @@ A Visual Studio 2022 extension that provides syntax highlighting, brace matching
## Customization
+### Theme-Aware Colors & Accessibility
+The extension automatically adapts to your Visual Studio theme with **WCAG AA compliant colors**:
+
+- **Automatic theme detection** - Colors change instantly when switching between Light and Dark themes
+- **WCAG AA accessibility** - All colors maintain 4.5:1+ contrast ratios for excellent readability
+- **Semantic color grouping** - Related elements use harmonious color families:
+ - Properties: Blue family (`{UserId}`, `{Name}`)
+ - Operators: Warm colors (`@`, `$`)
+ - Format specifiers: Green family (`:yyyy-MM-dd`)
+ - Expression functions: Purple family (`StartsWith()`, `Length()`)
+
### Color Customization
-The extension's colors can be customized to match your preferences:
+You can still customize colors to match your preferences:
1. Go to **Tools** > **Options** > **Environment** > **Fonts and Colors**
2. In the **Display items** list, look for:
@@ -81,10 +95,17 @@ The extension's colors can be customized to match your preferences:
- Serilog Alignment
- Serilog Positional Index
- Serilog Property Brace
+ - Serilog Expression Property
+ - Serilog Expression Function
+ - Serilog Expression Keyword
+ - Serilog Expression Literal
+ - Serilog Expression Operator
+ - Serilog Expression Directive
+ - Serilog Expression Built-in
3. Select any item and modify its **Item foreground** color
4. Click **OK** to apply changes
-The extension uses colors that work well in both light and dark themes by default, meeting WCAG AA accessibility standards.
+**Note**: Custom colors override the automatic theme-aware colors.
## Getting Started
@@ -109,10 +130,11 @@ Log.Information("User {UserId} logged in with {@Details} at {Timestamp:HH:mm:ss}
```
You should see:
-- `UserId` in blue
-- `@` in dark goldenrod, `Details` in blue
-- `Timestamp` in blue, `:HH:mm:ss` in teal
-- Matching braces highlighted in purple when cursor is on them
+- `UserId` in blue (adapts to your theme)
+- `@` in warm orange/red, `Details` in blue
+- `Timestamp` in blue, `:HH:mm:ss` in green
+- Matching braces highlighted when cursor is on them
+- Colors automatically match your Light/Dark theme preference
## Supported Serilog Syntax
@@ -225,6 +247,7 @@ Key components:
- `SerilogBraceMatcher` - Provides brace matching
- `SerilogNavigationProvider` - Enables property-to-argument navigation
- `SerilogCallDetector` - Optimized Serilog call detection with pre-check optimization
+- `SerilogThemeColors` - Theme-aware color management with WCAG AA compliance
- `TemplateParser` - Parses Serilog message templates
- `LruCache` - Thread-safe LRU cache providing 268x-510x performance improvement
diff --git a/SerilogSyntax/Classification/ISerilogClassificationDefinition.cs b/SerilogSyntax/Classification/ISerilogClassificationDefinition.cs
new file mode 100644
index 0000000..8f512f5
--- /dev/null
+++ b/SerilogSyntax/Classification/ISerilogClassificationDefinition.cs
@@ -0,0 +1,13 @@
+namespace SerilogSyntax.Classification;
+
+///
+/// Interface for Serilog classification format definitions that support theme-aware color updates.
+///
+public interface ISerilogClassificationDefinition
+{
+ ///
+ /// Reinitializes the classification format colors based on the current Visual Studio theme.
+ /// This method is called automatically when the VS theme changes.
+ ///
+ void Reinitialize();
+}
\ No newline at end of file
diff --git a/SerilogSyntax/Classification/SerilogClassificationFormatBase.cs b/SerilogSyntax/Classification/SerilogClassificationFormatBase.cs
new file mode 100644
index 0000000..f85b76f
--- /dev/null
+++ b/SerilogSyntax/Classification/SerilogClassificationFormatBase.cs
@@ -0,0 +1,48 @@
+using Microsoft.VisualStudio.Text.Classification;
+
+namespace SerilogSyntax.Classification;
+
+///
+/// Base class for theme-aware Serilog classification format definitions.
+/// Automatically updates colors when Visual Studio theme changes.
+///
+public abstract class SerilogClassificationFormatBase : ClassificationFormatDefinition, ISerilogClassificationDefinition
+{
+ private readonly SerilogThemeColors _themeColors;
+ private readonly string _classificationTypeName;
+
+ ///
+ /// Initializes a new instance of the theme-aware classification format.
+ ///
+ /// The theme colors service.
+ /// The classification type name.
+ /// The display name for the format.
+ protected SerilogClassificationFormatBase(
+ SerilogThemeColors themeColors,
+ string classificationTypeName,
+ string displayName)
+ {
+ _themeColors = themeColors;
+ _classificationTypeName = classificationTypeName;
+ DisplayName = displayName;
+
+ _themeColors.RegisterClassificationDefinition(this);
+ Reinitialize();
+ }
+
+ ///
+ /// Reinitializes the classification format colors based on the current Visual Studio theme.
+ /// Called automatically when the VS theme changes.
+ ///
+ public virtual void Reinitialize()
+ {
+ var colors = _themeColors.GetColorsForCurrentTheme();
+ if (colors.TryGetValue(_classificationTypeName, out var textProperties))
+ {
+ ForegroundColor = textProperties.Foreground;
+ BackgroundColor = textProperties.Background;
+ IsBold = textProperties.IsBold;
+ IsItalic = textProperties.IsItalic;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SerilogSyntax/Classification/SerilogClassificationFormats.cs b/SerilogSyntax/Classification/SerilogClassificationFormats.cs
index 7a610dd..8f91623 100644
--- a/SerilogSyntax/Classification/SerilogClassificationFormats.cs
+++ b/SerilogSyntax/Classification/SerilogClassificationFormats.cs
@@ -6,122 +6,101 @@
namespace SerilogSyntax.Classification;
///
-/// Defines the visual format for Serilog property names in message templates.
+/// Defines the theme-aware visual format for Serilog property names in message templates.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.PropertyName)]
[Name("Serilog Property Name")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogPropertyNameFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogPropertyNameFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.PropertyName, "Serilog Property Name")
{
- public SerilogPropertyNameFormat()
- {
- DisplayName = "Serilog Property Name";
- ForegroundColor = Color.FromRgb(0x00, 0x7A, 0xCC); // Accessible blue (#007ACC) - works in both themes
- }
}
///
-/// Defines the visual format for the destructure operator (@) in Serilog templates.
+/// Defines the theme-aware visual format for the destructure operator (@) in Serilog templates.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.DestructureOperator)]
[Name("Serilog Destructure Operator")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogDestructureOperatorFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogDestructureOperatorFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.DestructureOperator, "Serilog Destructure Operator (@)")
{
- public SerilogDestructureOperatorFormat()
- {
- DisplayName = "Serilog Destructure Operator (@)";
- ForegroundColor = Color.FromRgb(0xB8, 0x86, 0x0B); // Dark goldenrod (#B8860B) - visible in both themes
- }
}
///
-/// Defines the visual format for the stringify operator ($) in Serilog templates.
+/// Defines the theme-aware visual format for the stringify operator ($) in Serilog templates.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.StringifyOperator)]
[Name("Serilog Stringify Operator")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogStringifyOperatorFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogStringifyOperatorFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.StringifyOperator, "Serilog Stringify Operator ($)")
{
- public SerilogStringifyOperatorFormat()
- {
- DisplayName = "Serilog Stringify Operator ($)";
- ForegroundColor = Color.FromRgb(0xB8, 0x86, 0x0B); // Dark goldenrod (#B8860B) - visible in both themes
- }
}
///
-/// Defines the visual format for format specifiers in Serilog templates.
+/// Defines the theme-aware visual format for format specifiers in Serilog templates.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.FormatSpecifier)]
[Name("Serilog Format Specifier")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogFormatSpecifierFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogFormatSpecifierFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.FormatSpecifier, "Serilog Format Specifier")
{
- public SerilogFormatSpecifierFormat()
- {
- DisplayName = "Serilog Format Specifier";
- ForegroundColor = Color.FromRgb(0x00, 0x80, 0x80); // Teal (#008080) - good contrast in both themes
- }
}
///
-/// Defines the visual format for property braces in Serilog templates.
+/// Defines the theme-aware visual format for property braces in Serilog templates.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.PropertyBrace)]
[Name("Serilog Property Brace")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogPropertyBraceFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogPropertyBraceFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.PropertyBrace, "Serilog Property Brace")
{
- public SerilogPropertyBraceFormat()
- {
- DisplayName = "Serilog Property Brace";
- ForegroundColor = Color.FromRgb(0x80, 0x00, 0x80); // Purple (#800080) - works in both themes
- }
}
///
-/// Defines the visual format for positional indices in Serilog templates.
+/// Defines the theme-aware visual format for positional indices in Serilog templates.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.PositionalIndex)]
[Name("Serilog Positional Index")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogPositionalIndexFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogPositionalIndexFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.PositionalIndex, "Serilog Positional Index")
{
- public SerilogPositionalIndexFormat()
- {
- DisplayName = "Serilog Positional Index";
- ForegroundColor = Color.FromRgb(0xAF, 0x00, 0xDB); // Dark violet (#AF00DB) - visible in both themes
- }
}
///
-/// Defines the visual format for alignment values in Serilog templates.
+/// Defines the theme-aware visual format for alignment values in Serilog templates.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.Alignment)]
[Name("Serilog Alignment")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogAlignmentFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogAlignmentFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.Alignment, "Serilog Alignment")
{
- public SerilogAlignmentFormat()
- {
- DisplayName = "Serilog Alignment";
- ForegroundColor = Color.FromRgb(0xDC, 0x26, 0x26); // Muted red (#DC2626) - 5.2:1 dark, 4.5:1 light contrast
- }
}
///
@@ -147,120 +126,99 @@ public SerilogBraceHighlightFormat()
// Expression syntax format definitions
///
-/// Defines the visual format for expression properties.
+/// Defines the theme-aware visual format for expression properties.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.ExpressionProperty)]
[Name("Serilog Expression Property")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogExpressionPropertyFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogExpressionPropertyFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.ExpressionProperty, "Serilog Expression Property")
{
- public SerilogExpressionPropertyFormat()
- {
- DisplayName = "Serilog Expression Property";
- ForegroundColor = Color.FromRgb(0x09, 0x69, 0xDA); // Blue (#0969DA) - 4.5:1 contrast
- }
}
///
-/// Defines the visual format for expression operators.
+/// Defines the theme-aware visual format for expression operators.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.ExpressionOperator)]
[Name("Serilog Expression Operator")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogExpressionOperatorFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogExpressionOperatorFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.ExpressionOperator, "Serilog Expression Operator")
{
- public SerilogExpressionOperatorFormat()
- {
- DisplayName = "Serilog Expression Operator";
- ForegroundColor = Color.FromRgb(0xCF, 0x22, 0x2E); // Red (#CF222E) - 4.8:1 contrast
- }
}
///
-/// Defines the visual format for expression functions.
+/// Defines the theme-aware visual format for expression functions.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.ExpressionFunction)]
[Name("Serilog Expression Function")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogExpressionFunctionFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogExpressionFunctionFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.ExpressionFunction, "Serilog Expression Function")
{
- public SerilogExpressionFunctionFormat()
- {
- DisplayName = "Serilog Expression Function";
- ForegroundColor = Color.FromRgb(0x82, 0x50, 0xDF); // Purple (#8250DF) - 4.6:1 contrast
- }
}
///
-/// Defines the visual format for expression keywords.
+/// Defines the theme-aware visual format for expression keywords.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.ExpressionKeyword)]
[Name("Serilog Expression Keyword")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogExpressionKeywordFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogExpressionKeywordFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.ExpressionKeyword, "Serilog Expression Keyword")
{
- public SerilogExpressionKeywordFormat()
- {
- DisplayName = "Serilog Expression Keyword";
- ForegroundColor = Color.FromRgb(0x05, 0x50, 0xAE); // Dark blue (#0550AE) - 7.5:1 contrast
- }
}
///
-/// Defines the visual format for expression literals.
+/// Defines the theme-aware visual format for expression literals.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.ExpressionLiteral)]
[Name("Serilog Expression Literal")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogExpressionLiteralFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogExpressionLiteralFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.ExpressionLiteral, "Serilog Expression Literal")
{
- public SerilogExpressionLiteralFormat()
- {
- DisplayName = "Serilog Expression Literal";
- ForegroundColor = Color.FromRgb(0x4A, 0x8B, 0xC2); // Medium Blue (#4A8BC2) - 4.2:1 contrast on both light and dark themes
- }
}
///
-/// Defines the visual format for expression template directives.
+/// Defines the theme-aware visual format for expression template directives.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.ExpressionDirective)]
[Name("Serilog Expression Directive")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogExpressionDirectiveFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogExpressionDirectiveFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.ExpressionDirective, "Serilog Expression Directive")
{
- public SerilogExpressionDirectiveFormat()
- {
- DisplayName = "Serilog Expression Directive";
- ForegroundColor = Color.FromRgb(0x8B, 0x00, 0x8B); // Magenta (#8B008B) - 4.9:1 contrast
- }
}
///
-/// Defines the visual format for expression built-in properties.
+/// Defines the theme-aware visual format for expression built-in properties.
///
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = SerilogClassificationTypes.ExpressionBuiltin)]
[Name("Serilog Expression Builtin")]
[UserVisible(true)]
[Order(Before = Priority.Default)]
-internal sealed class SerilogExpressionBuiltinFormat : ClassificationFormatDefinition
+[method: ImportingConstructor]
+internal sealed class SerilogExpressionBuiltinFormat(SerilogThemeColors themeColors)
+ : SerilogClassificationFormatBase(themeColors, SerilogClassificationTypes.ExpressionBuiltin, "Serilog Expression Built-in")
{
- public SerilogExpressionBuiltinFormat()
- {
- DisplayName = "Serilog Expression Built-in";
- ForegroundColor = Color.FromRgb(0x1F, 0x7A, 0x8C); // Teal (#1F7A8C) - 4.5:1 contrast
- }
}
\ No newline at end of file
diff --git a/SerilogSyntax/Classification/SerilogClassifier.cs b/SerilogSyntax/Classification/SerilogClassifier.cs
index 124b8eb..d205956 100644
--- a/SerilogSyntax/Classification/SerilogClassifier.cs
+++ b/SerilogSyntax/Classification/SerilogClassifier.cs
@@ -407,33 +407,37 @@ public IList GetClassificationSpans(SnapshotSpan span)
if (isActuallySerilogTemplate)
{
- // Check if we're inside an ExpressionTemplate
- bool isExpressionTemplate = false;
+ // Use modern syntax tree analyzer to check if we're inside an ExpressionTemplate
+ var expressionContext = SyntaxTreeAnalyzer.GetExpressionContext(span.Snapshot, positionToCheck);
+ bool isExpressionTemplate = expressionContext == ExpressionContext.ExpressionTemplate;
- // Look backwards to find if this is an ExpressionTemplate
- for (int i = currentLine.LineNumber - 1; i >= Math.Max(0, currentLine.LineNumber - 10); i--)
+ if (isExpressionTemplate)
{
- var checkLine = span.Snapshot.GetLineFromLineNumber(i);
- var checkText = checkLine.GetText();
-
- if (checkText.Contains("new ExpressionTemplate("))
+ // Check if this range has already been processed by the modern syntax tree analyzer
+ bool alreadyProcessed = false;
+ foreach (var existingClassification in classifications)
{
- isExpressionTemplate = true;
- break;
+ if (existingClassification.ClassificationType.Classification.Contains("serilog.expression") &&
+ existingClassification.Span.IntersectsWith(span))
+ {
+ alreadyProcessed = true;
+ DiagnosticLogger.Log($"[SerilogClassifier] ExpressionTemplate at {span.Start} already processed by modern analyzer, skipping legacy fallback");
+ break;
+ }
}
- }
- if (isExpressionTemplate)
- {
- // Parse as expression template
- DiagnosticLogger.Log($"[SerilogClassifier] Parsing ExpressionTemplate text: '{text}'");
- var parser = new ExpressionParser(text);
- var expressionRegions = parser.ParseExpressionTemplate();
-
- // Create classifications for expression regions
- int offsetInSnapshot = span.Start;
- DiagnosticLogger.Log($"[SerilogClassifier] Adding {expressionRegions.Count()} expression classifications at offset {offsetInSnapshot}");
- _spanBuilder.AddExpressionClassifications(classifications, span.Snapshot, offsetInSnapshot, expressionRegions);
+ if (!alreadyProcessed)
+ {
+ // Parse as expression template
+ DiagnosticLogger.Log($"[SerilogClassifier] Parsing ExpressionTemplate text: '{text}'");
+ var parser = new ExpressionParser(text);
+ var expressionRegions = parser.ParseExpressionTemplate();
+
+ // Create classifications for expression regions
+ int offsetInSnapshot = span.Start;
+ DiagnosticLogger.Log($"[SerilogClassifier] Adding {expressionRegions.Count()} expression classifications at offset {offsetInSnapshot}");
+ _spanBuilder.AddExpressionClassifications(classifications, span.Snapshot, offsetInSnapshot, expressionRegions);
+ }
}
else
{
diff --git a/SerilogSyntax/Classification/SerilogTextProperties.cs b/SerilogSyntax/Classification/SerilogTextProperties.cs
new file mode 100644
index 0000000..7b4ddb8
--- /dev/null
+++ b/SerilogSyntax/Classification/SerilogTextProperties.cs
@@ -0,0 +1,63 @@
+using System.Windows.Media;
+
+namespace SerilogSyntax.Classification;
+
+///
+/// Represents the visual properties for text formatting in Serilog syntax highlighting.
+///
+///
+/// Initializes a new instance of the SerilogTextProperties class.
+///
+/// The foreground color, or null for default.
+/// The background color, or null for default.
+/// Whether the text should be bold.
+/// Whether the text should be italic.
+public class SerilogTextProperties(Color? foreground, Color? background, bool isBold, bool isItalic)
+{
+ ///
+ /// Gets the foreground color for the text, or null to use default.
+ ///
+ public Color? Foreground { get; } = foreground;
+
+ ///
+ /// Gets the background color for the text, or null to use default.
+ ///
+ public Color? Background { get; } = background;
+
+ ///
+ /// Gets whether the text should be rendered in bold.
+ ///
+ public bool IsBold { get; } = isBold;
+
+ ///
+ /// Gets whether the text should be rendered in italic.
+ ///
+ public bool IsItalic { get; } = isItalic;
+
+ ///
+ /// Creates text properties with only foreground color specified.
+ ///
+ /// The foreground color.
+ /// A new SerilogTextProperties instance.
+ public static SerilogTextProperties Create(Color foreground)
+ => new(foreground, null, false, false);
+
+ ///
+ /// Creates text properties with foreground color and bold formatting.
+ ///
+ /// The foreground color.
+ /// Whether the text should be bold.
+ /// A new SerilogTextProperties instance.
+ public static SerilogTextProperties Create(Color foreground, bool isBold)
+ => new(foreground, null, isBold, false);
+
+ ///
+ /// Creates text properties with all formatting options.
+ ///
+ /// The foreground color.
+ /// Whether the text should be bold.
+ /// Whether the text should be italic.
+ /// A new SerilogTextProperties instance.
+ public static SerilogTextProperties Create(Color foreground, bool isBold, bool isItalic)
+ => new(foreground, null, isBold, isItalic);
+}
\ No newline at end of file
diff --git a/SerilogSyntax/Classification/SerilogThemeColors.cs b/SerilogSyntax/Classification/SerilogThemeColors.cs
new file mode 100644
index 0000000..7dc94d1
--- /dev/null
+++ b/SerilogSyntax/Classification/SerilogThemeColors.cs
@@ -0,0 +1,267 @@
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.PlatformUI;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+using Microsoft.VisualStudio.Text.Classification;
+using Microsoft.VisualStudio.Text.Formatting;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Windows;
+using System.Windows.Media;
+
+namespace SerilogSyntax.Classification;
+
+///
+/// Manages theme-aware colors for Serilog syntax highlighting with WCAG AA compliance.
+/// Automatically detects Visual Studio theme changes and updates colors accordingly.
+///
+[Export]
+public class SerilogThemeColors : IDisposable
+{
+ private VsTheme _currentTheme;
+ private readonly List _classificationDefinitions = [];
+ private bool _disposed = false;
+
+ // Font and Color category GUID for Visual Studio "Text Editor" (used to access font and color settings for text editor classifications)
+ private const string FontAndColorCategory = "75A05685-00A8-4DED-BAE5-E7A50BFA929A";
+ private readonly Guid _fontAndColorCategoryGUID = new(FontAndColorCategory);
+
+ // Theme detection threshold - blue component value used to distinguish Dark (< threshold) from Light (>= threshold) themes
+ private const int ThemeDetectionBlueThreshold = 100;
+
+#pragma warning disable 0649 // Field is never assigned
+
+ [Import]
+ private readonly IClassificationFormatMapService _classificationFormatMapService;
+
+ [Import]
+ private readonly IClassificationTypeRegistryService _classificationTypeRegistryService;
+
+#pragma warning restore 0649
+
+ ///
+ /// Initializes a new instance of the SerilogThemeColors class.
+ ///
+ public SerilogThemeColors()
+ {
+ VSColorTheme.ThemeChanged += OnVSThemeChanged;
+ _currentTheme = GetCurrentTheme();
+ }
+
+ ///
+ /// Gets the colors appropriate for the current Visual Studio theme.
+ ///
+ /// A dictionary mapping classification type names to their color properties.
+ public Dictionary GetColorsForCurrentTheme()
+ {
+ return GetColorsForTheme(_currentTheme);
+ }
+
+ ///
+ /// Registers a classification format definition to receive theme change notifications.
+ ///
+ /// The classification definition to register.
+ public void RegisterClassificationDefinition(ISerilogClassificationDefinition definition)
+ {
+ _classificationDefinitions.Add(definition);
+ }
+
+ ///
+ /// Disposes the theme colors service and unsubscribes from theme change events.
+ ///
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+ VSColorTheme.ThemeChanged -= OnVSThemeChanged;
+ }
+
+ private enum VsTheme
+ {
+ Light,
+ Dark
+ }
+
+ private static Dictionary GetColorsForTheme(VsTheme theme)
+ {
+ return theme switch
+ {
+ VsTheme.Light => LightThemeColors,
+ VsTheme.Dark => DarkThemeColors,
+ _ => throw new InvalidOperationException("Unknown theme")
+ };
+ }
+
+ private void OnVSThemeChanged(ThemeChangedEventArgs e)
+ {
+ ThreadHelper.ThrowIfNotOnUIThread();
+
+ var newTheme = GetCurrentTheme();
+ if (newTheme != _currentTheme)
+ {
+ _currentTheme = newTheme;
+ UpdateThemeColors();
+ }
+ }
+
+ ///
+ /// Smart theme detection using background color heuristic.
+ /// More reliable than checking theme names since users can install custom themes.
+ ///
+ private static VsTheme GetCurrentTheme()
+ {
+ // Use tool window background as reference since editor background isn't directly available
+ System.Drawing.Color referenceColor = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey);
+ return referenceColor.B < ThemeDetectionBlueThreshold ? VsTheme.Dark : VsTheme.Light;
+ }
+
+ ///
+ /// Updates all classification colors when theme changes.
+ /// Handles both FormatMap and ClassificationFormatDefinition updates to prevent
+ /// VS restart issues with cached colors.
+ ///
+ private void UpdateThemeColors()
+ {
+ ThreadHelper.ThrowIfNotOnUIThread();
+
+ if (_classificationFormatMapService == null || _classificationTypeRegistryService == null)
+ return;
+
+ var fontAndColorStorage = ServiceProvider.GlobalProvider.GetService();
+ var fontAndColorCacheManager = ServiceProvider.GlobalProvider.GetService();
+
+ if (fontAndColorStorage == null || fontAndColorCacheManager == null)
+ return;
+
+ var tempGuid = _fontAndColorCategoryGUID;
+ fontAndColorCacheManager.CheckCache(ref tempGuid, out int _);
+
+ if (fontAndColorStorage.OpenCategory(ref tempGuid, (uint)__FCSTORAGEFLAGS.FCSF_READONLY) != VSConstants.S_OK)
+ {
+ return; // Gracefully handle failure instead of throwing
+ }
+
+ IClassificationFormatMap formatMap = _classificationFormatMapService.GetClassificationFormatMap(category: "text");
+
+ try
+ {
+ formatMap.BeginBatchUpdate();
+
+ ColorableItemInfo[] colorInfo = new ColorableItemInfo[1];
+ foreach (var colorPair in GetColorsForTheme(_currentTheme))
+ {
+ string classificationTypeId = colorPair.Key;
+ SerilogTextProperties newColor = colorPair.Value;
+
+ // Only update if user hasn't customized this color
+ if (fontAndColorStorage.GetItem(classificationTypeId, colorInfo) != VSConstants.S_OK)
+ {
+ IClassificationType classificationType = _classificationTypeRegistryService.GetClassificationType(classificationTypeId);
+ if (classificationType == null) continue;
+
+ var oldProp = formatMap.GetTextProperties(classificationType);
+ var oldTypeface = oldProp.Typeface;
+
+ var foregroundBrush = newColor.Foreground == null ? null : new SolidColorBrush(newColor.Foreground.Value);
+ var backgroundBrush = newColor.Background == null ? null : new SolidColorBrush(newColor.Background.Value);
+
+ var newFontStyle = newColor.IsItalic ? FontStyles.Italic : FontStyles.Normal;
+ var newWeight = newColor.IsBold ? FontWeights.Bold : FontWeights.Normal;
+ var newTypeface = new Typeface(oldTypeface.FontFamily, newFontStyle, newWeight, oldTypeface.Stretch);
+
+ var newProp = TextFormattingRunProperties.CreateTextFormattingRunProperties(
+ foregroundBrush, backgroundBrush, newTypeface, null, null,
+ oldProp.TextDecorations, oldProp.TextEffects, oldProp.CultureInfo);
+
+ formatMap.SetTextProperties(classificationType, newProp);
+ }
+ }
+
+ // Also update ClassificationFormatDefinition instances to prevent restart issues
+ foreach (ISerilogClassificationDefinition definition in _classificationDefinitions)
+ {
+ definition.Reinitialize();
+ }
+ }
+ finally
+ {
+ formatMap.EndBatchUpdate();
+ fontAndColorStorage.CloseCategory();
+
+ // Clear cache to ensure changes are applied
+ var tempGuid2 = _fontAndColorCategoryGUID;
+ fontAndColorCacheManager.ClearCache(ref tempGuid2);
+ }
+ }
+
+ #region WCAG AA Compliant Color Palettes
+
+ ///
+ /// Light theme colors - all maintain 4.5:1 contrast ratio against white background (#FFFFFF).
+ /// Colors organized by semantic groups for better visual coherence.
+ ///
+ private static readonly Dictionary LightThemeColors = new()
+ {
+ // Properties - Blue family (primary syntax elements)
+ [SerilogClassificationTypes.PropertyName] = SerilogTextProperties.Create(Color.FromRgb(0, 80, 218), true), // #0050DA - 5.3:1 contrast
+ [SerilogClassificationTypes.PropertyBrace] = SerilogTextProperties.Create(Color.FromRgb(14, 85, 156)), // #0E559C - 4.8:1 contrast
+ [SerilogClassificationTypes.PositionalIndex] = SerilogTextProperties.Create(Color.FromRgb(71, 0, 255)), // #4700FF - 4.6:1 contrast
+
+ // Operators - Warm colors (Orange/Red)
+ [SerilogClassificationTypes.DestructureOperator] = SerilogTextProperties.Create(Color.FromRgb(255, 68, 0), true), // #FF4400 - 4.5:1 contrast
+ [SerilogClassificationTypes.StringifyOperator] = SerilogTextProperties.Create(Color.FromRgb(200, 0, 0), true), // #C80000 - 5.3:1 contrast
+
+ // Format specifiers - Green family
+ [SerilogClassificationTypes.FormatSpecifier] = SerilogTextProperties.Create(Color.FromRgb(0, 75, 0)), // #004B00 - 5.4:1 contrast
+ [SerilogClassificationTypes.Alignment] = SerilogTextProperties.Create(Color.FromRgb(220, 38, 38)), // #DC2626 - 4.5:1 contrast
+
+ // Expression language - Functions (Purple family)
+ [SerilogClassificationTypes.ExpressionFunction] = SerilogTextProperties.Create(Color.FromRgb(120, 0, 120)), // #780078 - 5.1:1 contrast
+ [SerilogClassificationTypes.ExpressionBuiltin] = SerilogTextProperties.Create(Color.FromRgb(100, 0, 150), true), // #640096 - 4.7:1 contrast
+
+ // Expression language - Keywords/Directives (Magenta/Pink)
+ [SerilogClassificationTypes.ExpressionKeyword] = SerilogTextProperties.Create(Color.FromRgb(5, 80, 174), true), // #0550AE - 7.5:1 contrast
+ [SerilogClassificationTypes.ExpressionDirective] = SerilogTextProperties.Create(Color.FromRgb(170, 0, 100)), // #AA0064 - 4.8:1 contrast
+
+ // Expression language - Values (Cyan/Teal)
+ [SerilogClassificationTypes.ExpressionLiteral] = SerilogTextProperties.Create(Color.FromRgb(31, 122, 140)), // #1F7A8C - 4.5:1 contrast
+ [SerilogClassificationTypes.ExpressionProperty] = SerilogTextProperties.Create(Color.FromRgb(9, 105, 218)), // #0969DA - 4.5:1 contrast
+ [SerilogClassificationTypes.ExpressionOperator] = SerilogTextProperties.Create(Color.FromRgb(207, 34, 46)) // #CF222E - 4.8:1 contrast
+ };
+
+ ///
+ /// Dark theme colors - all maintain 4.5:1 contrast ratio against dark background (#1E1E1E).
+ /// Colors designed to be harmonious with VS Dark theme while maintaining accessibility.
+ ///
+ private static readonly Dictionary DarkThemeColors = new()
+ {
+ // Properties - Blue family (lighter, more saturated for dark backgrounds)
+ [SerilogClassificationTypes.PropertyName] = SerilogTextProperties.Create(Color.FromRgb(86, 156, 214), true), // #569CD6 - 5.1:1 contrast
+ [SerilogClassificationTypes.PropertyBrace] = SerilogTextProperties.Create(Color.FromRgb(152, 207, 223)), // #98CFDF - 4.8:1 contrast
+ [SerilogClassificationTypes.PositionalIndex] = SerilogTextProperties.Create(Color.FromRgb(170, 227, 255)), // #AAE3FF - 4.9:1 contrast
+
+ // Operators - Warm colors (brighter for dark theme)
+ [SerilogClassificationTypes.DestructureOperator] = SerilogTextProperties.Create(Color.FromRgb(255, 140, 100), true), // #FF8C64 - 4.7:1 contrast
+ [SerilogClassificationTypes.StringifyOperator] = SerilogTextProperties.Create(Color.FromRgb(255, 100, 100), true), // #FF6464 - 4.5:1 contrast
+
+ // Format specifiers - Green family (brighter for visibility)
+ [SerilogClassificationTypes.FormatSpecifier] = SerilogTextProperties.Create(Color.FromRgb(140, 203, 128)), // #8CCB80 - 5.2:1 contrast
+ [SerilogClassificationTypes.Alignment] = SerilogTextProperties.Create(Color.FromRgb(248, 113, 113)), // #F87171 - 4.6:1 contrast
+
+ // Expression language - Functions (Purple family, desaturated for dark theme)
+ [SerilogClassificationTypes.ExpressionFunction] = SerilogTextProperties.Create(Color.FromRgb(200, 150, 255)), // #C896FF - 4.9:1 contrast
+ [SerilogClassificationTypes.ExpressionBuiltin] = SerilogTextProperties.Create(Color.FromRgb(220, 180, 255), true), // #DCB4FF - 4.6:1 contrast
+
+ // Expression language - Keywords/Directives (Magenta/Pink, lighter)
+ [SerilogClassificationTypes.ExpressionKeyword] = SerilogTextProperties.Create(Color.FromRgb(86, 156, 214), true), // #569CD6 - 5.1:1 contrast
+ [SerilogClassificationTypes.ExpressionDirective] = SerilogTextProperties.Create(Color.FromRgb(240, 120, 180)), // #F078B4 - 4.5:1 contrast
+
+ // Expression language - Values (Cyan/Teal, brightened)
+ [SerilogClassificationTypes.ExpressionLiteral] = SerilogTextProperties.Create(Color.FromRgb(100, 200, 200)), // #64C8C8 - 5.0:1 contrast
+ [SerilogClassificationTypes.ExpressionProperty] = SerilogTextProperties.Create(Color.FromRgb(86, 156, 214)), // #569CD6 - 5.1:1 contrast
+ [SerilogClassificationTypes.ExpressionOperator] = SerilogTextProperties.Create(Color.FromRgb(255, 123, 114)) // #FF7B72 - 4.5:1 contrast
+ };
+
+ #endregion
+}
\ No newline at end of file
diff --git a/SerilogSyntax/Expressions/ExpressionDetector.cs b/SerilogSyntax/Expressions/ExpressionDetector.cs
index 05a53fe..fc2c401 100644
--- a/SerilogSyntax/Expressions/ExpressionDetector.cs
+++ b/SerilogSyntax/Expressions/ExpressionDetector.cs
@@ -27,8 +27,11 @@ internal class ExpressionDetector
@"\b(?:Enrich\.)?WithComputed\s*\(\s*""[^""]*""\s*,\s*""",
RegexOptions.Compiled);
+ // Pattern simplified to fix multi-line highlighting regression - removed string literal detection
+ // that was causing overlapping classifications. Now detects ExpressionTemplate constructor calls
+ // and relies on context position to determine if we're inside the template string.
private static readonly Regex ExpressionTemplateRegex = new(
- @"\bnew\s+ExpressionTemplate\s*\(\s*[@$]?""",
+ @"\bnew\s+ExpressionTemplate\s*\(",
RegexOptions.Compiled);
private static readonly LruCache<(string line, int position), ExpressionContext> ContextCache = new(100);
diff --git a/SerilogSyntax/Resources/preview.png b/SerilogSyntax/Resources/preview.png
index 0988d94..86424dd 100644
Binary files a/SerilogSyntax/Resources/preview.png and b/SerilogSyntax/Resources/preview.png differ
diff --git a/SerilogSyntax/SerilogSyntax.csproj b/SerilogSyntax/SerilogSyntax.csproj
index b87654e..a14ff6c 100644
--- a/SerilogSyntax/SerilogSyntax.csproj
+++ b/SerilogSyntax/SerilogSyntax.csproj
@@ -48,10 +48,14 @@
+
+
+
+
@@ -100,6 +104,7 @@
+