Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ The deploy script updates your extension in-place without uninstalling, which is
- The extension targets .NET Framework 4.7.2
- Uses Visual Studio SDK v17.0.32112.339
- Configured for Visual Studio Community 2022 (17.0-18.0)
- Fully functional with syntax highlighting, navigation, and brace matching
- Fully functional with syntax highlighting, navigation, and property-argument highlighting
- Supports C# 11 raw string literals ("""...""")

## Implementation Overview
Expand All @@ -156,11 +156,11 @@ This extension provides syntax highlighting and navigation for Serilog message t
- **Syntax highlighting** of properties within Serilog message template strings
- **Syntax highlighting** for Serilog.Expressions filter syntax and expression templates
- **Navigation** support (Go to Definition) between template properties and arguments
- **Brace matching** for template property delimiters and expression templates
- **Property-argument highlighting** that visually connects template properties with their corresponding arguments

### Technical Stack
- **Roslyn Classification API** - For syntax highlighting via `IClassifier`
- **Roslyn Tagging API** - For brace matching via `ITagger<TextMarkerTag>`
- **Roslyn Tagging API** - For property-argument highlighting via `ITagger<TextMarkerTag>`
- **Visual Studio Editor API** - For navigation features
- **MEF (Managed Extensibility Framework)** - For VS integration

Expand Down Expand Up @@ -193,7 +193,7 @@ The extension includes these components:
2. **Classification/SerilogClassifier.cs** - Implements `IClassifier` for syntax highlighting
3. **Classification/SerilogClassifierProvider.cs** - MEF export for classifier
4. **Navigation/SerilogNavigationProvider.cs** - Navigation from properties to arguments via light bulb
5. **Tagging/SerilogBraceMatcher.cs** - Implements `ITagger<TextMarkerTag>` for brace matching
5. **Tagging/PropertyArgumentHighlighter.cs** - Implements `ITagger<TextMarkerTag>` for property-argument highlighting
6. **Utilities/SerilogCallDetector.cs** - Centralized Serilog call detection logic
7. **Utilities/LruCache.cs** - Thread-safe LRU cache for parsed templates

Expand Down
2 changes: 1 addition & 1 deletion Example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Open `Program.cs` in Visual Studio with the Serilog Syntax extension installed t
- **Teal** for property paths in expressions

### Interactive Features
- **Brace matching** when cursor is on `{` or `}`
- **Property-argument highlighting** when cursor is on a property or argument
- **Light bulb navigation** from properties to arguments
- **Immediate highlighting** as you type (before closing quotes)
- **Multi-line support** for verbatim and raw strings
Expand Down
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,13 @@ A Visual Studio 2022 extension that provides syntax highlighting, brace matching
- **Navigate to argument** - jump from template properties to their corresponding arguments
- Click the light bulb and select "Navigate to 'PropertyName' argument"

### 🔍 Brace Matching
- Highlight matching braces when cursor is positioned on `{` or `}`
- Visual indication of brace pairs in complex templates
- **Multi-line support** - matches braces across line boundaries in verbatim and raw strings
### 🔍 Property-Argument Highlighting
- **Smart highlighting** that connects template properties with their corresponding arguments
- When cursor is on a property like `{UserId}`, both the property and its argument are highlighted
- When cursor is on an argument, both the argument and its corresponding property are highlighted
- **Multi-line support** - works across line boundaries in verbatim and raw strings
- Press **ESC** to temporarily dismiss highlights
- Helps identify mismatched or nested braces
- Helps visualize the connection between template properties and their values

## Installation

Expand Down Expand Up @@ -118,7 +119,7 @@ After installation, the extension works automatically - no configuration require
```
3. **See instant highlighting** as you type - properties turn blue, operators yellow, etc.
4. **Try navigation**: Hover over a property like `{UserId}` and click the light bulb to jump to its argument
5. **Test brace matching**: Place your cursor on any `{` or `}` to see its matching pair highlighted
5. **Test property-argument highlighting**: Place your cursor on a property or argument to see both highlighted

### Quick Test
Create a new C# file and paste this to see all features:
Expand All @@ -131,9 +132,9 @@ Log.Information("User {UserId} logged in with {@Details} at {Timestamp:HH:mm:ss}

You should see:
- `UserId` in blue (adapts to your theme)
- `@` in warm orange/red, `Details` in blue
- `@` in warm orange/red, `Details` in blue
- `Timestamp` in blue, `:HH:mm:ss` in green
- Matching braces highlighted when cursor is on them
- Property-argument pairs highlighted when cursor is on them
- Colors automatically match your Light/Dark theme preference

## Supported Serilog Syntax
Expand Down Expand Up @@ -238,13 +239,13 @@ log.LogError("Error with {Context}", context);
The extension uses Visual Studio's extensibility APIs:

- **Roslyn Classification API** - For syntax highlighting via `IClassifier`
- **Roslyn Tagging API** - For brace matching via `ITagger<TextMarkerTag>`
- **Roslyn Tagging API** - For property-argument highlighting via `ITagger<TextMarkerTag>`
- **Suggested Actions API** - For navigation features via `ISuggestedActionsSourceProvider`
- **MEF (Managed Extensibility Framework)** - For Visual Studio integration

Key components:
- `SerilogClassifier` - Handles syntax highlighting with smart cache invalidation
- `SerilogBraceMatcher` - Provides brace matching
- `PropertyArgumentHighlighter` - Provides property-argument connection highlighting
- `SerilogNavigationProvider` - Enables property-to-argument navigation
- `SerilogCallDetector` - Optimized Serilog call detection with pre-check optimization
- `SerilogThemeColors` - Theme-aware color management with WCAG AA compliance
Expand Down
74 changes: 73 additions & 1 deletion SerilogSyntax.Tests/Navigation/SerilogNavigationProviderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,79 @@ public void GetSuggestedActions_RawStringMultiLine_EarlyProperties_ShouldProvide

// All should work, but currently only later ones do
Assert.NotEmpty(appNameActions);
Assert.NotEmpty(versionActions);
Assert.NotEmpty(versionActions);
Assert.NotEmpty(environmentActions);
}

[Fact]
public void GetSuggestedActions_WideRangeFromVS_AllPropertiesShowUserNameBug()
{
// This test captures the EXACT bug: "all of the properties say the exact same thing -> Navigate to UserName argument"
// When VS passes a wide range, midpoint calculation always lands on UserName

// Arrange - exact scenario from user
var code = @"logger.LogInformation(""User {UserId} ({UserName}) placed {OrderCount} orders totaling {TotalAmount:C}"",
userId, userName, orderCount, totalAmount);";

var mockBuffer = MockTextBuffer.Create(code);
var mockTextView = new MockTextView(mockBuffer);
var provider = new SerilogSuggestedActionsSource(mockTextView);

// Simulate VS passing a wide range that covers multiple properties
// The midpoint of this range will land on UserName property
var rangeStart = code.IndexOf("{UserId}");
var rangeEnd = code.IndexOf("{OrderCount}") + 5; // Goes partway through OrderCount
var wideRange = new SnapshotSpan(mockBuffer.CurrentSnapshot, rangeStart, rangeEnd - rangeStart);

// Act - Get suggested actions with VS's wide range
var actions = provider.GetSuggestedActions(null, wideRange, CancellationToken.None);

// Assert
Assert.NotEmpty(actions);
var navigateAction = actions.First().Actions.First() as NavigateToArgumentAction;
Assert.NotNull(navigateAction);

// The bug: Because midpoint lands on UserName, it shows "Navigate to 'UserName' argument"
// even though the range covers multiple properties (UserId, UserName, OrderCount)
// This test should initially FAIL because it shows UserName instead of the property cursor is actually on
Assert.NotEqual("Navigate to 'UserName' argument", navigateAction.DisplayText);
}

[Fact]
public void GetSuggestedActions_VSPassesWideRangeFromLineStart_ShowsWrongProperty()
{
// This reproduces the EXACT bug: VS passes range from line start to cursor position
// When cursor is on {OrderCount}, the midpoint lands on {UserId}

var code = @"// Standard properties with multiple types
logger.LogInformation(""User {UserId} ({UserName}) placed {OrderCount} orders totaling {TotalAmount:C}"",
userId, userName, orderCount, totalAmount);";

var mockBuffer = MockTextBuffer.Create(code);
var mockTextView = new MockTextView(mockBuffer);
var provider = new SerilogSuggestedActionsSource(mockTextView);

// Find positions exactly as they appear in the screenshot scenario
var lineStart = code.IndexOf("logger.LogInformation"); // Position 44
var orderCountPos = code.IndexOf("OrderCount"); // Position 101 (inside the property name)

// VS passes a range from line start to cursor position when showing light bulb
// This wide range causes midpoint to land on UserId instead of OrderCount
// Range ends at position 101, which is inside "OrderCount"
var range = new SnapshotSpan(mockBuffer.CurrentSnapshot, lineStart, orderCountPos - lineStart);

// Act
var actions = provider.GetSuggestedActions(null, range, CancellationToken.None);

// Assert
Assert.NotEmpty(actions);
var actionSet = actions.First();
Assert.NotEmpty(actionSet.Actions);
var navigateAction = actionSet.Actions.First() as NavigateToArgumentAction;
Assert.NotNull(navigateAction);

// BUG: Shows "Navigate to 'UserId' argument" instead of "Navigate to 'OrderCount' argument"
// because midpoint calculation lands on UserId
Assert.Equal("Navigate to 'OrderCount' argument", navigateAction.DisplayText);
}
}
Loading
Loading