Skip to content

Commit e8fbbcb

Browse files
committed
fix: Add ESC key dismissal for property-argument highlighting
Implements ESC key dismissal for property-argument highlights following the same pattern as brace matching. Highlights automatically restore when cursor moves. - Created PropertyArgumentHighlightState for per-view dismissal tracking - Added PropertyArgumentEscCommandHandler to handle ESC in command chain - Updated PropertyArgumentHighlighter to respect dismissal state
1 parent c88e08d commit e8fbbcb

File tree

7 files changed

+172
-6
lines changed

7 files changed

+172
-6
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.7.1] - 2025-09-21
11+
12+
### Fixed
13+
- Added ESC key dismissal for property-argument highlighting
14+
- Press ESC to temporarily dismiss property-argument highlights
15+
- Highlights automatically restore when cursor moves to a new position
16+
- Consistent with brace matching ESC dismissal behavior
17+
- Uses Visual Studio command handler chain for proper key handling
18+
1019
## [0.7.0] - 2025-09-21
1120

1221
### Added

SerilogSyntax.Tests/Tagging/PropertyArgumentHighlighterTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,17 +1016,17 @@ public void ExpressionTemplate_BuiltInProperties_ShouldNotBeHighlighted()
10161016
cursorPosition = text.IndexOf("{@l:");
10171017
view.Caret.MoveTo(new SnapshotPoint(buffer.CurrentSnapshot, cursorPosition));
10181018

1019-
tags = highlighter.GetTags(new NormalizedSnapshotSpanCollection(
1020-
new SnapshotSpan(buffer.CurrentSnapshot, 0, buffer.CurrentSnapshot.Length))).ToList();
1019+
tags = [.. highlighter.GetTags(new NormalizedSnapshotSpanCollection(
1020+
new SnapshotSpan(buffer.CurrentSnapshot, 0, buffer.CurrentSnapshot.Length)))];
10211021

10221022
Assert.Empty(tags);
10231023

10241024
// Also test cursor on {@m}
10251025
cursorPosition = text.IndexOf("{@m}");
10261026
view.Caret.MoveTo(new SnapshotPoint(buffer.CurrentSnapshot, cursorPosition));
10271027

1028-
tags = highlighter.GetTags(new NormalizedSnapshotSpanCollection(
1029-
new SnapshotSpan(buffer.CurrentSnapshot, 0, buffer.CurrentSnapshot.Length))).ToList();
1028+
tags = [.. highlighter.GetTags(new NormalizedSnapshotSpanCollection(
1029+
new SnapshotSpan(buffer.CurrentSnapshot, 0, buffer.CurrentSnapshot.Length)))];
10301030

10311031
Assert.Empty(tags);
10321032
}

SerilogSyntax/SerilogSyntax.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,10 @@
7272
<Compile Include="Parsing\TemplateProperty.cs" />
7373
<Compile Include="Properties\AssemblyInfo.cs" />
7474
<Compile Include="SerilogSyntaxPackage.cs" />
75+
<Compile Include="Tagging\PropertyArgumentEscCommandHandler.cs" />
7576
<Compile Include="Tagging\PropertyArgumentHighlighter.cs" />
7677
<Compile Include="Tagging\PropertyArgumentHighlighterProvider.cs" />
78+
<Compile Include="Tagging\PropertyArgumentHighlightState.cs" />
7779
<Compile Include="Tagging\SerilogBraceMatcher.cs" />
7880
<Compile Include="Tagging\SerilogBraceHighlightState.cs" />
7981
<Compile Include="Tagging\SerilogBraceEscCommandHandler.cs" />
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using Microsoft.VisualStudio.Commanding;
2+
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
3+
using Microsoft.VisualStudio.Utilities;
4+
using System.ComponentModel.Composition;
5+
6+
namespace SerilogSyntax.Tagging
7+
{
8+
/// <summary>
9+
/// Handles ESC to dismiss the property-argument highlights.
10+
/// Participates in the editor command chain.
11+
/// </summary>
12+
[Export(typeof(ICommandHandler))]
13+
[Name("PropertyArgumentEscHandler")]
14+
[ContentType("CSharp")]
15+
internal sealed class PropertyArgumentEscCommandHandler
16+
: IChainedCommandHandler<EscapeKeyCommandArgs>
17+
{
18+
/// <summary>
19+
/// Gets the display name of the command handler.
20+
/// </summary>
21+
public string DisplayName => "Property-Argument ESC Dismissal";
22+
23+
/// <summary>
24+
/// Executes the ESC command, dismissing property-argument highlights if applicable.
25+
/// </summary>
26+
/// <param name="args">The ESC key command arguments.</param>
27+
/// <param name="nextHandler">The next handler in the command chain.</param>
28+
/// <param name="context">The command execution context.</param>
29+
public void ExecuteCommand(EscapeKeyCommandArgs args, System.Action nextHandler, CommandExecutionContext context)
30+
{
31+
var view = args.TextView;
32+
var state = PropertyArgumentHighlightState.GetOrCreate(view);
33+
34+
// Only handle ESC if we actually dismiss something; otherwise, pass through.
35+
bool handled = state.Dismiss();
36+
if (!handled)
37+
nextHandler();
38+
}
39+
40+
/// <summary>
41+
/// Gets the command state for the ESC key.
42+
/// </summary>
43+
/// <param name="args">The ESC key command arguments.</param>
44+
/// <param name="nextHandler">The next handler in the command chain.</param>
45+
/// <returns>The command state.</returns>
46+
public CommandState GetCommandState(EscapeKeyCommandArgs args, System.Func<CommandState> nextHandler)
47+
{
48+
// Enabled whenever we can access the view; we decide to handle at ExecuteCommand time.
49+
return CommandState.Available;
50+
}
51+
}
52+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using Microsoft.VisualStudio.Text;
2+
using Microsoft.VisualStudio.Text.Editor;
3+
using System;
4+
5+
namespace SerilogSyntax.Tagging
6+
{
7+
/// <summary>
8+
/// Per-view state for property-argument highlighting, including ESC dismissal.
9+
/// </summary>
10+
internal sealed class PropertyArgumentHighlightState : IDisposable
11+
{
12+
private readonly ITextView _view;
13+
14+
// Track whether highlighting is currently dismissed
15+
private bool _isDismissed;
16+
17+
/// <summary>
18+
/// Occurs when the highlight state changes (dismissal or restoration).
19+
/// </summary>
20+
public event EventHandler StateChanged;
21+
22+
public PropertyArgumentHighlightState(ITextView view)
23+
{
24+
_view = view ?? throw new ArgumentNullException(nameof(view));
25+
_view.Closed += OnViewClosed;
26+
_view.Caret.PositionChanged += OnCaretPositionChanged;
27+
}
28+
29+
/// <summary>
30+
/// Gets a value indicating whether highlighting has been dismissed via ESC.
31+
/// </summary>
32+
public bool IsDismissed => _isDismissed;
33+
34+
/// <summary>
35+
/// Dismisses the property-argument highlights.
36+
/// Called by the ESC command handler.
37+
/// </summary>
38+
/// <returns>True if highlights were dismissed; false if already dismissed.</returns>
39+
public bool Dismiss()
40+
{
41+
if (_isDismissed)
42+
return false; // Already dismissed, let other ESC handlers run
43+
44+
_isDismissed = true;
45+
OnStateChanged();
46+
return true;
47+
}
48+
49+
/// <summary>
50+
/// Restores highlighting after being dismissed.
51+
/// Called when the caret position changes.
52+
/// </summary>
53+
public void Restore()
54+
{
55+
if (_isDismissed)
56+
{
57+
_isDismissed = false;
58+
OnStateChanged();
59+
}
60+
}
61+
62+
private void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
63+
{
64+
// Restore highlighting when cursor moves after ESC dismissal
65+
Restore();
66+
}
67+
68+
private void OnStateChanged() => StateChanged?.Invoke(this, EventArgs.Empty);
69+
70+
private void OnViewClosed(object sender, EventArgs e) => Dispose();
71+
72+
public void Dispose()
73+
{
74+
_view.Closed -= OnViewClosed;
75+
_view.Caret.PositionChanged -= OnCaretPositionChanged;
76+
}
77+
78+
/// <summary>
79+
/// Gets or creates a singleton instance of the state for the specified view.
80+
/// </summary>
81+
/// <param name="view">The text view.</param>
82+
/// <returns>The state instance for the view.</returns>
83+
public static PropertyArgumentHighlightState GetOrCreate(ITextView view)
84+
=> view.Properties.GetOrCreateSingletonProperty(() => new PropertyArgumentHighlightState(view));
85+
}
86+
}

SerilogSyntax/Tagging/PropertyArgumentHighlighter.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ internal sealed class PropertyArgumentHighlighter : ITagger<TextMarkerTag>, IDis
2525
private SnapshotPoint? _currentChar;
2626
private readonly List<ITagSpan<TextMarkerTag>> _currentTags = [];
2727
private bool _disposed;
28+
private readonly PropertyArgumentHighlightState _state;
2829

2930
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
3031

@@ -35,6 +36,10 @@ public PropertyArgumentHighlighter(ITextView view, ITextBuffer buffer)
3536

3637
_currentChar = view.Caret.Position.Point.GetPoint(buffer, view.Caret.Position.Affinity);
3738

39+
// Set up state tracking for ESC dismissal
40+
_state = PropertyArgumentHighlightState.GetOrCreate(_view);
41+
_state.StateChanged += OnStateChanged;
42+
3843
_view.Caret.PositionChanged += CaretPositionChanged;
3944
_view.LayoutChanged += ViewLayoutChanged;
4045
}
@@ -48,6 +53,14 @@ private void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
4853
UpdateAtCaretPosition(_view.Caret.Position);
4954
}
5055

56+
private void OnStateChanged(object sender, EventArgs e)
57+
{
58+
// When state changes (ESC dismiss/restore), refresh tags
59+
var snapshot = _buffer.CurrentSnapshot;
60+
var fullSpan = new SnapshotSpan(snapshot, 0, snapshot.Length);
61+
TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(fullSpan));
62+
}
63+
5164
private void UpdateAtCaretPosition(CaretPosition caretPosition)
5265
{
5366
_currentChar = caretPosition.Point.GetPoint(_buffer, caretPosition.Affinity);
@@ -533,7 +546,7 @@ private List<ArgumentInfo> ExtractArguments(string callContent, int templateEnd,
533546

534547
public IEnumerable<ITagSpan<TextMarkerTag>> GetTags(NormalizedSnapshotSpanCollection spans)
535548
{
536-
if (_disposed || spans.Count == 0)
549+
if (_disposed || spans.Count == 0 || _state?.IsDismissed == true)
537550
return [];
538551

539552
return _currentTags.Where(tag => spans.Any(span => span.IntersectsWith(tag.Span)));
@@ -572,6 +585,11 @@ public void Dispose()
572585
if (_disposed) return;
573586
_disposed = true;
574587

588+
if (_state != null)
589+
{
590+
_state.StateChanged -= OnStateChanged;
591+
}
592+
575593
if (_view != null)
576594
{
577595
_view.Caret.PositionChanged -= CaretPositionChanged;

SerilogSyntax/Tagging/SerilogBraceEscCommandHandler.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Microsoft.VisualStudio.Commanding;
2-
using Microsoft.VisualStudio.Text.Editor;
32
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
43
using Microsoft.VisualStudio.Utilities;
54
using System.ComponentModel.Composition;

0 commit comments

Comments
 (0)