From 8fb8592fcf5637eb4bc04c1fced5e585b821d445 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Fri, 24 Dec 2021 16:14:27 +1100 Subject: [PATCH] Fix event hookup at the end of a document (#58477) --- .../EventHookup/EventHookupSessionManager.cs | 24 +++++++++++++- ...HookupSessionManager_EventHookupSession.cs | 5 ++- .../EventHookupCommandHandlerTests.cs | 31 +++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharp/EventHookup/EventHookupSessionManager.cs b/src/EditorFeatures/CSharp/EventHookup/EventHookupSessionManager.cs index 570b58457add4..de8b8614f8726 100644 --- a/src/EditorFeatures/CSharp/EventHookup/EventHookupSessionManager.cs +++ b/src/EditorFeatures/CSharp/EventHookup/EventHookupSessionManager.cs @@ -50,7 +50,7 @@ internal void EventHookupFoundInSession(EventHookupSession analyzedSession) if (_toolTipPresenter == null && CurrentSession == analyzedSession && caretPoint.HasValue && - analyzedSession.TrackingSpan.GetSpan(CurrentSession.TextView.TextSnapshot).Contains(caretPoint.Value)) + IsCaretWithinSpanOrAtEnd(analyzedSession.TrackingSpan, analyzedSession.TextView.TextSnapshot, caretPoint.Value)) { // Create a tooltip presenter that stays alive, even when the user types, without tracking the mouse. _toolTipPresenter = _toolTipService.CreatePresenter(analyzedSession.TextView, @@ -80,6 +80,28 @@ internal void EventHookupFoundInSession(EventHookupSession analyzedSession) } } + private static bool IsCaretWithinSpanOrAtEnd(ITrackingSpan trackingSpan, ITextSnapshot textSnapshot, SnapshotPoint caretPoint) + { + var snapshotSpan = trackingSpan.GetSpan(textSnapshot); + + // If the caret is within the span, then we want to show the tooltip + if (snapshotSpan.Contains(caretPoint)) + { + return true; + } + + // Otherwise if the span is empty, and at the end of the file, and the caret + // is also at the end of the file, then show the tooltip. + if (snapshotSpan.IsEmpty && + snapshotSpan.Start.Position == caretPoint.Position && + caretPoint.Position == textSnapshot.Length) + { + return true; + } + + return false; + } + internal void BeginSession( EventHookupCommandHandler eventHookupCommandHandler, ITextView textView, diff --git a/src/EditorFeatures/CSharp/EventHookup/EventHookupSessionManager_EventHookupSession.cs b/src/EditorFeatures/CSharp/EventHookup/EventHookupSessionManager_EventHookupSession.cs index e7f4f2bcbcfff..12dbd69c81d9e 100644 --- a/src/EditorFeatures/CSharp/EventHookup/EventHookupSessionManager_EventHookupSession.cs +++ b/src/EditorFeatures/CSharp/EventHookup/EventHookupSessionManager_EventHookupSession.cs @@ -114,7 +114,10 @@ public EventHookupSession( { var position = textView.GetCaretPoint(subjectBuffer).Value.Position; _trackingPoint = textView.TextSnapshot.CreateTrackingPoint(position, PointTrackingMode.Negative); - _trackingSpan = textView.TextSnapshot.CreateTrackingSpan(new Span(position, 1), SpanTrackingMode.EdgeInclusive); + + // If the caret is at the end of the document we just create an empty span + var length = textView.TextSnapshot.Length > position + 1 ? 1 : 0; + _trackingSpan = textView.TextSnapshot.CreateTrackingSpan(new Span(position, length), SpanTrackingMode.EdgeInclusive); var asyncToken = asyncListener.BeginAsyncOperation(GetType().Name + ".Start"); diff --git a/src/EditorFeatures/CSharpTest/EventHookup/EventHookupCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/EventHookup/EventHookupCommandHandlerTests.cs index 2cf1fe41ca81a..0d57db3ad040d 100644 --- a/src/EditorFeatures/CSharpTest/EventHookup/EventHookupCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/EventHookup/EventHookupCommandHandlerTests.cs @@ -1042,6 +1042,37 @@ public async Task EventHookupInTopLevelCode() System.AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; +void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e) +{ + throw new System.NotImplementedException(); +}"; + testState.AssertCodeIs(expectedCode); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.EventHookup)] + public async Task EventHookupAtEndOfDocument() + { + var markup = @" + +System.AppDomain.CurrentDomain.UnhandledException +$$"; + using var testState = EventHookupTestState.CreateTestState(markup); + testState.SendTypeChar('='); + + await testState.WaitForAsynchronousOperationsAsync(); + testState.AssertShowing("CurrentDomain_UnhandledException"); + + var expectedCode = @" + +System.AppDomain.CurrentDomain.UnhandledException +="; + testState.AssertCodeIs(expectedCode); + + testState.SendTab(); + await testState.WaitForAsynchronousOperationsAsync(); + + expectedCode = @" + +System.AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e) { throw new System.NotImplementedException();